Defining non-standard accessor methods using 'attr_accessor' - ruby

I'm wondering if it is possible to use attr_accessor to reference an attribute with a different name than the methods it will define. For example, I have a class GameWindow with an attribute bg_color, and I want attr_accessor to define methods background and background=. Is there any way to do this, or do I have to define the methods myself?

You want an attr_accessor to define the methods background and background=. attr_accessor is used to define getter and setter for instance variable & method is something which return value but you cannot have setter like background= for method.
Please check with alias_attribute & alias_method.

Use attr_accessor in conjunction with alias_method. For example:
class Foo
attr_accessor :bar
alias_method :baz, :bar
alias_method :baz=, :bar=
def initialize
end
end
Then verify it works as expected:
foo = Foo.new
=> #<Foo:0x00007fabb12229a0>
foo.bar = 'foobar'
=> 'foobar'
foo.baz
=> 'foobar'
foo.baz = 'barfoo'
=> 'barfoo'
foo.bar
=> 'barfoo'

attr_accessor :background
is a just a simple macro that generates these two methods for you:
def background
#background
end
def background=(value)
#background = value
end
When you want to have a getter and a setter method with a specific name but assign the value to a variable with a different name then just write the methods yourself - for example like this:
def background
#bg_color
end
def background=(value)
#bg_color = value
end

attr_accessor creates the instance variable (here, #bg_color; or more precisely, makes it accessible to call on the object, though as Cary and Jörg point out below, the instance variable isn't strictly created until it is assigned) and reader and writer methods named after the symbol arguments you use. If you want it to be named background, why don't you just change the name?

Related

Can I get a list of the symbols passed to attr_accessor in a class when inspecting?

Assuming I have defined a class with accessors defined using attr_accessor:
class A
attr_accessor :alpha, :beta, :gamma
def initialize
self.alpha = 1
end
end
Is there a built-in method that gives the list of method names passed to an attr_accessor call? Or do I have to define a constant with the symbols, and pass it to attr_accessor?
There's no built-in method. Your solution of storing the method names at creation time will work, as long as you know in advance and can control what the method names are.
In my answer to a different but similar question, I showed how to get the names of the methods dynamically, after the fact, using TracePoint. I've updated it below to include :attr_reader and :attr_writer.
module MethodTracer
TracePoint.trace(:c_call) do |t|
if %i[attr_accessor attr_writer attr_reader].include?(t.method_id)
t.self.extend(MethodTracer)
methods = t.self::Methods ||= []
MethodTracer.send(:define_method, :method_added) {|m| methods << m }
end
end
TracePoint.trace(:c_return) do |t|
if %i[attr_accessor attr_writer attr_reader].include?(t.method_id)
MethodTracer.send(:remove_method, :method_added)
end
end
end
class Foo
attr_accessor :a
attr_reader :b
attr_writer :c
def foo; end
end
Foo::Methods # => [:a, :a=, :b, :c=]
I've stored the method names in the Methods constant, but obviously you can store them wherever is most convenient for you.
Defining/removing method_added on MethodTracer ensures that you don't clobber any Foo.method_added you've defined yourself. This methodology, however, does require that if you define Foo.method_added before your calls to attr_*, you will need to call super inside it. Otherwise you will skip the temporary method_added defined by MethodTracer.
Grep an Instance for Setter Methods
One way to do this would be to grep an instance of the class for setters. For example:
A.new.methods.grep(/\p{alnum}+=\z/)
#=> [:alpha=, :beta=, :gamma=]
No, that's not possible. Methods generated by attr_accessor, attr_reader and attr_writer are indistinguishable from ones written by hand. In fact, they must be indistinguishable from ones written by hand!
Say, you have a simple attr_accessor, but you later want to refactor it to do something more intelligent (e.g. caching). This is a purely internal change, a client must not be able to observe the difference, otherwise it would be a breach of encapsulation!
If you simply want a list of setters, that's easy enough: setters are methods whose name ends with an = sign:
A.public_instance_methods(false).grep(/=$/)
# => [:alpha=, :beta=, :gamma=]
For getters, it's trickier: any method that doesn't take an argument could be a getter, but it could also be a side-effecting method (e.g. Array#clear):
A.public_instance_methods(false).select {|m|
A.public_instance_method(m).arity.zero?
}
# => [:alpha, :beta, :gamma]

Why are setter methods not used in initialization?

Recently I've been reading "Practical Object Oriented Design in Ruby", and I noticed one of the best practices was to use accessor methods instead of directly grabbing the #instance_variable. For example:
class Foo
attr_accessor :bar
def initialize(my_argument)
#bar = my_argument
end
# bad
# def lorem_ipsum
# #bar * 999
# end
# good
def lorem_ipsum
bar * 999
end
end
It makes sense to keep things DRY, and, in case I need to process #bar somehow before actually grabbing its value. However, I noticed that the initialize method sets the value of the #bar instance variable directly:
class Foo
attr_accessor :bar
def initialize(my_argument)
#bar = my_argument #<-- why isn't self.bar = my_argument used here?
end
Is there a reason for this? Shouldn't the setter method be used instead of directly using the = operator to set the value of the instance variable?
You're right, it would make much more sense to do
class Foo
attr_accessor :bar
def initialize(my_argument)
self.bar = my_argument
end
end
Arguments differ as to whether you should respect encapsulation within the object itself or not, but if you believe in that, then, yes, you should do this.
The initializer SETS the value upon initialization. The accessor lets you access (read/write) via the symbol after the object is already instantiated.
This post might help you understand:
What is attr_accessor in Ruby?
Actually, the setter can be used in initialize the same as in other methods, but setter cannot be used without a receiver.
I think you can use a_foo.bar= or self.bar=, but cannot use bar= without a receiver, because, in the later case, bar will be treated as a local variable rather than a setter method:
class Song
attr_accessor :name
def initialize(name)
self.name = name
end
def test_setter1(value)
#name = value
end
def test_setter2(value)
name = value #name is local variable
end
end
s = Song.new("Mike")
p s
s.test_setter1("John")
p s
s.test_setter2("Rosy")
p s
This results in:
#<Song:0x23a50b8 #name="Mike">
#<Song:0x23a50b8 #name="John">
#<Song:0x23a50b8 #name="John">
While you can use the setter in the initialization as shown in #uncutstone's answer, you cannot use it as you've proposed in the comment in your code.
The problem is that Ruby would interpret:
bar = my_argument
as an assignment to the bar local variable rather than an invocation of the bar= method.
This is discussed rather extensively in "Why do Ruby setters need "self." qualification within the class?".

attr vs attr_accessor

In Ruby there are four different getter and setter methods for instance variables, attr, attr_reader, attr_writer, and attr_accessor. The question is, is in Ruby attr :dilithium, the same as attr_reader :dilithium, and identical to attr_accessor :dilithium if an additional parameter true is passed? That is to say is
class Enterprise
attr :dilithium, true
identical to
class Enterprise
attr_accessor :dilithium
Are the two functions attr and attr_accessor more or less redundant?
One difference is that attr_accessor and friends are clearer, and the optional boolean argument to attr is now deprecated. Other than that, and the fact that attr has no documentation, there's no real difference.
In Ruby 1.8, attr can define only a single attribute, with an optional true to create a setter . In 1.9 it behaves like attr_reader:it allows for multiple attributes. As #Linuxios says, the optional boolean is deprecated.
If you look at the C code in Ruby 2.3.0, you will see that attr and attr_reader are actually doing the same thing (except for the deprecated code path). They are essentially equivalent functions.
For me, the main differences are:
attr is a bit easier to write down as it's shorter. I like the way it feels in the context of functional/immutable programming too (in those contexts, attr_writer and attr_accessor are irrelevant and thus using attr_reader feels verbose and redundant).
attr with multiple instance variables makes it hard to document, except in very specific situations, e.g.
# The coordinates in WGS84.
attr :x, :y
# The location name.
attr :name
# The popularity of the location.
attr :popularity
It would be harder to document :name and :popularity independently if they were on the same line attr :name, :popularity.
Apart from that it boils down to personal preference. There is in practice no performance difference or any other difference.
attr and attr_accessor are methods in Ruby that are used to define instance variables in classes.
attr creates a simple getter method that returns the value of an instance variable with the same name as the symbol passed as an argument. For example:
class MyClass
attr :my_variable
def initialize
#my_variable = "Hello World"
end
end
my_object = MyClass.new
puts my_object.my_variable # outputs "Hello World"
attr_accessor creates both getter and setter methods for an instance variable with the same name as the symbol passed as an argument. For example:
class MyClass
attr_accessor :my_variable
def initialize
#my_variable = "Hello World"
end
end
my_object = MyClass.new
puts my_object.my_variable # outputs "Hello World"
my_object.my_variable = "Goodbye World"
puts my_object.my_variable # outputs "Goodbye World"
In the example above, my_object.my_variable can be read and written. The attr_accessor method creates both getter and setter methods, allowing the value of the instance variable to be read and written.

Ruby Code not in Any Method

General Ruby question:
In Ruby, I frequently see code that's inside a class, but not part of a method. For example:
class DooDad
attr_accessor :foo
end
or
class Teacher < ActiveRecord::Base
has_many :students
end
I think attr_accessor and has_many are methods getting invoked with the :foo or :students arguments, respectively, is that right? If so, when do these kinds of statements get executed. I tried this:
class DooDad
attr_accessor :foo
puts "I happened!"
#foo = 7
end
It doesn't seem to run these a part of the new method:
dd = DooDad.new
dd.foo
outputs nil, and never spits out any puts stuff
How exactly does all that work?
Methods like attr_accessor and has_many are often called "mimic methods" because they kinda look like ruby keywords (mimic them) but they're in fact, as you and others have correctly pointed out, method calls.
dd = DooDad.new
dd.foo
outputs nil, and never spits out any puts stuff
How exactly does all that work?
When you're inside of a class definition the implicit receiver of all method calls and "variable definitions" is self, which in your case is DooDad.
So when you're writing
class DooDad
#foo = 1
end
you're actually defining an instance variable on self, that happens to be the class itself , since you're inside of that classes definition. (and outside of any other class, module or method definitions)
The method attr_accessor on the other hand generates, with some help of metaprogramming, accessor methods for an instance variable of the objects that are instantiated from class DooDad.
Back to your example:
class DooDad
attr_accessor :foo
puts "I happened!"
#foo = 7
end
With the stuff mentioned above, you should now understand that you're dealing with two different #foo variables, one for instances of class DooDad (i.e DooDad.new), the other (the one you created by writing #foo = 7) for the class DooDad itself!
When calling the new method on a class, you create an instance of it.
dd = DooDad.new
#=> dd is now an object of class DooDad
dd.foo
#=> You just called the "getter" method for an instance variable foo of object dd, which was never defined before, that's why it's returning nil.
The puts "I happened!" statement, just as the other two in fact, gets evaluated as soon as the class is loaded, but not when you call new on it.
If you want the behaviour you described (doing stuff when calling new), I suggest implementing an initialize() method for DooDad, which will get called when you call new:
class DooDad
attr_accessor :foo
def initialize()
puts "I happened!"
#foo = 7
end
end
dd = DooDad.new
#=> outputs "I happened!" and sets dd.foo = 7
dd.foo
#=> 7
But why does #foo = 7 now set the instance variable of dd and not DooDad?
When you define a method with the keyword def, you enter a new scope (you pass a scope gate). self is now no longer the class, but instead an instance of that class, that you created with new, just like dd. So when you're writing #foo = 7 inside of a method definition, you're talking about variables for an instance of class DooDad, not the class itself.
This post is probably way too long and might not even satisfy as an answer, but I hope it was somewhat comprehensive.
Methods like has_many and attr_accessor are actually methods on the Module or Class. You are absolutely right about them being normal methods, invoked with arguments just like any other. When a method is called directly in the class (outside of a method definition), it is being called on the class itself.
Here are the docs for attr_accessor.
They are syntactic sugar built into the language, so it is not the same as your test. When you write (for example)
class Foo
attr_accessor :bar
end
It is actually shorthand for...
class Foo
def bar
#bar
end
def bar=(value)
#bar = value
end
end

How do you access an instance variable within a mixin method?

How do you access an instance variable within a mixin method? I can think of 2 ways, but both seem problematic.
Have the mixin method access the instance variable directly as any class method would, e.g self.text. Problem with this is that it places restrictions on where the mixin method can be used, and forces the class doing the mixing to have a particular instance method named in a particular way.
Pass the instance variable as a parameter to the mixin method, which would result in code like this:
example
self.do_something(self.text)
or
#thing.do_something(#thing.text)
which looks nasty to me, and doesn't conform to the principles of object orientation.
Is there any other way to do it?, am I right to be concerned?
In general, avoid having mixins access member variables: It's a very tight form of coupling that can make future refactoring unnecessarily difficult.
One useful strategy is for the Mixin to always access variables via accessors. So, instead of:
#!/usr/bin/ruby1.8
module Mixin
def do_something
p #text
end
end
class Foo
include Mixin
def initialize
#text = 'foo'
end
end
Foo.new.do_something # => "foo"
the mixin accesses the "text" accessor, which is defined by the including class:
module Mixin
def do_something
p text
end
end
class Foo
attr_accessor :text
include Mixin
def initialize
#text = 'foo'
end
end
Foo.new.do_something # => "foo"
What if you need to include the Mixin in this class?
class Foo
def initialize
#text = "Text that has nothing to do with the mixin"
end
end
Using generic and common data names in mixins can lead to conflicts when the including class uses the same name. In that case, have the mixin look for data with a less common name:
module Mixin
def do_something
p mixin_text
end
end
and let the including class define the appropriate accessor:
class Foo
include Mixin
def initialize
#text = 'text that has nothing to do with the mixin'
#something = 'text for the mixin'
end
def mixin_text
#something
end
end
Foo.new.do_something # => "text for the mixin"
In this way, the accessor acts as sort of "impedance matcher" or "translator" between the mix-in's data and the including class's data.
Instance variable names start in ruby with an # eg. #variable. You can acces them with this name from a Module you include
module M
def t
#t
end
end
class A
include M
def initialize(t)
#t= t
end
end
A.new(23).t # => 23
If you wan't to access #t when it's not defined in your class before you can do it this way
module M
def t
instance_variable_defined?("#t") ? #t : nil
end
end
You can provide this instance method yourself in this module, but you have to be careful not to overwrite existing method
Example (in module you are mixing in):
def text
#text ||= ""
end

Resources