Model setters, override attribute=(attribute)?

Hi,

I have a model similar Basket shown below. If customer_type is set then it should also set customer to nil. Likewise, if I set customer, it should inspect the customer_type attached to the customer and set accordingly within Basket. However implementing it as below doesn't seem to work.

Am I missing a better way to achieve this? I had also considered using a before_save :set_customer_type kind of thing but felt the code may loose a little readability. I'm coming from a Java world with protected attributes, accessed via domain influence methods, .set_customer, .process_order etc... I wasn't really sure how best to interpret this into ruby on rails models.

class Basket < ActiveRecord::Base

  belongs_to :customer_type   belongs_to :customer

  def customer=(_customer)     write_attribute(:customer, _customer)     customer_type = _customer.customer_type unless _customer.nil?   end

end

Thanks, Andrew.

Andrew Edwards wrote:

I have a model similar Basket shown below. If customer_type is set then it should also set customer to nil. Likewise, if I set customer, it should inspect the customer_type attached to the customer and set accordingly within Basket. However implementing it as below doesn't seem to work. ... class Basket < ActiveRecord::Base   belongs_to :customer_type   belongs_to :customer

  def customer=(_customer)     write_attribute(:customer, _customer)     customer_type = _customer.customer_type unless _customer.nil?   end end

http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/4fe057494c6e23e8

I prefer to do this sort of stuff in a callback triggered by .save, such as before_save, after_save, etc. That way it’s not destructive until I “commit” it.

Just my .02.

Thanks for the thoughts.

I like the idea of using the callbacks, I think in many simple cases of setting associated fields it works well. The situations I think I am more referring to are those where you are responding to an event within the model. Such an event would trigger other methods and possibly execute within a database transaction. I suppose in reality these are not often tied to simple updates of a single field, in which case I would probably create a new all-encompassing method anyway.

I suppose I'm just used to encapsulating attributes behind setters and model event methods. The AR way of exposing everything probably means a different approach/mindset? Is it okay not to worry about accidentally setting an attribute that really should only be set as part of a larger transaction?

Make the callbacks public. You can call them when you want.

before_save :do_my_stuff

def do_my_stuff

does stuff

end

The thing to remember is that Ruby is not Java, or C#… Essesntially, everything is public (even private stuff), so encapsulating things behind accessors isn’t really necessary. Often times, it’s not even desireable.

When I do

user.first_name = “Brian”

I don’t want that saved to the db.

So when I do

foo.bar = “something”

I would expect that to work the same way.

There’s no real right or wrong answer though :slight_smile: Do what makes you comfortable. One thing you will find with Ruby is that simple is better, and almost always ends up working better too. That’s what I love about it.

The last line in #customer= should be "self.customer_type = ... "

Remember, attribute writers cannot stand alone or ruby interprets them as local variables. Most likely, your code is not working because of this bug, not because of a bigger design issue.

It's a nuisance, and has cost me hours of debugging in the past too.

regards, Jürgen

HIi,

I m not really sure and I just would like to mention it: my_basket.customer_id = 1 will not call your setter