Differences Between *, Self.* and @* When Referencing Associations/Attributes in Ruby/Rails Models/Controllers

Differences between *, self.* and @* when referencing associations/attributes in Ruby/Rails Models/Controllers

self.x/self.x=y are always method calls.

(self.x is just sugar for self.__send__(:x) and self.x = y is really just sugar for self.__send__(:x=, y))

@x, on the other hand, only refers to an instance variable.

Using @x will not work with AR associations as AR only defines x/x= (which are methods) for its magical operation. (AR essentially just "captures" intent access through these methods and routes through its own internal data structures which are unrelated to any similar-named instance variables.)

attr_accessor allows "accessing both ways" because and only because it uses the same-named instance variable as it's backing (it has to store the value somewhere). Consider that attr_accessor :x is equivalent to:

def x; @x; end
def x= (y); @x = y; end

Happy coding.

Do I need to use attr_accessor?

Using attr_accessor has nothing to do with Active Record. I discuss how it works in this post, that is also related to AR.

Now, what AR does do, is it automatically creates is own "accessor" methods (e.g. x/x=) based on the database model. These automatically created methods are really just stubs that it uses to proxy into the internal AR workings.

The point is, attr_accessor (automatically) wraps simple instance variable access, while AR (automatically) created methods wrap AR magic. The two operations are mutually exclusive. Because attr_accessor does not "link to" the AR magic, all it can be used for creating transient fields which are not persisted: AR does not know of or care about instance variables.

The "danger" comes from perhaps complicating the model objects with transient information -- if it is transient, why should it be part of a model object? This is the argument the most up-voted answer in the linked question makes.

Happy coding.


However, I do no know what would happen if using attr_accessor for the same field as that which happens to be in the AR model... confusion at the least.

Rails 5 add non persistent ActiveRecord attributes dynamically

While I am sure there is a more ActiveModel (RoR Guides, API - but it might not be ActiveModel but a similar module) way of doing this, with plain Ruby you would do it like this:

r_v.send("count_#{r.b_id}_erg_bst=", 0)

Basically, you "call" the method count_..._erg...= (a method assigning the local variable, defined by attr_accessor) with the argument 0.

For r.bid == 'my' it would be the same as calling r_v.count_my_erg_bst= 0.

Note that this will only work if something like attr_accessor :count_my_erg_bst is part of your class definition.

Otherwise, you can do it more meta-programmingish whith something like rv.instance_eval { @count_my_erg_bst = 0 } or, because you need string interpolation
rv.instance_eval " @count_#{r.bid}_erg_bst = 0 "

Note the security implications! If r.bid is provided by the user, it could be something like "a = 1; system("rm -rf /");" or other harmful code!

Instance variables vs. lazy loading method

You wrap an instance variable in a method when you do not want to get exactly the instance variable, but a little more than that. For example, if you want to memoize, then instead of calling

@foo

rather you need to put it in a method and call it

def foo; @foo ||= ... end
foo ...

Or if you want to return a different value than the variable's when the variable takes a certain value, then you need to put a condition in it

def foo
if @foo == ... then ... else @foo end
end
foo ...

Rails 3 - Update every attribute that is present in a model instance (encode every value)

class Book < ActiveRecord::Base
before_save :recode_attribs

private

def recode_attribs
attributes.each do |name, value|
next unless value.respond_to?(:encode)
attributes[name] = value.encode('ISO-8859-1')
end
end
end


Related Topics



Leave a reply



Submit