extension for drops

48 views
Skip to first unread message

dd

unread,
Jul 6, 2007, 1:39:59 PM7/6/07
to Liquid Templates
I wrote a little extension of the Module class that could be pretty
powerful and safe with very little code.
(you can also see a old post about this topic
http://groups.google.com/group/liquid-templates/browse_thread/thread/8503a069f64f2041/597dab903ced2e78?lnk=gst&q=Drop&rnum=2#597dab903ced2e78).

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

Tobias Lütke

unread,
Jul 6, 2007, 2:06:05 PM7/6/07
to liquid-t...@googlegroups.com
Very nice, i really like that. Can you add this as a patch to google
code with some test coverage?


--
Tobi
http://shopify.com - modern e-commerce software
http://typo.leetsoft.com - Open source weblog engine
http://blog.leetsoft.com - Technical weblog

dd

unread,
Jul 6, 2007, 6:32:11 PM7/6/07
to Liquid Templates
Nice to know that you like it and thank you for your appreciation: I
will add a little improvement and I hope I will post the patch anytime
soon during the next week.

Regards
Domizio

Jeffrey 'jf' Lim

unread,
Jul 7, 2007, 2:07:10 PM7/7/07
to liquid-t...@googlegroups.com
On 7/7/07, dd <dd.n...@gmail.com> wrote:
>
> I wrote a little extension of the Module class that could be pretty
> powerful and safe with very little code.
> (you can also see a old post about this topic
> http://groups.google.com/group/liquid-templates/browse_thread/thread/8503a069f64f2041/597dab903ced2e78?lnk=gst&q=Drop&rnum=2#597dab903ced2e78).
>

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

dd

unread,
Jul 7, 2007, 9:35:52 PM7/7/07
to Liquid Templates
> 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...

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

Jeffrey 'jf' Lim

unread,
Jul 8, 2007, 1:06:59 AM7/8/07
to liquid-t...@googlegroups.com
On 7/8/07, dd <dd.n...@gmail.com> wrote:
>
> > 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...
>
> 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.
>

:) 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"...

dd

unread,
Jul 8, 2007, 2:19:51 AM7/8/07
to Liquid Templates
> > 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...

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

Jeffrey 'jf' Lim

unread,
Jul 8, 2007, 2:52:58 AM7/8/07
to liquid-t...@googlegroups.com
On 7/8/07, dd <dd.n...@gmail.com> wrote:
>
> > > 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...
>
> 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>?

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

dd

unread,
Jul 8, 2007, 9:55:24 AM7/8/07
to Liquid Templates
> 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...

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

Jeffrey 'jf' Lim

unread,
Jul 8, 2007, 10:26:49 AM7/8/07
to liquid-t...@googlegroups.com
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
>
> that is exactly the same that you have to write also if you use my
> extension.
>

'self' is different, but hmm, yeah I guess I forgot about
instance_variable_get... Thanks for the correction!

Ian Leitch

unread,
Jul 9, 2007, 4:55:06 AM7/9/07
to liquid-t...@googlegroups.com
Nice work, I could probably put this to use in a few places.

I have a couple of concerns though:

* I like that this combats a certain amount of duplication inherent when using Drops, although at the same time, I find that my drop methods also perform actions I wouldn't like to see in a Model (which is where I would envision using this). E.g url creation, escaping strings etc. For quick reuse though, this could be a real time saver, and also lower the barrier for people looking at using Liquid.

* 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.

1: http://groups.google.com/group/liquid-templates/browse_thread/thread/dc204c8a783b5f5e/80ec33b24a643660?lnk=gst&q=drop+documentation&rnum=6#80ec33b24a643660

Cheers
Ian

On 08/07/07, Jeffrey 'jf' Lim <jfs....@gmail.com> wrote:

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

dd

unread,
Jul 9, 2007, 10:24:20 AM7/9/07
to Liquid Templates
> * I like that this combats a certain amount of duplication inherent when
> using Drops, although at the same time, I find that my drop methods also
> perform actions I wouldn't like to see in a Model (which is where I would
> envision using this). E.g url creation, escaping strings etc. For quick
> reuse though, this could be a real time saver, and also lower the barrier
> for people looking at using Liquid.

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

Reply all
Reply to author
Forward
0 new messages