views:

317

answers:

2

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:

  • If I omit :allow_blank, it fails validation complaining about it being blank
  • If I put in :allow_blank, it converts the blank to 0, which when fails on the :greater_than => 2

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?

+1  A: 

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.

giorgian
The thought of reimplementing `strip_attributes!` in my own code did occur to me (though it has to be `before_validate` rather than `before_save`, as my problem is occurring at validation.)
Trejkaz
ops, you're right. I'll edit the answer.
giorgian
I'll stress it once again: don't try to change the behaviour of how db attributes and non-db attributes are managed, it would be a huge work, and you should test it against *all* existing and future plugins.
giorgian
+1  A: 

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.

Ariejan
I wasn't keen on doing this, but my only reason is that I wanted to access these as normal fields from the form, i.e. I prefer to do this: licence.count = 5And it's also much easier to validate if you can use `validates_numericality_of` directly.Using serialize is on my list of things for the future. At the moment they're being serialised in a text format which I can directly use for other purposes but it's causing a bit of overhead implementing the conversion in both directions.
Trejkaz