In Ruby, how to write code inside a class so that getter foo and setter self.foo = ... look more similar?
Since local scope takes precedence, when you say foo = something
, a local variable foo
will be created and assigned the contents of something
.
The reason you can write foo
in order to use the getter is because Ruby will move up in scope when it can't find a variable with that name and it will eventually find the method.
If there is a local variable with the same name as the getter method, Ruby will use its value instead:
class Foo
attr_accessor :foo
def initialize
@foo = :one
end
def f
foo = :two
foo
end
end
Foo.new.f
# => :two
In order to make it clear that you want to access the setter, you must write self.foo = something
. That will tell Ruby you want to execute the foo=
method on the self
object with something
as parameter.
Why do Ruby setters need self. qualification within the class?
The important thing to remember here is that Ruby methods can be (un)defined at any point, so to intelligently resolve the ambiguity, every assignment would need to run code to check whether there is a method with the assigned-to name at the time of assignment.
self.class_eval DEF ... DEF
Everything between <<DEF
and DEF
is just a string and the #{ ... }
s work on that string like any other.
class_eval
will cause the interpreter to run on the string in the context of the module.
So, if you know what attr
and type
are then you can work out what code is being run to add methods to the class.
Lets say attr
is "foo"
and type
is "Bazzle"
. The code being run would be:
def foo
if defined?(@foo)
@foo
else
@foo = if self.foo_id
Bazzle.get(self.foo_id)
else
nil
end
end
end
def foo=(value)
self.foo_id = value.key
@foo = value
end
Ruby/Rails: Understanding ruby getter-setter methods and instances
When you do
@dog = Dog.new
You do two spearate things
1) Create an instance variable @dog for whatever object your code is currently inside
2) Instantiate a new instance of Dog (with all its methods and attributes) and assign a reference to it to @dog
@dog is a variable, that just happens to point at the Dog instance ("instance of class" generally same meaning as "object") you created at that point. You can set other variables to point to the same instance, and in Ruby this is generally how you pass data around. Objects contain instance variables, and those instance variables point to yet more objects.
Using the assignment operator (i.e "=") you can point a variable at any other object.
To answer your questions in turn:
When I am in the owner class and I call @dog.popularity how does it
know the value of popularity for that instance?
You have to be careful in Ruby (and OO languages in general) to differentiate between class and object in your descriptions and questions. Ruby I'm assuming you are referring to a line of code in the Owner class, and that you intend it to work with an owner object. I'd also assume that @dog is an attribute you have added to Owner.
In which case, Ruby knows because @dog points to the Dog object that you added to owner. Each Dog object has its own copy of all of Dog's instance variables. You do need to take care in Ruby though, because variables point to objects, that you aren't simply passing in the same Dog object to all the owners (i.e. they all effectively share a single dog). So you need to understand when you are creating new instances (via new) and when you are simply handling existing references.
At runtime are all methods processed and then that instance just
always is tied to the value at the time?
No. At runtime, basic Ruby will only perform the assignments that you have coded. Instance variables may not even exist until the code that assigns them has been run. If you use attr_reader etc methods, then the variables will at least exist (but will be nil unless you assign something during initialize)
Should we add attr_* methods in Ruby when creating a Class?
attr_reader
will create methods that will return your instance variables with matching names.
class Foo
attr_reader :bar
def initialize(bar)
@bar = bar
end
end
This means that instances of Foo
will both internally and externally have the method bar
, that will respond with the value of the instance variable @bar
:
foo = Foo.new(1)
foo.bar
=> 1
It's better to depend on behavior (a method) than on data (direct instance variable).
If you access the data through the methods generated by attr_reader
you're ensuring you'll make your code slightly more "future proof" by encapsulating behavior in the same place, so it's always best to access all your instance variables through them.
Another interesting aspect is that you can limit your interface to match your needs. There's no need to expose bar
to the outside world if you only use internally for instance, so:
class Foo
def initialize(bar)
@bar = bar
end
def zoo
bar
end
private
attr_reader :bar
end
Will behave like:
foo = Foo.new(1)
foo.zoo
=> 1
But:
foo.bar
NoMethodError (private method `bar' called for #<Foo:0x00005581544b83d8 @bar=1>)
Please let me know if that makes sense, otherwise I'll update the answer with more details.
Assign class variables in a do block with ruby?
There's the good old yield self
idiom for that too:
class Person
attr_accessor :name, :example
def initialize
yield self if block_given?
end
end
tester = Person.new do |p|
p.name = 'Andy'
p.example = 'Example'
end
puts "#{tester.name}:#{tester.example}"
what does a ruby method ending with an = mean?
the methods ending with "=" are setting the instance variable
look at the answer here: why-use-rubys-attr-accessor-attr-reader-and-attr-writer
Why Rails' attr_getter is actually redundant
As it was mentioned it the comments, it's not about Rails, it's a part of the Ruby core.
@name
is just an instance variable availbale only inside an instance of HelloWorld
. When you remove attr_accessor :name
you won't be able to read or write @name
from outside (actually you can by using instance_variable_get
and instance_variable_set
. But do you really need it?)
class HelloWorld
def initialize(name = "Old name")
@name = name
end
end
hello_world = HelloWorld.new
You can't read it
hello_world.name
undefined method `name' for #<HelloWorld:0x00007f3125ea51e8 @name="Old name"> (NoMethodError)
You can't write it
hello_world.name= 'New name'
undefined method `name=' for #<HelloWorld:0x00007f3125ea51e8 @name="Old name"> (NoMethodError)
It's up to you to decide do you need to give an access to a variable.
If you need only a reader use attr_reader
. If you want to write it use attr_writer
. If you need both, just leave it as it is now with attr_accessor
.
Related Topics
Ruby: How to "Require" a File from the Current Working Dir
Heroku App Fails to Start - 'Require': No Such File to Load -- Sinatratestapp (Loaderror)
Ruby - Activerecord::Connectionnotestablished
Rails 3: Call Functions Inside Controllers
Named Parameters in Ruby Structs
Ruby: Write Escaped String to Yaml
Why Is Devise Not Displaying Authentication Errors on Sign in Page
Appending to Rake Db:Seed in Rails and Running It Without Duplicating Data
How to Add Child Nodes in Nodeset Using Nokogiri
Detect Browser Language in Rails
Using Soap and Other Standard Libraries in Ruby 1.9.2
How to Add Confirm Message with Link_To Ruby on Rails
"Errno::Eaccess...Permission Denied" Running Compass Watch
How to Organize Minitest/Unit Tests
Don't Have Jekyll-Paginate or One of Its Dependencies Installed