I have one model Biblio and one model Authors. They are habtm in relation to each other.
Adding a new biblio, I have a nested form. I do that like so:
<%= form_for :biblio, url: administration_add_biblio_path do |f| %>
<%= f.fields_for :authors, Author.new do |aut| %>
<%= aut.hidden_field :name , :name =>"biblio[authors][1]" %>
<% end %>
This is part of the params that are sent to the controller :
"biblio"=>{"authors"=>{"1"=>{"name"=>"Vabien"}}, "title"=>"test",....
In biblios_controller, I have this :
def params_biblio
params.require(:biblio).permit(
:authors)
which has this problem: Unpermitted parameter: authors
the model Biblio looks like this:
class Biblio < ApplicationRecord
has_and_belongs_to_many :authors
validates_presence_of :authors
accepts_nested_attributes_for :authors
validates_associated :authors
and class author:
class Author < ApplicationRecord
has_and_belongs_to_many :biblios
validates :given_name, presence: true
validates :name, presence: true
The problem is that this validation of author always fails. I guess this is due to the fact that [:author][:name]is passed to Author model - but I'm not sure that that's the problem and if so how to solve it.
I should add that the biblio can have a whole bunch of authors, but only the presence of author["1"] needs to be validated.
Nested strong parameters are a bit tricky in this sense. You have to define your nested parameters to make this work. So try this:
params.require(:biblio).permit(authors: :name)
Or with other params:
params.require(:biblio).permit(:title, authors: :name)
I think the problem is the "1" key in your object being passed back to the controller. Would it make more sense to pass the authors back as an array rather than an object.
"biblio"=>{"authors"=>[{"name"=>"Vabien"}], "title"=>"test",....
Related
I'm running into an error when nesting parameters in Rails 5: Unpermitted parameter: specialties
I have an Expertise model:
class Expertise < ApplicationRecord
has_many :buckets, through: :specialties
has_many :specialties
end
A Bucket model:
class Bucket < ApplicationRecord
has_many :expertises, through: :specialties
has_many :specialties
end
And a Specialty model:
class Specialty < ApplicationRecord
belongs_to :expertise
belongs_to :bucket
end
I'm trying to allow the User to edit his or her Expertises and adjust the Specialties associated with them. The #buckets are passed in from the controller, and the form currently looks like this:
<%= form_for(expertise) do |f| %>
<%= f.fields_for :specialties do |s| %>
<%= s.collection_select :bucket_ids, #buckets, :id, :name, {}, { multiple: true, class: "input" } %>
<% end %>
<% end %>
I based the form on this answer.
Here's the relevant snippet from the ExpertisesController:
def expertise_params
params.require(:expertise).permit(:user_id, :name, :rating, :description, specialties_attributes: [:id, :expertise_id, :bucket_id, :_destroy, bucket_ids: []])
end
And here are the parameters that are being passed in:
Parameters: {"expertise"=>{"specialties"=>{"bucket_ids"=>["", "1"]}, "description"=>""}, "id"=>"97"}
Specialties should be an array, right? I'm not sure how to do that.
The aim is to easily enable the User to select from the available Buckets (#buckets) to toggle his or her Expertise Specialties on or off. So let's say there are 5 Buckets available, the User would only be able to toggle on/off 5 possible Specialties for that Expertise.
Unpermitted parameter: specialties
You didn't set up accept_nested_attributes_for which spits out with that error
class Expertise < ApplicationRecord
has_many :specialties
has_many :buckets, through: :specialties
accepts_nested_attributes_for :specialties
end
When I try that, the nested fields_for form doesn't return any
specialties and so the HTML element is empty. Then, when I try to use
#expertise.specialties.build, I get undefined method bucket_ids for
Specialty because bucket_ids isn't actually an attribute, but
bucket_id is. Worth keeping in mind that the User needs to be able to
toggle multiple Specialties, each of which is tied to a Bucket (via a
bucket_id), and from what I've ready I'm supposed to use bucket_ids
(the plural) there
You don't need to have plural form(_ids) just because to accept multiple values. Just keep bucket_id to accept multiple values. And don't forget to build the associated model in the controller
def new
#expertise = Expertise.new
#expertise.specialties.build
end
Change bucket_ids to bucket_id in the form
<%= s.collection_select :bucket_id, #buckets, :id, :name, {}, { multiple: true, class: "input" } %>
And finally, expertise_params should be
def expertise_params
params.require(:expertise).permit(:user_id, :name, :rating, :description, specialties_attributes: [:id, :expertise_id, :_destroy, bucket_id: []])
end
Update:
Ok after some research, it looks like it should be bucket_ids, but the bucket_ids should be allowed as attribute for expertise. Check this post and tweak your form and expertise_params accordingly. You won't be needing accept_nested_attributes_for too!
The situation: Expertise has_many Buckets through Specialties and you want to update some bucket status of a specific expertise. So you can do this:
class ExpertisesController < ApplicationController
def your_action
#expertise = Expertise.find params[:id]
bucket_ids = params[:expertise][:specialties][:bucket_ids]
#expertise.specialties.where(id: bucket_ids).update(status: :on)
end
end
Huston, we have a problem:
class FirstModel
has_many :merged_models
has_many :second_models, :through => :merged_models
end
class SecondModel
has_many :merged_models
has_many :first_models, :through => :merged_models
end
class MergedModel
belongs_to :first_model
belongs_to :second_model
end
Form:
<%= form_for(first_model) do |f| %>
<%= f.fields_for :merged_model do |ff| %>
<%= ff.label :date %>
<%= ff.date_select :start_date %>
Problem:
Processing by FirstModelsController#create as HTML Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"f+D8AaVzM6ahrUyo/nwxISFEleVrXGxo8m30sIiLIe7gvG8J9KfONjuT09j6z3M4Rvw+n3Hm6PMddOtfbgjt5g==",
"first_model"=>{"first_name"=>"yyyy", "last_name"=>"yyy",
"merged_model"=>{"start_date(1i)"=>"2017", "start_date(2i)"=>"2",
"start_date(3i)"=>"28", "second_model_id"=>"1"}}, "commit"=>"Create"}
Unpermitted parameter: merged_model Unpermitted parameter:
merged_model
First model's controller's strong params:
params.require(:first_model).permit(:first_name, :last_name, merged_models_attributes: [:id, :start_date])
First model acccepts nested attributes of merged model:
accepts_nested_attributes_for :merged_models
However, after creating a FirstModel, MergedModel does not get created. Tried to create it in form:
<%= f.fields_for :merged_model [first_model.merged_models.build] do |ff| %>
But got:
no implicit conversion of MergedModel into Integer
Not completely understand what that means..
Also tried creating a new MergedModel from a FirstModel's create action, with a bang:
#merge_model = MergedModel.create!
And got the same error - no implicit conversion...
Could anyone explain more about this? I feel its about passing an Array of my MergedModel's params into MergedModel's params...? I am totally lost here...
Your form should have fields_for :merged_models instead of just merged_model.
On fresh installs of Rails5 applications, belongs_to implies optional: false by default (previously called required: true).
You need to create a MergedModel both with FirstModel AND SecondModel associated...
It looks like you are trying create a MergedModel only with a FirstModel associated, if SecondModel is optional, you need to say that for belongs_to with...
belongs_to :second_model, optional: true
I'm trying to obtain a subset of records for a grouped_collection_select, in the schema I have a Brand and a Model, and we handle different lines of products, when we request a product, we specify the line that we are going to request, so, we can have the Brands, and the models of that line, so the models are defined as follows:
Model for Brand
class Brand < ActiveRecord::Base
validates :brand_key, uniqueness: true, presence:true
has_many :models
accepts_nested_attributes_for :models, allow_destroy: true
end
Model for Modelx
class Modelx < ActiveRecord::Base
belongs_to :brand
belongs_to :line
validates :model_key, uniqueness: true, presence:true
end
Model for Line
class Line < ActiveRecord::Base
validates :line_key, uniqueness: true, presence:true, length: {in: 1..6}
has_many :line_features, -> { order(:sort_order) }
has_many :modelx
accepts_nested_attributes_for :line_features, allow_destroy: true
end
Model for Product
class Product < ActiveRecord::Base
belongs_to :vendor
belongs_to :line
belongs_to :brand
belongs_to :modelx
belongs_to :operating_status
has_many :line_features, -> { order(:sort_order)}
store_accessor :line_features
end
Controller for Product (new)
def new
#brands=brand.where(id: Modelx.select(:brand_id).where(line_id: params[:line_id]))
#modelxs=Modelx.where(line_id: params[:line_id])
...
end
excerpt from partial form
<%= f.collection_select( :brand_id, #brands, :id, :brand,{:prompt =>'Brands'}) %>
<%= f.grouped_collection_select(:modelx_id, #brands, :modelx, :brand, :id, :modelox) %>
Now, the problem that I'm facing is that when I display a model I need to present only the models available for that brand and line, but, the request is bringing all the models for the brand as it's supposed to be and I don't know how to discriminate those lines that are not needed.
Any help, hint or suggestion is highly appreciated.
Update to question
I don't know if this is a workaround or a solution to the question, but, it was the only way that I found to get to a solution for the requirement, for that reason I'm posting as an update instead of answering the question for whom it may help.
Reviewed the documentation once again and find out that the method :modelx referred by <%= f.grouped_collection_select(:modelx_id, #brands, :modelx, :brand, :id, :modelox) %> was requesting all the models as noted in apidock, this method was the solution for the problem.
Created a method in the brand model according to the subset depicted above, due to the fact that the grouping was made by brand, here the excerpt
Model for Brand (Excerpt)
...
def modelx_ltvi
Modelx.where("line_id = ? and brand_id =?",$line_id, self.id)
end
Special Note: Due to my inexperience, I was not able to pass the value of the :line_id from the form, so, I put it in a global variable.
Modified the form partial
Excerpt from partial form
...
<% $line_id=#product.line_id %>
...
<%= f.grouped_collection_select(:modelx_id,
#brands, :modelx_ltvi, :brand,
:id, :modelx) %>
<% end %>
And that makes the hamster run.
Filter your models by the brands you just found, something like:
#brands=Brand.where(id: Modelx.select(:brand_id).where(line_id: params[:line_id]))
#modelxs=Modelx.where(line_id: params[:line_id], brand_id: #brands.map(&:id))
#selected_model_names = #modelxs.map(&:name).sort.uniq
I have a many-to-many relationship between Recipes and Ingredients. I am trying to build a form that allows me to add an ingredient to a recipe.
(Variants of this question have been asked repeatedly, I have spent hours on this, but am fundamentally confused by what accepts_nested_attributes_for does.)
Before you get scared by all the code below I hope you'll see it's really a basic question. Here are the non-scary details...
Errors
When I display a form to create a recipe, I am getting the error "uninitialized constant Recipe::IngredientsRecipe", pointing to a line in my form partial
18: <%= f.fields_for :ingredients do |i| %>
If I change this line to make "ingredients" singular
<%= f.fields_for :ingredient do |i| %>
then the form displays, but when I save I get a mass assignment error Can't mass-assign protected attributes: ingredient.
Models (in 3 files, named accordingly)
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredient_id
has_many :ingredients, :through => :ingredients_recipes
has_many :ingredients_recipes
accepts_nested_attributes_for :ingredients
accepts_nested_attributes_for :ingredients_recipes
end
class Ingredient < ActiveRecord::Base
attr_accessible :name, :recipe_id
has_many :ingredients_recipes
has_many :recipes, :through => :ingredients_recipes
accepts_nested_attributes_for :recipes
accepts_nested_attributes_for :ingredients_recipes
end
class IngredientsRecipes < ActiveRecord::Base
belongs_to :ingredient
belongs_to :recipe
attr_accessible :ingredient_id, :recipe_id
accepts_nested_attributes_for :recipes
accepts_nested_attributes_for :ingredients
end
Controllers
As RESTful resources generated by rails generate scaffold
And, because the plural of "recipe" is irregular, inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'recipe', 'recipes'
end
View (recipes/_form.html.erb)
<%= form_for(#recipe) do |f| %>
<div class="field">
<%= f.label :name, "Recipe" %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :ingredients do |i| %>
<div class="field">
<%= i.label :name, "Ingredient" %><br />
<%= i.collection_select :ingredient_id, Ingredient.all, :id, :name %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Environment
Rails 3.2.9
ruby 1.9.3
Some things tried
If I change the view f.fields_for :ingredient then the form loads (it finds Recipe::IngredientRecipe correctly, but then when I save, I get a mass-assignment error as noted above. Here's the log
Started POST "/recipes" for 127.0.0.1 at 2012-11-20 16:50:37 -0500
Processing by RecipesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"/fMS6ua0atk7qcXwGy7NHQtuOnJqDzoW5P3uN9oHWT4=", "recipe"=>{"name"=>"Stewed Tomatoes", "ingredient"=>{"ingredient_id"=>"1"}}, "commit"=>"Create Recipe"}
Completed 500 Internal Server Error in 2ms
ActiveModel::MassAssignmentSecurity::Error (Can't mass-assign protected attributes: ingredient):
app/controllers/recipes_controller.rb:43:in `new'
app/controllers/recipes_controller.rb:43:in `create'
and the failing lines in the controller is simply
#recipe = Recipe.new(params[:recipe])
So the params being passed, including the nested attributes, are incorrect in some way. But I have tried lots of variants that fix-one-break-another. What am I failing to understand?
Thanks to clues from all, I have found what was wrong with my approach. Here's how I solved it.
I had originally tried with a simple HABTM many-to-many relationship, where the join table was named following standard Rails convention: ingredients_recipes. Then I realized that in a way, accepts_nested_attributes_for is designed for a 1-to-many relationship. So I converted to using has_many_through, creating a model IngredientsRecipes.
That name was the core problem, because Rails needs to be able to convert from plural to singular when using build to create form elements. This caused it to look for the non-existant class Recipe::IngredientsRecipe. When I changed my form so it used fields_for :ingredient the form displayed, but still failed to save with a mass assignment error. It even failed when I added :ingredients_attributes to attr_accessible. It still failed when I added #recipe.ingredients.build to RecipesController#new.
Changing the model to a singular form was the final key to resolve the problem. IngredientsRecipe would have worked, but I chose RecipeIngredients, as it makes more sense.
So to summarize:
can't use accepts_nested_attributes_for with has_and_belongs_to_many; need has_many with through option. (Thanks #kien_thanh)
adding accepts_nested_attributes_for creates a accessor that must be added to attr_accessible in the form <plural-foreign-model>_attributes, e.g. in Recipe I added attr_accessible :name, :ingredients_attributes (Thanks #beerlington)
before displaying the form in the new method of the controller, must call build on the foreign model after creating a new instance, as in 3.times { #recipe.ingredients.build }. This results in HTML having names like recipe[ingredients_attributes][0][name] (Thanks #bravenewweb)
join model must be singular, as with all models. (All me :-).
If you inspect the form that is generated, you'll notice that the nested fields have a name like "ingredients_attributes". The reason you're getting the mass-assignment error is because you need to add these fields to the attr_accessible declaration.
Something like this should fix it (you'll need to doublecheck the field names):
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredients_attributes
#...
end
Update: There's a similar answer here
Leave the call as
<%= f.fields_for :ingredients do |i| %>
But before that do
<% #recipe.ingredients.build %>
Im guessing that will allow your form to be created the right way, but there are likely other errors with your models, I can look # it more in detail when I have more time if its still not working, but:
As far as what accepts_nested_attributes_for does, when you pass in a correctly formatted params hash to the Model.new or Model.create or Model.update, it allows those attributes on the related model to be saved if they are in the params hash. In addition though, you do need to make the attributes accessible if they are unaccessible in the parent model as stated by beerlington.
I think you just need set up a one-to-many association, one recipe has many ingredients and one ingredient belongs to one recipe, so your model look like:
class Recipe < ActiveRecord::Base
attr_accessible :name, :ingredients_attributes
has_many :ingredients
accepts_nested_attributes_for :ingredients
end
class Ingredient < ActiveRecord::Base
attr_accessible :name, :recipe_id
belongs_to :recipe
end
You are built right form, so I don't write it again here. Now in your new and create controller will be like this:
def new
#recipe = Recipe.new
# This is create just one select field on form
#recipe.ingredients.build
# Create two select field on form
2.times { #recipe.ingredients.build }
# If you keep code above for new method, now you create 3 select field
end
def create
#recipe = Recipe.new(params[:recipe])
if #recipe.save
...
else
...
end
end
How does params[:recipe] look like? If you just have one select field, maybe like this:
params = { recipe: { name: "Stewed Tomatoes", ingredients_attributes: [ { id: 1 } ] } }
If you have 2 ingredient select field:
params = { recipe: { name: "Stewed Tomatoes", ingredients_attributes: [ { id: 1 }, { id: 2 } ] } }
I'm reading Beginning Rails 3. It creates a blog with Users who can post Articles and also post Comments to these Articles. They look like this:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
has_many :articles, :order => 'published_at DESC, title ASC',
:dependent => :nullify
has_many :replies, :through => :articles, :source => :comments
class Article < ActiveRecord::Base
attr_accessible :body, :excerpt, :location, :published_at, :title, :category_ids
belongs_to :user
has_many :comments
class Comment < ActiveRecord::Base
attr_accessible :article_id, :body, :email, :name
belongs_to :article
in app/views/comments/new.html.erb there's a form which begins like this:
<%= form_for([#article, #article.comments.new]) do |f| %>
My confusion lies in why form_for() has two parameters. What do they resolve to and why are they necessary?
thanks,
mike
Actually, in your example, you are calling form_forwith one parameter (which is Array). If you check the documentation you will see parameters it expects: form_for(record, options = {}, &proc).
In this case a record can be ActiveRecord object, or an Array (it can be also String, Symbol, or object that quacks like ActiveRecord). And when do you need to pass it an Array?
The simplest answer is, when you have a nested resource. Like in your example, you have defined Article has many Comments association. When you call rake routes, and have correctly defined routes, you will see that Rails has defined for you different routes for your nested resource, like: article_comments POST /article/:id/comments.
This is important, because you have to create valid URI for your form tag (well not you, Rails does it for you). For example:
form_for([#article, #comments])
What you are saying to Rails is: "Hey Rails, I am giving you Array of objects as a first parameter, because you need to know the URI for this nested resource. I want to create new comment in this form, so I will give you just initial instance of #comment = Comment.new. And please create this comment for this very article: #article = Article.find(:id)."
This is roughly similar to writing:
form_for(#comments, {:url => article_comments_path(#aticle.id)})
Of course, there is more to the story, but it should be enough, to grasp the idea.
This is a form for commenting on an article. So you, you need the Article you're commenting on (#article) and a new Comment instance (#article.comments.new). The form action for this form will be something like:
/articles/1/comments
It contains the id of the article you're commenting on, which you can use in your controller.
If you omit the #article like this: form_for #article.comments.new, the form action will looks like this:
/comments
In the controller you would have no way of knowing to which article the comment belongs.
Note that for this to work, you need to define a nested resource in your routes file.