Modifying attributes on the join model with accepts_nested_attributes_for - ruby-on-rails

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.

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.

How do I build a Rails form using inheritance and nested attributes?

I have a survey app, basically along the lines of Railscast 196, but with one snag: where the Railscast has one Question class, which has_many :answers, I have several:
Question (self.abstract_class = true)
BasicQuestion < Question
MultipleChoiceQuestion < Question
To make this work I had to override the questions getter in Survey, which seems a bit of a kludge but not too bad (is there a standard way to do this?):
Survey.rb
has_many :questions
accepts_nested_attributes_for :questions
def questions # simplified a bit for brevity
questions = []
[BasicQuestion, LikertQuestion, MultipleChoiceQuestion].each do |model|
questions += model.where(:survey_id => self.id)
end
questions
end
Survey_Controller.rb
def survey_params
params.require(:survey).permit(:name, :questions_attributes => [:id, :name])
end
So far, so good. The problem is this:
Again from the Railscast, I have this in surveys/edit.html.erb:
surveys/edit.html.erb
<%= f.fields_for :questions do |builder| %>
<%= render 'edit_question_fields', f: builder %>
<% end %>
However, this returns a hash of the form:
{ "survey" => { "name" => "Howard", questions_attributes => { "id" => "1", "name" => "Vince" }}}
Rails gives me an error: ActiveRecord::StatementInvalid (Could not find table '') --
presumably, because there is no Questions table (it's an abstract class).
So, how do I fix it? Without abandoning nested_attributes or inheritance entirely, I can think of four ways:
Switch to STI (instead of Question being an abstract class), include the _type field in the params hash, and go from there.
Let Survey deal with each question type separately:
Survey.rb
has_many :basic_questions
accepts_nested_attributes_for :basic_questions
has_many :multiple_choice_questions
accepts_nested_attributes_for :multiple_choice_questions
def questions
# same as before, still comes in handy
end
surveys/edit.html.erb
<% #survey.questions.each do |question| %>
<%= f.fields_for question do |builder| %>
<%= render 'edit_question_fields', f: builder %>
<% end %>
<% end %>`
This almost works, except that now my hash looks like this:
{ "survey" => { "name" => "Howard", "basic_question" => { "id" => "1", "name" => "Vince" }, "multiple_choice_question" => { "id" => "1", "name" => "Naboo" }}}
I need the questions indexed by, e.g., "basic_questions_attributes" instead of "basic_question" -- anyone know how to do this?
Override questions_attributes= in Survey.rb to sort it all out.
Create a new QuestionsFormBuilder object to handle everything, along the lines of "Rails nested attributes form for polymorphic/single table inheritance associations".
Obviously a primary concern is being able to drop in new Question subclasses later (or change the behavior of existing ones) with a minimum of hassle.
At the moment I'm inclined to go with option #3, as it seems simplest and most elegant, however, I'm not sure I'm not missing some better way to do this. (Or somehow screwing up the Question subclassing implementation.) Does anyone have any better ideas or more Rails-like ways of getting this to work?!
Take a look at using a form object to encapsulate the logic and creation of the questions? http://railscasts.com/episodes/416-form-objects
I would also look at using STI so that your Survey.rb doesn't need to redefine questions

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

RecordNotFound with accepts_nested_attributes_for and belongs_to

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.

Resources