In Ruby, How to Write Code Inside a Class So That Getter Foo and Setter Self.Foo = ... Look More Similar

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



Leave a reply



Submit