1

I'm new to rails and I'm creating a simple app that has clients with addersses. After getting some advice and views from the stack overflow community, I decided to save addresses as a seperate model

I am now trying to implement this in my app but I'm having problems getting the address to save correctly from the "new client" form. here is my code so far:

class Address < ActiveRecord::Base
    belongs_to :client
end

class Client < ActiveRecord::Base
    has_one :address
    before_create :build_address, unless: Proc.new { |client| client.address }
end



<%= form_for(@client) do |f| %>
  <% if @client.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@client.errors.count, "error") %> prohibited this client from being saved:</h2>

      <ul>
      <% @client.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :phone_number %><br>
    <%= f.text_field :phone_number %>
  </div>

  <%= f.fields_for :address do |a| %>
    <div class="field">
      <%= a.label :house_number %><br>
      <%= a.number_field :house_number %>
    </div>
    <div class="field">
      <%= a.label :house_name %><br>
      <%= a.text_field :house_name %>
    </div>
    <div class="field">
      <%= a.label :post_code %><br>
      <%= a.text_field :post_code %>
    </div>
  <% end %>

  <div class="actions">
    <%= f.submit %>
  </div>

<% end %>

With this, the client is created successfully but the address record is created with empty fields. No errors.

Any help would be most appreciated.

Thanks

Community
  • 1
  • 1
Craig
  • 139
  • 3
  • 10
  • Do you have `accepts_nested_attributes_for :address`? What do you have in `params.require(:client)`? – j-dexx Oct 20 '15 at 11:24

2 Answers2

3

First off - there are very few situations where ActiveModel callbacks don't cause grief. Usually putting logic into your models is a good thing - but getting callbacks to run just when you need them and not for example in unrelated tests is near impossible.

In this case you only need to build the address so that the form inputs are pre populated in your new action. There is no other reason for all your Client instances to have an empty address record piggybacking on them all the time.

So instead we would do it like this:

class Client < ActiveRecord::Base
  has_one :address
  accepts_nested_attributes_for :address
end

class ClientController < ApplicationController
  def new 
    @client = Client.new
    @client.build_address
  end

  def create
    @client = Client.create(client_params)
    # ...
  end

  def client_params
    params.require(:client)
          .permit(
             :name, :phone_number, 
             address_attributes: [:house_number, :house_name]
          )
  end
end
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
max
  • 96,212
  • 14
  • 104
  • 165
  • Thanks for the reply. This is giving me an error: undefined method `build_address' for nil:NilClass – Craig Oct 20 '15 at 11:51
  • Turns out it was client.build_address not @client.address.build_address. Thanks again max – Craig Oct 20 '15 at 11:54
  • Also `before_create` is run when you call `.save` before the record is actually inserted into the DB (but after validations). If you were to use a callback it would be `after_initialize`. http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html – max Oct 20 '15 at 12:00
2

You'll need accepts_nested_attributes_for:

#app/models/client.rb
class Client < ActiveRecord::Base
    has_one :address
    accepts_nested_attributes_for :address
    before_create :build_address, unless: Proc.new { |client| client.address }
end

This will allow you to do the following:

#app/controllers/clients_controller.rb
class ClientsController < ApplicationController
   def new
      @client = Client.new
      @client.build_address
   end
end

This should work for you.

Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • Won't `build_address` run twice? – max Oct 20 '15 at 11:52
  • No for 2 reasons. 1) the build_address is only explicitly invoked in `new` (not `create`), and 2) the after_create callback has a condition which prevents building if the address exists – Richard Peck Oct 20 '15 at 11:54