RAILS 6
Hey, I'm working on a class system that uses units and assignments as a many-to-many relationship. When I submit a new assignment form with a dropdown collection for units, the unit is not being received by the controller, but no error log is displayed. When I use byebug, the following error is displayed:
Unpermitted parameter: :unit_ids
Even though it has been permitted. Here's my controller for assignments.
class AssignmentsController < ApplicationController
def new
#assignment = Assignment.new
end
def create
debugger
#assignment = Assignment.new(assignment_params)
#assignment.save
if #assignment.save
flash[:success] = "The unit was successfully submitted."
redirect_to units_path
else
render 'new'
end
end
def show
end
private
def assignment_params
params.require(:assignment).permit(:name, :description, :duedate, user_ids: [])
end
end
Using byebug, I know the unit_id is being correctly received, from this form:
<%= form_for(#assignment, :html => {class: "form-horizontal", role: "form"}) do |f| %>
<div class="form-group">
<div>
<%= f.collection_select(:unit_ids, Unit.all, :id, :name, placeholder: "Units" )%>
</div>
<div>
<%= f.text_field :name, class:"form-control", placeholder: "Title of Assignment", autofocus: true %>
</div>
<div>
<%= f.text_area :description, class:"form-control materialize-textarea", placeholder: "Assignment Description", autofocus: true %>
</div>
<div>
<%= f.text_field :duedate, class: "datepicker", placeholder: "Due Date"%>
</div>
<div class="form-group" id="submitbutton">
<div align = "center">
<%= f.submit class: "btn waves-effect waves-light" %>
</div>
</div>
</div>
<%end%>
Here are the relevant models just to be safe. Note that I added the nested lines to both after I received this error because I saw it on another thread, but it doesn't seem to be fixing it.
class Unit < ApplicationRecord
has_and_belongs_to_many :users
has_and_belongs_to_many :assignments
accepts_nested_attributes_for :assignments
end
And the Assignment model:
class Assignment < ApplicationRecord
has_and_belongs_to_many :units
has_many :users, :through => :units
accepts_nested_attributes_for :units
end
The answer was a mix of a couple things, as Rockwell pointed out I was using User instead of Units, but that still didn't fix it. My collection had multiple choices set to false, so my controller wanted simply
params.require(:assignment).permit(:name, :description, :duedate, :unit_ids)
However, when I set multiple to true, that didn't work. Then, it wanted
params.require(:assignment).permit(:name, :description, :duedate, unit_ids[])
My solution was to leave multiple as true, and use the unit_ids[].
You have to update the permitted parameters
def assignment_params
params.require(:assignment).permit(:name, :description, :duedate, user_ids: [], unit_ids: [])
end
You mentioned that is was permitted, but I do not see unit_ids in the permitted params, I do see user_ids. Is there a spelling error? Or do you just need to include the unit_ids in there?
unit_ids is not a column name. You can use accept_nested_attribute or form object to solve this problem.
Related
I have a many-to-many model ProductCategory product_category (joint-table) and
I'm having issue with nesting the parameter in the ProductsController. The error I keep getting is that its unpermitted params category_ids but I have nested it in the strong product params.
I took a picture of the important parts of the code. Please take a look and let me know thank you. Here is the most important part of the code I think:
<%= form_with(model: [:user, #product], local: true) do |f|%>
<h4>Category</h4>
<div class="dropdown-trigger btn">
<%= f.collection_select(:category_ids, Category.all, :id, :name) %>
</div>
<h4>Product Name:</h4>
<%= f.text_field :name %><br/>
<h4>Product Price:</h4>
<%= f.number_field :price, value: #product.price ? '%.2f' % #product.price : nil, min: 0, step: 0.01 %>$<br/>
<h4>Product Description:</h4>
<%= f.text_field :description %><br/>
<h4>Product Image (recommended)</h4>
<%= f.file_field :image %><br/>
The require in ProductsController:
def product_params
params.require(:product).permit(:name, :price, :description, :image, category_ids: [])
end
And the relevant parts of Product and ProductCategory model.
class Product < ApplicationRecord
belongs_to :user
has_many :product_categories
has_many :categories, though: :product_categories
has_one_attached :image
end
class ProductCategory < ApplicationRecord
belongs_to :product
belongs_to :category
end
class Category < ApplicationRecord
belongs_to :user
has_many :product_categories
has_many :products, though: :product_categories
end
code screenshot
You are receiving an "unpermitted params category_ids" error, because you first need to declare in your Product model the following:
accepts_nested_attributes_for :categories , allow_destroy: true
Once that is done, you should start receiving all the category_ids info, really nested inside your params.
However, I fully recommend DO NOT perform on your views and partials an ActiveRecord query over your DB. For example:
<div class="dropdown-trigger btn">
<%= f.collection_select(:category_ids, Category.all, :id, :name) %>
</div>
That is not advisable. Instead, you should receive from your controller the whole set of categories. The only function on the view in this case is to fill the data by the user, to select the categories, and then after a submit to send all that information back to the controller. That's all. Not performing any kind of query. It's true that you can do it. I mean, it is physically possible to do it there on that view, or even to do it on a helper (also wrong, a helper is to perform additional actions over resources already loaded or received from controllers), but MVC means the separation of duties for several reasons.
Anyway, in your case I would choose to go more or less with something like this:
On products_controller.rb:
def edit
#categories_to_assign = product_service.get_categories_to_assign(#product)
end
def product_service
ProductService
end
def product_params
params.require(:product).permit(:name, :price, :description, :image, categories_to_assign: [])
end
On product_service.rb it gets the categories:
def self.get_categories_to_assign(product)
categories_scope.where.not(id: product.categories.map(&:id)).map do |category|
["#{category.name}", category.id]
end
end
def self.categories_scope()
Category
end
Then on the edit/new view:
<%
categories_to_assign = #categories_to_assign || []
%>
<% content_for :products_main_content do %>
<div id="edit_product_content">
<%= render partial: 'products/form', locals: {
product: product,
return_to: return_to,
categories_to_assign: categories_to_assign
} %>
</div>
<% end %>
Then on the _form.html.erb partial:
<%
categories_to_assign = local_assigns.fetch(:categories_to_assign, [])
%>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><%= t('products.categories.title') %></h2>
</div>
<div class="panel-body">
<div class="form-horizontal" id="categories_container" data-sjr-placeholder>
<%= render partial: 'products/categories', locals: {f: f, categories_to_assign: categories_to_assign} %>
</div>
</div>
</div>
And finally on the _categories.html.erb partial:
<%
categories_to_assign = local_assigns.fetch(:categories_to_assign, [])
%>
<% if categories_to_assign.present? %>
<%= select_tag "#{f.object_name}[categories_to_assign][]", options_for_select(categories_to_assign), {id: "#{f.object_name}_categories_to_assign", include_blank: true, multiple: true, class: 'form-control', data: {placeholder: t('products.form.select_category')}} %>
<% end %>
As you can see, the general idea is passing the pertinent information from the controller, after been properly retrieved on the product_service (you should add it), and then it goes to the edit/new view and then it finally goes down into the nested partials. That way everything is separated in its respective areas of responsibilities.
When I press the submit button, it goes to my 'create' action and then saves the main object, but the foreign key is null as it has not been passed via post, here is my code:
Models:
class Ticket < ActiveRecord::Base
belongs_to :system
validates :title, presence: true, length: {minimum: 1, maximum: 60}
accepts_nested_attributes_for :system
end
class System < ActiveRecord::Base
has_many :tickets
validates :name, presence: true, length: {minimum: 1, maximum: 50}
end
Controller:
class TicketsController < ApplicationController
def new
#system = System.actives
#priority = Priority.actives
#ticket = Ticket.new
respond_to :js, :html
end
def create
#ticket = Ticket.new(ticket_params)
#ticket.save
respond_with(#ticket)
end
def ticket_params
params.require(:ticket).permit(:title, :active, systems_attributes: [:id,:name])
end
end
View
<%= form_for #ticket, remote: true do |f| %>
<div class="form-group">
<%= f.label :Título %><br>
<%= f.text_field :title, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :Sistema %><br>
<%= f.fields_for :systems do |fs| %>
<%= fs.collection_select :id,
#system.order(:name),:id,:name, {include_blank: 'Selecione...'},
{class: 'form-control', id: 'system_select'}%>
<% end %>
</div>
<div class="actions">
<%= f.submit "Abrir Chamado", class: "btn btn-info" %>
</div>
<% end %>
So, it saves the ticket object with a title, but without a system_id, any helpful tip would be great here.
There are few mistakes in your code.
Firstly,in your new action,you need to add this line #ticket.build_system
def new
#system = System.actives
#priority = Priority.actives
#ticket = Ticket.new
#ticket.build_system
respond_to :js, :html
end
Your ticket_params method should be like this
def ticket_params
params.require(:ticket).permit(:title, :active, system_id,system_attributes: [:id,:name])
end
Notice system_id and system_attributes.
And finally,you need to change this line <%= f.fields_for :systems do |fs| %> to <%= f.fields_for :system do |fs| %>
I don't entirely understand your form, but in ticket_params it looks like you have the :id you want in the array value for systems_attributes. If that is indeed what you're trying to pass your new Ticket, then it needs to be passed differently within Ticket.new. The arguments/options hash you're passing to Ticket.new with ticket_params should be key-value pairs like so:
Ticket.new(title: "yourtitle", other: "whatever", system_id: "your_id")
I'd either change the output of ticket_params so that it passes your system_id in this way, or manually pass it in (keying it out of the parameters) within your create action.
I looked for two days on the web but I am still blocked to update a child object of a parent object.
My parent:
class Pass < ActiveRecord::Base
has_many :fields
attr_accessor :fields_attributes
accepts_nested_attributes_for :fields, :allow_destroy => true, :update_only => true
My child:
class Field < ActiveRecord::Base
belongs_to :pass
My form view:
<%= f.fields_for :fields do |field|%>
<div class="control-group">
<%= field.label :id, :class => 'control-label' %>
<%= field.label :value, :class => 'control-label' %>
<div class="controls">
<%= field.text_field :value, :class => 'text_field' %>
</div>
</div>
<% end %>
I also define permitted parameters thanks to:
def pass_params params.require(:pass).permit(:pass,
:description,
:organization_name,
:logo_upload,
:icon_upload,
:strip_upload,
fields_attributes: [:id,:value])
#params.require(:pass).permit!
end
I have no problem to create a pass with 5 fields in my passes_controller
def new
#pass = Pass.new
5.times {#pass.fields.build}
##fields = #pass.fields
end
My problem happens is that child fields of my pass are not updated after an edit of the pass. I always get initial values (at the creation of the pass) of fields.
I tried to update using different ways without success
if #pass.update_attributes(pass_params)
if #pass.update_attributes[params[:pass][:fields_attributes]]
if #pass.update_attributes(params[:fields_attributes])
When I update my pass, the pass_params looks like this:
{"description"=>"Test22gg", "organization_name"=>"Toto", "fields_attributes"=>{"0"=>{"id"=>"30", "value"=>"testf"}, "1"=>{"id"=>"29", "value"=>"test"}, "2"=>{"id"=>"28", "value"=>"test"}, "3"=>{"id"=>"27", "value"=>"test"}, "4"=>{"id"=>"26", "value"=>"test"}}}
I don't see which requirement or thing I forget to update these fields!
I'm attempting to build a recipe-keeper app with three primary models:
Recipe - The recipe for a particular dish
Ingredient - A list of ingredients, validated on uniqueness
Quantity - A join table between Ingredient and Recipe that also reflects the amount of a particular ingredient required for a particular recipe.
I'm using a nested form (see below) that I constructed using an awesome Railscast on Nested Forms (Part 1, Part 2) for inspiration. (My form is in some ways more complex than the tutorial due to the needs of this particular schema, but I was able to make it work in a similar fashion.)
However, when my form is submitted, any and all ingredients listed are created anew—and if the ingredient already exists in the DB, it fails the uniqueness validation and prevents the recipe from being created. Total drag.
So my question is: Is there a way to submit this form so that if an ingredient exists whose name matches one of my ingredient-name fields, it references the existing ingredient instead of attempting to create a new one with the same name?
Code specifics below...
In Recipe.rb:
class Recipe < ActiveRecord::Base
attr_accessible :name, :description, :directions, :quantities_attributes,
:ingredient_attributes
has_many :quantities, dependent: :destroy
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true
In Quantity.rb:
class Quantity < ActiveRecord::Base
attr_accessible :recipe_id, :ingredient_id, :amount, :ingredient_attributes
belongs_to :recipe
belongs_to :ingredient
accepts_nested_attributes_for :ingredient
And in Ingredient.rb:
class Ingredient < ActiveRecord::Base
attr_accessible :name
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
Here's my nested form that displays at Recipe#new:
<%= form_for #recipe do |f| %>
<%= render 'recipe_form_errors' %>
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<h3>Ingredients</h3>
<div id='ingredients'>
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient_attributes do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name %>
<% end %>
<%= ff.label :amount %>
<%= ff.text_field :amount, size: "10" %>
<%= ff.hidden_field :_destroy %>
<%= link_to_function "remove", "remove_fields(this)" %><br>
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
</div><br>
<%= f.label :description %><br>
<%= f.text_area :description, rows: 4, columns: 100 %><br>
<%= f.label :directions %><br>
<%= f.text_area :directions, rows: 4, columns: 100 %><br>
<%= f.submit %>
<% end %>
The link_to and link_to_function are there to allow the addition and removal of quantity/ingredient pairs on the fly, and were adapted from the Railscast mentioned earlier. They could use some refactoring, but work more or less as they should.
Update: Per Leger's request, here's the relevant code from recipes_controller.rb. In the Recipes#new route, 3.times { #recipe.quantities.build } sets up three blank quantity/ingredient pairs for any given recipe; these can be removed or added to on the fly using the "Add ingredient" and "remove" links mentioned above.
class RecipesController < ApplicationController
def new
#recipe = Recipe.new
3.times { #recipe.quantities.build }
#quantity = Quantity.new
end
def create
#recipe = Recipe.new(params[:recipe])
if #recipe.save
redirect_to #recipe
else
render :action => 'new'
end
end
You shouldn't put the logic of ingredients match into view - it's duty of Recipe#create to create proper objects before passing 'em to Model. Pls share the relevant code for controller
Few notes before coming to code:
I use Rails4#ruby2.0, but tried to write Rails3-compatible code.
attr_acessible was deprecated in Rails 4, so strong parameters are used instead. If you ever think to upgrade your app, just go with strong parameters from the beginning.
Recommend to make Ingredient low-cased to provide uniform appearance on top of case-insensitivity
OK, here we go:
Remove attr_accessible string in Recipe.rb, Quantity.rb and Ingredient.rb.
Case-insensitive, low-cased Ingredient.rb:
class Ingredient < ActiveRecord::Base
before_save { self.name.downcase! } # to simplify search and unified view
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
end
<div id='ingredients'> part of adjusted form to create/update Recipe:
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name, size: "10" %>
<% end %>
...
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
We should use :ingredient from Quantity nested_attributes and Rails will add up _attributes-part while creating params-hash for further mass assignment. It allows to use same form in both new and update actions. For this part works properly association should be defined in advance. See adjusted Recipe#new bellow.
and finally recipes_controller.rb:
def new
#recipe = Recipe.new
3.times do
#recipe.quantities.build #initialize recipe -> quantities association
#recipe.quantities.last.build_ingredient #initialize quantities -> ingredient association
end
end
def create
#recipe = Recipe.new(recipe_params)
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
def update
#recipe = Recipe.find(params[:id])
#recipe.attributes = recipe_params
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
private
def prepare_recipe
#recipe.quantities.each do |quantity|
# do case-insensitive search via 'where' and building SQL-request
if ingredient = Ingredient.where('LOWER(name) = ?', quantity.ingredient.name.downcase).first
quantity.ingredient_id = quantity.ingredient.id = ingredient.id
end
end
end
def recipe_params
params.require(:recipe).permit(
:name,
:description,
:directions,
:quantities_attributes => [
:id,
:amount,
:_destroy,
:ingredient_attributes => [
#:id commented bc we pick 'id' for existing ingredients manually and for new we create it
:name
]])
end
In prepare_recipe we do the following things:
Find ID of ingredient with given name
Set foreign_key quantity.ingredient_id to ID
Set quantity.ingredient.id to ID (think what happens if you don't do that and change ingredient name in Recipe)
Enjoy!
I'm really new to both ruby on rails and programming. I am trying to develop an application but i am stucked now. I was watching http://railscasts.com/episodes/196-nested-model-form-part-1 to make nested model forms but i am having an error. My problem details are as follows;
I have employers model, and employers model has_many interviews, and interview model has_many customquestions. I'm trying to create a form through which i will collect info to create interview. Although i made all necessary assosications, when i submit the form it raises error saying that "Customquestions interview can't be blank". I am kinda sure that it is because of that i miss some code in interview controller. Below you can see my interview controller and the form template that i am using to submit info.
Interview Controller
class InterviewsController < ApplicationController
before_filter :signed_in_employer
def create
#interview = current_employer.interviews.build(params[:interview])
if #interview.save
flash[:success] = "Interview created!"
redirect_to #interview
else
render 'new'
end
end
def destroy
end
def show
#interview = Interview.find(params[:id])
end
def new
#interview = Interview.new
3.times do
customquestion = #interview.customquestions.build
end
end
end
Form which i use to submit info:
<%= provide(:title, 'Create a new interview') %>
<h1>Create New Interview</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#interview) do |f| %>
<%= render 'shared/error_messages_interviews' %>
<%= f.label :title, "Tıtle for Interview" %>
<%= f.text_field :title %>
<%= f.label :welcome_message, "Welcome Message for Candidates" %>
<%= f.text_area :welcome_message, rows: 3 %>
<%= f.fields_for :customquestions do |builder| %>
<%= builder.label :content, "Question" %><br />
<%= builder.text_area :content, :rows => 3 %>
<% end %>
<%= f.submit "Create Interview", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
In interview model, i used accepts_nested_attributes_for :customquestions
Interview Model
class Interview < ActiveRecord::Base
attr_accessible :title, :welcome_message, :customquestions_attributes
belongs_to :employer
has_many :customquestions
accepts_nested_attributes_for :customquestions
validates :title, presence: true, length: { maximum: 150 }
validates :welcome_message, presence: true, length: { maximum: 600 }
validates :employer_id, presence: true
default_scope order: 'interviews.created_at DESC'
end
The validation error gets raised in the customquestions model because (I assume) it validates :interview_id. The problem is that interview_id won't get set until the parent object (Interview) is saved, but validations for customquestion are run before Interview is saved.
You can let cusomtquestions know about this dependency by adding the option :inverse_of=> :customquestions to belongs_to :interview in the customquestions model.