views:

285

answers:

2

I set out to solve a problem we have, where under certain circumstances; we may need to make an attribute or relationship immutable, that is once the attribute is written it is written forever. attr_readonly wasn't appropriate, as that didn't work for relationships, so I tried to prove the concept here:

http://pastie.org/562329

This shows my tests, an example implementation (that ideally I would move to a module) -- I'm not sure if this is an acceptable way to overload a relationship setter, I have expanded the test to now read http://pastie.org/562417 which may be overkill, but helped me understand how customer_id relates to customer as an attribute as against a relationship, but I'd be more than happy to be corrected!

A: 

You could define the setter method in such a way that after its first write, the method redefined itself as a no-op.

class Order < ActiveRecord::Base
  def customer= c
    if c
      super
      class << self
        def customer= c
        end
      end
    end
  end
end
Duncan Beevers
I dislike that idea. Apart from being too clever for its own good, it just won't work. When you reload the object, it will have lost it's singleton class and one can override it again. Furthermore, this just won't get noticed by update_attributes
Stefan Kanev
+1  A: 

I haven't tried it, but I think update_attribute(:customer_id => 1) will break in some cases. Also, I really don't like the idea of overriding attribute setters.

A better (IMHO) approach would be to override 'before_update' and take a not of it if anything was changed. You can do that using the Dirty module. Check out this:

I haven't tested this, but it would go something of the lines of:

class Order < ActiveRecord::Base
  IMMUTABLE = %w{customer_id notes}

  before_save do |record|
    return false if IMMUTABLE.any? { |attr| record.changed.has_key?(attr) }
  end
end

Of course, you'll have a better idea on how to handle IMMUTABLE. If you put this in a Module, the before_save has to be in the Module#included hook

Stefan Kanev
Stefan, do you have any evidence that overloading attribute setters is bad, or ill advised? I agree with your answer, it's much cleaner than mine, but I also can't find any documentation that this is the preferred way!
Beaks
I don't think it is bad or ill advised -- I just don't like it. It gets a bit messy when you have a relation (you have to override both foo= and foo_id=). Also, everything I can think of can be accomplished by Observers and other approaches.Also, for your particular case, you might want to check your solution with record.name << "something". It will bypass mine and it will also bypass overriding name=, since you're not invoking a setter, but changing the attribute in place.
Stefan Kanev