Instance variables and attr_accessor - ruby

When I define #foo=3 in the initialize method, I expect to access my variable like this.
class Object
def initialize(v)
#foo = v
end
Object.new.foo
That doesn't happen though. I need to write attr_accessor :foo. Why do I need to do this even though # already does that for me?

One instance variable in Ruby is not public by default. And access should be granted based on accessors.
For read only attr_reader
For write only attr_writer
For read write attr_accessor
It is not accessible by default

# does not automatically do that for you. That's why. attr_accessor creates getters and setters for your instance variables ("#-variables").

The instance variables are private. You need accessors and mutators to access them. A common accessor/mutator pair looks like:
def foo
#foo
end
def foo=(value)
#foo=value
end
This creates an abstraction which you can now use as:
Classname.new.foo
Classname.new.foo="OOP"
Since this is such a common need and also reults in lot of boilerplate(read: unnecessary) code, ruby provides a dynamic method which literally defines these two methods for you.
attr_accessor :foo
If you want only one of accessor or mutator method, then use the corresponding from following:
attr_reader :foo
attr_writer :foo
This will save a lot of copy/paste. I hope I was clear.

All Ruby attributes are "private" and are invisible outside the class's methods. You need accessor methods to read and write an attribute. So, in your example, you need
class MyClass
def initialize(v)
#foo = v
end
def foo
#foo
end
def foo=(v)
#foo = v
end
end
Then MyClass.new(4).foo will work, and return 4.
You can also add the accessor methods using the convenience methods
attr_reader :foo
attr_writer :foo
or
attr_accessor :foo

An instance variable starts with an # character. All instance variables are private, which means you can't read them and you can't change their value. So what to do?
class Dog
def initialize(name)
#name = name
end
def name #getter
#name
end
def name=(str) #setter
#name = str
end
end
Well, that gets to be a pain to type out, so ruby provides a shortcut:
class Dog
attr_accessor :name
def initialize(name)
#name = name
end
end

Related

Ruby symbol and attribute

When I'm creating attributes, I can use symbols like this:
class SomeClass
attr_reader :variable1, :variable2
end
When I'm using the keyword attr_reader (or attr_writer) and use the same name as some instance variable, does Ruby automatically associate the attribute (or say, property) with the instance variable with the same name?
You need to understand what attr_reader does. When you do:
attr_reader :variable1, :variable2
It gets translated into:
def variable1
#variable1
end
def variable2
#variable2
end
As you can see variable1 is but the instance variable #variable1 whose value you are setting within your initialize method.
So yes, Ruby does associate the attribute with the instance variable with the same name.
No, there is no association going on.
And attr_reader, attr_writer, attr_accessor aren't keywords, they are methods just like any other method. In fact, you can easily write them yourself:
class Module
def attr_reader(*attrs)
attrs.each do |attr|
define_method(attr) { instance_variable_get(:"##{attr}") }
end
end
def attr_writer(*attrs)
attrs.each do |attr|
define_method(:"#{attr}=") do |val| instance_variable_set(:"##{attr}", val) end
end
end
def attr_accessor(*attrs)
attr_reader *attrs
attr_writer *attrs
end
end
All that those methods do, is generate some other methods whose name is based on the symbol you pass into them. That's it.

Ruby meta-programming - How can I set an instance's attr_accessor value through class method?

I'm trying to write a ruby module with some meta-programming features, but I'm getting a bit confused.
module MetaModule
extend ActiveSupport::Concern
module ClassMethods
def my_method(attribute)
# Define an attr_accessor for the original class
attr_accessor :test_accessor
# This is clearly wrong, but I don't know what is correct
self.test_accessor ||= []
self.test_accessor << attribute
end
end
end
class MyClass
include MetaModule
my_method :name
my_method :age
my_method :city
end
My desired output is: MyClass.new.test_accessor => [:name, :age, :city]
I think there might be a little bit of a mix up here. It is certainly possible to construct a module that will have your desired output, but ultimately it will look something like this
module MetaModule
extend ActiveSupport::Concern
module ClassMethods
def my_method(attribute)
# Define an attr_accessor for the original class
#class_test_accessor ||= []
#class_test_accessor << attribute
end
def class_test_accessor
#class_test_accessor
end
end
def test_accessor
self.class.class_test_accessor
end
end
But you might notice that ultimately we are adding an instance method that simply accesses a class instance variable. Because my_method is a class method, its value wont change per instance. Therefore I would suggest accessing it simply as self.class.class_test_accessor within an instance. If there is something else you were hoping to accomplish w/ my_method (like seed a class_test_accessor and then modify per instance) let me know and I will try to help.

Dynamically add (pre-defined) instance method in Ruby

I see how to dynamically add a method to an instance in Ruby with def [instance].[methodname]; [...]; end.
However, I'm interested in attaching a method that exists in another location to a given instance. e.g.
def my_meth
puts self.foo
end
class MyCls
attr_accessor :foo
end
my_obj = MyCls.new
my_obj.my_meth
How could I simply attach my_meth to my_obj so that the method call in the final line of the foregoing code would work?
You could use include or extend to add a module to your class, eg. extend:
module Foo
def my_meth
puts self.foo
end
end
class MyCls
attr_accessor :foo
end
my_obj = MyCls.new
my_obj.extend(Foo)
my_obj.foo = "hello"
my_obj.my_meth
Unless you have a need to mix-in a module on the fly like this it's generally better to include your module like so:
class MyCls
include Foo
attr_accessor :foo
end

Define methods from template at runtime in ruby

Say I have the following classes:
class Foo
attr_accessor :name, :age
end
class Bar
def initialize(name)
#foo = Foo.new
#foo.name = name
end
end
I'd like to define an accessor on Bar that is simply an alias to foo.name. Easy enough:
def name
#foo.name
end
def name=(value)
#foo.name = value
end
With only one property, this is easy enough. However, say Foo exposes several properties that I want to expose through Bar. Rather than defining each method manually, I want to do something like this, though I know the syntax isn't right:
[:name, :age].each do |method|
def method
#foo.method
end
def method=(value)
#foo.method = value
end
end
So...what is the correct way of defining methods like this?
To define a method dynamically you can use define_method which takes the method name as a symbol or string argument.
To call a method dynamically you can use send which also takes the method name as a symbol or string.
[:name, :age].each do |method|
define_method(method) do
#foo.send(method)
end
define_method("#{method}=") do |value|
#foo.send("#{method}=", value)
end
end
[:name, :age].each do |method|
define_method(method) do
foo.send(method)
end
define_method("#{method}=") do |value|
foo.send("#{method}=", value)
end
end
See the Delegator class in the Ruby Standard Library, depending on how many methods you want to pass along.

Why can't I use attr_accessor inside initialize?

I'm trying to do an instance_eval followed by a attr_accessor inside initialize, and I keep getting this: ``initialize': undefined method 'attr_accessor'`. Why isn't this working?
The code looks kind of like this:
class MyClass
def initialize(*args)
instance_eval "attr_accessor :#{sym}"
end
end
You can't call attr_accessor on the instance, because attr_accessor is not defined as an instance method of MyClass. It's only available on modules and classes. I suspect you want to call attr_accessor on the instance's metaclass, like this:
class MyClass
def initialize(varname)
class <<self
self
end.class_eval do
attr_accessor varname
end
end
end
o1 = MyClass.new(:foo)
o2 = MyClass.new(:bar)
o1.foo = "foo" # works
o2.bar = "bar" # works
o2.foo = "baz" # does not work
A cleaner implementation (NB: This will add the accessor to ALL instances of the class, not just the single instance, see comments below):
class MyClass
def initialize(varname)
self.class.send(:attr_accessor, varname)
end
end
Rob d'Apice has it almost right. You just need to write:
self.singleton_class.send(:attr_accessor, varname)
or
self.singleton_class.class_eval "attr_accessor :#{varname}"
or my favorite variant
self.singleton_class.class_exec do attr_accessor varname end
assuming the value of varname is a symbol

Resources