neezer
neezer

Reputation: 20580

RoR: before_save on nested object in form?

I have a form with a nested object (customer < order), and it works except that it keeps creating a new customer record.

I'd like to have it check to see if an existing customer is already present in the database (using the customer's email address to search), and if so, just attach the order to the existing record, but if not, continue creating a new customer record.

I think I have to do this by using an before_save call in my order model, but I don't know how to pass the form parameters (email address) in there. Is this the right approach, or is there some other way?


app/models/order.rb:

class Order < ActiveRecord::Base
  belongs_to :customer
  ...
  accepts_nested_attributes_for :customer
  ...
end

app/models/customer.rb:

class Customer < ActiveRecord::Base
  has_many :orders
  ...
end

app/controllers/orders_controller.rb:

def new
  @order = Order.new
  @order_number = "SL-#{Time.now.to_i}"
  @order.customer = Customer.new
  @services_available = Service.all
end

def create
  @order = Order.new params[:order]

  if @order.save
    flash[:notice] = 'Order was successfully created.'
    redirect_to @order
  else
    @order_number = "SL-#{Time.now.to_i}"
    render :action => "new"
  end
end

app/views/order/new.html.haml:

%h1 New order
- form_for @order do |order_form| 
  = error_messages_for :order
  %p
    = order_form.label :order_number
    %br
    = text_field_tag '', @order_number, :disabled => true, :id => 'order_number_display'
    = order_form.hidden_field :order_number, :value => @order_number
  - order_form.fields_for :customer do |customer_form| 
    %p
      = customer_form.label :first_name
      %br
      = customer_form.text_field :first_name
    %p
      = customer_form.label :last_name
      %br
      = customer_form.text_field :last_name
    %p
      = customer_form.label :phone
      %br
      = customer_form.text_field :phone
    %p
      = customer_form.label :email
      %br
      = customer_form.text_field :email
  %p= order_form.submit 'Create Order'
= link_to 'Back', orders_path

Upvotes: 2

Views: 1725

Answers (2)

John Kloian
John Kloian

Reputation: 1474

adding

update_only => true 

to

accepts_nested_attributes_for :[model]

will not destroy the nested model each time.

Upvotes: 0

EmFi
EmFi

Reputation: 23450

With accepts_nested_attributes_for the attributes for the nest model are passed in the params has with the key :model(s)_attributes in your case: params[:order][:customer_attributes]

Because you have the accepts_nested_attributes_for works on the belongs_to side of a one-to-many relationship you're going to have to do some extra work. By overriding the customer_attributes attr_writter created when you call accept_nested_attributes_for.

In short this should work, or at least help you figure it out:

class Order < ActiveRecord::Base
  accepts_nested_attributes_for :customer
  belongs_to :customer


  def customer_attributes_with_existence_check=(attributes)
    self.customer = Customer.find_by_email(attributes[:email])
    self.customer_attributes_without_existence_check=(attributes) if customer.nil?
  end

  alias_method_chain 'customer_attributes=', :existence_check

end

Upvotes: 2

Related Questions