-
Notifications
You must be signed in to change notification settings - Fork 12
Using Liquid without Rails
Want to use Liquid along with some plain old Ruby code?
First, of course, you need to tell Ruby about the Liquid library.
Assuming that you’re in the liquid directory, as cloned from GitHub,
just require 'lib/liquid'.
The big secret, however, is creating a to_liquid method for any classes you want Liquid to access.
Let’s say you have a Product class:
class Product
attr_accessor :name, :price
def initialize(name,price)
@name = name
@price = price
end
end
And you have a chunk of text that you want to Liquidize by inserting some details about your products:
sentence = "I'm running to the store with {{ product.price }} dollars in my pocket "
sentence += "to buy a {{ product.name }}."
And you create a product and parse the sentence:
my_purchase = Product.new('box of sausages', 20)
puts Liquid::Template.parse(sentence).render('product' => my_purchase)
You excitedly execute your code, expecting to see your beautiful new sentence, but instead you get:
I'm running to the store with Liquid error: undefined method `to_liquid' for #<Product:0x1388c08 ...
to_liquid ???
Where did that come from?
Turns out you need to add a method to your class that returns your instance variables as part of a hash.
(Liquid was built to be very paranoid about not letting people access something they’re not supposed to,
so you need to explicitly let Liquid know how to access everything in your object.)
So, we update the Product class description to include that method:
class Product
attr_accessor :name, :price
def initialize(name, price)
@name = name
@price = price
end
def to_liquid
{ "name" => self.name,
"price" => self.price }
end
end
Run that again and presto:
I'm running to the store with 20 dollars in my pocket to buy a box of sausages.
However, that’s a bit verbose, so you may want to use liquid_methods, instead:
class Product
attr_accessor :name, :price
liquid_methods :name, :price
def initialize(name, price)
@name = name
@price = price
end
end
(Thanks to Tom’s Jekyll code for helping me figuring this out.)
Every variable rendered by liquid must either:
- implement a
to_liquidmethod; or - be a Proc. This is called with the
Liquid::Contextas a parameter and must return an object which responds toto_liquid
Liquid defines to_liquid for the following standard classes: String, Array, Hash, Numeric, Time, DateTime, Date, TrueClass, FalseClass, NilClass. For each of these, to_liquid just returns the object itself. (See: lib/liquid/extensions.rb)
If the value returned by to_liquid is inserted into the output, it will be further converted with to_s (actually this is done implicitly by Array#join – see lib/liquid/template.rb)
When used in a chained expression like foo.bar or foo['bar'], the result of to_liquid on the left-hand subexpression must either:
- respond to
has_key?and[](i.e. duck-type like a Hash); or - respond to
fetchand[]with an Integer argument (i.e. duck-type like an Array); or - respond to
sizeorfirstorlast
In each case, the object returned after applying the right-hand subexpression must also respond to to_liquid or be a Proc which returns an object that responds to to_liquid. In the latter case, it will only be called if the previous element in the chain also responds to []= – that is, if the value can be cached in the parent object.
(See: lib/liquid/context.rb)
>> assigns = {"foo"=>{"bar"=>{"baz"=>lambda { |x| Time.now } }}}
=> {"foo"=>{"bar"=>{"baz"=>#<Proc:0xb7cd9af8@(irb):11>}}}
>> t = Liquid::Template.parse("{{ foo.bar.baz }}")
=> #<Liquid::Template:0xb7d2e738 ... >
>> t.render(assigns)
=> "Fri Jun 05 11:23:56 +0100 2009"
>> assigns
=> {"foo"=>{"bar"=>{"baz"=>Fri Jun 05 11:23:56 +0100 2009}}}