views:

167

answers:

2

How do you persist a derived attribute which depends on the value of id in rails? The snippet below seems to work-- Is there a better rails way?

class Model < ActiveRecord::Base
  ....
  def save
    super
    #derived_attr column exists in DB
    self.derived_attr = compute_attr(self.id)
    super
  end
end
+2  A: 

Callbacks are provided so you should never have to override save. The before_save call in the following code is functionally equivalent to all the code in the question.

I've made set_virtual_attr public so that it can be calculated as needed.

class Model < ActiveRecord::Base
  ...
  # this one line is functionally equivalent to the code in the OP.
  before_save :set_virtual_attr
  attr_reader :virtual_attr

  def set_virtual_attr
    self.virtual_attr = compute_attr(self.id)
  end
  private
  def compute_attr
    ...
  end  
end
EmFi
I was assuming that he has a column in the DB called virtual_attr that is based on ID.
Steve Weet
Could be, but generally attributes are virtual because they don't have a corresponding DB column. They are usually derived from existing columns. I've never heard of any other definition of virtual attributes when it comes to Rails.
EmFi
Good points. I have rephrased the question to use derived attribute instead of virtual attribute.
Chandra Patni
Thanks for pointing that out Chandra, I've updated my answer to address this new development.
EmFi
+1  A: 

I think the more accepted way to do this would be to provide a custom setter for the virtual attribute and then provide an after_create hook to set the value after the record is created.

The following code should do what you want.

class Virt < ActiveRecord::Base

  def after_create()
    self.virtual_attr = nil # Set it to anything just to invoke the setter

    save  # Saving will not invoke this callback again as the record exists
          # Do NOT try this in after_save or you will get a Stack Overflow
  end

  def virtual_attr=(value)
    write_attribute(:virtual_attr, "ID: #{self.id} #{value}")
  end
end

Running this in the console shows the following

v=Virt.new
=> #<Virt id: nil, virtual_attr: nil, created_at: nil, updated_at: nil>

>> v.save
=> true
>> v
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17", 
          updated_at: "2009-12-23 09:25:17">

>> Virt.last
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17", 
          updated_at: "2009-12-23 09:25:17">
Steve Weet