parameters missing in update action rails - ruby-on-rails

What is causing this update action to not work? When I debug the controller all instance variables appear to contain the correct data, but i am left with an error param is missing or the value is empty
This is the abridged form
= simple_form_for [:admin, #job, #new_delivery] do |f|
- if jdc.new_record?
%tr
%td
.......
= f.input :quantity, label: false
......
= f.submit "Add", class: "btn btn-s btn-default"
- else
%tr
%td
.......
= jdc.delivery_cost.unit
%td
.........
%td
= link_to 'Delete', admin_job_job_delivery_cost_path(#job, jdc), method: :delete, class: "btn btn-xs btn-danger"
%td
= link_to 'Update', admin_job_job_delivery_cost_path(#job, jdc), method: :patch, class: "btn btn-xs btn-info"
I'll add the relevant controller actions below:
def update
set_job_delivery_cost
if #job.present? && #job_delivery_cost.present?
#job_delivery_cost.update_attributes(job_delivery_cost_params)
raise #job.inspect
if #job_delivery_cost.save
flash[:success]="Changes Saved"
else
flash.now[:error]="Check for Errors"
return render('admin/client_types/edit')
end
end
redirect_to admin_job_job_delivery_costs_path(#job)
end
def set_job_delivery_cost
#job_delivery_cost ||= JobDeliveryCost.find_by(id: params[:id])
end
def job_delivery_cost_params
params.require(:job_delivery_cost).permit!
end
these are the request parameters:
{"_method"=>"patch", "authenticity_token"=>"66fRYMn6/2ges7JU75d5sWUausx8GJvM5dsNpbRxlYQ=", "action"=>"update", "controller"=>"admin/job_delivery_costs", "job_id"=>"53", "id"=>"17"}
Server logs...
Started PATCH "/admin/jobs/53/job_delivery_costs/17" for 127.0.0.1 at 2014-07-23 18:36:27 +0100
Processing by Admin::JobDeliveryCostsController#update as HTML
Parameters: {"authenticity_token"=>"66fRYMn6/2ges7JU75d5sWUausx8GJvM5dsNpbRxlYQ=", "job_id"=>"53", "id"=>"17"}
Job Load (0.4ms) SELECT `jobs`.* FROM `jobs` WHERE `jobs`.`id` = 53 LIMIT 1
JobDeliveryCost Load (0.4ms) SELECT `job_delivery_costs`.* FROM `job_delivery_costs` WHERE `job_delivery_costs`.`id` = 17 LIMIT 1
Completed 500 in 6ms
ActionController::ParameterMissing - param is missing or the value is empty: job_delivery_cost:
delivery cost.rb
# == Schema Information
#
# Table name: delivery_costs
#
# id :integer not null, primary key
# title :string(255)
# unit :string(255)
# cost_per_unit :float
# created_at :datetime
# updated_at :datetime
#
class DeliveryCost < ActiveRecord::Base
UNIT_DAY='day'
UNIT_HOUR='hour'
UNIT_MILE='mile'
UNITS=[UNIT_DAY,UNIT_HOUR,UNIT_MILE]
has_many :job_delivery_costs
has_many :jobs, through: :job_delivery_costs
validates :cost_per_unit, presence: true
validates :unit, inclusion: UNITS
validates :title, presence: true
before_destroy :survive_if_jobs
private
def survive_if_jobs
jobs.empty?
end
end
job_delivery_cost.rb
# == Schema Information
#
# Table name: job_delivery_costs
#
# id :integer not null, primary key
# job_id :integer
# delivery_cost_id :integer
# cost_per_unit :float
# quantity :integer
# timing :string(255)
# created_at :datetime
# updated_at :datetime
#
class JobDeliveryCost < ActiveRecord::Base
TIMING_INSTALL='install'
TIMING_BREAKDOWN='breakdown'
TIMINGS=[TIMING_INSTALL,TIMING_BREAKDOWN]
belongs_to :delivery_cost
belongs_to :job
validates :quantity, presence: true, numericality: {greater_than_or_equal_to:1}
validates :timing, inclusion: TIMINGS
#validates :cost_per_unit, presence: true
validate :validate_cost_per_unit
validate :check_associates
# validate :quantity_default
before_save :init
private
def check_associates
associated_object_exists DeliveryCost, :delivery_cost_id
associated_object_exists Job, :job_id
end
def validate_cost_per_unit
if delivery_cost and cost_per_unit.blank?
self.cost_per_unit=delivery_cost.cost_per_unit
end
return false if cost_per_unit.blank?
end
def init
if self.quantity.nil?
self.quantity = 1
end
end
end

The error should be in your line params.require(:job_delivery_cost).permit!. It's happening because you are not actually sending the form to the update action.
You should change the line:
link_to 'Update', admin_job_job_delivery_cost_path(#job, jdc), method: :patch, class: "btn btn-xs btn-info"
with an actual submit button, something like this:
f.submit 'Update', class: "btn btn-xs btn-info"
For that to work, however, you need to change your form action and method to the update action.

The problem is in that the job_delivery_cost field isn't passed via params. So I see three variants of processing the variable:
Add belongs_to :job_delivery_cost to DeliveryCost, then you have to assign job_delivery_cost to either visible input, or hidden one:
Visible real input:
= f.input :job_delivery_cost
Hidden input, so you can't change it explicitly:
= f.input :job_delivery_cost, as: hidden, label: false
Try to read the value for the DeliveryCost model instance:
= f.input :job_delivery_cost, as: hidden, label: false, value_method: :job_delivery_cost
and model:
class DeliveryCost
# ...
def job_delivery_cost
# ... some code to return job_delivery_cost
end
end
Just remove requirement for job_delivery_cost field for params, and calculate it based on ids.

Related

Select field to create or update has_many records on update

I want to have a simple select field that allows users to choose some users to be on a job's team.
#jobs.rb
class Job < ApplicationRecord
has_many :team_members
end
#jobs_controller.rb
class JobsController < ApplicationController
def update
#job = Job.find(params[:id])
if #job.update_attributes(job_params)
flash[:notice] = "Job Saved"
redirect_to job_path(#job)
else
flash[:alert] = "Job Not Saved"
render :edit
end
end
private
def job_params
params.require(:job).permit(
team_member_ids: []
)
end
end
# jobs/edit.html.erb
<%= f.select :team_member_ids, User.all, {:include_blank => "None"},{ :multiple => true} %>
This gives me an error
Couldn't find TeamMembers with ids 117, 23, 30.
Am I missing something simple here? Or is the wrong approach to managing the data in the form?
Update, params hash:
{"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"xxxx",
"job"=>
{
"jobtype"=>"Cutting",
"status"=>"Not_Started",
"team_member_ids"=>["", "117", "23", "30"]},
"commit"=>"Update Job",
"id"=>"84"}
Oops, This was a bit silly.
The id's are user id's, but I was passing them as team_member ids. Where team_member is the join table.
Fix was:
#add to job.rb
has_many :users, through: :team_members
# update jobs_controller.rb
private
def job_params
params.require(:job).permit(
user_ids: []
)
end
# update form field:
<%= f.select :user_ids, User.all,{:include_blank => "None"},{ :multiple => true} %>

ensure fields hold values as more fields are added rails

I have a form for JobDeliveryCost where a user can add delivery costs. Every time the user adds one, an additional field is created to add another. at the moment, I have a form and I display the fields.
-jdc_array=(#job.job_delivery_costs.any? ? [#job.job_delivery_costs,#new_delivery].flatten : [#new_delivery])
-jdc_array.each do |jdc|
= simple_form_for [:admin, #job, #new_delivery] do |f|
%tr
%td
= jdc.timing
= f.input :timing, collection: JobDeliveryCost::TIMINGS, :include_blank => "please select"
%td
= f.input :delivery_cost_id, collection: DeliveryCost.order(:title), :label_method => :title,:value_method => :id
%td
-if jdc.new_record?
=f.submit "add"
-else
%td
= jdc.cost_per_unit
= f.input :cost_per_unit
%td
= jdc.quantity
= f.input :quantity
Instead of displaying the inputted value above each form entry, how do I get the fields to hold their value instead?
Also, how would I display the value of this
= f.input :delivery_cost_id, collection: DeliveryCost.order(:title), :label_method => :title,:value_method => :id
as it is a child attribute of the DeliveryCost model?
For extra enfo I have added my controller and relevant models
class Admin::JobDeliveryCostsController < ApplicationController
before_action :set_job
def index
# raise #job.inspect
if get_deliverable
#jdc_array=(#job.job_delivery_costs.any? ? [#job.job_delivery_costs,#new_delivery] : [#new_delivery])
# raise #jdc_array.inspect
#new_delivery = #deliverable.job_delivery_costs.build
end
set_job_delivery_cost
end
def create
if #job
#job_delivery_cost = JobDeliveryCost.new(job_delivery_cost_params)
#job_delivery_cost.job = #job
if #job_delivery_cost.quantity.nil?
#job_delivery_cost.quantity = 1
end
# raise #job_delivery_cost.inspect
if #job_delivery_cost.save
flash[:success] = "Delivery Costs Added"
else
flash[:error] = "Delivery Costs not Added"
end
else
flash[:error] = "Couldn't find the Job."
end
redirect_to admin_job_job_delivery_costs_path(#job)
end
def destroy
set_job_delivery_cost
if #job.present? && #job_delivery_cost.present?
#job_delivery_cost.destroy
flash[:success] = "Job delivery cost removed"
else
flash[:error] = "Couldn't find the record"
end
redirect_to admin_job_job_products_path(#job)
end
private
def set_job
#job = Job.find_by(id: params[:job_id])
end
def set_job_delivery_cost
#job_delivery_cost ||= JobDeliveryCost.find_by(id: params[:id])
end
def job_delivery_cost_params
params.require(:job_delivery_cost).permit!
end
def get_deliverable
return #deliverable if #deliverable
if params[:contact_id].present?
#deliverable = Contact.find_by(id: params[:contact_id])
elsif params[:client_id].present?
#deliverable = Client.find_by(id: params[:client_id])
elsif params[:job_id].present?
#deliverable = Job.find_by(id: params[:job_id])
end
#deliverable
end
end
delivery_cost.rb
# == Schema Information
#
# Table name: delivery_costs
#
# id :integer not null, primary key
# title :string(255)
# unit :string(255)
# cost_per_unit :float
# created_at :datetime
# updated_at :datetime
#
class DeliveryCost < ActiveRecord::Base
UNIT_DAY='day'
UNIT_HOUR='hour'
UNIT_MILE='mile'
UNITS=[UNIT_DAY,UNIT_HOUR,UNIT_MILE]
has_many :job_delivery_costs
has_many :jobs, through: :job_delivery_costs
validates :cost_per_unit, presence: true
validates :unit, inclusion: UNITS
validates :title, presence: true
before_destroy :survive_if_jobs
private
def survive_if_jobs
jobs.empty?
end
end
JobDeliveryCost
# == Schema Information
#
# Table name: job_delivery_costs
#
# id :integer not null, primary key
# job_id :integer
# delivery_cost_id :integer
# cost_per_unit :float
# quantity :integer
# timing :string(255)
# created_at :datetime
# updated_at :datetime
#
class JobDeliveryCost < ActiveRecord::Base
TIMING_INSTALL='install'
TIMING_BREAKDOWN='breakdown'
TIMINGS=[TIMING_INSTALL,TIMING_BREAKDOWN]
belongs_to :delivery_cost
belongs_to :job
validates :quantity, presence: true, numericality: {greater_than_or_equal_to:1}
validates :timing, inclusion: TIMINGS
#validates :cost_per_unit, presence: true
validate :validate_cost_per_unit
validate :check_associates
# validate :quantity_default
before_save :init
private
def check_associates
associated_object_exists DeliveryCost, :delivery_cost_id
associated_object_exists Job, :job_id
end
def validate_cost_per_unit
if delivery_cost and cost_per_unit.blank?
self.cost_per_unit=delivery_cost.cost_per_unit
end
return false if cost_per_unit.blank?
end
def init
if self.quantity.nil?
self.quantity = 1
end
end
end
If I understand correctly, you are going to want to use javascript to solve your problem. There is an excellent railscast that discusses nested model forms but it also shows you how to build javascript for dynamically adding field elements with links.
Instead of links, you will most likely want to register an onChange event (meaning the user changed data in the field) to add another field.

Same Issue in Two Tests: Something Wrong With Events

I believe something is wrong with the creation of events in my testing environment.
When I navigate in the browser everything is fine.
The two errors I get are:
1) Error:
test_should_post_save_period(PeriodRegistrationsControllerTest):
NoMethodError: undefined method `event' for nil:NilClass
2) Error:
test_should_get_index(PeriodsControllerTest):
ActionView::Template::Error: undefined method `name' for nil:NilClass
Error 1 test:
def setup
#period_registration= FactoryGirl.create(:period_registration)
end
test "should post save_period" do
sign_in(FactoryGirl.create(:user))
assert_difference('PeriodRegistration.count') do
post :save_period, period_registration: FactoryGirl.attributes_for(:period_registration)
end
assert_not_nil assigns(:period_registration)
# assert_response :success
end
Error 2 test:
test "should get index" do
sign_in(FactoryGirl.create(:user, admin: true))
get :index
assert_not_nil assigns(:periods)
assert_response :success
end
Error number one corresponds with this action in the controller:
def save_period
#period_registration = PeriodRegistration.new(params[:registration])
#period_registration.save
flash[:success] = "Successfully Registered for Session."
redirect_to event_url(#period_registration.period.event) #problem line
end
The second error corresponds with this line in my view:
<h6><%= period.event.name %> in <%= period.event.city %>, <%= period.event.state%></h6>
Here is my event factory:
factory :event do
name 'First Event'
street '123 street'
city 'Chicago'
state 'Iowa'
date Date.today
end
factory :period do
name 'First Period'
description 'This is a description'
start_time Time.now + 10.days
end_time Time.now + 10.days + 2.hours
event
product
end
factory :period_registration do
user
period
end
And my event model looks like this:
# == Schema Information
#
# Table name: events
#
# id :integer not null, primary key
# name :string(255)
# date :date
# street :string(255)
# city :string(255)
# state :string(255)
# created_at :datetime not null
# updated_at :datetime not null
#
class Event < ActiveRecord::Base
attr_accessible :city, :date, :name, :state, :street
has_many :periods
validates :name, presence: true
validates :street, presence: true
validates :city, presence: true
validates :state, presence: true
end
and here is my period model:
# == Schema Information
#
# Table name: periods
#
# id :integer not null, primary key
# name :string(255)
# event_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# start_time :time
# end_time :time
# description :text
# product_id :integer
#
class Period < ActiveRecord::Base
attr_accessible :event_id, :name, :time, :start_time, :end_time, :description, :product_id
belongs_to :event
belongs_to :product
has_many :period_registrations
validates_time :end_time
validates_time :start_time
validates_presence_of :name
validates_presence_of :start_time
validates_presence_of :end_time
validates_presence_of :description
end
Any ideas on what could be causing this?
I think it's because FactoryGirl.attributes_for(:period_registration) returns {} (empty hash). You can check it in rails console. And also you have typo in code: in test you send period_registration: FactoryGirl.attributes_for(:period_registration), but in controller you expects params[:registration]. This leads to the empty PeriodRegistration model is created in db. This model does not contain event_id and when you request event from model, it returns nil.
Why you do not use mock for these kind of tests?
I think it's because you are missing your Factory for the User model

Am I using the rails form properly?

I feel like I have no idea what I'm doing.. I have a vague Idea. I'm hoping I did this all right so far.
Any way you can see to refactor this would be greatly appreciated.
One thing I noticed it does wrong is it won't load the proper options that were previously submitted if there is an error and it posts to the same URL. The text inputs seem to load the previous value but the select and the radio buttons reset to the default every submit.
ResourcesController#new
def new
#resource = Resource.new
#title = "Submit Resource"
#categories = Category.all
end
ResourcesController#create (notice I have #categories = Category.all in both... according to DRY im not sure where else it should go, or it only works on the first form submit.
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
category_ids = #categories.map { |c| c[1] }
if #resource.valid? and category_ids.include? params[:category_id]
#resource.cost = params[:cost]
#resource.category_id = params[:category_id]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
Resource.rb (model)
# == Schema Information
#
# Table name: resources
#
# id :integer not null, primary key
# upvotes :integer default(0)
# downvotes :integer default(0)
# url :string(255)
# title :string(255)
# cost :integer default(0)
# description :text
# flags :integer
# category_id :integer
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Resource < ActiveRecord::Base
belongs_to :category
belongs_to :user
has_many :favorites
has_many :resource_tags
has_many :tags, :through => :resource_tags
attr_accessible :url, :title, :cost, :description, :category_id, :user_id
# Pseudo-Enum
COST = [:free, :paid, :both]
url_regex = /^(?:http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}(:[0-9]{1,5})?(\/.*)?$/ix
validates :url, :presence => true,
:format => { :with => url_regex,
:message => "must be valid"},
:uniqueness => { :case_sensitive => false,
:message => "has already been submitted"}
validates :title, :presence => true,
:length => { :within => 6..75 }
validates :cost, :presence => true
validates :description, :presence => true,
:length => { :within => 25..200 }
validates :category_id, :presence => true,
:format => { :with => /\d+/ }
validates :user_id, :presence => true,
:format => { :with => /\d+/ }
def cost
COST[read_attribute(:cost)]
end
def cost=(value)
write_attribute(:cost, COST.index(value.downcase.to_sym))
end
def category_id
read_attribute(:category_id).to_i
end
def category_id=(value)
write_attribute(:category_id, value.to_i)
end
end
My view file for the Resource#new form
<div class="field">
<%= f.label :category %>
<%= select_tag(:category_id, options_for_select(#categories.map {|c|[c.name, c.id]})) %>
</div>
Last Q: i havent worked with the user_id field yet. This is going to be pulled from devise and will associate a User with a submitted resource. But how do I assign this without making some sort of input, like a hidden input. Would this go on behind the scenes in the controller?
To your last question:
devise adds a current_user method which is the logged in user. So if a user has multiple resources you could do something like:
#resource = current_user.resources.new(params[:resource])
First question:
When a form is rendered it is done so based on the #resource & #categories variables. When you post the form the create action is called which creates a new #resource. If the save fails for whatever reason the form is rerendered using the new #resource variable. The problem you have is that #resource.category is not set when you show the form again. So you'll have to do this before the is_valid? check.
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
#resource.category = Category.find(params[:category_id])
if #resource.valid? # won't be valid if there is no category found.
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
But the real problem is with your form. It should nest the category_id in the resource params so that the category is set when you do Resource.new(params[:resource]).
Check the POST request body in your console or something and see if it's nested in the resource or not. I don't know the exact syntax for it but if you change this you can drop the #resource.category = Category.find line.
To piggyback on Sandip, you can dry up your actions by using a before_filter
class ResourcesController < ApplicationController
before_filter :load_categories, :only => [:show, :create]
def new
#resource = Resource.new
end
def create
#resource = Resource.new(params[:resource])
#resource.category = Category.find(params[:category_id])
if #resource.valid? # won't be valid if there is no category found.
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
private
def load_categories
#categories = Category.all
end
end
also if you plan on sticking #title inside of your application layout, I would change #title in your view to:
yield(:title) || 'My Site'
and on the appropriate pages use:
content_for(:title) do
Submit Resource
It will default to 'My Site' unless otherwise specified.
Looks like there is problem with create action
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
if #categories.collect(&:id).include?(params[:category_id].to_i)
#resource.category_id = params[:category_id]
end
#resource.user = current_user
if #resource.valid?
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
view
<div class="field">
<%= f.label :category %>
<%= select_tag(:category_id, options_for_select(#categories.map {|c|[c.name, c.id]}, :selected => #resource.category_id)) %>
</div>

Validations misfiring in a form with multiple models

I'm building a web app that saves a user's goals and tasks, where a user has_many goals, and a goal has_many tasks. When I try to save a goal and task together, I keep getting validation errors saying "Tasks goal can't be blank" and "Tasks content can't be blank," even though they clearly aren't. I'm certain the problem isn't with the actual form, but with the goal controller's 'new' or 'create' code, but whatever I try, I can't seem to get it right. Any ideas on why the validations for the task model are misfiring? I've been working on this issue for too long and I'm about to give up. I've included the goal controller, goal model, task model, and debug info. If you need to see any other code, let me know.
Goal Controller:
def new
#title = "New Goal"
#goal = Goal.new
#goal.tasks.build
end
def create
#user = current_user
#goal = #user.goals.build(params[:goal])
#task = #goal.tasks.build(params[:goal][:task])
if #goal.save
flash[:success] = "Goal created!"
redirect_to user_path(#user)
else
render 'new'
end
end
Goal Model:
# Table name: goals
#
# id :integer not null, primary key
# user_id :integer
# content :string(255)
# completed :boolean
# completion_date :date
# created_at :datetime
# updated_at :datetime
#
class Goal < ActiveRecord::Base
attr_accessible :content, :completed, :completion_date
belongs_to :user
has_many :tasks, :dependent => :destroy
accepts_nested_attributes_for :tasks
validates :user_id, :presence => true
validates :content, :presence => true, :length => { :maximum => 140 }
end
Task Model:
# id :integer not null, primary key
# goal_id :integer
# content :string(255)
# occur_on :date
# recur_on :string(255)
# completed :boolean
# completion_date :date
# created_at :datetime
# updated_at :datetime
#
class Task < ActiveRecord::Base
attr_accessible :content, :occur_on, :recur_on, :completed
belongs_to :goal
validates :goal_id, :presence => true
validates :content, :presence => true, :length => { :maximum => 140 }
end
Debug Dump after an unsuccessful save:
--- !map:ActiveSupport::HashWithIndifferentAccess
utf8: "\xE2\x9C\x93"
authenticity_token: NF/vVwinKQlGAvBwEnlVX/d9Wvo19MipOkYb7qiElz0=
goal: !map:ActiveSupport::HashWithIndifferentAccess
content: some goal
tasks_attributes: !map:ActiveSupport::HashWithIndifferentAccess
"0": !map:ActiveSupport::HashWithIndifferentAccess
content: some task
commit: Submit
action: create
controller: goals
This is a problem with nested attributes. You cannot validate the presence of the encapsulating model from the nested model (in your case, you cannot validate the presence of goal_id from Task). When validations are run the goal is not yet saved, and thus has no id, so it is impossible to assign it.
You can either eliminate the validation that is causing the problem, or not use the built-in nested attributes. In the latter case, you would need to add your own logic to first save the goal and then create any nested tasks.
Bummer, huh? I wish someone would come up with a good solution for this...maybe I'll work on it if I ever get some free time.

Resources