Ruby: access to attr_accessor's method from internal method - ruby

I have a code:
class A
attr_accessor :somevar
def a
somevar = 'something'
puts #somevar
end
def b
send :somevar=, 'something'
puts #somevar
end
end
A.new.a #=> nil
A.new.b #=> 'something'
Why is there a difference? Why can't I just assign an instance variable through a writer? But why local variable has been created instead of method (setter) has been called?

attr_accessor :somevar references the instance variable #somevar. Instance variables must be preceded by the # sign. All other variables like 'somevar' without an # sign in a method are just local variables to that method or scope, not instance variables for the object.
Thus, changing the first line in method "a" to
#somevar = 'something'
will result in the answer you expect.
Related note: you don't have to declare instance variables in Ruby, you just create them with #somevar type notation. The attr_accessor method creates setters and getters for that instance variable.
Methods in Ruby are attached to objects, so in order for class A to invoke its own somevar setter method, you would need to write self.somevar = 'something', otherwise the Ruby parser thinks you are just creating a local variable.
This could be confusing because you could invoke method a from b, by just doing:
def b
send :somevar=,'something'
puts #somevar
a # would invoke its own method 'a'
end
But the setter method somevar= is ambiguous with creating a local variable with the same notation:
somevar='something' # Ruby assumes you want to create a local variable
So to call the somevar setter method you need to explicitly say that you are calling the method on self with:
self.somevar = 'something'
When you called send :somevar=,'something' you were invoking the somevar instance method also.

It's because method a is creating a local variable called somevar. It's just one of Ruby's little quirks. You can get around this by doing self.somevar = 'something' or #somevar = 'something'.

Related

Ruby: Working with instance variables within a case statement [duplicate]

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 2 months ago.
I'm perplexed as to why I need to selectively refer to an instance variable with "self" inside a case statement inside a class method:
I have a class with an instance method #working_dir:
class FileSystem
attr_accessor :sizes, :working_dir
attr_reader :input
def initialize(input)
#input = input.split("\n")
#sizes = Hash.new(0)
#working_dir = []
end
...
end
I've defined a method parse_cmd that performs an operation on #working_dir depending on the outcome of a case statement:
...
def parse_cmd(str)
cmd_arr = str.split(' ')
return unless cmd_arr[1] == 'cd'
case cmd_arr[2]
when '..'
working_dir.pop
when '/'
self.working_dir = ['/']
else
working_dir << cmd_arr[2]
end
end
...
Rubocop/the interpreter yells at me if I exclude the self on self.working_dir = ['/']. Why is this? Why do I need to include it here, but not on other references to #working_dir within the case statement?
Consider a simple example:
class A
attr_accessor :b
def initialize(b)
#b = b
end
def c
b = 42
end
end
a = A.new(27)
# => #<A:0x00007f7999088bc0 #b=27>
a.c
# => 42
a.b
# => 27
Calling a.c is assigning 42 to a local variable b, and is not modifying the instance variable #b.
I'd either need to use self.b = 42 or #b = 42 to ensure I am modifying the instance variable.
In your case, you don't need to use self.working_dir elsewhere because those uses cannot be construed as assigning to a local variable. Because no local variable working_dir exists, the accessor method is used.
In your case statement, you are NOT directly refering to the #working_dir instance variable. Instead, you are using the accessor methods defined by attr_accessor :working_dir at the top of your class.
When calling attr_accessor, it will effectively define two methods on your class:
def working_dir
#working_dir
end
def working_dir=(value)
#working_dir = value
end
This allows you to access the value of the instance variable via the method call as if it were a local variable (but it's not, it's always a method call).
Now, in order to call the setter method working_dir=, Ruby requires that you call it with an explicit receiver (self in your case).
This is because without an explicit receiver, if you assign some value, Ruby will always assign to local variable. With working_dir = 'value', you are thus always creating a local variable named working_dir and assign a value to it.
If you use an explicit receiver however, e.g. self.working_dir = 'value', Ruby knows that this can not be a variable assignment anymore and will thus call your setter method.

What does it mean creating an instance variable outside a class and inside a method?

What does it mean in the ruby language to create an instance variable outside of a class?
e.g:
def my_method:
#animal = "cat"
end
I've also seen it written outside of a method, like this:
#foo = "bar"
Is this just sugar syntax?
thanks!
what it means to create instance of variable outside class in ruby?
Instance variables have nothing to do with classes. They belong to objects, i.e. instances, that's why they are called "instance" variables.
def my_method
#animal = "cat"
end
This does not create an instance variable. It creates a method named my_method, which when you call it assigns to the instance variable named #animal.
There is nothing special about this. When you call this method, it will assign to the #animal instance variable of whatever object you call this method on, just like any other method that assigns an instance variable.
For example here:
class Foo
def my_method
#animal = 'cat'
end
end
foo = Foo.new
bar = Foo.new
If you call foo.my_method, it will assign to the instance variable #animal of foo, if you call bar.my_method, it will assign to the instance variable #animal of bar.
There is actually nothing different between those two examples as far as the instance variable is concerned.
The only interesting question is "Which class is the method defined in?" But that has nothing to do with instance variables.
Methods defined on the top-level become private instance methods of Object.
#foo = "bar"
Again, this is no different than any other assignment to an instance variable. This assigns to the instance variable named #foo of whatever object self is at the moment.
From an instance variable standpoint, there is absolutely nothing special about this. #foo = 'bar' always means "assign to the instance variable named #foo of whatever object self is at the moment". There is nothing else it can mean.
The only interesting question is: "What is self at this point?" But that has nothing to do with instance variables.
At the top-level, self is always the anonymous object commonly called main in the Ruby community.
is just sugar sintaxe?
No, this is not syntactic sugar for anything. It's just an assignment like any other assignment. There is absolutely nothing special about it.
If you do this on the command line:
% ruby -e "puts self; puts self.class"
main
Object
You'll see at the top-level scope (not inside a class or module), you'll be in the scope of an instance of Object called main.
What you already know about using instance variables and defining methods etc are the same for main as it is for class and module.
class Foo
end
foo_main = Foo.new # create instance of `Foo`
class Foo
self # then, inside scope of class `Foo`
# => Foo
def foo # define instance method for class `Foo`
puts "foo"
end
end
foo_main.foo # calls method on an instance of `Foo`
# => foo
# at the top level, it's as if the Ruby runtime has already done something
# like "Object.new" and then put you "inside" the created instance
my_main = self # inside scope of `main` already
# => main
def bar # define instance method for class `Object`
puts "bar"
end
# below are all equivalent
my_main.bar
self.bar
bar # implied `self` is main, an instance of `Object`
You may have noticed:
inside Foo, self returns Foo, a class
at top-level, self returns main, an instance of class Object
Although it seems like they're two different things, what makes Ruby work the way it does is they're actually the same! More specifically, it's:
inside Foo, self returns Foo, an instance of class Class
at top-level, self returns main, an instance of class Object

Binding method to instance

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.

Ruby instance variables and methods

Sometimes I see an instance variable defined as #my_variable. However, sometimes I see self.my_variable. When is each used?
Instance variables (#variable) correspond to private variables in other languages. self.myvariable is actually not a variable, but a call to a method. Similarly, if you write self.myvariable = something, it is actually a call to self.myvariable=(something). This corresponds to properties with getters and setters in other languages.
class Foo
def initialize
#bar = 42
end
def xyzzy
123
end
def xyzzy=(value)
puts "xyzzy set to #{value}!"
end
end
obj = Foo.new
puts obj.xyzzy # prints: 123
obj.xyzzy = 2 # prints: xyzzy set to 2
puts obj.bar # error: undefined method 'bar'
You can use attr_reader and attr_accessor to automatically define getters and setters for an instance variable. attr_reader will only generate a getter, while attr_accessor generates both.
class Parrot
attr_accessor :volts
def voom
puts "vooming at #{#volts} volts!"
end
end
polly = Parrot.new
polly.volts = 4000
polly.voom
Instance variables are more primary things than methods calling them. In self.myVariable, myVariable is a method referring to the instance variable #myVariable, and that method is defined usually by attr_reader or attr_accessor.
One purpose of object orientated programming is to encapsule things particular to an instance inside that instance and make it inaccessible from outside of it. This way, you can avoid unwanted conflicts of name. This is true for instance variables. They are usually parameters to be handeled within the instance, and not to be used outside of it.
Within an instance, its instance variables can be directly referred to, and hence there is no need to refer to them via method calls. You should directly call the variable #myVariable.
From outside of an instance, you cannot directly refer to the instance variables because of the reason mentioned above. But sometimes, you do need to refer to them. The purpose of using the method myVariable is to refer to the instance variable from outside of an instance.
#my_variable refers directly to the instance variable, and is (for the most part) inaccessible from outside that instance.
self.my_variable is using an accessor method (as defined with attr_reader, attr_writer or attr_accessor internally. This is in cases where there may not be an instance variable named #my_variable (as is the case with ActiveRecord model attributes) or where the internal state differs from what is exposed publicly.

When to use 'self' in Ruby

This method:
def format_stations_and_date
from_station.titelize! if from_station.respond_to?(:titleize!)
to_station.titleize! if to_station.respond_to?(:titleize!)
if date.respond_to?(:to_date)
date = date.to_date
end
end
Fails with this error when date is nil:
NoMethodError (You have a nil object when you didn't expect it!
The error occurred while evaluating nil.to_date):
app/models/schedule.rb:87:in `format_stations_and_date'
app/controllers/schedules_controller.rb:15:in `show'
However, if I change date = date.to_date to self.date = self.date.to_date, the method works correctly.
What's going on? In general, when do I have to write self?
Edit: It's not related to the question, but please note that there is no "titleize!" method.
Whenever you want to invoke a setter method on self, you have to write self.foo = bar. If you just write foo = bar, the ruby parser recognizes that as a variable assignment and thinks of foo as a local variable from now on. For the parser to realize, that you want to invoke a setter method, and not assign a local variable, you have to write obj.foo = bar, so if the object is self, self.foo = bar
You disambiguiate between the instance method name and a local variable using self (it is allowed to have both with the same name in the same scope). In other words, there will be a method name resolution only if there is no local or block variable of the same name in scope. Behold:
class Foo
attr_accessor :boo
def do_boo
boo = 123
puts "Locvar: #{boo} Method: #{self.boo}"
end
end
Foo.new.do_boo
Here's why: imagine you have a module which implements a method. This method assigns something to it's internal local variable
"foo" which is used for some computation. If you skip the "self" part, the method will make a "foo=" method call on the object
whose class includes the module, which was not the intention of the author and can be downright disastrous.
class Foo
def bar=(new_value_of_bar)
set_off_nukes(new_value_of_bar / 3)
end
end
module InnocentModule # written by a different author elsewhere
def do_useful_stuff
...
bar = Math.sin(something) # we're dead
end
end
Foo.send(:include, InnocentModule)
Another crucial part where you have to use self is when invoking the Object#class method, because simply saying "class" means a class keyword for Ruby.

Resources