RecordNotFound with accepts_nested_attributes_for and belongs_to - ruby-on-rails

I get
ActiveRecord::RecordNotFound: Couldn't find Client with ID=3 for Order with ID=
when trying to submit an Order form for an existing client. This happens through the form or the console by typing:
Order.new(:client_attributes => { :id => 3 })
payment_form.html.erb:
<%= semantic_form_for #order, :url => checkout_purchase_url(:secure => true) do |f| %>
<%= f.inputs "Personal Information" do %>
<%= f.semantic_fields_for :client do |ff| %>
<%= ff.input :first_name %>
<%= ff.input :last_name %>
<!-- looks like semantic_fields_for auto-inserts a hidden field for client ID -->
<% end %>
<% end %>
<% end %>
Order.rb:
class Order < ActiveRecord::Base
belongs_to :client
accepts_nested_attributes_for :client, :reject_if => :check_client
def check_client(client_attr)
if _client = Client.find(client_attr['id'])
self.client = _client
return true
else
return false
end
end
end
The reject_if idea came from here but I logged the method and it's not even being called! It doesn't matter what its name is!

Note: Feb 2020
Since I'm starting to get downvotes on this 8 years later, adding this note. While this was the original solution I went with 8 years ago, a better one has been proposed by MatayoshiMariano (5 years after my OP).
My Original Fix
Fixed the issue by overloading the client_attributes= method, as described here:
def client_attributes=(client_attrs)
self.client = Client.find_or_initialize_by_id(client_attrs.delete(:id))
self.client.attributes = client_attrs
end

If you only want a new Order with an existing client, without modifying the client, you need to assign the id.
Order.new(client_id: 3)
This is another way to do this without overloading the client_attributes= method and cleanest
The new Order now has the client with ID 3
If you also want to update ant client's attributes you must add the client_attributes, for example:
Order.new(client_id: 3, client_attributes: { id: 3, last_order_at: Time.current })
See https://github.com/rails/rails/issues/7256 from 2012.

If you have has_many relationship, this will work. Tested on Rails 6.0.2
def clients_attributes =(attributes)
# Get IDs for any clients that already exist.
client_ids = attributes.values.map { |a| a[:id] }.compact
# Now find them all and move them to this section.
clients << Client.find(client_ids)
# Update them with standard `accepts_nested_attributes_for` behaviour.
super attributes
end

Had the same error creating a new Thing for existing model with has_many and belongs_to relations.
Fixed it by adding a hidden field for the id of the existing model, for instance User, to the form.
= form.input :user_id, as: :hidden
Then new Thing was created without the error.

Related

How do I call the name of a user when they belong to a model

I have two models, Chasing and User, a chasing belongs_to :user and a user has_many :chasings.
I created a migration for linking the two models together:
class AddUsersToChasings < ActiveRecord::Migration
def change
add_reference :chasings, :user, index: true, foreign_key: true
end
end
I have a controller for creating new users which I then want to be able to assign to chasings. I currently have this code in my chasings form for selecting the user:
<%= f.select :user_id, options_for_select(User.all.map {|c| [c.name, c.id]}), { :include_blank => "Please select user"}, {:class => "form-control"} %>
This seems to do the trick, after calling Chasing.first in rails console I can see the chasing now has user_id relevant to the user I picked. I can also run Chasing.first.user.name to give me the name of the user who is associated with the chasing. I'm wanting to show this name in my index view, the code I currently have for this is:
ChasingsController:
def index
#chasing = Chasing.all
end
Index view:
<% #chasing.each do |chasing| %>
<%= chasing.user %>
<% end %>
This shows a random string (seems to change every time I update a chasing - #<User:0xf5b0ba8> for example). when I change this to chasing.user.name I get 'undefined method `name' for nil:NilClass'.
Is there a way I can call the name for my view?
EDIT:
As per NickM's comment below I had chasings without users assigned to them causing active record to throw the error.
Looks like you have some Chasing objects in your database without user_ids. You can test by doing <%= chasing.user.name if chasing.user %>

Can't convert symbol to integer error with rails simple_form nested attributes

I've seen many similar questions, and looked at the answers, but nothing seems to be helping me and I've been working on this for a while now. The error is 'Can't convert symbol into integer'. My goal is to be able to create multiple sub_groups for each race. I'm just starting with trying to create one for the time being. Here's the relevant code...
** UPDATE **
VIEW
<%= simple_form_for(#race, :url => form_path, :method => form_method, :html => { :class =>
'form-horizontal form-compressed' }) do |f| %>
<fieldset>
<%= f.simple_fields_for :sub_groups do |g| %>
<%= g.input :name, requred: false %>
<%= g.collection_radio_buttons :discount_type,
[['dollars', '$'], ['percent', '%']], :first, :last %>
<%= g.input :discount_amount, :as => :integer, required: false %>
<% end %>
<hr/>
** RACE MODEL**
class Race < ActiveRecord::Base
has_many :sub_groups
accepts_nested_attributes_for :sub_groups
attr_accessible :sub_groups_attributes
** SUB_GROUP MODEL **
class SubGroup < ActiveRecord::Base
belongs_to :race
has_many :race_users
attr_accessible :discount_amount, :discount_type, :display_results, :name
end
PARAMS
after my code update...
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"VihBL4TDT/Lte4YBji/4fp4XvOri1UgUZ8B33wQuCko=", "race"=>
{"sub_group"=>{"name"=>"dfd", "discount_type"=>"dollars", "discount_amount"=>"2"}},
"commit"=>"Next", "wizard"=>"2", "id"=>"13-test5"}
CONTROLLER
class RacesController < ApplicationController
def new
#race = Race.new
#sub_groups = #race.sub_groups.build
#wizard_step = -1
#wizard_step_name = Race.wizard_step_name_from_id #wizard_step
#wizard_mode = true
render :layout => "race_wizard"
end
def update
#race = Race.find params[:id]
#wizard_step = params[:wizard].to_i + 1
#race.wizard_step = #wizard_step
#race.update_attributes(params[:race])
end
So I took advice from answer 1, and switched to using :sub_groups in the view. Now I have a new problem, which is the sub-group fields don't show up at all, despite the fact that I built a sub_groups thing in the #new method.
I'm really stumped on how I can make this happen. This is driving me bonkers. Any help is much appreciated. Thanks!
The way fields_for works is that if you supply a symbol it checks whether your model respond to {given_symbol}_attributes=. If it does the name of sub-fields is {given symbol}_attributes, otherwise just {given_symbol}.
What you need is to add accepts_nested_attributes_for :sub_groups to your Race model. This methods will create a default setter sub_groups_attributes=, which will make fields_for :sub_groups to generate fields with name sub_groups_attributes.
You can also write your own sub_groups_attributes= method, but you need to be sure you know what you're doing there as it might be a little tricky to debug.
Note, that fields_to :sub_groups won't display fields if there are no sub_group associated with given object - you will need to build one in your controller first.

Rails 4 NOT updating nested attributes

Issue: Instead of updating nested attributes, they are being created on top of the existing nested attributes when I hit the #update action of the associated features_controller.rb
Likely Cause: I think the problem lies in my lack of understanding in Rails' form_for. I think the breakdown is in my views, how I render the persisting nested attributes, and/or how I fail to specify the nested attribute's id, causing it to simply create a new one
feature.rb
class Feature < ActiveRecord::Base
...
has_many :scenarios
accepts_nested_attributes_for :scenarios,
allow_destroy: true,
reject_if: :all_blank
...
end
features_controller.rb
def update
...
project = Project.find(params[:project_id])
#feature = Feature.find(params[:id])
if #feature.update_attributes(feature_params)
# checking feature_params looks good...
# feature_params['scenarios'] => { <correct object hash> }
redirect_to project
else
render :edit
end
end
...
private
def feature_params
params.require(:feature).permit(:title, :narrative, :price, :eta, scenarios_attributes[:description, :_destroy])
end
_form.html.haml (simplified)
= form_for [#project, #feature] do |f|
...
- if #feature.new_record? -# if we are creating new feature
= f.fields_for :scenarios, #feature.scenarios.build do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
- else -# if we are editing an existing feature
= f.fields_for :scenarios do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
I'm sure there's a nicer way to achieve the if #feature.new_record? check. I'm also using a few Javascript hooks to create dynamic nested attribute forms (which I've left out), heavily influenced by Railscast #196 Nested Model Form (revised)
I would love a really nice Rails-y implementation of dealing with these sorts of nested forms.
Try adding :id to the :scenario_attributes portion of your feature_params method. You only have the description field and the ability to allow a destroy.
def feature_params
# added => before nested attributes
params.require(:feature).permit(:id, :title, :narrative, :price, :eta, scenarios_attributes => [:id, :description, :_destroy])
end
As #vinodadhikary suggested, you no longer need to check if feature is a new record, since Rails, specifically using the form_for method, will do that for you.
Update:
You don't need to define if #feature.new_record? ... else in your form. It will be taken care by Rails when you use form_for. Rails checks if the action is going to be create or update based on object.persisted?, so, you can update your form to:
= form_for [#project, #feature] do |f|
...
= f.fields_for :scenarios, #feature.scenarios.build do |builder|
= builder.label :description, "Scenario"
= builder.text_area :description, rows: "3", autocomplete: "off"
As #Philip7899 mentioned as a comment in the accepted answer, allowing the user to set the id means that they could "steal" children records belonging to another user.
However, Rails accepts_nested_attributes_for actually checks the id and raises:
ActiveRecord::RecordNotFound:
Couldn't find Answer with ID=5 for Questionnaire with ID=5
Basically the ids are looked for in the children association (again, as said by #glampr). Therefor, the child record belonging to another user is not found.
Ultimately, 401 is the response status (unlike the usual 404 from ActiveRecord::RecordNotFound)
Follows some code I used to test the behaviour.
let :params do
{
id: questionnaire.id,
questionnaire: {
participation_id: participation.id,
answers_attributes: answers_attributes
}
}
end
let :evil_params do
params.tap do |params|
params[:questionnaire][:answers_attributes]['0']['id'] = another_participant_s_answer.id.to_s
end
end
it "doesn't mess with other people's answers" do
old_value = another_participant_s_answer.value
put :update, evil_params
expect(another_participant_s_answer.reload.value).to eq(old_value) # pass
expect(response.status).to eq(401) # pass
end
In conclusion, adding the id to the permitted params as stated above is correct and safe.
Fascinating Rails.

Rails 4- Associations -Tutorial Movielist

Hi I am trying to use Rails on Rest 2 - movielist tutorial with rails 4 and making adjustments as I go for new rails. I am stuck on associations of Movies Roles. I have added to Movie.rb
class Movie < ActiveRecord::Base
has_many :roles, :dependent => :destroy
has_many :people, :through => :roles
validates_presence_of :title
def new_role=(values)
values.each do |i, hash|
unless hash[:name].blank?
roles.create(:person_id => hash[:person_id], :name => hash[:name])
roles.save
end
end
end
def deleted_roles=(values)
values.each do |role_id|
roles.find(role_id).destroy
end
end
end
and also to show _Form.html.rb that I render (excert below)
<b>Add New People</b><br />
<% (1..3).each do |i| %>
<%= select_tag 'movie_new_role_person_id', options_for_select(#people), {
:name => "movie[new_role][#{i}][person_id]"
} %>
<%= text_field_tag 'movie_new_role_name', '', {
:name => "movie[new_role][#{i}][name]"
} %><br />
<% end %>
</p>
<p>
<%= f.submit "Update" %>
</p>
It renders the list to choose from but when i submit - nothing is written to database table.
If I manually enter data in database then it displays on movielist page ie: "Stephen Spielberg - Director" etc...
Any help appreciated- Driving me nuts at this stage
I thought it might be params driven restriction but I do not have a good example of associations style params filter
Thanks
Alan
After debugging including Chicagogrrl's !flag I investigated the params.permits in the movies_Controller again and figured I would have to add the method types to the list of excepted. I could not find detailed info on syntax for this but trial and error paid off.
app/controllers/movies_controllers.rb (excerpt)
......
# Never trust parameters from the scary internet, only allow the white list through.
def movie_params
params.require(:movie).permit(:title,:description, :rating, :role, :deleted_roles=>[],
:new_role=> ['person_id', 'name'])
end
......
The delete_roles=>[] takes the array params and process to delete_roles method in movies.rb
the new_role=>['person_id', 'name'] takes the new_role individual params.
I Hope this saves somebody else some time andIf anybody needs anymore info just ask thanks again Alan

Modifying attributes on the join model with accepts_nested_attributes_for

Simply, a Contact can have various associated Time Windows, which may or may not be Active as a Schedule. To wit:
Models
class Contact < ActiveRecord::Base
has_many :schedules
has_many :time_windows, :through => :schedules
accepts_nested_attributes_for :schedules, :allow_destroy => true
end
class TimeWindow < ActiveRecord::Base
has_many :schedules
has_many :contacts, :through => :schedules
end
class Schedule < ActiveRecord::Base
belongs_to :contact
belongs_to :time_window
end
View
<% TimeWindow.all.each do |tw| %>
<% schedule = Schedule.find_by_contact_id_and_time_window_id(#contact.id, tw.id)
schedule ||= Schedule.new %>
<p>
<%= f.label tw.description %>
<%= hidden_field_tag "contact[schedules_attributes][][id]", schedule.id %>
<%= check_box_tag "contact[schedules_attributes][][time_window_id]",
tw.id, #contact.time_windows.include?(tw) %>
<%= check_box_tag "contact[schedules_attributes][][active]", nil,
schedule.active %>
</p>
<% end %>
This submits something like this:
Parameters: { "commit" => "Update", "contact" => {
"group_ids" => ["2"], "enabled" => "1",
"schedules_attributes" => [ { "time_window_id"=>"1", "id"=>"46"},
{ "time_window_id" => "2", "id" => "42", "active" => "on" },
{ "time_window_id" => "3", "id" => "43"},
{ "time_window_id" => "4", "id" => "44", "active" => "on"}],
"last_name" => ...
The update action in the controller is basically stock, except to handle another instance of another related model which I coded using the "Handling Multiple Models" example from the Advanced Rails Recipes book.
According to this API doc, I think the above ought to work. However, nothing about the Schedules is getting updated. This shows up in the server log:
[4;35;1mSchedule Update (0.2ms)[0m [0mUPDATE `schedules` SET `updated_at` = '2010-09-30 20:39:49', `active` = 0 WHERE `id` = 42[0m
[4;36;1mSchedule Update (0.1ms)[0m [0;1mUPDATE `schedules` SET `updated_at` = '2010-09-30 20:39:49', `active` = 0 WHERE `id` = 44[0m
(NetBeans is giving me those stupid "[0m"'s in the output. I don't know what's wrong there.)
The SQL shows that the "active" boolean field is getting set to 0 where checked. How do I get this to correctly set the active bit?
As a followup, how would I organize this to get rid of the Schedule "connection" at all? I'm thinking I need to submit a :_delete with the Schedule from the form, but how would I do that conditionally when a checkbox is involved?
Thanks for any help you can provide. Rails is turning out to be a vast subject for me, and I want to do it "right." I'm really close here, but there's got to be a way to make this -- not just correct -- but elegant. The view code just feels way too cumbersome to be proper Rails. ;-)
I've kept trying different approaches to this problem, and I've come up with this, which works. Mostly. The only problem is that it doesn't handle NOT having a "Schedule" for each "Time Window". The form will render, and I'll get a disabled check_box (to prevent me from trying to delete something that isn't there), but I don't have a way to add it back, and submitting without it throws off the params hash (and causes Rails to give me an "Expected Hash (got Array)" error)
<% TimeWindow.all.each do |tw| %>
<% schedule = Schedule.find_by_contact_id_and_time_window_id(#contact.id, tw.id)
schedule ||= Schedule.new %>
<% f.fields_for "schedules_attributes[]", schedule do |sf| %>
<p>
<%= sf.label tw.description %>
<%= sf.hidden_field :id %>
<%= sf.check_box :_destroy, :disabled => schedule.new_record? %>
<%= sf.check_box :active %>
</p>
<% end %>
<% end %>
Note that the "schedules_attributes[]" array will automatically give you an existing ID within the braces in your HTML (which is nice), but the _attributes hash is expecting an "id" alongside the other attributes in order to make sense of the sub-hashes.
One of the big lessons I've learned here is that the "check_box_tag" method doesn't (seem to) give me a paired-up hidden field for Rails to parse in the unchecked case. I would have expected this. Adding one in by hand made a mess, which led me to finally giving into the "fields_for" method, and trying many incarnations before finding the appropriate syntax to get what I wanted out of it.
I've realized that my model isn't quite appropriate in this setup, so I'm going to change it, but I was so close to this answer, I wanted to at least get to the point of being able to see the end before I moved on.

Resources