Rails Dropdown has_many through - ruby-on-rails

I can't save the id's in the join table (document_configuration).
I have tree models:
document.rb
belongs_to :languages
has_many :document_configurations
has_many :document_catalogs, through: :document_configurations
accepts_nested_attributes_for :document_catalogs
accepts_nested_attributes_for :document_configurations
document_catalog.rb
has_many :document_configurations
has_many :documents, through: :document_configurations
document_configuration.rb
belongs_to :document
belongs_to :document_catalog
So, I want to get a list of all document_catalog in my document_form So, when I create a new document I can include the corresponding catalog.
This is my form:
<div class="form-group">
<%= f.select :document_catalog_ids, DocumentCatalog.all.collect {|x| [x.name, x.id]}, {}%>
</div>
Is listing the catalogs as I want.
This is my controller:
def new
#document = Document.new
#document.document_catalogs.build
end
def document_params
params.require(:document).permit(:name, :description, :document_file,
:language_id, {:document_catalog_ids=>[]}) #I tried this too: :document_catalog_ids=>[] without the {}
end
I'm just getting this error: Unpermitted parameter: document_catalog_ids and I really need to save the document_id and document_catalog_id in document_configuration model.
Another thing is: I need to add anything else in my create, update and destroy methods?

Handling of Unpermitted Keys
By default parameter keys that are not explicitly permitted will be logged in the development and test environment. In other environments these parameters will simply be filtered out and ignored.
Additionally, this behaviour can be changed by changing the config.action_controller.action_on_unpermitted_parameters property in your environment files. If set to :log the unpermitted attributes will be logged, if set to :raise an exception will be raised.
Found this in a documentation from GitHub : https://github.com/rails/strong_parameters#handling-of-unpermitted-keys

Related

How to pass attributes in Serializer not in model in Rails (ActiveRecord)?

I am building a Rails backend that has Users, Sightings and Comments. The modelComment joins Sighting and User. I would like to pass the user's name or username along with the user_id which is an attribute of join table Comments in CommentSerializer to my front-end.
I can access this data using Active Record or Ruby methods but how do actually make this an attribute to be passed through Serializer?
Here's my CommentSerializer file:
class CommentSerializer < ActiveModel::Serializer
attributes :id, :body, :likes, :user_id
belongs_to :commentable, :polymorphic => true
belongs_to :sighting
has_many :comments, as: :commentable
end
add to your attributes :user_name for example and then add
def user_name
object.user.name
end
in your CommentSerializer class
So I had good luck with this method in my Serializer file to pass down this attribute:
def user_name
User.all.find { |f| f.id == object.user_id }.username
end

Unpermitted parameter for join table via multiple select in Rails

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

Validate presence of nested attributes within a form

I have the following associations:
#models/contact.rb
class Contact < ActiveRecord::Base
has_many :contacts_teams
has_many :teams, through: :contacts
accepts_nested_attributes_for :contacts_teams, allow_destroy: true
end
#models/contacts_team.rb
class ContactsTeam < ActiveRecord::Base
belongs_to :contact
belongs_to :team
end
#models/team.rb
class Team < ActiveRecord::Base
has_many :contacts_team
has_many :contacts, through: :contacts_teams
end
A contact should always have at least one associated team (which is specified in the rich join table of contacts_teams).
If the user tried to create a contact without an associated team: a validation should be thrown. If the user tries to remove all of a contact's associated teams: a validation should be thrown.
How do I do that?
I did look at the nested attributes docs. I also looked at this article and this article which are both a bit dated.
For completion: I am using the nested_form_fields gem to dynamically add new associated teams to a contact. Here is the relevant part on the form (which works, but currently not validating that at least one team was associated to the contact):
<%= f.nested_fields_for :contacts_teams do |ff| %>
<%= ff.remove_nested_fields_link %>
<%= ff.label :team_id %>
<%= ff.collection_select(:team_id, Team.all, :id, :name) %>
<% end %>
<br>
<div><%= f.add_nested_fields_link :contacts_teams, "Add Team"%></div>
So when "Add Team" is not clicked then nothing gets passed through the params related to teams, so no contacts_team record gets created. But when "Add Team" is clicked and a team is selected and form submitted, something like this gets passed through the params:
"contacts_teams_attributes"=>{"0"=>{"team_id"=>"1"}}
This does the validations for both creating and updating a contact: making sure there is at least one associated contacts_team. There is a current edge case which leads to a poor user experience. I posted that question here. For the most part though this does the trick.
#custom validation within models/contact.rb
class Contact < ActiveRecord::Base
...
validate :at_least_one_contacts_team
private
def at_least_one_contacts_team
# when creating a new contact: making sure at least one team exists
return errors.add :base, "Must have at least one Team" unless contacts_teams.length > 0
# when updating an existing contact: Making sure that at least one team would exist
return errors.add :base, "Must have at least one Team" if contacts_teams.reject{|contacts_team| contacts_team._destroy == true}.empty?
end
end
In Rails 5 this can be done using:
validates :contacts_teams, :presence => true
If you have a Profile model nested in a User model, and you want to validate the nested model, you can write something like this: (you also need validates_presence_of because validates_associated doesn't validate the profile if the user doesn't have any associated profile)
class User < ApplicationRecord
has_one :profile
accepts_nested_attributes_for :profile
validates_presence_of :profile
validates_associated :profile
docs recommend using reject_if and passing it a proc:
accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Model Names:
1: approval
2: approval_sirs
Associations:
1: approval
has_many :approval_sirs, :foreign_key => 'approval_id', :dependent => :destroy
accepts_nested_attributes_for :approval_sirs, :allow_destroy => true
2: approval_sirs
belongs_to :approval , :foreign_key => 'approval_id'
In approvals.rb
## nested form validations
validate :mandatory_field_of_demand_report_sirs
private
def mandatory_field_of_demand_report_sirs
self.approval_sirs.each do |approval_sir|
unless approval_sir.marked_for_destruction?
errors.add(:base, "Demand Report Field are mandatory in SIRs' Detail") unless approval_sir.demand_report.present?
end
end
end

Rails 3, many-to-many form using accepts_nested_attributes_for, how do I set up correctly?

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 } ] } }

Rails nested form with has_many :through, how to edit attributes of join model?

How do you edit the attributes of a join model when using accepts_nested_attributes_for?
I have 3 models: Topics and Articles joined by Linkers
class Topic < ActiveRecord::Base
has_many :linkers
has_many :articles, :through => :linkers, :foreign_key => :article_id
accepts_nested_attributes_for :articles
end
class Article < ActiveRecord::Base
has_many :linkers
has_many :topics, :through => :linkers, :foreign_key => :topic_id
end
class Linker < ActiveRecord::Base
#this is the join model, has extra attributes like "relevance"
belongs_to :topic
belongs_to :article
end
So when I build the article in the "new" action of the topics controller...
#topic.articles.build
...and make the nested form in topics/new.html.erb...
<% form_for(#topic) do |topic_form| %>
...fields...
<% topic_form.fields_for :articles do |article_form| %>
...fields...
...Rails automatically creates the linker, which is great.
Now for my question: My Linker model also has attributes that I want to be able to change via the "new topic" form. But the linker that Rails automatically creates has nil values for all its attributes except topic_id and article_id. How can I put fields for those other linker attributes into the "new topic" form so they don't come out nil?
Figured out the answer. The trick was:
#topic.linkers.build.build_article
That builds the linkers, then builds the article for each linker. So, in the models:
topic.rb needs accepts_nested_attributes_for :linkers
linker.rb needs accepts_nested_attributes_for :article
Then in the form:
<%= form_for(#topic) do |topic_form| %>
...fields...
<%= topic_form.fields_for :linkers do |linker_form| %>
...linker fields...
<%= linker_form.fields_for :article do |article_form| %>
...article fields...
When the form generated by Rails is submitted to the Rails controller#action, the params will have a structure similar to this (some made up attributes added):
params = {
"topic" => {
"name" => "Ruby on Rails' Nested Attributes",
"linkers_attributes" => {
"0" => {
"is_active" => false,
"article_attributes" => {
"title" => "Deeply Nested Attributes",
"description" => "How Ruby on Rails implements nested attributes."
}
}
}
}
}
Notice how linkers_attributes is actually a zero-indexed Hash with String keys, and not an Array? Well, this is because the form field keys that are sent to the server look like this:
topic[name]
topic[linkers_attributes][0][is_active]
topic[linkers_attributes][0][article_attributes][title]
Creating the record is now as simple as:
TopicController < ApplicationController
def create
#topic = Topic.create!(params[:topic])
end
end
A quick GOTCHA for when using has_one in your solution.
I will just copy paste the answer given by user KandadaBoggu in this thread.
The build method signature is different for has_one and has_many associations.
class User < ActiveRecord::Base
has_one :profile
has_many :messages
end
The build syntax for has_many association:
user.messages.build
The build syntax for has_one association:
user.build_profile # this will work
user.profile.build # this will throw error
Read the has_one association documentation for more details.

Resources