Issue
I'm encountering a problem when editing a form with a belongs_to relationship (extra_guest belongs_to age_table).
I am able to create a new extra_guest and assign it to an age_table, but I cannot get the edit/update to work as my update function returns a falseClass.--> #extra_guest.update(extra_guest_params).errors.full_messages returns undefined method `errors' for false:FalseClass
Code
models
class ExtraGuest < ApplicationRecord
belongs_to :age_table
validates :age_table, presence: true
end
class AgeTable < ApplicationRecord
belongs_to :park
has_many :extra_guests, dependent: :destroy
validates :name, :age_from, :age_to, presence: true
validates_associated :extra_guests
end
class Attraction < ApplicationRecord
belongs_to :park
has_many :extra_guests, dependent: :destroy
accepts_nested_attributes_for :extra_guests, allow_destroy: true
validates :name, presence: true
end
class Park < ApplicationRecord
has_many :attractions, dependent: :destroy
has_many :age_tables, dependent: :destroy
validates :name, :currency, presence: true
end
extra_guests_controller
def edit
#extra_guest = ExtraGuest.find(params[:id])
#age_table = #extra_guest.age_table
#age_table_list = AgeTable.where(park: #attraction.park)
end
def update
#extra_guest = #attraction.extra_guests.find(params[:id])
#age_table = AgeTable.find(params[:age_table])
authorize #extra_guest
if #extra_guest = #extra_guest.update(extra_guest_params)
redirect_to root_path
else
#attraction = Attraction.find(params[:attraction_id])
#extra_guest = ExtraGuest.find(params[:id])
#age_table_list = #attraction.park.age_tables
render 'edit'
end
end
private
def extra_guest_params
params.require(:extra_guest).permit(:name, :age_table_id,
extra_guest_prices_attributes: [:id, :name, :price_type, :start_date, :end_date, :price, :duration, :duration_min, :duration_max, :backend_only, :weekend_extra, :_destroy])
end
views/extra_guests/form
<%= simple_form_for [#attraction, #extra_guest] do |f|%>
<%= f.input :age_table, :as => :select, :selected => #age_table.id, :collection => #age_table_list.map {|u| [u.name, u.id]}, :include_blank => false %>
<% f.button :submit %>
Error message + params
Couldn't find AgeTable without an ID
{"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"l8HMnVIRybZg==",
"extra_guest"=>
{"age_table"=>"104",
"extra_guest_prices_attributes"=>
{"0"=>{"price"=>"1.0", "weekend_extra"=>"", "start_date"=>"2019-10-15", "end_date"=>"20-09-2019", "duration"=>"", "duration_min"=>"", "duration_max"=>"", "_destroy"=>"false", "id"=>"42"},
"1"=>{"price"=>"1.0", "weekend_extra"=>"", "start_date"=>"2019-10-15", "end_date"=>"2019-10-16", "duration"=>"", "duration_min"=>"", "duration_max"=>"", "_destroy"=>"false", "id"=>"43"}}},
"commit"=>"Save new option",
"attraction_id"=>"185",
"id"=>"55"}
First of all, you say you have an error with this code #extra_guest.update(extra_guest_params).errors.full_messages but the code you show does not have that line.
Now, the update method returns false if it fails https://apidock.com/rails/ActiveRecord/Persistence/update
This line:
#extra_guest = #extra_guest.update(extra_guest_params)
will set #extra_guest to false if it fails, you don't need to set #extra_guest, just use if #extra_guest.update(extra_guest_params)
Using the line of code you name but it's not on the code you showed,#extra_guest.update(extra_guest_params).errors.full_messages, if there are errors then #extra_guest.update(extra_guest_params) will be false, so no .errors method is found.
you have to split it in two lines:
#extra_guest.update(extra_guest_params) # after this, #extra_guest will have the errors hash set
#extra_guest.errors.full_messages # call it on the object and not on the result value from the update method
EDIT: you are permitting age_table_id but the parameter is age_table, fix the name of the parameter to be age_table_id too
It looks to me like you tried to use #attraction before defining it. You could fix this by moving your definition of #attraction further up in the method, but I would move it into its own method like so:
private
def attraction
#attraction ||= Attraction.find(params[:attraction_id])
end
Then you use the method name, which is now defined for the whole controller and invoked when you use it (as opposed to an instance variable which will just be 'nil' if you invoke it without defining it). The ||= allows the method to return the existing value of the instance variable if it is defined, rather than running the query every time the method is called. So first line of your update action would be
#extra_guest = attraction.extra_guests.find(params[:id])
I would do something similar for the other instance variables you have there (#extra_guest, #age_table, and #age_table_list should be defined in private methods separately). Incidentally, using a lot of instance variables for a single controller (you have 4 in this controller, which is a lot) is considered a bit of a code smell, but you should make something that works first and then refactor. Reference for later: https://thoughtbot.com/blog/sandi-metz-rules-for-developers
Related
I can't get rails to update my nested attributes, though regular attributes work fine. This is my structure:
unit.rb:
class Unit < ApplicationRecord
has_many :unit_skill_lists
has_many :skill_lists, through: :unit_skill_lists, inverse_of: :units, autosave: true
accepts_nested_attributes_for :skill_lists, reject_if: :all_blank, allow_destroy: true
end
unit_skill_list.rb:
class UnitSkillList < ApplicationRecord
belongs_to :unit
belongs_to :skill_list
end
skill_list.rb:
class SkillList < ApplicationRecord
has_many :unit_skill_lists
has_many :units, through: :unit_skill_lists, inverse_of: :skill_lists
end
And this is (part of) the controller:
class UnitsController < ApplicationController
def update
#unit = Unit.find(params[:id])
if #unit.update(unit_params)
redirect_to edit_unit_path(#unit), notice: "Unit updated"
else
redirect_to edit_unit_path(#unit), alert: "Unit update failed"
end
end
private
def unit_params
unit_params = params.require(:unit).permit(
...
skill_list_attributes: [:id, :name, :_destroy]
)
unit_params
end
end
The relevant rows in the form (using formtastic and cocoon):
<%= label_tag :skill_lists %>
<%= f.input :skill_lists, :as => :check_boxes, collection: SkillList.where(skill_list_type: :base), class: "inline" %>
Any idea where I'm going wrong? I have tried following all guides I could find but updating does nothing for the nested attributes.
Edit after help from Vasilisa:
This is the error when I try to update a Unit:
ActiveRecord::RecordInvalid (Validation failed: Database must exist):
This is the full unit_skill_list.rb:
class UnitSkillList < ApplicationRecord
belongs_to :unit
belongs_to :skill_list
belongs_to :database
end
There is no input field for "database". It is supposed to be set from a session variable when the unit is updated.
If you look at the server log you'll see something like skill_list_ids: [] in params hash. You don't need accepts_nested_attributes_for :skill_lists, since you don't create new SkillList on Unit create/update. Change permitted params to:
def unit_params
params.require(:unit).permit(
...
skill_list_ids: []
)
end
UPDATE
I think the best options here is to set optional parameter - belongs_to :database, optional: true. And update it in the controller manually.
def update
#unit = Unit.find(params[:id])
if #unit.update(unit_params)
#unit.skill_lists.update_all(database: session[:database])
redirect_to edit_unit_path(#unit), notice: "Unit updated"
else
redirect_to edit_unit_path(#unit), alert: "Unit update failed"
end
end
I have a model job and a model user, the both can choose contracts types (that's why i use polymorphic).
I created a model contract for each contracts and i create an attached_contract model too.
Job model
class Job < ApplicationRecord
has_many :contracts, through: :attached_contracts
has_many :attached_contracts, as: :contractable, dependent: :destroy
accepts_nested_attributes_for :attached_contracts, reject_if: :all_blank, allow_destroy: true
end
AttachedContract model
class AttachedContract < ApplicationRecord
belongs_to :contract
belongs_to :contractable, polymorphic: true
validates :contract, uniqueness: { scope: [:contractable_type,:contractable_id] }
end
Contract model
class Contract < ApplicationRecord
validates :name, presence: true, allow_blank: false
has_many :attached_contracts
end
Jobs_controller
def new
#job = Job.new
#job.attached_contracts.build
end
def create
#job = current_company.jobs.build(set_params)
if #job.save
redirect_to job_path(#job)
end
else
render :new
end
end
def set_params
params.require(:job).permit(:title, :description, :address, attached_contracts_attributes: [:id, :contract_id, :_destroy]
end
In my view :
<%= simple_form_for([:company, #job]) do |f| %>
<div class="nested-fields">
<%= f.association :contracts, as: :check_boxes %>
</div>
<% end %>
When I submit my form my model AttachedContract still empty, and the data are lost.
I try tu put a "raise" in my controller after #job = current_company.jobs.build(set_params)
and I have a empty array if I call #job.attached_contracts
I don't understand beause in the "Request parameters" (rails debug console) I have the values : "contract_ids"=>["", "1", "3"]
Any idea ? May be the problem is in the polymorphic implantation ?
Finally, I changed the requested parameters by "contract_ids: [ ]" and that's work perfectly !
I am upgrading from Rails 3 to 4, and so I am adopting strong parameters. It seems that nested attributes are not being passed successfully. I have read several related SO questions and blog posts, and I'm still stumped.
An Event has many Occurrences. When I submit the form to create a new Event and one or more Occurrences, I get "1 error prohibited this class from being saved: Occurrences start can't be blank." However, start is not blank, as I confirmed by looking at the Parameters that are posted:
{"utf8"=>"✓",
"authenticity_token"=>"aPRLtUbjW7EMxO2kWSzCEctHYZgvBvwuk2QUymfiwkM=",
"event"=>{"name"=>"some name",
"photo"=>#<ActionDispatch::Http::UploadedFile:0x007f9e9bc95310 #tempfile=# <File:/var/folders/rz/1p7tmbmj2t5fbfv2wjhvwcsh0000gn/T/RackMultipart20140927-52743-10pcxtg>,
#original_filename="10435871_671176211140_3488560836686101357_n.jpg",
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"event[photo]\"; filename=\"10435871_671176211140_3488560836686101357_n.jpg\"\r\nContent-Type: image/jpeg\r\n">,
"summary"=>"",
"facebook_event_link"=>"",
"occurrences_attributes"=>{"0"=>{"start"=>"09/30/2014",
"_destroy"=>"0"}},
"special"=>"0",
"prose"=>""},
"commit"=>"Create Event"}
Here are the relevant sections of the models and controller.
app/models/event.rb:
class Event < ActiveRecord::Base
has_many :occurrences, :dependent => :destroy
accepts_nested_attributes_for :occurrences, allow_destroy: true, reject_if: proc {|o| o[:start].blank? }
app/models/occurrence.rb:
class Occurrence < ActiveRecord::Base
belongs_to :event
validates_presence_of :start
app/controllers/events_controller.rb:
class EventsController < ApplicationController
def new
#event = Event.new
#event.occurrences.build
#event.passes.build
end
def create
#event = Event.create(event_params)
if #event.save
redirect_to #event, notice: 'Event was successfully created.'
else
render :action => "new"
end
end
...
private
def event_params
params.require(:event).permit(
:name, :expiration, :prose, :special, :summary, :photo, :link, :price, :student_price, :registration_switch, :facebook_event_link,
:occurrences_attributes => [:id, :start, :end, :_destroy])
end
end
Why isn't the information about Occurrences being passed correctly?
I'm not sure, but I think that strong parameters requires you permit the foreign key on associated models. So, perhaps you are missing event_id in your occurrences_attributes?
Not even close to 100% sure, but this could be it:
def event_params
params.require(:event).permit(
:name, :expiration, :prose, :special, :summary, :photo, :link, :price, :student_price, :registration_switch, :facebook_event_link,
:occurrences_attributes => [:id, :start, :end, :_destroy, :event_id])
end
Perhaps you need to ensure that your form has multipart: true
I keep getting this undefined error when I try to submit this form with nested attributes, not sure where it's coming from been wrestling with it for quite a while now, I am trying to let users select an option in the council model the submit their choice, I am not sure if I have my associations wired up correctly or if the error is coming from the form. Am a noob to rails. Thanks in advance.
Error Updated
Properties::BuildController#update
app/controllers/properties/build_controller.rb, line 21
Started PUT "/properties/5/build/council" for 127.0.0.1 at 2013-08-18 08:52:07 +0100
Processing by Properties::BuildController#update as HTML
Parameters: {"utf8"=>"✓","authenticity_token"=>"wBWQaxtBioqzGLkhUrstqS+cFD/xvEutXnJ0jWNtSa0=", "council_id"=>"1", "commit"=>"Save changes", "property_id"=>"5", "id"=>"council"}
Property Load (0.2ms) SELECT "properties".* FROM "properties" WHERE "properties"."id" = ? LIMIT 1 [["id", "5"]]
Completed 500 Internal Server Error in 35ms
NoMethodError - undefined method `[]=' for nil:NilClass:
Council View
<h1>Select Council</h1>
<%= form_tag url_for(:action => 'update', :controller => 'properties/build'), :method => 'put' do %>
<%= select_tag :council_id, options_from_collection_for_select(Council.all, :id, :name) %>
<%= submit_tag %>
<% end %>
Controller
class Properties::BuildController < ApplicationController
include Wicked::Wizard
steps :tenant, :meter, :council, :confirmed
def show
#property = Property.find(params[:property_id])
#tenants = #property.tenants.new(params[:tenant_id])
#meter = #property.build_meter
#council = #property.build_council
render_wizard
end
def edit
#property = Property.find(params[:property_id])
end
def update
#property = Property.find(params[:property_id])
params[:property][:status] = step.to_s
params[:property][:status] = 'active' if step == steps.last
#property.update_attributes(params[:property])
render_wizard #property
end
end
Council.rb
class Council < ActiveRecord::Base
attr_accessible :CouncilEmail, :name, :CouncilTel
belongs_to :property
end
UPDATED Propery.rb
class Property < ActiveRecord::Base
attr_accessible :name, :address_attributes, :tenants_attributes, :meter_attributes, :council_attributes, :property_id, :status
belongs_to :user
has_one :address, :as => :addressable
accepts_nested_attributes_for :address, :allow_destroy => true
has_one :council
accepts_nested_attributes_for :council, :allow_destroy => true
has_many :tenants, :inverse_of => :property
accepts_nested_attributes_for :tenants, :allow_destroy => true, :reject_if => :all_blank
has_one :meter
accepts_nested_attributes_for :meter, :allow_destroy => true
validates :name, :presence => :true
validates :address, :presence => :true
validates :tenants, :presence => true, :if => :active_or_tenants?
validates :council, :presence => true, :if => :active_or_council?
def active?
status == 'active'
end
def active_or_tenants?
(status || '').include?('tenants') || active?
end
def active_or_council?
(status || '').include?('council') || active?
end
end
I think this
params[:property]
is nil. So Ruby complains when doing
params[:property][:status] = 'foo'
You might want to do something like this:
if params[:property]
params[:property][:status] = 'foo'
end
However in your case the issue is because you are using a form_tag instead of a form_for, therefor params[:property] is not defined.
A better approach to check for nested attributes in ruby hashes nowadays is to use dig
Example:
params.dig(:property, :status)
If the key is not defined nil is returned.
If using a data file, make sure there's not a stray - in the data.yml. See this GitHub comment
I've been puzzling over this for quite some time now and can't figure it out.
I've got 2 models:
class Vehicle < ActiveRecord::Base
attr_accessible :year, :capacity,
:size, :body, :model_id, :maker_id, :parameters_attributes
validates :year, numericality: { greater_than: 1900 }
validates :year, :capacity, :size, :body, presence: true
belongs_to :model
belongs_to :maker
has_many :parameters
accepts_nested_attributes_for :parameters
end
and
class Parameter < ActiveRecord::Base
attr_accessible :tag, :value
validates :tag, :value, presence: true
belongs_to :vehicle
end
in new vehicle view i've got:
= form_for [:admin, #vehicle], html: { multipart: true } do |f|
=# some other stuff in between
= f.text_field :value, size: 4
I get this error
undefined method `value'
Just can't seem to get it working. Help, anyone?
EDIT
routes.rb
resources :vehicles
resources :parameters
resources :makers do
resources :models
end
If you are using nested form, you should have something like
f.fields_for :parameters do |parameter|
and than:
parameter.text_field :value, size: 4
Also, remember to create the some parameters in the controller, for example:
def new
#vehicle = Vehicle.new
2.times { #vehicle.parameters.build } #it will create 2 parameters
...
end
f refers to #vehicle, it seems only Parameter bears this field. That's why it fails.
Sidenotes:
In Vehicle you have accepts_nested_attributes_for :parameters but you don't have parameters_attributes in the attr_accessible, can't be good.
If you want to call the relationship in the form consider using fields_for
Ok, I've made a mess of things.
Firstly I've been trying to
def new
#vehicle = #vehicle.parameters.build
end
hence the error undefined method. After a while I got to the correct syntax, which is the one gabrielhilal added after a while.
def new
#vehicle = Vehicle.new
#vehicle.parameters.build
end
No matter ;) Still had problems, because after clicking "create" he wouldn't add records in the database. Turned out that I've set the validates presence: true for tag, but didn't assign any value to it. After fixing that, it worked like a charm. Thanks a lot for all the help.
On to the next puzzle.