Passing variables to model instance methods in Liquid templates
I've been playing around with the Liquid templating engine this weekend, and I wonder if开发者_JAVA技巧 the following is possible.
Say I have a latest_posts
method in a Blog
model, which I can pass an integer to to get the latest N posts. Is it possible to use that method in a liquid template?
For example:
class Blog
has_many :posts
def latest_posts(n)
posts.latest(n) # using a named scope
end
def to_liquid(*args)
{
'all_posts' => posts.all, # allows me to use {% for posts in blog.all_posts %}
'last_post' => post.last, # allows me to use {% assign recent = blog.last_post %}
'latest_posts' => posts.latest_posts(args[0]) # how do I pass variables to this?
}
end
end
In the simplified example above, in my liquid templates I can use blog.all_posts
and blog.last_post
, but have no idea how I would do anything like blog.latest_posts: 10
.
Can anyone point my in the right direction?
One idea I thought of was to create a Liquid filter and pass both the Blog object and an integer to that. Something like:
{% for post in blog | latest_posts(10) %}
- but haven't tried that yet as feel like I'm stabbing around in the dark a bit. Would appreciate some help from more experienced Liquid users.
Answering my own question here, I found a solution documented in the Liquid groups pages.
Essentially, I needed to create a drop for the latest posts - a LatestPostsDrop
- and kind of hack passing a variable to it using the before_method
method. Here is the complete solution:
class Blog
has_many :posts
def latest_posts
LatestPostsDrop.new(posts)
end
def to_liquid
{
'all_posts' => posts.all,
'last_post' => post.last,
'latest_posts' => latest_posts
}
end
end
class LatestPostsDrop < Liquid::Drop
def initialize(posts)
@posts = posts
end
def before_method(num)
@posts.latest(num) # Post.latest is a named scope
end
end
Doing the above, allows you to iterate through any number of latest posts using something like:
{% for post in blog.latest_posts.10 %} # the last attribute can be any integer
<p>{{ post.title }}</p>
{% endfor %}
It seems a bit hacky, but it works :)
I think liquid is a fantastic template system. Congrats on investigating/using it.
By default, none of the model's methods are available to the liquid template. This is a good thing. You then specify which methods shall be available. (A white list.)
I use an extension to Module which was sent on the mailing list. Complete extension is below. It handles the Liquid::Drop creation for you by adding a simple #liquid_methods method to classes and modules.
Then, in your models, just do:
class Blog
# id
# name
has_many :posts
def latest_posts(n)
posts.latest(n) # using a named scope
end
def latest_10_posts;latest_posts(10); end
liquid_methods :id, :name, :posts, :latest_10_posts
end
I'm not sure offhand how/if you can pass params into a drop. Ask on the Liquid mailing list. I think you can.
Added: I now re-read your question and see that you really want to send in that param to the method. You can send in more than one argument/parameter to a Liquid filter. So you could have a filter:
# Define as a Liquid filter
def latest_posts(blog, n)
blog.latest(n)
end
# then call the filter in a template:
{{ blog2 | latest_posts: 10 }}
# Note that the second param is after the filter name.
In this example, also remember that you'll need to declare liquid methods in the Post class too.
Here is the module extension.
# By dd -- http://groups.google.com/group/liquid-templates/browse_thread/thread/bf48cfebee9fafd9
# This extension is usesd in order to expose the object of the implementing class
# to liquid as it were a Drop. It also limits the liquid-callable methods of the instance
# to the allowed method passed with the liquid_methods call
# Example:
#
# class SomeClass
# liquid_methods :an_allowed_method
#
# def an_allowed_method
# 'this comes from an allowed method'
# end
# def unallowed_method
# 'this will never be an output'
# end
# end
#
# if you want to extend the drop to other methods you can define more methods
# in the class <YourClass>::LiquidDropClass
#
# class SomeClass::LiquidDropClass
# def another_allowed_method
# 'and this is another allowed method'
# end
# end
# end
#
# usage:
# @something = SomeClass.new
#
# template:
# {{something.an_allowed_method}}{{something.unallowed_method}}{{something.another_allowed_method}}
#
# output:
# 'this comes from an allowed method and this is another allowed method'
#
# You can also chain associations, by adding the liquid_method calls in the
# association models.
#
class Module
def liquid_methods(*allowed_methods)
drop_class = eval "class #{self.to_s}::LiquidDropClass < Liquid::Drop; self; end"
define_method :to_liquid do
drop_class.new(self)
end
drop_class.class_eval do
allowed_methods.each do |sym|
define_method sym do
@object.send sym
end
end
def initialize(object)
@object = object
end
end
end
end
精彩评论