andrunix
andrunix

Reputation: 1754

Rails: How to include form for related model

I have a Rails app using a "has_many :through" relationship. I have 3 tables/models. They are Employee, Store, and StoreEmployee. An employee can work for one or more stores. On the Store view (show.html.erb), I want to include a form to add an employee to the store. If the employee doesn't already exist, it is added to the Employees table. If it does already exist, it is just added to the store.

Here are the model definitions:

# models
class Employee < ActiveRecord::Base
  has_many :store_employees
  has_many :stores, :through => :store_employees
  attr_accessible :email, :name
end

class Store < ActiveRecord::Base
  has_many :store_employees 
  has_many :employees, :through => :store_employees
  attr_accessible :address, :name
end

class StoreEmployee < ActiveRecord::Base
  belongs_to :store
  belongs_to :employee
  attr_accessible :employee_id, :store_id
end

Using the rails console, I can prove the data models are working correctly, like so:

emp = Employee.first
str = Store.first
str.employees << emp    # add the employee to the store
str.employees           # shows the employees that work at the store
emp.stores              # shows the stores where the employee works

I have also updated my routes.rb to nest the employees resource under the stores resource like this:

resources :stores do
  resources :employees
end

Edit: I also added the resources :employees as "unnested" resource. So that now looks like this:

resources :stores do
  resources :employees
end
resources :employees

So, in the store controller's show action, I think it should look like this:

def show
  @store = Store.find(params[:id])
  @employee = @store.employees.build     # creating an empty Employee for the form

  respond_to do |format|
    format.html # show.html.erb
    format.json { render json: @store }
  end
end

And here is the store's show.html.erb including the form to add an employee:

<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @store.name %>
</p>

<p>
  <b>Address:</b>
  <%= @store.address %>
</p>

<%= form_for([@store, @employee]) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>

<%= f.label :email %>
<%= f.text_field :email %>

<%= f.submit "Add Employee" %>
<% end %>

<%= link_to 'Edit', edit_store_path(@store) %> |
<%= link_to 'Back', stores_path %>

I get this error message when I click the 'Add employee' button:

NoMethodError in EmployeesController#create

undefined method `employee_url' for #

So, what I am I missing? I think it has to do with the 'form_for' but I'm not sure. I am not sure why the EmployeesController#create is getting called. Am I doing something wrong with the routes.rb?

I don't know where the logic for adding the employee should go. In the Employees controller? Store Controller?

### Update

Here is the updated create method in the EmployeesController. I included the recommendation from Mischa's answer.

def create
  @store = Store.find(params[:store_id])
  @employee = @store.employees.build(params[:employee])

  respond_to do |format|
    if @employee.save
      format.html { redirect_to @store, notice: 'Employee was successfully created.' }
      format.json { render json: @employee, status: :created, location: @employee }
    else
      format.html { render action: "new" }
      format.json { render json: @employee.errors, status: :unprocessable_entity }
    end
  end
end

After making this change, I successfully saved the Employee but the store_employees association record is not created. I have no idea why. Here is the output in my terminal when creating a record. Note the store is SELECTed and the Employee is INSERTed but that is all. No store_employees record anywhere:

Started POST "/stores/1/employees" for 127.0.0.1 at 2012-10-10
13:06:18 -0400 Processing by EmployeesController#create as HTML  
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"LZrAXZ+3Qc08hqQT8w0MhLYsNNSG29AkgCCMkEJkOf4=",
"employee"=>{"name"=>"betty", "email"=>"[email protected]"},
"commit"=>"Add Employee", "store_id"=>"1"}   Store Load (0.4ms) 
SELECT "stores".* FROM "stores" WHERE "stores"."id" = ? LIMIT 1 
[["id", "1"]]    (0.1ms)  begin transaction   SQL (13.9ms)  INSERT
INTO "employees" ("created_at", "email", "name", "updated_at") VALUES
(?, ?, ?, ?)  [["created_at", Wed, 10 Oct 2012 17:06:18 UTC +00:00],
["email", "[email protected]"], ["name", "betty"], ["updated_at", Wed,
10 Oct 2012 17:06:18 UTC +00:00]]    (1.3ms)  commit transaction
Redirected to http://localhost:3000/stores/1 Completed 302 Found in
23ms (ActiveRecord: 15.6ms)

Upvotes: 2

Views: 166

Answers (1)

Mischa
Mischa

Reputation: 43298

The problem is caused by this snippet from your EmployeesController#create method:

redirect_to @employee

This tries to use employee_url, but that does not exist, because you have nested the employees resource in your routes file. Solution is to also add the "unnested" resource:

resources :stores do
  resources :employees
end
resources :employees

Or simply redirect to a different location. E.g. back to StoresController#show.

By the way, the default scaffolding won't do what you expect it to do: it won't create an employee that is related to a store. For that you have to rewrite it somewhat:

def create
  @store = Store.find(params[:store_id])
  @employee = @store.employees.build(params[:employee])

  respond_to do |format|
    if @employee.save
      format.html { redirect_to @employee, notice: 'Employee was successfully created.' }
      format.json { render json: @employee, status: :created, location: @employee }
    else
      format.html { render action: "new" }
      format.json { render json: @employee.errors, status: :unprocessable_entity }
    end
  end
end

Upvotes: 1

Related Questions