Extends Rails with liquid-powered views stored in the database
= Liquidizer
Liquidizer is a gem for Ruby on Rails that allows to render pages using templates stored in the database. It uses liquid (http://www.liquidmarkup.org/) templating system (thus the name), because it's easy to use, extend and it's safe to edit by users.
One example of when you might want to use it is this: Let's say you want to have a blogging system where people can create their blogs about kittens, ninjas or vampires. Using this gem, you can allow them to edit the look and feel of their blog using just their browser. Every blog can have it's own set of templates.
== Installation
It's hosted on http://gemcutter.org. Add it to your source list if you don't have it already:
sudo gem source --add http://gemcutter.org
Then install:
sudo gem install liquidizer
And add this to your config/environment.rb:
config.gem 'liquidizer'
== How to use
First you have to specify which actions of which controllers should be liquified (rendered with loadable liquid templates). To do that, use the +liquify+ controller macro:
class PostsController < ApplicationController
# This will apply the templates to all actions in this controller.
liquify
end
You can use the :only and :except options that work the same as for filters to limit the set of actions to liquify:
class PostsController < ApplicationController
liquify :only => [:show, :index]
end
class CommentsController < ApplicationController
liquify :except => :edit
end
The +liquify+ macro is inherited, so if you want to apply liquid templates to all actions in all controller, put it into ApplicationController. You can fine-tune it in derived controllers, if you want:
class ApplicationController < ActionController::Base
liquify
end
class PostsController < ApplicationController
liquify :only => :show
end
The layout is also liquified by default. You can disable it using the :layout => false options. Note that this will disable layout rendering with liquid, not layout rendering in general.
class ApplicationController < ActionController::Base
liquify :layout => false
end
The Liqidizer uses the same naming convention for templates as Rails. So for the Blog::PostsController's action :index, the looked up template will be "blog/posts/index". If you want to override it, do it the standard Rails way:
class PostsController < ApplicationController
liquify
def index
render :template => 'more_awesome_index_template'
end
end
The last step is to tell Liquidizer where to load the liquid templates from. The way to implement this is completely up to you. For example, you can associate the templates with the Blog model (to follow the blog example) and have something like current_blog, which is loaded by the current subdomain.
In any case, you need to provide a +current_liquid_templates+ method, which should return collection of liquid templates to use. This method should return something that responds at least to +find_by_name+ which returns an object that responds to +content+ which returns a string containing the liquid template.
The easies way to do this, is to have a +LiquidTemplate+ model and return a collections of those in the +current_liquid_templates+. Liquidizer provides one such model for you, but you will probably want to use your own. To make your life easier, there is a module Liquidizer::LiquidTemplate which you can include into your template model to extend it with some helpful methods (see the docs for more info):
class Blog < ActiveRecord::Base
has_many :liquid_templates
end
# The
class LiquidTemplate < ActiveRecord::Base
include Liquidizer::LiquidTemplate
belongs_to :blog
end
class ApplicationController < ActionController::Base
private
def current_liquid_templates
current_blog.liquid_templates
end
end
And that's it. You are now ready to kick asses!
== Instance variables
All instance variables that you assign in the controller are automatically available in the liquid templates. The variable will be automatically wrapped in a "drop", if necessary (please check the liquid docs for more details about what types can be passed directly to liquid templates and what are drops). For a class +Foo+, a +FooDrop+ will be used if it exists. If variable is not compatible with liquid and there is no corresponding drop class, it won't be passed to the template.
Example:
A controller:
class PostsController < ActiveRecord::Base
liquify
def show
@post = current_blog.posts.find(params[:id])
end
end
A drop:
class PostDrop < Liquid::Drop
def initialize(post)
@post = post
end
def title
filter_nasty_stuff(@post.title)
end
end
Then you can do this in your liquid template:
<h1>{{ post.title }}</h1>
<!-- more stuff ... -->
And the post.title will call PostDrop#title, filtering all nasty stuff.
If the instance variable is an array (also association collection, active record scope or this kind of stuff), all it's elements will be dropified too:
class PostsController < ActiveRecord::Base
liquify
def index
@posts = current_blog.posts
end
end
Then in your liquid tempalte:
{% for post in posts %}
{% endfor %}
The way how variables are wrapped with drops can be completelly customized by overriding the +dropify+ method.
== Default templates
The Liquidizer::LiquidTemplate module gives your model capability to fallback to a default template, if one is not found in the database. The default templates are stored in db/liquid_templates. You can configure this location by setting the Liquidizer.template_path:
Liqduidizer.template_path = Rails.root.join('app', 'views', 'liquid_templates')
== TODO
There are several possible improvements to this gem:
Extend it so it can handle different template systems in addition (unlikely, since I don't need it and can't be bothered :) )
The Rails 3 has improved ActionView API and as far as I understand, abstracts away the concept of template storage. Taking advantage of this could potentialy simplify this gem. Also, support Rails 3 in general would be nice.
This does some nasty hacks into the ActionController::Base's render method. That method originaly has quite liberal API, allowing to specify it's parameters in many ways. To make this gem simple, some of those ways were sacrificed. Would be nice to support them too.
Potentialy many more. In any case, it's open source project (MIT), so no fear, fork me and hack away!
== Legal stuff
Copyright (c) 2010 Adam Cigánek [email protected]. Released under the MIT License: www.opensource.org/licenses/mit-license.php