If you're coming over to Ruby from the Java world, and want a good primer on what metaprogramming is all about, then definitely read Adding Properties to Ruby Metaprogramatically today.
InfoQ Homepage News Article: Adding Properties to Ruby Metaprogramatically
Article: Adding Properties to Ruby Metaprogramatically
We're proud to publish a feature article by InfoQ's own Werner Schuster, where he walks us through a very simple and familiar example of adding Java-style properties support (declarative getters, setters and change listeners) to Ruby classes. He does so via a Mixin and using elements of Ruby meta-programming. Taking the idea a step forward, he gently guides the reader into usage of design-by-contract principles and gives a teasing taste of what a pluggable type system might look like in a Ruby setting.
If you're coming over to Ruby from the Java world, and want a good primer on what metaprogramming is all about, then definitely read Adding Properties to Ruby Metaprogramatically today.
If you're coming over to Ruby from the Java world, and want a good primer on what metaprogramming is all about, then definitely read Adding Properties to Ruby Metaprogramatically today.
Community comments
Good article, but many errors.
by Jules Jacobs,
Working on fixes and article will be updated.
by Obie Fernandez,
Re: Good article, but many errors.
by Werner Schuster,
Extending Modules
by Werner Schuster,
Re: Extending Modules
by Jules Jacobs,
The article has been re-published with corrections.
by Obie Fernandez,
Listeners and predicate
by Victor Cosby,
Re: Listeners and predicate
by Victor Cosby,
Good article, but many errors.
by Jules Jacobs,
Your message is awaiting moderation. Thank you for participating in the discussion.
The subject of this article is good,but the code is full of bugs and style errors.
define_method("#{sym}".to_sym) {
instance_variable_get("@#{sym.to_s}")
}
Why do you convert sym to a string and then back to a symbol? It's more common to use do...end for blocks where the open brace is not on the same line as the end brace.
define_method("#{sym}=".to_sym) {|value|
instance_variable_set("@#{sym.to_s}", value)
}
You don't need to convert the thing to a symbol. define_methods accepts strings too. You don't have to use sym.to_s either as it's already converted to a string by string interpolation. Same thing for do...end.
define_method(:direction=){|value|
instance_variable_set("@#{sym.to_s}", value)
fire_event_for(:direction)
}
Why do you have :direction= and :direction in there, but also #{sym.to_s}? This code will not work.
define_method("add_#{sym}_listener".to_sym){|x|
@listener[:sym] << x
}
to_sym is not necessary here. @listener[:sym] should be @listener[sym].
"The code for setting up the listener lists and the rest is left to the reader as exercise. (Stop moaning, it's just 3 lines of code)."
Could you give these 3 lines? I don't know how to do it (without cheating by putting multiple things on one line). The fire_event_for will be at least three lines of code. Setting up the listener hash table is also hard with this implementation. So again: please show the code :)
h = CruiseShip.new
h.add_direction_listener(Listener.new())
h.add_bar_listener lambda {|x| puts "Oy... someone changed the property to #{x}"}
h.hello = 10
Where do you define the class Listener? Where does add_bar_listener come from? Where does hello= come from? Why does the bar_listener get called if you call hello=(10)?
class Ship
extend Properties
end
Maybe it's good to explain that extend adds the methods to the Ship class object and not to the Ship instances? This is not the common way to use mixins in Ruby. Well maybe it's better to pretend that it's this simple.
property :speed {|v| v >= 0 && vn< 300 }
This code won't parse. You need parens here.
def property(x, &predicate)
define_method("#{sym}=".to_sym) {|arg|
if(!predicate.nil?)
if !predicate.call(arg)
return
end
end
instance_variable_set("@#{sym.to_s}", arg)
prop_holder = instance_variable_get("@#{sym.to_s}_property_holder")
prop_holder.fire_event(arg) if prop_holder
}
end
OK. to_sym not necessary. "!predicate.nil?" is the same as "predicate". Why do you use braces for the first if but not for the second? to_s is redundant (2x). Where do you set @#{sym}_property_holder? What is prop_holder? What is prop_holder.fire_event?
property :speed, in([0..300]
Missing close paren ;-). Why do you put the range in an array?
property :speed, in(0..300)
def in(x)
lambda{|value| x === value}
end
This will allow you to pass a regex:
property :email, in(/email regex/)
But the name "in" isn't very good now. That's not a problem because you cannot use "in" anyway because it's a reserved word.
I hope you fix the errors. This is a great article if you do.
Working on fixes and article will be updated.
by Obie Fernandez,
Your message is awaiting moderation. Thank you for participating in the discussion.
Thank you for pointing out improvements!
Re: Good article, but many errors.
by Werner Schuster,
Your message is awaiting moderation. Thank you for participating in the discussion.
Wow, thanks for the detailed comments Jules!
I guess it serves me right for copy/pasting code between evolving source code and an evolving article text - nothing good can come from that.
The more stupid errors will be fixed right away;
Let me address your questions:
If I _include_ a Module, the methods will be added as instance methods, but to have
something like this
class Foo
property :xyz
end
they need to be available as class methods. With include, you'd call them as instance
methods so... I guess you'd have to setup the properties in the constructor or somewhere else. If I'm missing a better way to do this, I'd be interested to hear;
well, I don't know, I like the braces; there's a precedence difference between the two, but otherwise it seems to be a matter of taste... also: the word "do" feels like clutter, and I wanted to make the solution seem as clean as possible; There's no real reason why there's a difference for delimiting blocks - Smalltalk uses brackets for _all_ blocks;
Again... thanks for keeping me honest, Jules!
Extending Modules
by Werner Schuster,
Your message is awaiting moderation. Thank you for participating in the discussion.
This include vs extend issue made me curious... I looked into Rails (actually ActiveRecord) and how it does it's has_many etc methods, and it also uses extend (grep the Rails source code for "extend" or "base.extend"). Again, if you want the fancy, declarative look, this seems to be the only way.
Re: Extending Modules
by Jules Jacobs,
Your message is awaiting moderation. Thank you for participating in the discussion.
Here's a nice trick (I think Rails uses a similar thing):
If you need only class methods just using extend in the class that needs the Properties seems cleaner.
The article has been re-published with corrections.
by Obie Fernandez,
Your message is awaiting moderation. Thank you for participating in the discussion.
Thanks to alert reader Jules Jacobs for pointing out areas for improvement.
Listeners and predicate
by Victor Cosby,
Your message is awaiting moderation. Thank you for participating in the discussion.
Here's one way to setup the listeners...
but I'd love to know how you've gotten the predicate business to work. won't even parse.
Re: Listeners and predicate
by Victor Cosby,
Your message is awaiting moderation. Thank you for participating in the discussion.
Ah, looking through Jules's comments, it looks like there was one other error that hasn't been corrected. So here's one implementation. I love it that with Ruby it takes only a minor method signature to property to support the block vs. lambda switch. (I wonder: is there is a way to get the method to support both with a single signature?)