Reputation: 1754
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
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?
### UpdateHere 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
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