Unable to add rows to join table in rails 5 app - ruby-on-rails

I have three tables - result, feedback_qs, and feedback_qs_results (join table). Given an existing result and feedback_qs, I want to populate the join table with the result_id, feedback_q_id and the subsequent answer (text_area).
I believe the relationships are correct....
# result.rb
has_many :feedback_q_results
has_many :feedback_qs, :through => :feedback_q_results
# feedback_q.rb
has_many :feedback_q_results
has_many :results, :through => :feedback_q_results
# feedback_q_result.rb
belongs_to :result
belongs_to :feedback_q
I have access to the result_id (result/result.id/feedback) via config/routes.rb below...
resources :results do
member do
get 'feedback'
post 'feedback_create'
end
end
What would this form look like to populate the join table? Something similar to this (I realize this isn't correct)...? How do I take in the data through the post_controller to populate the join table? Or should it go through another controller?
<%= form_for :result, url: feedback_create_result_path(result), method: :post do |form| %>
<% for q in FeedbackQ.all %>
<div class="col field">
<p>
<%= form.label q.question_text %>
</p>
<%= text_area_tag id: q.id %>
</div>
<% end %>
<div class="actions">
<%= form.submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
What would my feedback_create method look like? While I've seen several questions regarding rails' many to many relationships and populating join tables, none of those use cases seems to suit my need. Thanks in advance.
Update - My use case is this: user takes an exam (called a result), and they then review the proctor by answering feedback questions. I want to record the feedback in the join table

If your has_many associations are working correctly, then in the rails console you should be able to do:
> result.feedback_qs
=> []
and
> feedback_q.results
=> []
Where result and feedback_q are are Result and FeedbackQ objects, respectively.
To associate a feedback_q to a result, simply:
> result.feedback_qs << feedback_q
This should create a record in the feedback_q_result table.
Now, when you do
> result.feedback_qs
It should return the feedback_q record that you just added.

I altered my original models. One issue I ran into was simple naming convention in rails, so I simplified the table names to eliminate that from the equation. New models:
# result.rb
has_many :feedbacks
has_many :questions, :through => :feedbacks
# question.rb
has_many :feedbacks
has_many :results, :through => :feedbacks
# feedback.rb
belongs_to :question
belongs_to :result
validates_uniqueness_of :question_id, scope: :result_id
form in my view:
<%= form_for :result, url: feedback_create_result_path(result), method: :post do |form| %>
<% Question.all.each do |question| %>
<div class="col field">
<p>
<%= form.label :question, question.question_text %>
</p>
<%= hidden_field_tag 'question[][id]', question.id %>
<%= text_area_tag 'question[][answer]', "", id: "question_" + question.id.to_s, class: "stretch_textarea" %>
</div>
<% end %>
<div class="actions">
<%= form.submit "Submit", class: "btn btn-primary" %>
</div>
This SO page helped quite a bit as well.

Related

Rails Forms Passing Array in Params for M to M Associaton

Edit: Essentially looking to pass something like this:
{
'tabled_id' : '1',
'recipes' : [{
{ 'recipe_id' : '3',
'quantity' : '2'
}
{ 'recipe_id' : '5',
'quantity' : '1'
}
}]
}
And I think I should do params.require(:order).permit(:table_id, {recipes:, [:id,:quantity]} ) on the controller side.
I'm learning Rails building an ordering system and I'm stuck trying to build a form for Orders that passes quantity. Where Orders is a nested resource for Restaurant.
My models look like this:
class Restaurant < ActiveRecord::Base
has_many :orders
has_many :recipes, dependent: :destroy
end
class Order < ActiveRecord::Base
belongs_to :restaurant
has_many :order_recipes, dependent: :destroy
has_many :recipes, through: :order_recipes
end
class Recipe < ActiveRecord::Base
belongs_to :restaurant
has_many :order_recipes
has_many :orders, through: :order_recipes
end
View:
<%= form_for([#restaurant, #order]) do |order_form| %>
<%= order_form.label :Table_Number %>
<%= order_form.number_field :table_id %>
<h3>Recipes: </h3>
<br>
<% #restaurant.recipes.each do |recipe| %>
<%= order_form.fields_for :recipe, recipe do |r| %>
<%= r.label recipe.name %>
<%= r.hidden_field :id %>
<%= r.number_field :quantity %>
<% end %>
<% end %>
<%= order_form.submit(#order.new_record? ? "Create Order" : "Edit Order", class: "btn btn-success") %>
<% end %>
This will yield a form that looks correct, but won't pass all parameters. Let's say I have 3 recipes. And I set their quantities to 2,3,4 respectively, and the table_id to 1. When I inspect the parameters, I see that only the last recipe with its quantity has been passed. params[:order] => {"table_id"=>"1", "recipe"=>{"id"=>"4", "quantity"=>"4"}} I need to be able to send all recipes with their assigned quantities. Also, I'm using the accepted answer in this question to be able to access the quantity column: Rails 4 Accessing Join Table Attributes
When you hand in fields_for :recipes multiple times, the fields_for method is not aware of you sending an array of things. Therefore it will name the parameters as if it was only one instance, so only the last instance will come through. You have to hand in the array of recipes to the fields_for, so it can name the parameters, so that rails knows it is an array of things when it gets picked up again (docs).
This is because form parameters in browsers do not support nesting by default. The actual parameters are flat key-value paramters. Rails has some naming conventions on how paramters can be named, so they will automatically be coerced to an array.
<%= form_for([#restaurant, #order]) do |order_form| %>
<%= order_form.label :Table_Number %>
<%= order_form.number_field :table_id %>
<h3>Recipes: </h3>
<br>
<%= order_form.fields_for :recipes, #restaurant.recipes do |r| %>
<%= r.label recipe.name %>
<%= r.hidden_field :id %>
<%= r.number_field :quantity %>
<% end %>
<%= order_form.submit(#order.new_record? ? "Create Order" : "Edit Order", class: "btn btn-success") %>
<% end %>

Rails 4 - checkboxes for has_and_belongs_to_many association

I recently had a problem getting checkboxes to work for a has_and_belongs_to_many (HABTM) association in Rails 4. I was able to find the information on how to get it working correctly in a few disparate places, but thought it would be good to document the few simple steps necessary to get it working correctly in one place here on StackOverflow.
As a setup assume a model of Kennel with a HABTM association to Handler.
class Kennel
has_and_belongs_to_many :handlers
end
This is all you need to do for the form: Don't do it manually when there is a built in helper.
<%= form_for #kennel do |f| %>
<%= f.collection_check_boxes(:handler_ids, Handler.all, :id, :to_s) %>
<% end %>
The form should have something like this:
<%= form_for(#kennel) do |form| %>
...
<div class="field">
<div class="field_head">Handlers</div>
<%= hidden_field_tag("kennel[handler_ids][]", nil) %>
<% Handler.order(:name).each do |handler| %>
<label><%= check_box_tag("kennel[handler_ids][]", id, id.in?(#kennel.handlers.collect(&:id))) %> <%= handler.name %></label>
<% end %>
</div>
...
<% end %>
The hidden_field_tag allows the user to uncheck all the boxes and successfully remove all the associations.
The controller needs to allow the parameter through strong parameters in the permitted_params method:
params.permit(kennel: [:city, :state
{handler_ids: []},
:description, ...
])
References:
http://railscasts.com/episodes/17-habtm-checkboxes
https://coderwall.com/p/_1oejq
I implement has_and_belongs_to_many association this way:
model/role
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
model/user
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
users/_form.html.erb
---
----
-----
<div class="field">
<% for role in Role.all %>
<div>
<%= check_box_tag "user[role_ids][]", role.id, #user.roles.include?(role) %>
<%= role.name %>
</div>
<% end %>
</div>
users_controller.rb
def user_params
params.require(:user).permit(:name, :email, { role_ids:[] })
end
Intermediate table_name should be roles_users and there should be two fields:
role_id
user_id

Rails - Display Nested Attributes in an "Update" Form

I am having a bit of difficulty getting my "update form" to display the nested attributes. Specifically, images (e.g., "choices") to display. All other data fields are showing. Just not this is my form:
<%= bootstrap_form_for #template, :url => {:action => 'update', :id => #template.id } do |f| %>
<fieldset>
<legend>Update Your Template</legend>
<%= f.text_field :prompt, :class => :span6, :placeholder => "Which one is running?", :autocomplete => :off %>
<%= f.select 'group_id', options_from_collection_for_select(#groups, 'id', 'name', selected: #template.group.id) %>
<div class="row-fluid">
<ul class="thumbnails">
<%= f.fields_for :template_assignments do |builder| %>
<li class="span3" id="">
<div class="thumbnail">
<%= builder.text_field :choice_id %>
<%= image_tag #template.template_assignments.builder.choice.image %>
</div>
</li>
<% end %>
</ul>
</div>
<% end %>
The main line I am having trouble with is:
<%= image_tag #template.template_assignments.builder.choice.image %>
I cannot get it to iterate through each of the 4 nested attributes for the image. It iterates through the 4 nested attributes pertaining to :choice_id, which displays correctly in the text_field.
If i change it to:
<%= image_tag #template.template_assignments.first.choice.image %>, it displays the first image no problem.
However, I need it to iterate and display the "first", "second", "third", and "fourth" images.
Any help on how to display these images, just as the image_id's are being displayed?
EDIT:
Here are my models
# app/models/template.rb
class Template < ActiveRecord::Base
belongs_to :group
has_many :template_assignments, dependent: :destroy
has_many :choices, :through => :template_assignments
accepts_nested_attributes_for :template_assignments, allow_destroy: true
end
# app/models/template_assignment.rb
class TemplateAssignment < ActiveRecord::Base
belongs_to :template
belongs_to :choice
end
# app/models/choice.rb
class Choice < ActiveRecord::Base
has_many :template_assignments
has_many :templates, :through => :template_assignments
end
You'll probably want to just use builder directly, just like you're doing in the text_field.
<%= image_tag builder.choice.image %>
[UPDATE] after some trial and error the correct form would be :
<%= image_tag builder.object.choice.image %>
What's happening is that when f.fields_for :template_assignments do |builder| is used to render the nested items, the builder object that is yielded to the block is not the object itself (in this case a TemplateAssignment), but is a FormBuilder object, which is what supplies the convenience methods like builder.text_field. (If you tried to do template_assignment.text_field you'd get an error.) The builder stores the object that it is representing in the form as object, so you can get a hold of your template_assignment object by using builder.object. From there you can deal the the template_assignment like normal. I hope that helps.

collection_select not filtering (in nested_form w/nested resources)

I've got an application that uses nested resources (see routes.rb below) to completely segregate users. It works great until I use collection_select to allow users to select objects from other models. For example, if I visit the store index view as user A, I only see stores created by user A. However, if I visit the store_group view and try to select a store to add to the group from the collection_select menu under the fields_for :store_group_details, I see all stores created by all users.
As far as I can tell, the problem might happen because there is no filter for stores in the store_group controller. store_group_details doesn't have a controller, but from what I've read, that seems to be correct since the model can only be accessed through a nested form in the store_group view. I have another situation where another view for another resource has several collection_select menus for selecting objects from other models, and all of those have the same problem (they display all objects in that model, regardless of which user created them).
How can I filter the objects shown in the collection_select menus? Is it a problem with what I'm passing into collection_select, or is it because the controllers don't do anything to filter the other models before those models' objects are displayed? I've looked at the docs for collection_select, and couldn't make it work based on that.
Thanks for your help, I've spent quite a bit of time trying to get this to work.
user.rb
class User < ActiveRecord::Base
has_many :store_groups
has_many :stores
has_many :store_group_details
end
store.rb
class Store < ActiveRecord::Base
belongs_to :user
has_many :store_group_details
has_many :store_groups, :through => :store_group_details
end
store_group.rb
class StoreGroup < ActiveRecord::Base
belongs_to :user
has_many :store_group_details, :inverse_of => :store_group
has_many :stores, :through => :store_group_details
accepts_nested_attributes_for :store_group_details
attr_accessible :store_group_details_attributes
end
store_group_detail.rb
class StoreGroupDetail < ActiveRecord::Base
belongs_to :store
belongs_to :store_group
belongs_to :user
attr_accessible :store_id
delegate :store_name, :to => :store
end
_store_group_form.html.erb
<div class="container">
<div class="span8">
<%= nested_form_for([#user, #store_group]) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label "Store Group Name (required)" %>
<%= f.text_field :store_group_name %>
<%= f.label "Store Group Description" %>
<%= f.text_area :store_group_description %>
<%= f.fields_for :store_group_details %>
<p><%= f.link_to_add "Add store to group", :store_group_details %></p>
<br>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
_store_group_detail_fields.html.erb
<p>
<%= f.label "Select Store:" %>
<%= f.collection_select :store_id, Store.order(:store_name),
:id, :store_name, include_blank: true %>
<%= f.link_to_remove "remove" %>
</p>
routes.rb
resources :users do
resources :stores
resources :store_groups
resources :store_group_details
end
There must be a problem with your controller. Did you ever solve this issue?
Look at this answer as it might lead to a solution. Or add you stores_controller.rb.

Rails has_many checkboxes with extra attribtues

We have the following code working for a complex rails form with checkboxes. I'm not really happy with the solution we have in place and I was wondering if anyone knows of a more proper way to do this in rails. All the code below is working I just want to know if there is a cleaner approach.
In my Admins controller I want to remove the need to call the following code on each update.
#user.admin.school_admin_roles.destroy_all
params[:roles].each do |school_role|
ids = school_role.split('_')
#user.admin.school_admin_roles.find_or_create_by_school_id_and_school_role_id(ids[0], ids[1])
end if !params[:roles].nil?
So I basically want to be able to call #user.update_attributes(params[:user]) and have rails take care of creating the needed relationships for me. I have that working with AccountRole in the form below. I want to know if there is a way to do the same thing with SchoolRole given I have an extra variable school_id in the join table.
We have the following form for editing a user and assigning roles
Screenshot of form ->
http://i.stack.imgur.com/PJwbf.png
I have the following form where an admin can edit other users and assign account based roles and school based roles via checkboxes. The account based roles were easy to implement. The school based rules are a bit complicated since the join table school_admin_roles has school_id, user_id, role_id fields. We had to implement the school roles part of the form in a rather hackish way. We have the form implemented like this - notice how we hacked together school.id.to_s+'_'+role.id.to_s into the same checkbox on school roles.
In the Admins controller's update function we manually destroy all school_admin roles on each update then loop through the school roles params do a split on the ids on '-' then manually re-create each school based role. I really hate the way we've had to go about this. Could anyone shed some light on a cleaner more rails centric approach to solving this scenario?
The form -
<%= form_for #user, :url => {:controller => 'admins', :action => 'update'} do |f| %>
<%= f.label :username %>
<%= f.text_field :username %>
<%= f.fields_for :admin do |uf| %>
<div class="field">
<%= uf.label :first_name %>
<%= uf.text_field :first_name %>
</div>
<label>Admin Permissions</label>
#account level permissions works fine
<%= hidden_field_tag "#{uf.object_name}[account_role_ids][]" %>
<% AccountRole.find(:all).each do |role| %>
<div class="account_role">
<%= check_box_tag "#{uf.object_name}[account_role_ids][]", role.id, #user.admin.account_roles.include?(role)%>
<%= role.name %>
</div>
<% end %>
#school level permissions a bit of a hack
<%= hidden_field_tag "#{uf.object_name}[school_role_ids][]" %>
<% SchoolRole.find(:all).each_with_index do |role, index| %>
<div class="school_role">
<%= check_box_tag "#{uf.object_name}[school_role_ids][]",role.id, #user.admin.school_roles.include?(role) %>
<%= role.name %>
<span class="advanced_box admin_permissions" <% if #user.admin.school_roles.include?(role) %>style="display:inline"<% end %>>
<div class="content" id="perm_<%= index %>">
<h4><%= role.name %></h4>
<% uf.object.account.schools.each do |school|%>
<div>
<%= check_box_tag "roles[]", school.id.to_s+'_'+role.id.to_s, role.school_admin_roles.where(:admin_id => uf.object.id).collect(&:school_id).include?(school.id)%>
<%= school.name %>
</div>
<% end %>
<%= link_to 'Done', '#', :class => "done" %>
</div>
Advanced
</span>
</div>
<% end %>
</div>
<% end %>
The controller
class AdminsController < ApplicationController
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
# TODO find a way to refactor this
#user.admin.school_admin_roles.destroy_all
params[:roles].each do |school_role|
ids = school_role.split('_')
#user.admin.school_admin_roles.find_or_create_by_school_id_and_school_role_id(ids[0], ids[1])
end if !params[:roles].nil?
#
flash[:notice] = "Successfully updated Admin."
redirect_to admins_path
else
render "edit"
end
end
end
Given the following models
class User < ActiveRecord::Base
has_one :parent
has_one :admin
has_many :scool_admin_roles
has_many :account_admin_roles
end
class AccountAdminRole < ActiveRecord::Base
before_save :set_account_id
belongs_to :admin
belongs_to :account_role
end
class SchoolAdminRole < ActiveRecord::Base
belongs_to :admin
belongs_to :school_role
belongs_to :school
end
class SchoolRole < ActiveRecord::Base
has_many :school_admin_roles
end
class AccountRole < ActiveRecord::Base
has_many :account_admin_role
end
When I face code that I know smells bad, usually it leads me to the design.
In this case, the problem is the database table design.
You are hacking the value passed from a checkbox with a delimiter because the "join" table does more than just join. I believe that the relationship to school belongs_to the SchoolRole and not the SchoolAdminRole. Changing this will create a pattern much like your AccountRole.
Correcting the model design, might be a bit painful now, but it is much cleaner and will be maintainable in the future. You will thank yourself later.
We refactored the code above as follows
In the model we added accepts_nested_attributes_for :school_admin_roles, :reject_if => proc { |attr| attr['school_role_id'].blank? }
and added school_admin_roles_attributes to attr_accessible
class Admin < ActiveRecord::Base
belongs_to :account
belongs_to :user
has_many :school_admin_roles
has_many :school_roles, :through => :school_admin_roles
has_many :account_admin_roles
has_many :account_roles, :through => :account_admin_roles
accepts_nested_attributes_for :account
accepts_nested_attributes_for :school_admin_roles, :reject_if => proc { |attr| attr['school_role_id'].blank? }
attr_accessible :account_role_ids, :email, :first_name, :last_name, :account_id, :user_id, :account_attributes, :school_admin_roles_attributes
default_scope where(:deleted => false)
end
We then built the form as follows
<% index2 = 0 %>
<% SchoolRole.find(:all).each_with_index do |role, index| %>
<div class="school_role">
<%= check_box_tag "school_roles[]",role.id, #user.admin.school_roles.include?(role) %>
<%= role.name %>
<span class="advanced_box admin_permissions" <% if #user.admin.school_roles.include?(role) %>style="display:inline"<% end %>>
div class="content" id="perm_<%= index %>">
<h4><%= role.name %></h4>
<% uf.object.account.schools.each do |school|%>
<div>
<%= check_box_tag "#{uf.object_name}[school_admin_roles_attributes][#{index2}][school_role_id]", role.id, role.school_admin_roles.where(:admin_id => uf.object.id).collect(&:school_id).include?(school.id)%>
<%= school.name %>
<%= hidden_field_tag "#{uf.object_name}[school_admin_roles_attributes][#{index2}][school_id]", school.id %>
</div>
<% index2 += 1 %>
<% end %>
<%= link_to 'Done', '#', :class => "done" %>
</div>
Advanced
</span>
</div>
<% end %>
</div>
<% end %>
Which then enabled us to refactor the controller without splitting the ids but we still have to call destroy all each time which I can live with.
def update
#user = User.find(params[:id])
#user.admin.school_admin_roles.destroy_all
if #user.update_attributes(params[:user])
flash[:notice] = "Successfully updated Admin."
redirect_to admins_path
else
render "edit"
end
end

Resources