Rails: How to include form for related model - ruby-on-rails-3

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"=>"betty#kitchen.com"},
"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", "betty#kitchen.com"], ["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)

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

Related

Wrong controller is active when using an external SQL table.

I am working on a back-office app. The apps I am deploying have a model/table of user_messages. The user messages are used to display admin messages from a central app. The idea is that I can use that app to publish messages to the individual apps such as "System will be unavailable from noon to 1 on Friday".
The individual apps use their own schema in the database. for example, the research library would be rl.user_messages etc.
Since I will need to access multiple tables, I set it up so I can access external tables.
production:
adapter: sqlserver
host: server1
port: 1435
database: web
username: XX
password: xXX
schema_search_path: umc
technical_libraries:
adapter: sqlserver
host: server1
port: 1435
database: XXXX
username: XX
password: XXXXXXXX
schema_search_path: tl
The model that lets me connect to the technical library as an external model is
class TechnicalLibrary < ActiveRecord::Base
self.abstract_class = true
def self.table_name_prefix
'tl_'
end
establish_connection "technical_libraries" # TODO might want to name this to a generic
end
class UserMessage < TechnicalLibrary
self.table_name = "tl.user_messages" # for this one, as opposed to the product development, we need to specify the schema.
end
My technical Libraries controller is
class TechnicalLibrariesController < ApplicationController
def index
#user_messages= TechnicalLibrary::UserMessage.all
end
def show
#technical_library = TechnicalLibrary::UserMessage.first # TODO HARDWIRED -
end
def new
#technical_library = TechnicalLibrary::UserMessage.new
end
def edit
#technical_library = TechnicalLibrary::UserMessage.find(params[:id])
end
def create
#technical_library = TechnicalLibrary::UserMessageRl.new(technical_library_params)
respond_to do |format|
if #technical_library.save
format.html { redirect_to #technical_library, notice: 'Technical library was successfully created.' }
format.json { render :show, status: :created, location: #technical_library }
else
format.html { render :new }
format.json { render json: #technical_library.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #technical_library.update(technical_library_params)
format.html { redirect_to #technical_library, notice: 'technical library was successfully updated.' }
format.json { render :show, status: :ok, location: #technical_library }
else
format.html { render :edit }
format.json { render json: #technical_library.errors, status: :unprocessable_entity }
end
end
end
def destroy
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_technical_library
#technical_library = TechnicalLibrary.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def technical_library_params
params.require(:technical_library).permit(:message, :expires)
end
My technical Libraries form is
<%= form_for(#technical_library) do |f| %>
<% if #technical_library.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#technical_library.errors.count, "error") %> prohibited this technical_library from being saved:</h2>
<ul>
<% #technical_library.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :message %><br>
<%= f.text_field :message %>
</div>
<div class="field">
<%= f.label :expires %><br>
<%= f.datetime_select :expires %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<hr>
Controller <%= controller_name %> | <%= action_name %>
<hr>
The form looks as follows. The submit button seems to be wrong. it is pointing to another model.
If I click on the submit button, I get an error message as follows. I suspect that the problem lies in inheriting from another model.
NameError in UserMessagesController#show
uninitialized constant TechnicalLibrary::UserMessageRl
Rails.root: C:/Users/cmendla/RubymineProjects/user_message_console_3
Application Trace | Framework Trace | Full Trace
app/controllers/user_messages_controller.rb:15:in `show'
Request
Parameters:
{"id"=>"1"}
I had a UserMessage model that I'm probably not going to use since I will connect to the individual application's tables.
class UserMessage < ActiveRecord::Base
end
OK - figured it out. I had to specify the controller and action in the form_for statement.
<%= form_for #technical_library, :url => { :controller => "technical_libraries", :action => "update" }, :html => {:method => :post} do |f| %>
That seems to be doing the trick

How to move database operations from controller to model

I've started to work on old project with new tools. This time - Rails on Ruby. I managed to make some progress, and now i want to improve one element of my code.
Whole project is about bugtracking with full history search of all tracked bugs. Now i'm on stage where user is entering bugs. Every bug belong to table which belongs to projects.
Only problem - now - is autocompletion of table name when i'm using completely new name (with tables that are already present in database it's working just fine, fills table_id in Bug entry).
Part of view responsible for entering (or selecting from existing) table looks like this:
<div class="control-group">
<%= f.label :table_name, :class => 'control-label' %>
<div class="controls">
<%= f.autocomplete_field :table_name, autocomplete_table_name_bugs_path %>
</div>
</div>
Nothing unusual. This one goes to the model (Bug.rb)
class Bug < ActiveRecord::Base
attr_accessible :bugid, :fixdate, :fixnote, :fixstate_id, :milestone, :newtarget, :notes, :oldtarget, :project_id, :bugreason, :reason_id, :regressiondate, :regressionstate_id, :source, :stringid, :table_id
belongs_to :project
belongs_to :table
belongs_to :reason
belongs_to :state
def table_name
table.name if table
end
#def table_name=(name)
# self.table = Table.find_or_create_by_name(name) unless name.blank?
#end
end
No validation for now as you can see. table_name=(name) commented as it's apparently doing nothing in my code.
This is working with bugs controller (bugs_controller.rb)
def create
if params[:bug][:table_id].nil?
if Table.find_or_create_by_name(params[:bug][:table_name])
params[:bug][:table_id] = Table.find_by_name(params[:bug][:table_name]).id
params[:bug].delete :table_name
end
end
#bug = Bug.new(params[:bug])
respond_to do |format|
if #bug.save
format.html { redirect_to bugs_path, notice: 'Bug was successfully created.' }
format.json { render json: #bug, status: :created, location: #bug }
else
format.html { render action: "new" }
format.json { render json: #bug.errors, status: :unprocessable_entity }
end
end
end
I put here only part responsible for saving new bugs, i'll manage to handle update part when i do this part right.
What i want to improve is first part. Now it's responsible not only for changing table_name to table_id but for creation of new table if it doesn't exist. I'm aware that this part should be handled by model, but i've no idea how to do that, could use some help.
Another part, as btw. is my dropdown menu where user can select active project. It's handled by partial:
<% #projects.each do |project| %>
<% if project.id == session[:current_project].to_i %>
<li class="disabled"><%= link_to project.name, '#' %></li>
<% else %>
<li><%= link_to project.name, choose_project_path(project.id) %></li>
<% end %>
<% end %>
But it works fine only when used from projects controller. How - by the book - i can handle this from other controllers? To be exact, i want it working same way in whole project.
For now i'm handling it by snippet in every controller, but i'm pretty sure that RoR gods are not happy with me for that.
before_filter :projects
def projects
#projects = Project.all
end
How it should be done in proper way? :)
So i managed to move logic from controller to model. Also, it's saving all data i need - table_id to bugs table and project_id (stored in session) to tables table when creating new table.
So part of form render is not changed:
<div class="control-group">
<%= f.label :table_name, :class => 'control-label' %>
<div class="controls">
<%= f.autocomplete_field :table_name, autocomplete_table_name_bugs_path %>
</div>
</div>
Model now looks like this:
class Bug < ActiveRecord::Base
attr_accessible :table_name, :bugid, :fixdate, :fixnote, :fixstate_id, :milestone, :newtarget, :notes, :oldtarget, :project_id, :bugreason, :reason_id, :regressiondate, :regressionstate_id, :source, :stringid, :table_id
belongs_to :project
belongs_to :table
belongs_to :reason
belongs_to :state
def table_name
table.name if table
end
def table_name=(name)
self.table = Table.find_or_create_by_name(name, project_id: self.project_id ) unless name.blank?
end
end
So only change is uncommenting table_name= method and adding project_id to creation of new table (and lack of project_id was the reason it didn't work earlier).
Controller looks like this:
def create
#bug = Bug.new(params[:bug])
#bug.project_id = session[:current_project]
respond_to do |format|
if #bug.save
format.html { redirect_to bugs_path, notice: 'Bug was successfully created.' }
format.json { render json: #bug, status: :created, location: #bug }
else
format.html { render action: "new" }
format.json { render json: #bug.errors, status: :unprocessable_entity }
end
end
end
It's working like charm. Still i've question remaining, but will post it as separate one.

Rails validation count limit on has_many :through

I've got the following models: Team, Member, Assignment, Role
The Team model has_many Members. Each Member has_many roles through assignments. Role assignments are Captain and Runner. I have also installed devise and CanCan using the Member model.
What I need to do is limit each Team to have a max of 1 captain and 5 runners.
I found this example, and it seemed to work after some customization, but on update ('teams/1/members/4/edit'). It doesn't work on create ('teams/1/members/new'). But my other validation (validates :role_ids, :presence => true
) does work on both update and create. Any help would be appreciated.
Update: I've found this example that would seem to be similar to my problem but I can't seem to make it work for my app.
It seems that the root of the problem lies with how the count (or size) is performed before and during validation.
For Example:
When updating a record...
It checks to see how many runners there are on a team and returns a count. (i.e. 5) Then when I select a role(s) to add to the member it takes the known count from the database (i.e. 5) and adds the proposed changes (i.e. 1), and then runs the validation check. (Team.find(self.team_id).members.runner.count > 5) This works fine because it returns a value of 6 and 6 > 5 so the proposed update fails without saving and an error is given.
But when I try to create a new member on the team...
It checks to see how many runners there are on a team and returns a count. (i.e. 5) Then when I select a role(s) to add to the member it takes the known count from the database (i.e. 5) and then runs the validation check WITHOUT factoring in the proposed changes. This doesn't work because it returns a value of 5 known runner and 5 = 5 so the proposed update passes and the new member and role is saved to the database with no error.
Member Model:
class Member < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :password, :password_confirmation, :remember_me
attr_accessible :age, :email, :first_name, :last_name, :sex, :shirt_size, :team_id, :assignments_attributes, :role_ids
belongs_to :team
has_many :assignments, :dependent => :destroy
has_many :roles, through: :assignments
accepts_nested_attributes_for :assignments
scope :runner, joins(:roles).where('roles.title = ?', "Runner")
scope :captain, joins(:roles).where('roles.title = ?', "Captain")
validate :validate_runner_count
validate :validate_captain_count
validates :role_ids, :presence => true
def validate_runner_count
if Team.find(self.team_id).members.runner.count > 5
errors.add(:role_id, 'Error - Max runner limit reached')
end
end
def validate_captain_count
if Team.find(self.team_id).members.captain.count > 1
errors.add(:role_id, 'Error - Max captain limit reached')
end
end
def has_role?(role_sym)
roles.any? { |r| r.title.underscore.to_sym == role_sym }
end
end
Member Controller:
class MembersController < ApplicationController
load_and_authorize_resource :team
load_and_authorize_resource :member, :through => :team
before_filter :get_team
before_filter :initialize_check_boxes, :only => [:create, :update]
def get_team
#team = Team.find(params[:team_id])
end
def index
respond_to do |format|
format.html # index.html.erb
format.json { render json: #members }
end
end
def show
respond_to do |format|
format.html # show.html.erb
format.json { render json: #member }
end
end
def new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #member }
end
end
def edit
end
def create
respond_to do |format|
if #member.save
format.html { redirect_to [#team, #member], notice: 'Member was successfully created.' }
format.json { render json: [#team, #member], status: :created, location: [#team, #member] }
else
format.html { render action: "new" }
format.json { render json: #member.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #member.update_attributes(params[:member])
format.html { redirect_to [#team, #member], notice: 'Member was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #member.errors, status: :unprocessable_entity }
end
end
end
def destroy
#member.destroy
respond_to do |format|
format.html { redirect_to team_members_url }
format.json { head :no_content }
end
end
# Allow empty checkboxes
# http://railscasts.com/episodes/17-habtm-checkboxes
def initialize_check_boxes
params[:member][:role_ids] ||= []
end
end
_Form Partial
<%= form_for [#team, #member], :html => { :class => 'form-horizontal' } do |f| %>
#...
# testing the count...
<ul>
<li>Captain - <%= Team.find(#member.team_id).members.captain.size %></li>
<li>Runner - <%= Team.find(#member.team_id).members.runner.size %></li>
<li>Driver - <%= Team.find(#member.team_id).members.driver.size %></li>
</ul>
<div class="control-group">
<div class="controls">
<%= f.fields_for :roles do %>
<%= hidden_field_tag "member[role_ids][]", nil %>
<% Role.all.each do |role| %>
<%= check_box_tag "member[role_ids][]", role.id, #member.role_ids.include?(role.id), id: dom_id(role) %>
<%= label_tag dom_id(role), role.title %>
<% end %>
<% end %>
</div>
</div>
#...
<% end %>
Try
class Member < ActiveRecord::Base
...
def validate_runner_count
if self.team.members.runner.count > 5
errors.add(:role_id, 'Error - Max runner limit reached')
end
end
def validate_captain_count
if self.team.members.captain.count > 1
errors.add(:role_id, 'Error - Max captain limit reached')
end
end
end

Nested model form not working properly - need to pinpoint POST/GET redirect

I have a nested model form that isn't functioning properly. The POST is to the proper place, but then the GET reroutes me. So I'm wondering if anyone can help explain what I'm doing wrong.
I have two models: User and Profile. Code for them below:
User:
class User < ActiveRecord::Base
attr_accessor :password, :email
has_one :profile, :dependent => :destroy
accepts_nested_attributes_for :profile
...
end
Profile:
class Profile < ActiveRecord::Base
attr_accessible :first_name, :last_name, etc.
belongs_to :user
accepts_nested_attributes_for :user
...
end
New/Create from both models:
class UsersController < ApplicationController
def new
#user = User.new
if logged_in?
redirect_to current_user.profile
end
end
def create
#user = User.new(params[:user])
if #user.save
redirect_to signup_path, :notice => 'User successfully added.'
else
render :action => 'new'
end
end
class ProfilesController < ApplicationController
def new
#profile = Profile.new
end
def create
#profile = Profile.new(params[:profile])
if #profile.save
redirect_to profile_path, :notice => 'User successfully added.'
else
render :action => 'new'
end
end
def index
#profile = current_user.profile
end
My signup (two step process) mixes the models, so as I said I'm using a nested model form in my Users new.html.erb file. Code form_for and f.fields_for below:
<%= form_for(:user, :url => signup_path, :html => {:id => 'homepage'}) do |f| %>
<%= f.fields_for :profile do |f| %>
Now when I enter data into the form, my routes.rb file seems to POST to the proper place (/signup so profile can be filled out further), but GET routes me to /login.
Routes.rb:
match '/login' => "sessions#new", :as => "login"
match '/signup' => 'profiles#new', :as => "signup"
match 'skip/signup', :to => 'info#signupskip'
match 'skip/profiles/new', :to => 'profiles#newskip'
root :to => 'users#new'
resources :users
resources :profiles
In rails server:
Started POST "/signup" for 127.0.0.1 at Sun Aug 28 19:54:11 -0400 2011
Processing by ProfilesController#new as HTML
Started GET "/login" for 127.0.0.1 at Sun Aug 28 19:54:11 -0400 2011
Processing by SessionsController#new as HTML
Rendered sessions/new.html.erb within layouts/application (32.1ms)
I'm wondering if the problem is in my layouts/application file, specifically this code:
<% if logged_in? %>
<%= render 'layouts/header_in' %>
<% else %>
<%= render 'layouts/header_out' %>
<% end %>
Can anyone help explain to me what I'm doing wrong?
UPDATE:
I deleted the if/else argument in `layouts/application' and it was still redirected. So I'm back to wondering what's going on.
I believe your problem has to do with an inherent issue (though arguably not problem) with HTTP protocol. You cannot return a redirect to a POST request. Alternatives include calling the other method from within the first controller action, or rendering the correct page directly from that action, or some mix of both.

New record not being saved with any values

I started the question differently, about a collection_select, but I found out that is not the problem.
This particular model won't save any data at all. It just ignores the values in the parameters. I can only save new records with NULL values (except for the timestamp fields).
See my comment for my latest try to fix it.
I have generated a few models with the handy scaffold command. Now I have tried to change a textbox to a collection_select for linking the new entity to the correct related one.
Using rails 3.1RC4 (hopefully this is not a bug).
In the _form.html.erb I use the following code:
<div class="field">
<%= f.label :category_id %><br />
<%= f.collection_select(:category_id, Admin::Category.all, :id, :name) %>
</div>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
...all other items...
<div class="actions">
<%= f.submit %>
</div>
After I click the submit button I receive error messages. It says that the name and permalink do not comply to the validation. I don't understand however, because in the logfiles I find this:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"my token is here", "admin_branche"=>{"category_id"=>"3", "name"=>"Verzekeraars", "permalink"=>"verzekeraars", "visible"=>"1"}, "commit"=>"Create Branche"}
To me it seems that the params contain all the needed values.
For the sake of completeness I will post my create method and model below.
So far I have tried switching back and forth between collection_select and f.coll... with no success. The current setup seems most appropriate to me, based on the logs.
I have also googled a lot, but haven't been able to find the answer. Question 2280106 on this site looks the same, but it had to do with attr_accessible which I have commented out in the model (I restarted the server afterwards and retried, just to be sure).
Help is much appreciated!
branche.rb:
class Admin::Branche < ActiveRecord::Base
# attr_accessible :name, :permalink
#relationships
has_many :courses, :as => :parent
belongs_to :category
#validations
validates :name, :presence => true, :length => {:maximum => 255}
validates :permalink, :presence => true, :length => { :within => 4..25 }
end
create action in the controller:
def create
#admin_branch = Admin::Branche.new(params[:admin_branch])
respond_to do |format|
if #admin_branch.save
format.html { redirect_to #admin_branch, notice: 'Branche was successfully created.' }
format.json { render json: #admin_branch, status: :created, location: #admin_branch }
else
format.html { render action: "new" }
format.json { render json: #admin_branch.errors, status: :unprocessable_entity }
end
end
end
In the controller, you're doing this:
#admin_branch = Admin::Branche.new(params[:admin_branch])
You should do this:
#admin_branch = Admin::Branche.new(params[:admin_branche])
If you look at the request parameters, the attributes are under "admin_branche", not "admin_branch".
I think that should solve your problems, if not, please let us know.
If you have problems with the generated inflections, you can completely customize them in the config/initializers/inflections.rb
just add something like this:
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'branch', 'branches'
end