0

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 :-)

max
  • 96,212
  • 14
  • 104
  • 165
MDickten
  • 105
  • 1
  • 10

1 Answers1

0

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.

max
  • 96,212
  • 14
  • 104
  • 165
  • Thank you for pointing out things I ought to have known long ago :-/ – MDickten Jan 19 '21 at 10:51
  • And another thing I noticed only now, this class is *not* an ActiveModel. That's the whole trouble. There isn't a database table behind it. – MDickten Jan 20 '21 at 18:15
  • I think you're confusing ActiveModel and ActiveRecord. – max Jan 20 '21 at 18:24
  • ActiveModel is a set of modules that you can include in any class to get model like behavior. You don't even need a database at all to use ActiveModel. ActiveRecord is the ORM that "magically" creates attributes from your database. – max Jan 20 '21 at 21:03
  • OOPS. Oh. Thank you. – MDickten Jan 22 '21 at 10:13