we use the ro gem
https://github.com/ahoward/ro
a-2:~/git/dojo4/www $ ls public/ro/
chiclets pages people posts
a-2:~/git/dojo4/www $ tree public/ro/posts/|head -18
public/ro/posts/
├── 28-days-of-kindness
│ ├── assets
│ │ ├── cover.jpg
│ │ ├── donateoldbooks.jpg
│ │ ├── fred-love.jpg
│ │ ├── goodreview.jpg
│ │ ├── hh-5.jpg
│ │ ├── lift.jpg
│ │ ├── mahakala.jpg
│ │ ├── officialdaymaker.jpg
│ │ ├── pancakes.jpg
│ │ ├── paytoll.jpg
│ │ ├── pema.jpg
│ │ └── test.jpeg
│ ├── attributes.yml
│ └── body.md
├── 30-years-ago-i-said-pager-first-but-no-one-listened
we keep our ro stuff out of the way so middleman doesn't process it
### file: config.rb
# ...
#
Ro.root = File.join(root, 'public', 'ro')
# locally we will serve from public too though
#
use(Rack::Static, :urls => %w( /ro/ ), :root => "#{ root }/public")
ignore '/ro/**/**'
# ...
our deployment includes everything in public, including our ro data
### file: ./script/deploy
# ...
system 'rm -rf deploy'
system 'mkdir deploy'
system 'rsync -a build/ deploy/'
system 'rsync -a public/ro deploy'
# ...
ro gives us really nice jquery-like access to all our data, including template expansion and easy-cheasy relative assets
a-2:~/git/dojo4/www $ ./script/shell
loaded people from /Users/ahoward/git/dojo4/www/tmp/ro/cache/people.yml
loaded posts from /Users/ahoward/git/dojo4/www/tmp/ro/cache/posts.yml
loaded chiclets from /Users/ahoward/git/dojo4/www/tmp/ro/cache/chiclets.yml
loaded pages from /Users/ahoward/git/dojo4/www/tmp/ro/cache/pages.yml
== Ro reloaded...
www ~> ro.posts.first.class
=> Ro::Node
www ~> ro.posts.first
=> posts/28-days-of-kindness
www ~> ro.posts.first.body.slice(0,100)
=> "<p><a href=\"/team/jessica-watson\">Jessica Watson</a> has put together a super fun project for the mo"
www ~> ro.posts.first.assets.first
=> "cover.jpg"
www ~> ro.posts.first.assets.first.url
=> "/ro/posts/28-days-of-kindness/assets/cover.jpg"
and then, of course, we can do things like this in our config.rb
#
if defined?(Post)
#
posts = Post.published
n = posts.size
i = 0
per_page = 6
pageno = 0
num_pages = (posts.size / per_page).ceil
#
posts.each_slice(per_page) do |slice|
#
locals = {
:posts => slice,
:pageno => pageno,
:per_page => per_page,
:num_pages => num_pages,
:next_page => (pageno < num_pages ? pageno + 1 : nil),
:prev_page => (pageno > 0 ? pageno - 1 : nil)
}
#
proxy "blog/page/#{ pageno }/index.html", "blog/page.html", :ignore => true, :locals => locals
if pageno.zero?
proxy "blog/index.html", "blog/page.html", :ignore => true, :locals => locals
end
pageno += 1
#
posts.each do |post|
proxy "blog/#{ post.id }/index.html", "blog/post.html", :ignore => true do
@content = post
@post = post
end
locals = {
:posts => [posts],
:pageno => pageno,
:per_page => per_page,
:num_pages => num_pages,
:next_page => (pageno < num_pages ? pageno + 1 : nil),
:prev_page => (pageno > 0 ? pageno - 1 : nil)
}
i += 1
end
end
end
#
if defined?(Page)
pages = Page.published
pages.each do |page|
proxy File.join(page.path_info, "index.html"), page.template, :ignore => true, :locals => {:page => page} do
@content = page
end
end
end
note that we actually wrap the ro nodes, which you can think of as being at the database driver level (but with clever tilt based template expansion) with some lighteight models for convenience
class Post < ::Ro::Model
extend(Ro::Caching)
def Post.published
now = Time.now.utc
all.
select{|post| post.status == 'published' and post.published_at and post.published_at <= now}.
sort{|a, b| b.published_at <=> a.published_at}
end
def Post.recent(options = {})
options = Map.for(options)
newer_than = options[:newer_than] || RECENT_TIME_LINE
time_ago = Coerce.time(newer_than).utc
seconds_diff = Time.now.utc - time_ago
time_diff = Post.published.first.published_at - seconds_diff
Post.published.select{|post| post.published_at.utc >= time_diff}
end
RECENT_TIME_LINE = "six months ago"
def url
"/blog/#{ slug }"
end
def author
if((author = attributes[:author]))
Person.detect{|person| person.email == author or person.slug == author}
end
end
def photo
return @photo if defined?(@photo)
@photo = (
re = /\.(gif|jpeg|jpg|png|tif|tiff)$/i
image = assets.detect{|asset| asset =~ re}
url_for(image)
)
end
def photo?
photo
end
# ....
end
and, of course, all our posts render directly in github because of relative assets:
i think think middleman blog extension fails badly at adding the correct abstraction layers. to make things really work you need:
- a good directory layout that will render in github using relative assets
- a low-level library for manipulating these directories as objects
- a model layer on top of the low-level library
you might also need
- some adaptation of your deployment/local rendering
in the end the first three are just what we do in any mvc stack at the db layer:
- storage
- db driver
- models
good design can't stop just because we are in plain-text land