I have a Rails 5 class which includes ActiveAttr::Model, ActiveAttr:MassAssignment and ActiveAttr::AttributeDefaults.
It defines a couple of attributes using the method attribute
and has some instance methods. I have some trouble manipulating the defined attributes. My problem is how to set an attribute value within the initializer. Some code:
class CompanyPresenter
include ActiveAttr::Model
include ActiveAttr::MassAssignment
include ActiveAttr::AttributeDefaults
attribute :identifier
# ...
attribute :street_address
attribute :postal_code
attribute :city
attribute :country
# ...
attribute :logo
attribute :schema_org_identifier
attribute :productontology
attribute :website
def initialize(attributes = nil, options = {})
super
fetch_po_field
end
def fetch_po_field
productontology = g_i_f_n('ontology') if identifier
end
def uri
@uri ||= URI.parse(website)
end
# ...
end
As I have written it, the method fetch_po_field
does not work, it thinks that productontology is a local variable (g_i_f_n(...)
is defined farther down, it works and its return value is correct). The only way I have found to set this variable is to write self.productontology
instead. Moreover, the instance variable @uri
is not defined as an attribute, instead it is written down only in this place and visible from outside.
Probably I have simply forgotten the basics of Ruby and Rails, I've done this for so long with ActiveRecord and ActiveModel. Can anybody explain why I need to write self.productontology
, using @productontology
doesn't work, and why my predecessor who wrote the original code mixed the @ notation in @uri
with the attribute-declaration style? I suppose he must have had some reason to do it like this.
I am also happy with any pointers to documentation. I haven't been able to find docs for ActiveAttr showing manipulation of instance variables in methods of an ActiveAttr class.
Thank you :-)
To start you most likely don't need the ActiveAttr gem as it really just replicates APIs that are already available in Rails 5.
See https://api.rubyonrails.org/classes/ActiveModel.html.
As I have written it, the method fetch_po_field does not work, it thinks that productontology is a local variable.
This is really just a Ruby thing and has nothing to do with the Rails Attributes API or the ActiveAttr gem.
When using assignment you must explicitly set the recipient unless you want to set a local variable. This line:
self.productontology = g_i_f_n('ontology') if identifier
Is actually calling the setter method productontology=
on self using the rval as the argument.
Can anybody explain why I need to write self.productontology, using @productontology doesn't work
Consider this plain old ruby example:
class Thing
def initialize(**attrs)
@storage = attrs
end
def foo
@storage[:foo]
end
def foo=(value)
@storage[:foo] = value
end
end
irb(main):020:0> Thing.new(foo: "bar").foo
=> "bar"
irb(main):021:0> Thing.new(foo: "bar").instance_variable_get("@foo")
=> nil
This looks quite a bit different then the standard accessors you create with attr_accessor
. Instead of storing the "attributes" in one instance variable per attribute we use a hash as the internal storage and create accessors to expose the stored values.
The Rails attributes API does the exact same thing except its not just a simple hash and the accessors are defined with metaprogramming. Why? Because Ruby does not let you track changes to simple instance variables. If you set @foo = "bar"
there is no way the model can track the changes to the attribute or do stuff like type casting.
When you use attribute :identifier
you're writing both the setter and getter instance methods as well as some metadata about the attribute like its "type", defaults etc. which are stored in the class.