I have following code, which creates instance variables dynamically. If the instance variable does not exist, I use the no method error to create the attr_reader and attr_writer methods dynamically. All works fine, but I don't understand why I don't see the #anything instance variable after having created the first attr_reader.
require "pry-byebug"
class UberHash
attr_accessor :color
def initialize
#color = nil
end
def method_missing(m, *args, &block)
if m.match(/=/)
puts "create attr writer"
self.instance_variable_set("##{m[0..-2]}", args[0])
else
puts "create attr reader"
self.instance_variable_get("##{m}")
end
end
end
uber_hash = UberHash.new
puts "call 1 .................."
p "uber_hash.anything: #{uber_hash.anything}"
p "uber_hash.inspect: #{uber_hash.inspect}"
p uber_hash.anything = "hola"
p uber_hash.inspect
With following results:
call 1 ..................
create attr reader
"uber_hash.anything: "
"uber_hash.inspect: #<UberHash:0x00007f851b1e41a8 #color=nil>"
"#<UberHash:0x00007f851b1e41a8 #color=nil>"
create attr writer
"hola"
"#<UberHash:0x00007f851b1e41a8 #color=nil, #anything=\"hola\">"
After creating the first instance variable anything, with the method instnace_variable_set, I understand that I create an attr_reader right?
Why do I not see the #anything instance variable if I inspect the instance?
You don't see the instance variable in the first inspect. You expect it the be there because in previous line you call uber_hash.anything, right?
Well, the uber_hash.anything call evaluates the else in the #method_missing conditional: self.instance_variable_get("##{m}") - that's why no instance variable is set.
Also, in #method_missing conditional you print two messages: puts "create attr writer" and puts "create attr reader" - they are wrong. It should be: puts "create instance variable" and puts "read instance variable"
After creating the first instance variable anything, with the method instnace_variable_set, I understand that I create an attr_reader right?
No, that is not correct. Your class never creates (or runs) attr_reader. Try this (after running your example commands)
p( uber_hash.methods - Object.new.methods )
and you see only the methods additionally defined in your class be [:color, :color=, :method_missing] in your class.
The method :color is defined because of attr_accessor :color. Remember attr_accessor etc is just a shortcut to define methods.
By contrast, the method :anything is not defined because your class has never defined the method.
Instead, in your class, every time a method uber_hash.anything is called, uber_hash.method_missing is run and does the job, that is, manipulation or viewing of the instance variable #anything.
Secondly, while instance_variable_set does set a value to an instance variable (and creates it if it does not exists), instance_variable_get refers to it only if it exists, else returns nil and does not create an instance variable. That is why #anything is created after instance_variable_set, but not just after instance_variable_get. Try this to see the point (after your definition of the class).
class UberHash
def test_pr
print 'color='; p instance_variable_get("#color")
print 'other='; p instance_variable_get("#other")
p instance_variables # => #other is not defined
print 'other='; p instance_variable_set("#other", 99)
p instance_variables # => #other is NOW defined
end
end
uber_hash.test_pr
Consequently, the behaviour you see is perfectly legitimate.
Note: this past answer explains it.
Related
Sorry that I have no clue how to title this, I'm having a hard time looking this up because I don't know how to say this. Anyway...
Let's say I have a class that looks like this for example:
class Run
def self.starting
print "starting..."
end
def self.finished
print "Finished!"
end
end
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting. Now let's say that I wanted to add some instance variables...
class Run
attr_accessor :starting, :finished
def self.starting
print "starting..."
#starting = true
#finished = false
end
def self.finished
print "finished!"
#starting = false
#finished = true
end
end
What if I wanted to access those instance variables from outside the class? I know that something like print "#{Run.finished}" or print "#{Run.starting}" won't do anything. Can I do that without run = Run.new? Or should I just remove self and then use run = Run.new? (Sorry if this question is a mess.)
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting
There's much more to it than this. In your case you're calling class methods. If you did runner = Runner.new - then you'd be calling instance methods (those are defined without self.
In general, if you need "the thing" to hold some kind of state (like #running = true) then you'd rather want to instantiate an object, and call those methods.
Now, #whatever are instance variables, and you don't have the access to them in class methods.
class Run
attr_reader :running
def start
#running = true
end
def stop
#running = false
end
end
runner = Run.new
runner.running # nil
runner.start
runner.running # true
runner.stop
runner.running # false
I'd recommend you doing some tutorial or basic level book on rails programming, find a chapter about objects and classes. Do some exercises.
In Ruby instance variables are just lexical variables scoped to an instance of a class. Since they are scoped to the instance they always act like a private variable.
If you want to provide access to an instance variable from the outside you create setter and getter methods. Thats what attr_accessor does.
class Person
attr_accessor :name
def initialize(name:)
#name = name
end
def hello
"Hello my name is #{#name}"
end
end
john = Person.new(name: 'John')
john.name = "John Smith"
puts john.hello # "Hello my name is John Smith"
puts john.name # "John Smith"
Methods defined with def self.foo are class methods which are also referred to as singleton methods. You can't access variables belonging to an instance from inside a class method since the recipient when calling the method is the class itself and not an instance of the class.
Ruby also has class variables which are shared by a class and its subclasses:
class Person
##count = 0
def initialize
self.class.count += 1
end
def self.count
##count
end
def self.count=(value)
##count = value
end
end
class Student < Person
end
Person.new
Student.new
puts Person.count # 2 - wtf!
And class instance variables that are not shared with subclasses:
class Person
#count = 0 # sets an instance variable in the eigenclass
def initialize
self.class.count += 1
end
def self.count
#count
end
def self.count=(value)
#count = value
end
end
class Student < Person
#count = 0 # sets its own class instance variable
end
Person.new
Student.new
puts Person.count # 1
Class variables are not used as often and usually hold references to things like database connections or configuration which is shared by all instances of a class.
You can't access instance variables from outside the instance. That is the whole point of instance variables.
The only thing you can access from outside the instance are (public) methods.
However, you can create a public method that returns the instance variable. Such a method is called an attribute reader in Ruby, other languages may call it a getter. In Ruby, an attribute reader is typically named the same as the instance variable, but in your case that is not possible since there are already methods with the names starting and finished. Therefore, we have to find some other names for the attribute readers:
class Run
def self.starting?
#starting
end
def self.finished?
#finished
end
end
Since this is a common operation, there are helper methods which generate those methods for you, for example Module#attr_reader. However, they also assume that the name of the attribute reader method is the same as the name of the instance variable, so if you were to use this helper method, it would overwrite the methods you have already written!
class << Run
attr_reader :starting, :finished
end
When you do this, you will get warnings (you always have warning turned on when developing, do you?) telling you that you have overwritten your existing methods:
run.rb:19: warning: method redefined; discarding old starting
run.rb:2: warning: previous definition of starting was here
run.rb:19: warning: method redefined; discarding old finished
run.rb:5: warning: previous definition of finished was here
class Artist
##song_count = []
attr_accessor :name, :songs
def initialize(name)
#name = name
#songs = []
end
def add_song(song)
#songs << song
end
def print_songs
songs.each {|song| puts song.name}
end
end
So in this example, it uses all two types, #songs and songs.
I'm having a hard time understanding why these are used, instead of using #songs for everything.
And then in this example,
def add_song(song)
self.songs << song
song.artist = self
##song_count +=1
end
Why is self.songs used instead of #songs?
Ok, so I forgot to say one more thing. In the first code snippet above,for method print_songs, why am I able to use songs.each instead of #songs.each? I was expected it to generate an error undefined songs.
Why is self.songs used instead of #songs
Using the method is more flexible. You're abstracting yourself from knowing how exactly it gets/stores data. The less you rely on implementation details, the easier it will be for you to change code later.
One small example, consider this implementation of songs
def songs
#songs ||= []
#songs
end
#songs may or may not have been assigned value prior to invocation of this method. But it doesn't care. It makes sure that #songs does have a sane default value. The concept is called "lazy initialization" and it's very tedious and error-prone to do if you use instance variables directly.
So, when in doubt, always use methods.
Difference between foo and #foo
Instance variables
Instance variables are defined within instance methods, and their names begin with #. Their value is only accessible within the specific object on which it was set. In other words, when we modify the value of an instance variable, the change only applies to that particular instance. Unlike local variables which are only available within the method where they were defined, instance variables are accessible by all methods within the object (instance methods of the class). Instance variables are the most commonly used type of variable in Ruby classes.
class Car
attr_reader :color
def set_color(color_receiverd_as_argument)
#color = color_receiverd_as_argument
end
end
car1 = Car.new
car1.color # Output: => nil
car1.set_color "black"
car1.color # Output: => "black"
car2 = Car.new
car2.set_color "silver"
car2.color # Output: => "silver"
In the example above, notice that:
Trying to access an instance variable before it's initialized will not raise an exception. Its default value is nil.
Changing the value of the color variable in one instance of the Car class does not affect the value of the same variable in the other instances.
Local variables
A local variable within a class is like any other local variable in Ruby. It is only accessible within the exact scope on which it's created. If defined within a method, it is only available inside that method.
class Car
def initialize
wheels = 4
end
def print_wheels
print wheels
end
end
c = Car.new
c.print_wheels # Output: NameError: undefined local variable or method `wheels'…
The self keyword
The self keyword is always available, and it points to the current object. In Ruby, all method calls consist of a message sent to a receiver. In other words, all methods are invoked on an object. The object on which the method is called is the receiver, and the method is the message. If we call "foo".upcase, the "foo" object is the receiver and upcase is the message. If we don't specify an object (a receiver) when calling a method, it is implicitly called on the self object.
Self keyword at class level
When used within a class but outside any instance methods, self refers to the class itself.
class Foo
##self_at_class_level = self
def initialize
puts "self at class level is #{##self_at_class_level}"
end
end
f = Foo.new # Output: self at class level is Foo
Self keyword at instance methods
When inside an instance method, the self keyword refers to that specific instance. In other words, it refers to the object where it was called.
class Meditation
def initialize
puts "self within an instance method is #{self}"
end
end
zazen = Meditation.new # Output: self within an instance method is #<Meditation:0x00000000ab2b38>
Notice that #<Meditation:0x00000000ab2b38> is a string representation of the zazen object, which is an instance of the Meditation class.
Here're the codes from about_classes.rb. I'm not very sure about why the answers are [ ] and [:#name]
class Dog2
def set_name(a_name)
#name = a_name
end
def test_instance_variables_can_be_set_by_assigning_to_them
fido = Dog2.new
assert_equal [ ], fido.instance_variables
#In this case, fido doesn't have any instance_variables,
because it is assigned to a new "Dog2" Hash/class which has none methods?
fido.set_name("Fido")
assert_equal [:#name], fido.instance_variables
#In this case, fido has an instance_variable,
because it uses the set_name methods inherited from "Dog2" classes?
end
assert_raise(SyntaxError) do
eval "fido.#name"
# NOTE: Using eval because the above line is a syntax error.
end
#So Eval here means that if the above returns "fido.#name", give it a SyntaxError?
I added some comments under those 2 cases, see if I understand it correctly.
When the first assert_equal is called, the Dog2 instance (fido) has no instance variables, because none were defined in the initializer or in any other way.
When set_name is called the #name instance variable gets set, so by the time the second assert_equal is called the fido instance does have an instance variable, one, #name, so the fido.instance_variables method returns an array with that one symbol.
Update
In response to the questions you pose in the comments in your code sample:
No, your first two comments are not accurate. It's not because it has no methods, it's because it has no instance variables. The fido instance does have a method, the set_name method.
Your second comment is not accurate because there's no inheritance going on here, fido is an instance of Dog2 and so once the set_name method has been called it has an instance variable, because #name is initialized in that set_name method.
Your final comment/question about the eval is just because if the authors had actually written just fido.#name then the code itself would have failed to run, they wanted it to run but display that if you'd written fido.#name in your code then Ruby would have exited and refused to run your code because of that syntax error.
Another Update
After another question from the OP I wanted to just add, although #name exists inside the set_name method, the point really of this example is to show that in Ruby until set_name is called that #name variable doesn't exist yet. In other languages you would define all the instance variables up front, so they'd always exist for any instantiated object of that class.
Ruby is a much more dynamic language and so until that #name = a_name line of code is actually executed, the #name variable doesn't exist, so is not returned in fido.instance_variables
This would also be true even if that method is called, but that #name = a_name line isn't executed, so, eg.
class Dog2
def set_name a_name
if false
#name = a_name
end
end
end
fido = Dog2.new
fido.set_name "Fido"
fido.instance_variables # => []
Hope that helps.
Is there a way to bind an existing method to an existing instance of an object if both the method and the instance are passed as symbols into a method that does that if the instance is not a symbol?
For example:
def some_method
#do something
end
some_instance = Klass.new(something)
def method_that_binds(:some_method, to: :some_instance)
#how do I do that?
end
Your requirements are a little unusual, but it is possible to do this mostly as you say:
class Person; end
harry = Person.new
barry = Person.new
def test
puts 'It works!'
end
define_method :method_that_binds do |a_method, to|
eval(to[:to].to_s).singleton_class.send(:define_method, a_method, &Object.new.method(a_method))
end
method_that_binds :test, to: :harry
harry.test
# It works! will be sent to STDOUT
barry.test
# undefined method 'test'
This doesn't actually use a named parameter, but accepts a hash with a to key, but you can see you can call it in the way you want. It also assumes that the methods you are defining are defined globally on Object.
The API you want doesn't easily work, because you have to know from which scope you want to access the local variable. It's not quite clear to me why you want to pass the name of the local variable instead of passing the content of the local variable … after all, the local variable is present at the call site.
Anyway, if you pass in the scope in addition to the name, this can be accomplished rather easily:
def some_method(*args)
puts args
puts "I can access some_instance's ivar: ##private_instance_var"
end
class Foo; def initialize; #private_instance_var = :foo end end
some_instance = Foo.new
def method_that_binds(meth, to:, within:, with: [])
self.class.instance_method(meth).bind(within.local_variable_get(to)).(*with)
end
method_that_binds(:some_method, to: :some_instance, within: binding, with: ['arg1', 'arg2'])
# arg1
# arg2
# I can access some_instance's ivar: foo
As you can see, I also added a way to pass arguments to the method. Without that extension, it becomes even simpler:
def method_that_binds(meth, to:, within:)
self.class.instance_method(meth).bind(within.local_variable_get(to)).()
end
But you have to pass the scope (Binding) into the method.
If you'd like to add a method just to some_instance i.e. it's not available on other instances of Klass then this can be done using define_singleton_method (documentation here.)
some_instance.define_singleton_method(:some_method, method(:some_method))
Here the first use of the symbol :some_method is the name you'd like the method to have on some_instance and the second use as a parameter to method is creating a Method object from your existing method.
If you'd like to use the same name as the existing method you could wrap this in your own method like:
def add_method(obj, name)
obj.define_singleton_method(name, method(name))
end
Let's say we have a class A with a method a and a local variable c.
class A
def a; 10 end
end
c = '5'
And we want to add the method A#a to c.
This is how it can be done
c.singleton_class.send :define_method, :b, &A.new.method(:a)
p c.b # => 10
Explanations.
One way to add a method to an object instance and not to its class is to define it in its singleton class (which every ruby object has).
We can get the c's singleton class by calling the corresponding method c.signleton_class.
Next we need to dynamically define a method in its class and this can usually be accomplished by using the define_method which takes a method name as its first argument (in our case :b) and a block. Now, converting the method into a block might look a bit tricky but the idea is relatively simple: we first transform the method into a Method instance by calling the Object#method and then by putting the & before A.new.method(:a) we tell the interpreter to call the to_proc method on our object (as our returned object is an instance of the Method, the Method#to_proc will be called) and after that the returned proc will be translated into a block that the define_method expects as its second argument.
This question pretty much sums up the simple case for dynamically extending the class hierarchy in Ruby.
The problem I'm having is that I want to define this subclass with a DSL, and I think I'm a victim of my own complicated scope.
I have working code which uses a base class:
module Command
class Command
...
end
end
And then each command is implemented as a subclass:
module Command
class Command_quit < Command
def initialize
name = "quit"
exec do
#user.client.should_terminate = true
end
end
end
end
There is a lot of rote and repetition here, and I have envisioned a DSL which cleans this up significantly:
module Command
define :quit do
exec do # this is global.rb:7 from the error below
#user.client.should_terminate = true
end
end
end
As you can see, I want to DRY out the boilerplate as I am only concerned with the contents of #initialize, which sets some metadata (such as name) and defines the exec block (which is the important part).
I have gotten stuck with the following module method:
module Command
def self.define(cmd_name, &init_block)
class_name = "Command_#{cmd_name.to_s}"
class_definition = Class.new(Command)
class_initializer = Proc.new do
name = cmd_name
init_block.call
end
::Command.const_set class_name, class_definition
::Command.const_get(class_name).send(:define_method, :initialize, class_initializer)
end
end
This code yields lib/commands/global.rb:7:in 'exec': wrong number of arguments (0 for 1+) (ArgumentError)
And suppose I have some metadata (foo) which I want to set in my DSL:
module Command
define :quit do
foo "bar" # this becomes global.rb:7
exec do
#user.client.should_terminate = true
end
end
end
I see lib/commands/global.rb:7:in block in <module:Command>': undefined method 'foo' for Command:Module (NoMethodError)
I think I've got my Proc/block/lambda-fu wrong here, but I'm struggling to get to the bottom of the confusion. How should I write Command::define to get the desired result? It seems like although Ruby creates Command::Command_help as a subclass of Command::Command, it's not actually inheriting any of the properties.
When you refer to something in Ruby, it first look up something in local bindings, if it fails, it then look up self.something. self represents a context of the evaluation, and this context changes on class definition class C; self; end, method definition class C; def m; self; end; end, however, it won't change on block definition. The block captures the current self at the point of block definition.
module Command
define :quit do
foo "bar" # self is Command, calls Command.foo by default
end
end
If you want to modify the self context inside a block, you can use BasicObject.instance_eval (or instance_exec, class_eval, class_exec).
For your example, the block passed to define should be evaluated under the self context of an instance of the concrete command.
Here is an example. I added some mock method definition in class Command::Command:
module Command
class Command
# remove this accessor if you want to make `name` readonly
attr_accessor :name
def exec(&block)
#exec = block
end
def foo(msg)
puts "FOO => #{msg}"
end
def run
#exec.call if #exec
end
end
def self.define(name, &block)
klass = Class.new(Command) do
define_method(:initialize) do
method(:name=).call(name) # it would be better to make it readonly
instance_eval(&block)
end
# readonly
# define_method(:name) { name }
end
::Command.const_set("Command_#{name}", klass)
end
define :quit do
foo "bar"
exec do
puts "EXEC => #{name}"
end
end
end
quit = Command::Command_quit.new #=> FOO => bar
quit.run #=> EXEC => quit
puts quit.class #=> Command::Command_quit
Your problem is that blocks preserve the value of self (among other things) - when you call init_block.call and execution jumps to the block passed to define, self is the module Command and not the instance of Command_quit
You should be ok if you change your initialize method to
class_initializer = Proc.new do
self.name = cmd_name # I assume you didn't just want to set a local variable
instance_eval(&init_block)
end
instance_eval executes the block, but with the receiver (in this case your instance of Command_quit as the subclass.
An exception to the "blocks preserve self" behaviour is define_method: in that case self will always be object on which the method is called, much like with a normal method.