How to Generate Listing from Custom Collections

I’m trying to build a site using the Middleman Blog extension and I would like to set up some pages to list projects that I have. I want a main project listing page that just has a list of links to all the projects, possibly with a small summary for each. Then I want a page for each individual project, possibly with an extended summary, and with links to all the blog posts associated with that project.

I want to be able to associate blog posts with a project by including a line in the Frontmatter like so:

---
project: Foo
---

If project Foo does not already exist, I would like it to be added to the main project listing page as well as have a Foo specific page generated.

I’ve been trying to get main listing page generated using custom collections, but currently, I’m getting an error when I have a post with a project line:

== The Middleman is loading
/opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/extensions/proxies.rb:52:in `proxied_to_resource': Path posts/projects/foo.html proxies to unknown file layouts/project.html:[] (RuntimeError)
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/extensions/proxies.rb:64:in `get_source_file'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/resource.rb:35:in `source_file'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/core_extensions/front_matter.rb:66:in `raw_data'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-more/extensions/directory_indexes.rb:16:in `block in manipulate_resource_list'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-more/extensions/directory_indexes.rb:9:in `each'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-more/extensions/directory_indexes.rb:9:in `manipulate_resource_list'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:217:in `block (2 levels) in ensure_resource_list_updated!'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:216:in `each'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:216:in `inject'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:216:in `block in ensure_resource_list_updated!'
	from /opt/local/lib/ruby2.1/2.1.0/monitor.rb:211:in `mon_synchronize'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:210:in `ensure_resource_list_updated!'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/extensions/on_disk.rb:35:in `block in initialize'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:53:in `instance_exec'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:53:in `block in run_hook_for'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:49:in `each'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:49:in `run_hook_for'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:107:in `run_hook'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/core_extensions/request.rb:53:in `inst'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/preview_server.rb:103:in `new_app'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/preview_server.rb:21:in `start'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/cli/server.rb:79:in `server'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/command.rb:27:in `run'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/invocation.rb:120:in `invoke_command'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor.rb:363:in `dispatch'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/base.rb:439:in `start'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/cli.rb:77:in `method_missing'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/command.rb:29:in `run'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/command.rb:128:in `run'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/invocation.rb:120:in `invoke_command'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor.rb:363:in `dispatch'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/base.rb:439:in `start'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/cli.rb:22:in `start'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/bin/middleman:18:in `<top (required)>'
	from /opt/local/bin/middleman:23:in `load'
	from /opt/local/bin/middleman:23:in `<main>'

Here’s my blog settings in config.rb:

# Setup blog
activate :blog do |blog|
	blog.custom_collections = {
		project: {
			link: '/projects/{project}.html',
			template: '/project.html'
		}
	}
	blog.paginate = true
	blog.permalink = "/{year}/{month}/{title}.html"
	blog.prefix = "/posts"
	blog.sources = "/{year}/{month}-{day}-{title}.html"
end

# Create pretty urls
activate :directory_indexes

My source folder structure looks something like this:

  • blog.html.haml
  • index.html.haml
  • projects.html.haml -> this should be the main project listing
  • posts/
    • 2014
      • 02-02-test.html.md
      • 02-03-a-post.html.md
  • images/
  • scripts/
  • layouts/
    • _header.haml
    • base.haml
    • project.haml
  • styles/
  • projects/ -> I’ll put files with extended summaries in here. I’d prefer for them to be optional and have the specific project pages generated regardless of the extended summary files.

Being very new to Middleman, Ruby, and Haml, I’m pretty lost. I would quite like some help figuring out what I’m doing wrong or am missing.

Something is trying to use a file that does not exist. Search for project.html and I think you will find the code with the error.

Well there’s a line in my blog settings in config.rb that looks like this:

template: '/project.html'

but I based that off of the documentation for custom collections.

Getting rid of that line still gives me an error, though it does seem to be a different one:

== The Middleman is loading
/opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/util.rb:72:in `normalize_path': undefined method `sub' for nil:NilClass (NoMethodError)
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/extensions/proxies.rb:34:in `proxy_to'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-blog-3.5.2/lib/middleman-blog/custom_pages.rb:42:in `block in build_resource'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-blog-3.5.2/lib/middleman-blog/custom_pages.rb:41:in `tap'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-blog-3.5.2/lib/middleman-blog/custom_pages.rb:41:in `build_resource'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-blog-3.5.2/lib/middleman-blog/custom_pages.rb:33:in `block in manipulate_resource_list'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-blog-3.5.2/lib/middleman-blog/custom_pages.rb:32:in `each'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-blog-3.5.2/lib/middleman-blog/custom_pages.rb:32:in `map'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-blog-3.5.2/lib/middleman-blog/custom_pages.rb:32:in `manipulate_resource_list'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:217:in `block (2 levels) in ensure_resource_list_updated!'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:216:in `each'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:216:in `inject'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:216:in `block in ensure_resource_list_updated!'
	from /opt/local/lib/ruby2.1/2.1.0/monitor.rb:211:in `mon_synchronize'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/store.rb:210:in `ensure_resource_list_updated!'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/sitemap/extensions/on_disk.rb:35:in `block in initialize'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:53:in `instance_exec'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:53:in `block in run_hook_for'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:49:in `each'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:49:in `run_hook_for'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/vendored-middleman-deps/hooks-0.2.0/lib/hooks.rb:107:in `run_hook'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/core_extensions/request.rb:53:in `inst'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/preview_server.rb:103:in `new_app'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/preview_server.rb:21:in `start'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/cli/server.rb:79:in `server'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/command.rb:27:in `run'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/invocation.rb:120:in `invoke_command'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor.rb:363:in `dispatch'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/base.rb:439:in `start'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/cli.rb:77:in `method_missing'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/command.rb:29:in `run'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/command.rb:128:in `run'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/invocation.rb:120:in `invoke_command'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor.rb:363:in `dispatch'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/thor-0.18.1/lib/thor/base.rb:439:in `start'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/lib/middleman-core/cli.rb:22:in `start'
	from /opt/local/lib/ruby2.1/gems/2.1.0/gems/middleman-core-3.2.2/bin/middleman:18:in `<top (required)>'
	from /opt/local/bin/middleman:23:in `load'
	from /opt/local/bin/middleman:23:in `<main>'

Try removing the ‘s’ so that it corresponds to

template: '/project.html'

Okay, I added an additional file call project.html.haml into source/ and that does seem to work as a template for the project pages. Putting this:

- projects = blog.articles.group_by {|a| a.metadata[:page]['project'] }
%h1<
  Projects
%ul
  - projects.each do |project, articles|
    - if !project.blank?
      %li
        = link_to project, project_path(project)

in source/projects.html.haml gets me something that generates a listing of all the projects that have blog posts, so I’m most of the way there.

Is there a way to include the contents of files in the projects folder in the relevant project page generated from source/project.html.haml? So if I have blog posts with project: foo and others with project: bar in their Frontmatter, and the source/projects/ folder has a file foo.html.haml, then the foo.html file that is generated will include the contents of source/projects/foo.html.haml and the bar.html will also successfully generate without having a matching file in source/projects/.

File.exist? + some simple Ruby code to modify the page, depending on if the file is there or not.

Or check out partials.

Okay, for some reason I was thinking partials had to be in the layouts folder. Fortunately, I was mistaken.

I was able to get things working with this code in project.html.haml:

- if File.exists?("source/projects/_#{project}.haml")
  = partial "projects/#{project}"

Thanks for your help!