Hakanai
Hakanai

Reputation: 12670

How do I define synthetic attributes for an ActiveRecord model?

I have an ActiveRecord model whose fields mostly come from the database. There are additional attributes, which come from a nested serialised blob of stuff. This has been done so that I can use these attributes from forms without having to jump through hoops (or so I thought in the beginning, anyway) while allowing forwards and backwards compatibility without having to write complicated migrations.

Basically I am doing this:

class Licence < ActiveRecord::Base
  attr_accessor :load_worker_count
  strip_attributes!

  validates_numericality_of :load_worker_count,
    :greater_than => 2, :allow_nil => true, :allow_blank => true

  before_save :serialise_fields_into_properties

  def serialise_fields_into_properties
    ...
  end

  def after_initialize
    ...
  end

  ...
end

The problem I noticed was that I can't get empty values in :load_worker_count to be accepted by the validator, because:

In tracking down why these blank values are getting to the validation stage in the first place, I discovered the root of the problem: strip_attributes! only affects actual attributes, as returned by the attributes method. So the values which should be nil at time of validation are not. So it feels like the root cause is that the synthetic attributes I added in aren't seen when setting which attributes to strip, so therefore I ask:

Is there a proper way of creating synthetic attributes which are recognised as proper attributes by other code which integrates with ActiveRecord?

Upvotes: 1

Views: 1910

Answers (2)

giorgian
giorgian

Reputation: 3825

I assume you are talking of the strip_attributes plugin; looking at the code, it uses the method attributes, defined in active_record/base.rb, which uses @attributes, which is initialized (in initialize) as @attributes = attributes_from_column_definition.

Maybe it's possible to hack ActiveRecord::Base somehow, but it would be a hard work: @attributes is also used when getting/putting stuff from/to db, so you would have to do a lot of hacking.

There's a much simpler solution:

before_validate :serialise_fields_into_properties
...

def serialise_fields_into_properties
  if load_worker_count.respond_to? :strip
    load_worker_count = load_worker_count.blank? ? nil : load_worker_count.strip
  end
  ...
end

After all, this is what strip_attributes! does.

Upvotes: 1

Ariejan
Ariejan

Reputation: 11069

Wouldn't it be easier to just use Rails' serialize macro here?

class License < ActiveRecord::Base
  serialize :special_attributes
end

Now you can assign a hash or array or whatever you need to special_attributes and Rails will serialize it a text field in the database.

license = License.new
license.special_attributes = { :beer => true, :water => false }

This will keep your code clean and you don't have to worry about serializing/deserializing attributes yourself.

Upvotes: 1

Related Questions