In the example below (source),
class Greeter
def initialize(name = "World")
#name = name
end
def say_hi
puts "Hi #{#name}!"
end
def say_bye
puts "Bye #{#name}, come back soon."
end
end
What is def initialize(name = "World") doing?
What is #name = name doing? Is it initializing the variable name and setting the initial value to be name?
I do not see any use of "World" anywhere further as I follow the example from the site (more code), hence my confusion on it's use.
Now let’s create a greeter object and use it:
greeter = Greeter.new("Pat")
greeter.say_hi
# => Hi Pat!
greeter.say_bye
# => Bye Pat, come back soon.
1. What is def initialize(name = "World") doing?
When you create a new instance of Greeter, if no argument is passed then def initialize sets the default #name attribute of your new instance to equal "World".
2. What is #name = name doing? Is it initializing the variable name and setting the initial value to be name?
#name is an instance variable which other methods from the same class can see and use. In this case it takes the name parameter that is passed to the method. See instance variables for more information.
Example
In your example greeter = Greeter.new("Pat"), the argument is defined as"Pat" so the default argument of "World" is ignored.
It's important to understand the difference between instance variables like #name and local variables like name. They're not the same, but can be easily confused.
The initialize function is best explained as this:
# Define an initialize method with an argument called name that has
# a default value of "World"
def initialize(name = "World")
# Assign the value of the name variable, which only exists in the
# scope of this method, to the name instance variable which exists
# for the lifespan of this Greeter object.
#name = name
end
Instance variables are the backbone of Ruby's object oriented programming. They're the most convenient method to persist data between different method invocations.
To exercise the default behaviour you simply fail to specify what name should be:
greeter = Greeter.new
greeter.say_hi
Related
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.
I have a curiosity regarding which is the preferred way of accessing instance variables inside a class that has defined an accessor for that instance variable.
One way would be by referencing the instance variable directly:
class Example
attr_accessor :attribute
def meth
puts #attribute
end
end
The other way would be by calling the reader created by the accessor:
class Example
attr_accessor :attribute
def meth
puts attribute
end
end
It's a small difference, but I am curious which is the preferred approach and why. The only advantage I see on readers vs. direct instance variable access is that it is easier to stub a reader inside a test.
It's best to go through the accessors. For instance, if you access the instance variable directly, and then you later convert the value with the reader, then accessing the instance variable directly won't see that change.
Using the accessors rather than accessing the instance variables directly causes one small quirk when you want to use the accessor to set the value of the instance variable.
Normally when you write:
some_meth 10
...ruby will interpret that as:
self.some_meth(10)
But if you write:
age = 10
...ruby will not interpret that as:
self.age=(10)
Instead, ruby will create a local variable named age and set it to 10, which has no affect on an instance variable named #age.
In order to call the setter for #age, you have to explicitly write self:
self.age = 10
Here is a complete example:
class Dog
attr_reader :age
def age=(val)
#age = val * 7
end
def initialize val
self.age = val #age = val will not call the setter
end
end
d = Dog.new 10
puts d.age #=> 70
I need to load a YAML file (I'm experimenting with SettingsLogic) and I'd like the instance to load the YAML with the same name as it. Briefly:
class MySettings < SettingsLogic
source "whatever_the_instance_is_called.yml"
# Do some other stuff here
end
basic_config = MySettings.new # loads & parses basic_config.yml
advanced_cfg = MySettings.new # loads & parses advanced_cfg.yml
...and so on...
The reason for this I don't yet know what configuration files I'll have to load, and typing:
my_config = MySettings.new("my_config.yml")
or
my_config = MySettings.new(:MyConfig)
just seems to be repeating myself.
I took a look around both Google and Stackoverflow, and the closest I came to an answer is either "Get Instance Name" or a discussion about how meaningless an instance name is! (I'm probably getting the query wrong, however.)
I have tried instance#class, and instance#name; I also tried instance#_id2ref(self).
What am I missing?!
Thanks in advance!
O.K., so with local variable assignment, there are snags, such as that assignment might occur slightly later than local variable symbol addition to the local variable list. But here is my module ConstMagicErsatz that I used to implement something similar to out-of-the box Ruby constant magic:
a = Class.new
a.name #=> nil - anonymous
ABC = a # constant magic at work
a.name #=> "ABC"
The advantage here is that you don't have to write ABC = Class.new( name: "ABC" ), name gets assigned 'magically'. This also works with Struct class:
Koko = Struct.new
Koko.name #=> "Koko"
but with no other classes. So here goes my ConstMagicErsatz that allows you to do
class MySettings < SettingsLogic
include ConstMagicErsatz
end
ABC = MySettings.new
ABC.name #=> "ABC"
As well as
a = MySettings.new name: "ABC"
a.name #=> "ABC"
Here it goes:
module ConstMagicErsatz
def self.included receiver
receiver.class_variable_set :##instances, Hash.new
receiver.class_variable_set :##nameless_instances, Array.new
receiver.extend ConstMagicClassMethods
end
# The receiver class will obtain #name pseudo getter method.
def name
self.class.const_magic
name_string = self.class.instances[ self ].to_s
name_string.nil? ? nil : name_string.demodulize
end
# The receiver class will obtain #name setter method
def name= ɴ
self.class.const_magic
self.class.instances[ self ] = ɴ.to_s
end
module ConstMagicClassMethods
# #new method will consume either:
# 1. any parameter named :name or :ɴ from among the named parameters,
# or,
# 2. the first parameter from among the ordered parameters,
# and invoke #new of the receiver class with the remaining arguments.
def new( *args, &block )
oo = args.extract_options!
# consume :name named argument if it was supplied
ɴς = if oo[:name] then oo.delete( :name ).to_s
elsif oo[:ɴ] then oo.delete( :ɴ ).to_s
else nil end
# but do not consume the first ordered argument
# and call #new method of the receiver class with the remaining args:
instance = super *args, oo, &block
# having obtained the instance, attach the name to it
instances.merge!( instance => ɴς )
return instance
end
# The method will search the namespace for constants to which the objects
# of the receiver class, that are so far nameless, are assigned, and name
# them by the first such constant found. The method returns the number of
# remaining nameless instances.
def const_magic
self.nameless_instances =
class_variable_get( :##instances ).select{ |key, val| val.null? }.keys
return 0 if nameless_instances.size == 0
catch :no_nameless_instances do search_namespace_and_subspaces Object end
return nameless_instances.size
end # def const_magic
# ##instances getter and setter for the target class
def instances; const_magic; class_variable_get :##instances end
def instances= val; class_variable_set :##instances, val end
# ##nameless_instances getter for the target class
def nameless_instances; class_variable_get :##nameless_instances end
def nameless_instances= val; class_variable_set :##nameless_instances, val end
private
# Checks all the constants in some module's namespace, recursivy
def search_namespace_and_subspaces( ɱodule, occupied = [] )
occupied << ɱodule.object_id # mark the module "occupied"
# Get all the constants of ɱodule namespace (in reverse - more effic.)
const_symbols = ɱodule.constants( false ).reverse
# check contents of these constant for wanted objects
const_symbols.each do |sym|
# puts "#{ɱodule}::#{sym}" # DEBUG
# get the constant contents
obj = ɱodule.const_get( sym ) rescue nil
# is it a wanted object?
if nameless_instances.map( &:object_id ).include? obj.object_id then
class_variable_get( :##instances )[ obj ] = ɱodule.name + "::#{sym}"
nameless_instances.delete obj
# and stop working in case there are no more unnamed instances
throw :no_nameless_instances if nameless_instances.empty?
end
end
# and recursively descend into the subspaces
const_symbols.each do |sym|
obj = ɱodule.const_get sym rescue nil # get the const value
search_namespace_and_subspaces( obj, occupied ) unless
occupied.include? obj.object_id if obj.kind_of? Module
end
end
end # module ConstMagicClassMethods
end # module ConstMagicErsatz
The above code implements automatic searching of whole Ruby namespace with the aim of finding which constant refers to the given instance, whenever #name method is called.
The only constraint using constants gives you, is that you have to capitalize it. Of course, what you want would be modifying the metaclass of the object after it is already born and assigned to a constant. Since, again, there is no hook, you have to finde the occasion to do this, such as when the new object is first used for its purpose. So, having
ABC = MySettings.new
and then, when the first use of your MySettings instance occurs, before doing anything else, to patch its metaclass:
class MySettings
def do_something_useful
# before doing it
instance_name = self.name
singleton_class.class_exec { source "#{instance_name}.yml" }
end
# do other useful things
end
Shouldn't you be able to do either
File.open(File.join(File.expand_path(File.dir_name(__FILE__)), foo.class), "r")
or
require foo.class
The first one need not be that complicated necessarily. But if I'm understanding you correctly, you can just use foo.class directly in a require or file load statement.
Adjust as necessary for YAML loading, but #class returns a plain old string.
Well if you have tons of variables to instantiate, I'd personally just create a Hash to hold them, it's cleaner this way. Now to instantiate all of this, you could do a loop other all your yaml files :
my_settings = {}
[:basic_config, :advanced_cfg, :some_yaml, :some_yaml2].each do |yaml_to_parse|
my_settings[yaml_to_parse] = MySettings.new(yaml_to_parse)
end
Make sure your initialize method in MySettings deals with the symbol you give it!
Then get your variables like this :
my_settings[:advanced_cfg]
Unfortunately, Ruby has no hooks for variable assignment, but this can be worked around. The strategy outline is as follows: First, you will need to get your MySettings.new method to eval code in the caller's binding. Then, you will find the list of local variable symbols in the caller's binding by calling local_variables method there. Afterwards, you will iterate over them to find which one refers to the instance returned by super call in your custom MySettings.new method. And you will pass its symbol to source method call.
# encoding: utf-8
class Person
attr_reader :short_name
def initialize(short_name)
#short_name = short_name
end
def greeting_line
short_name = short_name.downcase
"Hello #{short_name}"
end
end
person = Person.new("MS. LEE")
puts person.short_name => "MS. LEE"
puts person.greeting_line => NoMethodError: undefined method `downcase' for nil:NilClass
The exception occurs at "short_name = short_name.downcase" since (short_name = short_name) makes short_name become nil.
Why is "short_name" on the right side is not getting the value from the instance method "short_name"?
When you say
var = value
it always refers to the local variable var, even if you have the methods var and var= defined. (In your case, you have the method short_name defined by attr_reader, but you don't have short_name= defined.)
You have a couple ways around this. You can use the instance variable directly:
#var = value
Or you can use the var= method with an explicit self receiver:
self.var = value
This second form only works if you have a var= method defined, either explicitly, or with attr_accessor or attr_writer.
Now, when you do something like
foo = foo
This always introduces a local variable, foo on the left hand side. When ruby sees foo on the right hand side of the =, it resolves to the local variable foo, since foo is now in scope. So this always just sets foo back to its default value, nil.
In your case in greeting_line scope short_name is pointing to local variable and not to instance method short_name, for this example to work you should use self.short_name (this will tell ruby to use instance method)
def greeting_line
short_name = self.short_name.downcase
"Hello #{short_name}"
end
You simply forgot the # when referencing short_name
#short_name
is an instance variable
short_name
is a local variable bound to the scope of the greeting_line function
Try this:
def greeting_line
short_name = #short_name.downcase
"Hello #{short_name}"
end
You should use:
attr_accessor :short_name
And not attr_reader.
attr_reader produces the following method in your class:
def short_name
#short_name
end
attr_accessor will also produce the method:
def short_name=(val)
#short_name = val
end
Which will allow you to freely set the short_name as it was a local variable.
This is a nice blog post that demonstrates attr_reader, attr_writer and attr_accessor statements in ruby.
In Ruby, isn't an instance variable like #foo and a class variable, ##bar?
In some code, I see a few
self.user_name = #name
or even
a += 1 if name != user_name # this time, without the "self."
# and it is the first line of a method so
# it doesn't look like it is a local variable
what is the self for? I thought it might be an accessor, but then can't it be just user_name instead of self.user_name? And I don't even see any code to make it an accessor, like attr_accessor, and not in the base class either.
In Ruby:
#foo is an instance variable
##bar is a class variable
Instance and class variables are private by default. It means, you can't set or get the value outside the class or the module itself.
If you want to set a value for foo, you need an attribute accessor.
class Model
def foo
#foo
end
def foo=(value)
#foo = value
end
end
For convenience (and performance reason), this is the same of
class Model
attr_accessor :foo
end
Then you can do
m = Model.new
m.foo = "value"
And within the class you can doo
class Model
# ...
def something
self.foo = "value"
# equivalent to
#foo = value
end
end
what is the self for? I thought it might be an accessor
It's a method at least - probably an accessor. self.name = something will call the method name= and self.name, or if no local variable called name exists, just name will call the method name.
but then can't it be just user_name instead of self.user_name?
When invoking the name= method you need the self because name = something would just create a local variable called name. When invoking the name method, it doesn't matter whether you write name or self.name unless there is also a local variable called name.
And I don't even see any code to make it an accessor, like attr_accessor, and not in the base class either.
If there is no call to attr_accessor and no explicit definition of name and name= anywhere, they might be handled by method_missing or defined by a different method than attr_accessor.
isn't self.user_name the same as #user_name
Only if it's defined to be. If you define user_name and user_name=? usingattr_accessorthey will get and set#user_name`. However if you define them through other means (or manually), they can do whatever you want.
For example ActiveRecord uses method_missing to "define" getter and setter methods that correspond to data base columns. So if your ActiveRecord class belongs to a table with a user_name column, you'll have user_name and user_name= methods without defining them anywhere. The values returned by user_name and set by user_name = will also not correspond to instance variables. I.e. there will be no instance variable named #user_name.
Another way to automatically define user_name and user_name= without using instance variables is Struct:
MyClass = Struct.new(:user_name)
Here MyClass will have the methods user_name and user_name=, but no instance variable #user_name will be set at any point.
Welcome in the "scope" hell!
Of course, if we have the full code, it should be easier to explain what you see.
Let me try:
class Woot
attr_accessor :name
def initialize
self.name = 'Bistromathic Drive'
end
def foo
puts name # hum… I don't know name. An attr? Oh Yes!
name = '42' # New local var named 'name'
puts name # Yeah! I know a local named 'name'
puts self.name # I know the attr named 'name'
name = 'Improbability Drive' # Change the local
puts name
puts self.name # attr didn't move
self.name = 'What was the question?'
puts name
puts self.name
end
end
w = Woot.new
w.foo
# => Bistromathic Drive
42
Bistromathic Drive
Improbability Drive
Bistromathic Drive
Improbability Drive
What was the question?
The partial code you show is "suspect" to me, and I'd prefer to explain the base of var scope.