Ruby's attr_accessor magic method defining - ruby

For a better understanding of Ruby I decided to recreate the attr_accessor method. Succesfully. I now understand how it works except for one detail regarding Ruby's syntactic sugar. Here's the attr_accessor method I created:
def attr_accessor(*attributes)
attributes.each do |a|
# Create a setter method (obj.name=)
setter = Proc.new do |val|
instance_variable_set("##{a}", val)
end
# Create a getter method (obj.name)
getter = Proc.new do
instance_variable_get("##{a}")
end
self.class.send(:define_method, "#{a}=", setter)
self.class.send(:define_method, "#{a}", getter)
end
end
The way I see it, I just defined two methods, obj.name as getter and obj.name= as the setter. But when I execute the code in IRB and call obj.name = "A string" it still works, even though I defined the method without a space!
I know this is just part of the magic that defines Ruby, but what exactly makes this work?

When the ruby interpreter sees obj.name = "A string, it will ignore the space between name and = and look for a method named name= on your obj.

Never mind, 'A string' is a perfectly good message name, just try
obj.send "A string" # ^_^
You can even use numbers:
o = Object.new
o.define_singleton_method "555" do "kokot" end
o.send "555"

Related

Dynamically defined setter method is not called from block

I've been writing a DSL, and I'm trying to get a dynamically defined method to be accessible from a lambda. This works fine unless you try to do a setter something= in which case the lambda invocation just sets a local variable instead.
A simplified example:
class Caller
attr_accessor :cmd
def callme
self.class.send(:define_method, "something") { puts "Retrieve Something" }
self.class.send(:define_method, "something=") {|val| puts "Set Something = #{val}" }
instance_exec &cmd
end
end
c = Caller.new
c.cmd = lambda { something = 1 }
c.callme
This also works fine if I use self.something=. However that's less than ideal in the case of a DSL.
Is it possible to get this to work without self. in front of the method?
This has absolutely nothing to do with blocks or dynamically defined methods. It's just simple basic Ruby syntax:
foo = bar
is local variable assignment. Always.
self.foo = bar
is a method call.
Is it possible to get this to work without self. in front of the method?
No.
This is just basic Ruby syntax. The define_method metaprogramming, the instance_exec, the blocks in your code sample are just a red herring, the problem can be demonstrated with a much simpler example:
def foo=(*)
puts 'I was called!'
end
foo=('bar') # even removing spaces and adding parentheses won't help!
self.foo = 'bar'
# I was called!
Note also that foo= is private but was actually called with an explicit receiver (which is illegal for private methods). That's a special exception for setter methods in the rule for private methods, because they otherwise couldn't be called at all, precisely because they would always be interpreted as a local variable assignment.

Dynamic constant assignment

class MyClass
def mymethod
MYCONSTANT = "blah"
end
end
gives me the error:
SyntaxError: dynamic constant assignment error
Why is this considered a dynamic constant? I'm just assigning a string to it.
Your problem is that each time you run the method you are assigning a new value to the constant. This is not allowed, as it makes the constant non-constant; even though the contents of the string are the same (for the moment, anyhow), the actual string object itself is different each time the method is called. For example:
def foo
p "bar".object_id
end
foo #=> 15779172
foo #=> 15779112
Perhaps if you explained your use case—why you want to change the value of a constant in a method—we could help you with a better implementation.
Perhaps you'd rather have an instance variable on the class?
class MyClass
class << self
attr_accessor :my_constant
end
def my_method
self.class.my_constant = "blah"
end
end
p MyClass.my_constant #=> nil
MyClass.new.my_method
p MyClass.my_constant #=> "blah"
If you really want to change the value of a constant in a method, and your constant is a String or an Array, you can 'cheat' and use the #replace method to cause the object to take on a new value without actually changing the object:
class MyClass
BAR = "blah"
def cheat(new_bar)
BAR.replace new_bar
end
end
p MyClass::BAR #=> "blah"
MyClass.new.cheat "whee"
p MyClass::BAR #=> "whee"
Because constants in Ruby aren't meant to be changed, Ruby discourages you from assigning to them in parts of code which might get executed more than once, such as inside methods.
Under normal circumstances, you should define the constant inside the class itself:
class MyClass
MY_CONSTANT = "foo"
end
MyClass::MY_CONSTANT #=> "foo"
If for some reason though you really do need to define a constant inside a method (perhaps for some type of metaprogramming), you can use const_set:
class MyClass
def my_method
self.class.const_set(:MY_CONSTANT, "foo")
end
end
MyClass::MY_CONSTANT
#=> NameError: uninitialized constant MyClass::MY_CONSTANT
MyClass.new.my_method
MyClass::MY_CONSTANT #=> "foo"
Again though, const_set isn't something you should really have to resort to under normal circumstances. If you're not sure whether you really want to be assigning to constants this way, you may want to consider one of the following alternatives:
Class variables
Class variables behave like constants in many ways. They are properties on a class, and they are accessible in subclasses of the class they are defined on.
The difference is that class variables are meant to be modifiable, and can therefore be assigned to inside methods with no issue.
class MyClass
def self.my_class_variable
##my_class_variable
end
def my_method
##my_class_variable = "foo"
end
end
class SubClass < MyClass
end
MyClass.my_class_variable
#=> NameError: uninitialized class variable ##my_class_variable in MyClass
SubClass.my_class_variable
#=> NameError: uninitialized class variable ##my_class_variable in MyClass
MyClass.new.my_method
MyClass.my_class_variable #=> "foo"
SubClass.my_class_variable #=> "foo"
Class attributes
Class attributes are a sort of "instance variable on a class". They behave a bit like class variables, except that their values are not shared with subclasses.
class MyClass
class << self
attr_accessor :my_class_attribute
end
def my_method
self.class.my_class_attribute = "blah"
end
end
class SubClass < MyClass
end
MyClass.my_class_attribute #=> nil
SubClass.my_class_attribute #=> nil
MyClass.new.my_method
MyClass.my_class_attribute #=> "blah"
SubClass.my_class_attribute #=> nil
SubClass.new.my_method
SubClass.my_class_attribute #=> "blah"
Instance variables
And just for completeness I should probably mention: if you need to assign a value which can only be determined after your class has been instantiated, there's a good chance you might actually be looking for a plain old instance variable.
class MyClass
attr_accessor :instance_variable
def my_method
#instance_variable = "blah"
end
end
my_object = MyClass.new
my_object.instance_variable #=> nil
my_object.my_method
my_object.instance_variable #=> "blah"
MyClass.new.instance_variable #=> nil
In Ruby, any variable whose name starts with a capital letter is a constant and you can only assign to it once. Choose one of these alternatives:
class MyClass
MYCONSTANT = "blah"
def mymethod
MYCONSTANT
end
end
class MyClass
def mymethod
my_constant = "blah"
end
end
Constants in ruby cannot be defined inside methods. See the notes at the bottom of this page, for example
You can't name a variable with capital letters or Ruby will asume its a constant and will want it to keep it's value constant, in which case changing it's value would be an error an "dynamic constant assignment error". With lower case should be fine
class MyClass
def mymethod
myconstant = "blah"
end
end
Ruby doesn't like that you are assigning the constant inside of a method because it risks re-assignment. Several SO answers before me give the alternative of assigning it outside of a method--but in the class, which is a better place to assign it.
Many thanks to Dorian and Phrogz for reminding me about the array (and hash) method #replace, which can "replace the contents of an array or hash."
The notion that a CONSTANT's value can be changed, but with an annoying warning, is one of Ruby's few conceptual mis-steps -- these should either be fully immutable, or dump the constant idea altogether. From a coder's perspective, a constant is declarative and intentional, a signal to other that "this value is truly unchangeable once declared/assigned."
But sometimes an "obvious declaration" actually forecloses other, future useful opportunities. For example...
There are legitimate use cases where a "constant's" value might really need to be changed: for example, re-loading ARGV from a REPL-like prompt-loop, then rerunning ARGV thru more (subsequent) OptionParser.parse! calls -- voila! Gives "command line args" a whole new dynamic utility.
The practical problem is either with the presumptive assumption that "ARGV must be a constant", or in optparse's own initialize method, which hard-codes the assignment of ARGV to the instance var #default_argv for subsequent processing -- that array (ARGV) really should be a parameter, encouraging re-parse and re-use, where appropriate. Proper parameterization, with an appropriate default (say, ARGV) would avoid the need to ever change the "constant" ARGV. Just some 2¢-worth of thoughts...

I guess some Ruby internals

class MyClass
def instance_variable=(var)
puts "inside getter"
instance_variable = var
end
def function_1
self.instance_variable = "whatever"
end
def function_2
#instance_variable = "whatever"
end
end
myclass = MyClass.new
myclass.function1
results wiht "inside getter" on the console
myclass.function2
does not.
Im new to Ruby, do not know the difference, couldnt find it on the web...
Thanks in advance!
EDIT:
I assumed that by appending the "=", I overwrite a getter method for an implicitly defined instance variable "instance_variable."
That's also the reason why I called it that way.
Im not used to be allowed to use "=" in function names.
Thats why I assumed it would had some special meaning.
Thanks for your help.
EDIT2:
I just thought I really overwrite the assignment and not only the getter. I got it all mixed up.
Sorry and Thanks.
You have (misleading) named your setter instance_variable. It is not an instance variable, it is a method that sets an instance variable.
When you call self.instance_variable= you are calling that method. When you set #instance_variable directly you are setting the variable itself, and that is why the setter method is not called.
A more idiomatic naming convention would be something like:
def name=(value)
#name = value
end
Of course, for simply, pass-through type getters and setters you can use
attr_reader :name #generates getter only
attr_writer :name #generates setter only, not very common
attr_accessor :name #generates getter and setter
The above methods are syntactic sugar which generate the get and/or set methods for you. They can be overriden later to provide additional functionality if needed.
EDIT: I see that you have made an update and just wanted to point out that this method doesn't set an instance variable at all:
def instance_variable=(var)
puts "inside getter"
instance_variable = var
end
In this case instance_variable is simply a local variable and will be discarded as soon as the method exits. Local variables take precedence over instance methods, and instance variables always begin with a # symbol.

Define a method that is a closure in Ruby

I'm re-defining a method in an object in ruby and I need the new method to be a closure. For example:
def mess_it_up(o)
x = "blah blah"
def o.to_s
puts x # Wrong! x doesn't exists here, a method is not a closure
end
end
Now if I define a Proc, it is a closure:
def mess_it_up(o)
x = "blah blah"
xp = Proc.new {||
puts x # This works
end
# but how do I set it to o.to_s.
def o.to_s
xp.call # same problem as before
end
end
Any ideas how to do it?
Thanks.
This works (tested in irb):
NOTE: This changes only str - not all instances of String. Read below for details as to why this works
another_str = "please don't change me!"
str = "ha, try to change my to_s! hahaha!"
proc = Proc.new { "take that, Mr. str!" }
singleton_class = class << str; self; end
singleton_class.send(:define_method, :to_s) do
proc.call
end
puts str.to_s #=> "take that, Mr. str!"
puts another_str.to_s #=> "please don't change me!"
# What! We called String#define_method, right?
puts String #=> String
puts singleton_class #=> #<Class:#<String:0x3c788a0>>
# ... nope! singleton_class is *not* String
# Keep reading if you're curious :)
This works because you are opening str's singleton class and defining a method there. Because this, as well as the call to Module#define_method, have what some call a "flat scope", you're able to access variables that would be out of scope if you used def to_s; 'whatever'; end.
You may want to check out some of these other "metaprogramming spells" here:
media.pragprog.com/titles/ppmetr/spells.pdf
Why does it only change str?
Because Ruby has a couple interesting tricks up it's sleeves. In the Ruby object model, a method invocation results in the reciever searching not only it's class (and it's ancestors), but also it's singleton class (or as Matz would call it, it's eigenclass). This singleton class is what allows you to [re]define a method for a single object. These methods are called "singleton methods". In the example above, we are doing just that - defining a singleton method name to_s. It's functionaly identical to this:
def str.to_s
...
end
The only difference is that we get to use a closure when calling Module#define_method, whereas def is a keyword, which results in a change of scope.
Why can't it be simpler?
Well, the good news is that you're programming in Ruby, so feel free to go crazy:
class Object
def define_method(name, &block)
singleton = class << self; self; end
singleton.send(:define_method, name) { |*args| block.call(*args) }
end
end
str = 'test'
str.define_method(:to_s) { "hello" }
str.define_method(:bark) { "woof!" }
str.define_method(:yell) { "AAAH!" }
puts str.to_s #=> hello
puts str.bark #=> woof!
puts str.yell #=> AAAH!
And, if you're curious...
You know class methods? Or, in some languages, we'd call them static methods? Well, those don't really exist in Ruby. In Ruby, class methods are really just methods defined in the Class object's singleton class.
If that all sounds crazy, take a look at the links I provided above. A lot of Ruby's power can only be tapped into if you know how to metaprogram - in which case you'll really want to know about singleton classes/methods, and more generally, the Ruby object model.
HTH
-Charles
Feature #1082 implemented in Ruby 1.9.2 makes this an easy task with Object#define_singleton_method:
def mess_it_up(o)
x = "blah blah"
# Use Object#define_singleton_method to redefine `to_s'
o.define_singleton_method(:to_s) { x }
end
The concepts involved are still the same as in my previous answer, which provides a more in-depth description of how this works in Ruby's object model, as well as a Object#define_method definition that is conceptually the same as Ruby 1.9.2's Object#define_singleton_method.
Other methods that you might find useful for similar tasks:
Object#singleton_class
Object#singleton_methods
Object#respond_to_missing? (great blog post here)
This seems to work.
class Foo
def mess_it_up(o)
x = "blah blah"
o.instance_variable_set :#to_s_proc, Proc.new { puts x }
def o.to_s
#to_s_proc.call
end
end
end
var = Object.new
Foo.new.mess_it_up(var)
var.to_s
The problem is that code in def is not evaluated until it's run, and in a new scope. So you have to save the block to an instance variable on the object first and retieve it later.
And define_method doesn't work because it's a class method, meaning you would have to call it on the class of your object, giving that code to ALL instances of that class, and not just this instance.

I want to add a singleton method with a closure to a Ruby object

I wish to add a singleton method to a particular object. I wish that when a instance method on a object is first called, it does some work, and then creates a singleton method for said object of the same name (that contains the work). On all subsequent calls on said object, the singleton method would shadow the instance method and would be called.
I know how to create a singleton method, my problem is that I want the singleton method created to call a lambda (l in this case). def does not create a closure, so I cannot reference variable l (code below) when the method is subsequently called (l.call() is commented out in this example) I wish to know how I can create a closure when creating a singleton method on a particular object. Any help would be appreciated. Thank you.
class Thing
end
t = Thing.new
t2 = Thing.new
Thing.instance_eval() do
def speak
puts "I speak for all Things, I am a class method"
end
end
Thing.class_eval() do
def speak
puts "This is the instance method referenced by the Thing object #{self}"
r = "something I wish to hold on to, maybe expensive to calculate"
l = lambda {puts r}
instance_eval() do
def speak()
puts "This is the singleton method in the Thing object #{self}"
#l.call() # I want this to work! How?
end
end
end
end
Thing.speak()
t.speak()
t2.speak()
t.speak()
t2.speak()
Gives the following results when run: (I changed '<' to '#' so they show up in html)
I speak for all Things, I am a class
method
This is the instance method referenced
by the Thing object #Thing:0x1d204>
This is the instance method referenced
by the Thing object #Thing:0x1d1dc>
This is the singleton method in the
Thing object #Thing:0x1d204>
This is the singleton method in the
Thing object #Thing:0x1d1dc>
You can define a method with a block using define_method.
Example:
class Object
def eigenclass
class <<self; self end
end
end
a = "Hello"
other_word = "World"
a.eigenclass.class_eval do
define_method(:cliche) {"#{self} #{other_word}"}
end
a.cliche # => "Hello World"
"Goodbye".cliche # => NoMethodError: undefined method `cliche' for "Goodbye":String
Here is an implementation of a define_singleton_method method:
class Object
def define_singleton_method(name, &block)
eigenclass = class<<self; self end
eigenclass.class_eval {define_method name, block}
end
end
Now that 1.9 is out, you can use define_singleton_method:
jruby --1.9 -S irb
irb(main):019:0> fn = -> { length * 10 }
=> #<Proc:0x77cb8e0f#(irb):19 (lambda)>
irb(main):020:0> s.define_singleton_method :length_times_ten, fn
=> #<Proc:0x77cb8e0f#(irb):19 (lambda)>
irb(main):021:0> s
=> "a string"
irb(main):022:0> s.length_times_ten
=> 80
Well, one way to do it would be to pack it into an instance variable:
(FYI you can just do class Thing to reopen Thing (it's a little shorter than using #class_eval, and you don't need #instance_eval to define methods from within a method).
class Thing
def speak
puts "This is the instance method referenced by the Thing object #{self}"
r = "something I wish to hold on to, maybe expensive to calculate"
#l = lambda {puts r}
instance_eval do
def speak()
puts "This is the singleton method in the Thing object #{self}"
#l[]
end
end
end
end
This will redefine #speak, but only for that instance of Thing. Other instances of Thing will still have the original definition.
The alternative is, as Chuck pointed out, to use the singleton class (aka metaclass, aka eigenclass) associated with the instance. The singleton class is the object that stores all the singleton methods associated with an object. You can get the context for singleton class evaluation by using the funny class <<object ; ... ; end syntax (similar to the context given by #class_eval by normal classes).
class Thing
def speak
puts "This is the instance method referenced by the Thing object #{self}"
r = "something I wish to hold on to, maybe expensive to calculate"
singleton_class = class <<self # open singleton class context for current instance
# in this context, self now refers to the singleton class itself
self
end
l = lambda {puts r}
singleton_class.class_eval do
# since we used #class_eval, local variables are still in scope
define_method(:speak) do
puts "This is the singleton method in the Thing object #{self}"
# since we used #define_method, local variables are still in scope
l[]
end
end
end
end

Resources