Variables in Ruby method names created by attr_accessor - ruby

I have an object that is using attr_accessor. I want to be able to call a method on that object with a variable with #send. My problem is that the = method doesn't seem to work.
class Foo
attr_accessor :bar
end
class Test_class
def test_method(x)
f = Foo.new
f.send(x)
f.send(x) = "test" #this doesnt work
f.send("#{x} =") "test" #this also doesn't work
# How can I set bar?
end
end
t = Test_class.new
t.test_method("bar")

You want f.send "#{x}=", "test". In Ruby, method names may include punctuation, such as = or !. The methods created by attr_accessor :bar are simply named bar and bar=. In fact, attr_accessor :bar is just shorthand for:
def bar
#bar
end
def bar=(value)
#bar = value
end
When you're calling foo.bar = "baz", you're actually calling the #bar= method with foo as the receiver and "bar" as the first parameter to the function - that is, foo.bar=("baz"). Ruby just provides syntactic sugar for methods ending in = so that you can write the more natural-looking form.

Related

Get attr_reader, writer, or accessor oustide of the class

I'm currently doing some metaprogramming with ruby, and I'm trying to isolate the methods of class (that class is in another file, that I get by a require). I can get all the methods, thanks to klass.public_instance_methods(false), but I in the sametime, the array given also have all the attributes of the class. How could I isolate them ? In others related questions on SO, they suggest to use klass.instance_variables but when I do that, it only returns an empty array.
I can't seem to wrap my head around that one. I don't understand why there isn't a method specifically for that already...
For example:
I have in a file this class :
class T
attr_reader:a
def initialize(a)
#a = a
end
def meth
#code here
end
end
And, in another file, i have
require_relative 'T.rb'
class meta
def initialize
methods = T.public_instance_methods(false) #=> here methods = [:a,:meth] but I would want only to have [:meth]
#rest of code
end
end
For class defined like this:
class Klass
attr_accessor :variable
def initialize(variable)
#variable = variable
end
def method
end
end
you can find public non-attr instance methods using public_instance_methods and instance_variables methods.
public_instance_methods = Klass.public_instance_methods(false)
# [:method, :variable, :variable=]
instance_variables = Klass.new(nil).instance_variables
# [:#variable]
getters_and_setters = instance_variables
.map(&:to_s)
.map{|v| v[1..-1] }
.flat_map {|v| [v, v + '=']}
.map(&:to_sym)
# [:variable, :variable=]
without_attr = public_instance_methods - getters_and_setters
# [:method]
This is impossible. Ruby's "attributes" are completely normal methods. There is no way to distinguish them from other methods. For example, these two classes are completely indistinguishable:
class Foo
attr_reader :bar
end
class Foo
def bar
#bar
end
end
You can try to be clever and filter them out based on instance variables, but that is dangerous:
class Foo
# can filter this out using #bar
attr_writer :bar
def initialize
#bar = []
end
end
class Foo
def initialize
#bar = []
end
# this looks the same as above, but isn't a normal attribute!
def bar= x
#bar = x.to_a
end
end

Can you omit the # when calling instance variables

When you have a class with a attr_accessor, can't you omit the # symbol when calling your instance variables since self.instance_variable would be implied if you don't use the # symbol?
attr_accessor :foo is basically just a macro that generates two methods (a getter and a setter) for you:
def foo
#foo
end
def foo=(foo)
#foo = foo
end
Can you omit the # when calling your instance variable? Kind of – calling the instance variable name without the # means you are calling the instance method generated by attr_accessor instead of calling the instance variable. This works as long as the getter method is not overridden or extended.
But when you would try to set an instance variable without the #, it would not work that way, because then Ruby would set a local variable with that name. To set the instance variable through the instance method generated by attr_accessor you need to write self. (or another receiver) instead of the #:
#foo = 'bar' # assigns 'bar' to the instance variable `#foo`
foo = 'bar' # assigns 'bar' to a local variable `#foo`
But to use the setter method generated by attr_accessor:
self.foo = 'bar' # passes 'bar' to the instance method `foo=`
In the case where you have an attr_accessor and you want to read the value, you'll get the same value. It isn't exactly that you're omitting the # but that you're calling a method with the same name that returns the instance variable.
Omitting the # when trying to set the value will not work the same way; it will just set a local variable with the same name. Using an object's setter within the object requires that you precede it with self..
class Foo
attr_accessor :bar
def getter_equivalent?
bar.equal?(#bar) # Returns true. They are the same object.
end
def set_with_at(value)
#bar = value # Will set the instance variable
end
def set_without_at(value)
bar = value # Will not set the instance variable
end
def set_with_self(value)
self.bar = value # Will set the instance variable
end
end
A class example with attr_accessor expanded for clarity. The usual initialize method has been omitted to focus on the task at hand:
class Foo
def bar
#bar
end
def bar=(bar)
#bar = bar
end
#the above two methods represent attr_accessor :bar
def call_method
bar
#same as self.bar
#self refers to the current object i.e. the current instance of Foo
#bar refers to the method bar defined above
end
def call_instance_var
#bar
#refers directly to the the instance variable
end
end
You can use either one, I personally prefer calling the method rather than the instance variable.
Example
foo = Foo.new
foo.bar = "bar"
foo.call_method #=> "bar"
foo.call_instance_var #=> "bar"
Although it's possible to use accessor methods within the class, it's better practice to use the # symbol to refer to instance variables.
Here's an example where using a reader method within a class would produce unexpected results:
class A
def var
#var
end
def defined_using_method?
defined?(var)
end
def defined_using_at?
defined?(#var)
end
end
A.new.defined_using_method? # => "method"
A.new.defined_using_at? # => nil

Ruby modify original variable

In C programming I believe they call this pass by reference. What I want to do is this.
class A
attr_accessor :foo
def initialize
#foo = 'foo'
end
def get_b
_b = Object.new
_b.extend B
_b.foo = #foo
end
module B
attr_accessor :foo
def change_foo
#foo = 'bar'
end
end
end
a = A.new
puts a.foo # 'foo'
b = a.get_b
puts b.foo # 'foo'
b.change_foo
puts b.foo # 'bar'
puts a.foo # This should also be 'bar' but is instead still 'foo'
After b.change_foo I would like the value of a.foo to be modified. Is there a way of passing the reference of #foo from class A to module B instead of the value?
With this concrete example of strings, you can make it work.
module B
attr_accessor :foo
def change_foo
# #foo = 'bar' # this won't work
foo.replace('bar')
end
end
When you do a #foo = 'bar', you're completely breaking any connection between foo of B and foo of A. They're now two separate objects.
What the code above does is, instead of making another object, use the reference to the object to call a method on it (which will change its state). Will work equally well with other mutable objects (arrays, hashes, instances of your custom classes). But not with immutable primitives like integers.
Is there a way of passing the reference of #foo from class A to module B instead of the value?
A reference (or a "pointer", in C-speak) is actually passed here. You then overwrite it.
Objects are passed as reference in Ruby, but not pointers to variables. This means that, although you can modify any object, if you change the variable's reference, this change will occur only in the current scope. I'd recommend you to actually change your architecture and use more object-oriented techniques, instead of trying to rely on error-prone language features like this. For example, you can use composition and delegation to implement what you're trying to accomplish:
class A
attr_accessor :foo
def initialize
#foo = 'foo'
end
def get_b
_b = Object.new
_b.extend B
_b.a = self
end
module B
attr_accessor :a
def foo
a.foo
end
def foo=(value)
a.foo = value
end
def change_foo
a.foo = 'bar'
end
end
end
I don't know exactly your purpose, but I'd probably not dynamically extend modules in a factory method like this. I prefer to create classes whose purpose is clear, without depending on context.

Ruby - accessing instance methods / variables from anonymous class

I have following Ruby code:
class Baz
def foo()
qux = Class.new() {
def call()
bar()
end
}.new()
qux.call()
end
def bar()
puts "bar"
end
end
b = Baz.new()
b.foo()
How can I access method bar from the anonymous class, that means from qux.call? Is it possible?
I'm keeping getting this message:
classes-test.rb:5:in `call': undefined method `bar' for #<#<Class:0x00000002d9c248>:0x00000002d9c1a8> (NoMethodError)
I'm new to Ruby, so any advice or even deeper explanation of the problem will be appreciated. Thanks in advance.
Since .bar is an instance method of Baz, you need to have an instance of Baz available in your context to call .bar. You can do that by passing the instance object to the class on initialization, so you can call its .bar method on it.
This works:
class Baz
def foo
qux = Class.new do
def initialize(a)
#a = a
end
def call
#a.bar
end
end.new(self)
qux.call
end
def bar
puts "bar"
end
end
b = Baz.new
b.foo
=> 'bar'
If you need to pass a class to Class.new() as you mention in the comments, you can override the initializer method like this (please note that you may have to consider the arguments that your Closure class initialize needs for super:
qux = Class.new(Fiddle::Closure) do
def initialize(baz)
#baz = baz
super
end
def call
#baz.bar
end
end.new(self)
On a side note, you don't need all those (), it's Ruby style to omit them if not needed.

How do I access an instance variable from a module that was included dynamically?

I am dynamically including a module into the Baz class in the foobarbaz method.
However, when I execute this in ruby, I get a nil puts. Doesn't the module have access to Foo's instance variables?
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
Baz.send(:include, Bar) # For Ruby < 2.1
# Baz.include(Bar) # For Ruby > 2.1
end
end
class Baz
end
module Bar
def foobar
#current_session
# 'foobar'
end
end
puts Foo.new('current_session').foobarbaz.new.foobar # nil
NOTE, for this, I was using Ruby 2.0.0. The following also does not puts desired result in Ruby 2.1.2.
Here is a meta programming for you :
#!/usr/bin/env ruby
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
session = current_session
Bar.module_eval { #current_session = session }
Baz.send(:include, Bar)
end
end
module_eval says
Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected....
Thus inside Bar.module_eval { #current_session = session }, #current_session is the instance variable of Bar only and I am setting the value of it to the instance variable value of the class Foo, which is #current_session.
Baz.send(:include, Bar) is helpfull, which returns class/module itself, which is including the other module. include(module, ...) → self.
class Baz
end
Read this post to understand the below stuff.
module Bar
class << self
attr_reader :current_session
end
def foobar
Bar.current_session
end
end
puts Foo.new('current_session').foobarbaz.new.foobar
# >> current_session
Update
As #Christian Fazzin gave a good suggestion :-
If you want Bar module to have write method also, then you have to do 2 changes -
Bar should contain then attr_accesor :current_session, instead of what it has now.
You don't need to use the power of module_eval there, rather use syntactic sugraness of write methods, like put Bar.current_session = current_session inside the method foobarbaz . Remove the lines session = current_session and Bar.module_eval { #current_session = session }.
After creating an instance of Foo (foo, say) and in doing so initializing foo's instance variable #current_session to 'current session', it appears to me that you want foo.foobarbaz to do the following:
cause Baz to include the module Bar
create an instance of Baz (baz, say)
create an instance variable named #current_session for baz and assign it the value of foo's instance variable of the same name
invoke baz.foobar to return the value of baz's instance variable #current_session.
If my understanding is correct, we can perform these four steps with four lines in Foo#foobarbaz:
class Baz
end
module Bar
def foobar
#current_session + ' in Baz'
end
end
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
Baz.include(Bar)
baz = Baz.new
baz.instance_variable_set(:#current_session, self.current_session)
baz.foobar
end
end
foo = Foo.new('current session')
foo.foobarbaz
#=> "current session in Baz"
I've slightly modified what foobarbaz returns to show where it is coming from.
Note that the third line of foobarbaz could be changed to either of the following
baz.instance_variable_set(:#current_session, #current_session)
baz.instance_variable_set(:#current_session,
instance_variable_get(:#current_session))
If the latter of these were used, #current_session's accessor would not be needed.
You'd just need to set instance variable (not class instance variable!) #current_session of class Baz.
With slightest modification of your code without need of additional class/module methods the most straightforward way is to define initialization method that sets the required variable:
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
  # define Baz#initialize on-the-fly, alternatively with define_method
Baz.class_eval "def initialize; #current_session = '#{#current_session}';end"
Baz.send(:include, Bar) # For Ruby < 2.1
end
end
class Baz
end
module Bar
def foobar
#current_session
# 'foobar'
end
end
puts Foo.new('current_session').foobarbaz.new.foobar
# current_session

Resources