This extension allows to use any method of a model (or any other
object) that you explicitly want to allow to liquid. For associations,
you have just to add one line of code in each model that you want to
make available to liquid.
You can do it very simply with just one line of code:
class SomeClass # could be an AR model
liquid_methods :attrA, :methB, :associationC, ...
...
end
Here it is:
liquid_module_ex.rb
================
# 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 defines 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
=====================
Improvements/comments are welcome ;-)
dd
end
--
Tobi
http://shopify.com - modern e-commerce software
http://typo.leetsoft.com - Open source weblog engine
http://blog.leetsoft.com - Technical weblog
Regards
Domizio
WOW! if this is as simple as it looks (and it apparently is!!!), I'd
have to say that I'm mighty impressed - both with you, and with
Ruby...
Just one question/clarification, let me see if I got this right:
you *need* to call 'liquid_methods' in your class definition - and
then only after that can you simply very easily (without calling
'liquid_methods' again) add drop methods by simply defining the
methods in <MyClass>::LiquidDropClass? Perhaps adding into the
documentation this note, as well as the distinction that the drop
methods defined in these 2 different ways are really different animals
would be good...
All in all, great stuff!!!
-jf
--
Find me (temporarily) at http://RUBBEDCode.com
In the meantime, here is your PSA:
"It's so hard to write a graphics driver that open-sourcing it would not help."
-- Andrew Fear, Software Product Manager, NVIDIA Corporation
http://kerneltrap.org/node/7228
that's just a little bit of ruby meta-programming. It happens that I
am a very lazy programmer, so I always try to find the way to write as
less code as possible. BTW I love metaprogramming: if you like it too,
you could find a few very interesting tricks in the meta-tools
library.
> Just one question/clarification, let me see if I got this right:
>
> you *need* to call 'liquid_methods' in your class definition - and
> then only after that can you simply very easily (without calling
> 'liquid_methods' again) add drop methods by simply defining the
> methods in <MyClass>::LiquidDropClass? Perhaps adding into the
> documentation this note, as well as the distinction that the drop
> methods defined in these 2 different ways are really different animals
> would be good...
I'm not sure your definition "these 2 different ways are really
different animals" applies here: it depends on the point of view. :-)
Indeed what this extension does is just creating the drop class for
you, buti t is a regular drop class, just meta-programmed instead of
manually-programmed, so if you add your own method to the same class,
you are doing the same thing, just in a manual way.
To better explain how it works, what the liquid_method called from
<YourClass> does is the following:
1. it adds a #to_liquid method to <YourClass> (as you usually should
do). That method will return an instance of
<YourClass>::LiquidDropClass (i.e. it will return a regular drop
object)
2. it creates the needed <YourClass>::LiquidDropClass as a subclass of
Liquid::Drop (so a regular Drop class)
3. It adds a simple initialize method to it,that is used to store the
<YourClass> instance
4. it adds also the method passed as the arguments. Each method just
calls the same-named method on the stored <YourClass> instance
That's it.
The added benefit is that since in the generated ::LiquidDropClass
there are defined ONLY the methods you passed with the liquid_method
call, all the other methods in your class are not available to the
drop, so it is safe.
Another added benefit is that you can chain AR association very easily
with that. For example consider the following example (taken from the
extension tests): imagine that the ':chained*' symbols represent
associations:
class TestClassA
liquid_methods :allowedA, :chainedB
def allowedA
'allowedA'
end
def restrictedA
'restrictedA'
end
def chainedB
TestClassB.new
end
end
class TestClassB
liquid_methods :allowedB, :chainedC
def allowedB
'allowedB'
end
def chainedC
TestClassC.new
end
end
class TestClassC
liquid_methods :allowedC
def allowedC
'allowedC'
end
end
class TestClassC::LiquidDropClass
def another_allowedC
'another_allowedC'
end
end
Somewhere in your controller:
@a = TestClassA.new (notice that you pass YOUR object, not the
instance of a drop object)
that will allow you to write in the template:
{{ a.chainedB.chainedC.allowedC }}
{{ a.chainedB.chainedC.another_allowedC }}
The complete patch is already available for download in the google
code.
Enjoy and hank you for your positive comments.
ciao
Domizio
:) yeah, as it turns out, I love metaprogramming too! I've mainly been
a "drive-by observer", though. I should really sit down, and pick up
on that...
> > Just one question/clarification, let me see if I got this right:
> >
> > you *need* to call 'liquid_methods' in your class definition - and
> > then only after that can you simply very easily (without calling
> > 'liquid_methods' again) add drop methods by simply defining the
> > methods in <MyClass>::LiquidDropClass? Perhaps adding into the
> > documentation this note, as well as the distinction that the drop
> > methods defined in these 2 different ways are really different animals
> > would be good...
>
> I'm not sure your definition "these 2 different ways are really
> different animals" applies here: it depends on the point of view. :-)
>
I think they are. The key thing (in my mind) would be the difference
of scopes - you are restricted to having to do things through methods
of <MyClass> if you define your drop method in
<MyClass>::LiquidDropClass, whereas if you define your drop method as
a regular <MyClass> method, and then use the 'liquid_methods' call on
it, you're pretty much free to do whatever you want (ie. access class
variables, instance variables freely) inside your "Class aka Liquid"
method...
> Indeed what this extension does is just creating the drop class for
> you, ...
Yeah, I pretty much got that already, but thanks! I definitely loved
your "AR chaining code/example"...
I tried Liquid for the first time just 4 days ago, so I am not sure
to fully understand your point., although it sound interesting. Are
you sayng that in the conventional way that drop classes are used,
you have no reference to object of class <YourClass>? If it is so,
then it's just a matter of passing arguments when you create the drop
object, and you could do exactly what my extension does. If you do it
without any to_liquid method, chaining becomes a little tricky though.
I mean something like:
class YourDropClass
def initialize(o)
@your_object = o
end
def methodA
@your_object.anything
end
def association
AssociationDropClass.new(@your_object.any_association)
end
end
class AssociaitionDropClass
def initialize(o)
@your_object = o
end
def methodX
@your_object.anythingX
end
end
@drop = YourDropClass.new(YourClass.find :first)
and you will do pretty much the same, just with a lot more typing,
or... I probably missed the point :-)
ciao
Domizio
No, you have. I'm only saying that you don't have access to the stuff
that you would have access to as an actual method of <MyClass>... You
know, stuff like instance variables, and others... (sorry about the
Class variables note! turns out that I thought/typed too quickly when
I said that one...)
class MyClass
liquid_methods :firstclassmethod
def someprocessing
@var1 = "some instance var"
@var2 = 5
@var3 = "rubbedcode.com"
end
def firstclassmethod
@var1
end
end
class MyClass::LiquidDropClass
def dropmethod
# won't be able to access @ivar here...
@var2*2
end
end
Maybe we are just talking about convention, about how a drop class is
usually built, because IMHO it just depends on what you pass to your
drop object, if the drop object conains a reference to your ovject,
then you can access whatever you want e.g.:
instead this
@var2*2
you can do this:
@object.instance_variable_get(:var2)*2
that is exactly the same that you have to write also if you use my
extension.
ciao
dd
'self' is different, but hmm, yeah I guess I forgot about
instance_variable_get... Thanks for the correction!
On 7/8/07, dd <dd.n...@gmail.com> wrote:
>
> Maybe we are just talking about convention, about how a drop class is
> usually built, because IMHO it just depends on what you pass to your
> drop object, if the drop object conains a reference to your ovject,
> then you can access whatever you want e.g.:
> instead this
> @var2*2
> you can do this:
> @object.instance_variable_get (:var2)*2
It's for this reason that you can reopen the ::LiquidDropClass and add
there the methods you don't want to see in the model. Both needs are
easily fulfilled by using liquid_methods (quick reuse of your model
methods) AND custom drop methods (that you can define in the
<YourClass>::LiquidDropClass). In other words, you should put in
the ::LiquidDropClass everything is not yet in the model .
> * As you may know, Drops aren't supposed to act like classes, they're
> actually more like a Hash. The commonly used dot-notation just makes them
> look this way (you can also do {{ drop[meth] }}). You can't actually pass an
> argument to a drop method (without some nasty before_method hackery, see
> [1]). Therefore you may want to raise an exception or something when someone
> tries to use liquid_methods on a method that takes arguments.
I see that the main policy of drop classes is just returning output
generated only if/when needed. One possible interpreatation of that is
"Drops are actually more like a Hash". If you want to take that
restriction on your code, you can, but why should you take that
restriction if you can take advantage from the fact that Drops are
actually classes and are used as instances. I mean: the fact that you
pass a Drop instance to an assign and not a Drop class, opens a lot of
possibility and somehow clashes with the interpretation "Drops aren't
supposed to act like classes". If they really were so, you would have
no reason to use instances, but you could have just class methods and
use that like simple functions, i mean, if that restriction would have
been taken seriously:, Drops should have been designed in a different
way.
On the other hand, if you mean that from the point of view of the user
that edits the template, drops are more like hashes, well, every key
that a user uses in the template is like a hash-key, and that is a
good thing, although i am not so sure that the no-arguments
restriction is a good thing.
I am not reluctant to allow users to pass arguments with their
templates, specially when the arguments are simple arguments. For
esample you want to display a list of elements and want to allow the
user to decide how much items it should show in a range 5..50. Passing
a simple integer in the template itself could be very straightforward
to understand and easy to use.
Anyway, since I am really new to Liquid, what is the supposed way to
pass argument in the template?
ciao
Domizio