views:

64

answers:

1

I have two models below. They can be explained as follows:

A report has a report_detail (which determines start/end months). Many reports can have the same report detail, but no two report details can be the same.

class Report < ActiveRecord::Base
  # attr: name :: String
  # attr: report_detail_id :: Integer
  belongs_to :report_detail
  accepts_nested_attributes_for :report_detail
end

class ReportDetail < ActiveRecord::Base
  # attr: duration :: Integer
  # attr: starting_month :: Integer
  # attr: offset :: Integer
end

I have a unique constraint on an index on ReportDetail for [:duration, :starting_month, :offset]

What I'm trying to accomplish is this: If a new Report has ReportDetail that has a unique combination attrs (:duration, :starting_month, :offset), create the new ReportDetail and save as normal. If a report has a ReportDetail whereby an existing ReportDetail has the same attributes, associate the report's detail with this ReportDetail and save report.

I got this to work by aliasing the setter on report_detail= using a ReportDetail.find_or_create_by... but it's ugly (and it also creates unnecessary ReportDetail entries just by instantiating new Reports with the detail attributes, and for some reason i couldn't get the save to work properly using .find_or_initialize_by...). I also tried a before_save on the ReportDetail to say, if I match something else, set self to that something else. Apparently you can't set self like that.

Any thought on the best way to go about this?

see this gist for my current setter overwrite with alias

A: 

Just ran at this problem today, my solution was based to object_attributes= method, which is provided by accepts_nested_attributes_for, i think this creates less mess instead of overriding the standard association setter method. Dived into rails source a bit to find this solution, here's the github link. The code:

class Report < ActiveRecord::Base
  # attr: name :: String
  # attr: report_detail_id :: Integer
  belongs_to :report_detail
  accepts_nested_attributes_for :report_detail

  def report_detail_attributes=(attributes)
    report_detail = ReportDetail.find_or_create_by_duration_and_display_duration_and_starting_month_and_period_offset(attrs[:duration],attrs[:display_duration],attrs[:starting_month],attrs[:period_offset])
    attributes[:id]=report_detail.id
    assign_nested_attributes_for_one_to_one_association(:report_detail, attributes)
  end
end

Some explanation, if you provide an id, will be considered as update, so no longer creates new objects. Also I know that this approach needs a query in plus, but i can't find a better solution for now.

Also, it seems like you have has_one association between report and report detail, if this is your case you might try this:

   class Report < ActiveRecord::Base
      # attr: name :: String
      # attr: report_detail_id :: Integer
      has_one :report_detail
      accepts_nested_attributes_for :report_detail, :update_only=>true
    end

Accoring to documentation this should work for you. From rails3 documentation:

:update_only

Allows you to specify that an existing record may only be updated. A new record may only be created when there is no existing record. This option only works for one-to-one associations and is ignored for collection associations. This option is off by default.

Hope this helps, anyway if you find a better solution please let me know.

dombesz
sorry for not answering/accepting, i've switched over to something else, haven't had a chance to test, will update when I do, thx for the answer!
brad