Rails nested form with cocoon. Attributes using model - ruby-on-rails

I am trying to create a nested form which has options and suboptions, both from the same model called Option. Here is the content of the files:
Model:
class Option < ApplicationRecord
belongs_to :activity
has_many :option_students
has_many :students, through: :option_students
has_many :suboptions,
class_name: "Option",
foreign_key: "option_id"
belongs_to :parent,
class_name: "Option",
optional: true,
foreign_key: "option_id"
accepts_nested_attributes_for :suboptions,
reject_if: ->(attrs) { attrs['name'].blank? }
validates :name, presence: true
end
Controller:
class OptionsController < ApplicationController
include StrongParamsHolder
def index
#options = Option.where(option_id: nil)
end
def show
#option = Option.find(params[:id])
end
def new
#option = Option.new()
1.times { #option.suboptions.build}
end
def create
#option = Option.new(option_params)
if #option.save
redirect_to options_path
else
render :new
end
end
def edit
#option = Option.find(params[:id])
end
def update
#option = Option.find(params[:id])
if #option.update_attributes(option_params)
redirect_to options_path(#option.id)
else
render :edit
end
end
def destroy
#option = Option.find(params[:id])
#option.destroy
redirect_to options_path
end
end
_form.html.erb:
<%= form_for #option do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<%= f.label :activity %><br>
<%= select_tag "option[activity_id]", options_for_select(activity_array) %><br>
</p>
<div>
<div id="suboptions">
<%= f.fields_for :suboptions do |suboption| %>
<%= render 'suboption_fields', f: suboption %>
<% end %>
<div class="links">
<%= link_to_add_association 'add suboption', f, :suboptions %>
</div>
</div>
</div>
<p>
<%= f.submit "Send" %>
</p>
<% end %>
_suboption_fields.html.erb
<div class="nested-fields">
<%= f.label :suboption %><br>
<%= f.text_field :name %>
<%= link_to_remove_association "X", f %>
</div>
StrongParamsHolder:
def option_params
params.require(:option).permit(:name, :activity_id, :students_ids => [], suboptions_attributes: [:id, :name])
end
The view is created correctly, but it is not saving. It goes to "render :new" on create controller. I think it should be a problem with the params, but I am not sure what.

Probably not saving because of a failed validation. If you are using rails 5, the belongs_to is now more strict, and to be able to save nested-params you need to make the connection/relation between association explicit.
So imho it will work if you add the inverse_of to your relations as follows:
has_many :suboptions,
class_name: "Option",
foreign_key: "option_id",
inverse_of: :parent
belongs_to :parent,
class_name: "Option",
optional: true,
foreign_key: "option_id"
inverse_of: :suboptions
If another validation is failing, it could also help to list the errors in your form (e.g. something like #option.errors.full_messages.inspect would help :)
As an aside: I would rename the option_id field in the database to parent_id as this more clearly conveys its meaning.

Related

Create controller method with accepts_nested_attributes_for and fields_for

i'm building a web application with Rails 5.2.0 about recipes and I have a doubt about the create method of the controller.
This are my models:
class Recipe < ApplicationRecord
belongs_to :user
has_many :quantities
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true
end
class Quantity < ApplicationRecord
belongs_to :recipe
belongs_to :ingredient
end
class Ingredient < ApplicationRecord
has_many :quantities
has_many :recipes, through: :quantities
end
And here the view to create new recipes:
<%= form_for(#recipe) do |f| %>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
<%= f.label :servings, "Servings" %>
<%= f.number_field :servings %>
<%= f.fields_for :quantities do |quantity| %>
<%= f.hidden_field :_destroy, class: "hidden-field-to-destroy" %>
<%= f.label :ingredient_id, "Ingredient Name" %>
<%= f.text_field :ingredient_id%>
<%= f.label :amount, "Amount" %>
<%= f.number_field :amount %>
<%= f.label :unit, "Unit" %>
<%= f.select(:unit, ["kg","g","l","ml"], {include_blank: true}) %>
<% end %>
<%= f.submit 'Add new recipe' %>
<% end %>
I can add new ingredients dynamically with jquery and also delete them in the same form.
The update method of the controller works perfectly but the create method does not work:
class RecipesController < ApplicationController
def create
#recipe = current_user.recipes.build(recipe_params)
if #recipe.save
flash[:success] = "New recipe created correctly."
redirect_to #recipe
else
render 'new'
end
end
def update
#recipe = Recipe.find(params[:id])
if #recipe.update_attributes(recipe_params)
flash[:success] = "The recipe has been updated correctly."
redirect_to #recipe
else
render 'edit'
end
end
private
def recipe_params
params.require(:recipe).permit( :name, :servings, quantities_attributes: [:ingredient_id, :amount, :unit,:_destroy, :id, :recipe_id])
end
end
I'm trying to do #recipe = current_user.recipes.build(recipe_params) but I get the following error in te view:
Quantities recipe can't be blank
I think this occurs because when trying to create the relation, it is necessary to indicate the recipe_id, but the recipe has not yet been created and the id can not be indicated.
Could you please tell me someone what would be the correct way to create the recipe first and then be able to add the ingredients through Quantity in the create method of the recipe controller?
As per the message shared the qunatity_recipes cannot be blank and you haven't specified any condition to manage this.
Current
class Recipe < ApplicationRecord
belongs_to :user
has_many :quantities
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true
end
Update the accepts nested attributes to allow_nil for Recipe class
class Recipe < ApplicationRecord
belongs_to :user
has_many :quantities
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true, allow_nil: true
end

form_for models with specific relations

I have
Models
class Group < ApplicationRecord
has_many :group_artists
has_many :singers, -> { where role: "Singer" }, class_name: "GroupArtist"
has_many :guitarists, -> { where role: "Guitarist" }, class_name: "GroupArtist"
end
class GroupArtist < ApplicationRecord
belongs_to :group
belongs_to :artist
end
class Artist < ApplicationRecord
has_many :group_artists
has_many :groups, through: :group_artists
end
group_artists table has these columns
class CreateGroupArtists < ActiveRecord::Migration[5.1]
def change
create_table :group_artists, id: false do |t|
t.references :group, foreign_key: true, null: false
t.references :artist, foreign_key: true, null: false
t.string :role
t.string :acting
t.timestamps
end
end
end
Controller
class GroupsController < ApplicationController
def new
#group = Group.new
#singers = #group.singers.build
#guitarists = #group.guitarists.build
#artists = Artist.all // for a selection
end
def create
#group = Group.new(allowed_params)
#group.save
end
private
def allowed_params
params.require(:group).permit(:name, :singers, :guitarists, group_artists_attributes: [:group_id, :artist_id, :role, :acting])
end
end
views/groups/_form.html.erb
<%= form_for #group do |f| %>
<%= f.label "Singers" %>
<%= f.fields_for :singers do |singer| %>
<%= singer.select(:artist_id, #artists.collect { |a| [a.name, a.id.to_i] }, { include_blank: true }) %>
<% end %>
<%= f.label "Guitarists" %>
<%= f.fields_for :guitarists do |guitarist| %>
<%= guitarist.select(:artist_id, #artists.collect { |a| [a.name, a.id.to_i] }, { include_blank: true }) %>
<% end %>
<%= f.submit "Submit" %>
<% end %>
It creates the group all right, but doesn't create the relation in GroupArtist.
I know something's missing in the controller part. I should add something after the ".build" like (role: "Singer") but it doesn't do anything as well.
Ruby -v 2.4.1
Rails -v 5.1.3
Since you are using group_artists as more than just a simple join table you need to use nested attributes to create a row with metadata:
class Group < ApplicationRecord
has_many :group_artists
has_many :singers, -> { where role: "Singer" }, class_name: "GroupArtist"
has_many :guitarists, -> { where role: "Guitarist" }, class_name: "GroupArtist"
accepts_nested_attributes_for :group_artists,
reject_if: ->{|a| a[:artist_id].blank? || a[:role].blank?}
end
Also the structure using different associations to create nested records based on the role of the band members is not really scalable - for every possible role the class / form will swell.
Instead you may want to use two selects:
<%= form_for #group do |f| %>
<fields_for :group_artists do |ga| %>
<div class="field">
<%= f.label :artist_id, "Artist" %>
<%= f.collection_select :artist_id, Artist.all, :id, :name %>
<%= f.label :role %>
<%= f.select :role, %w[ Singer Guitarist ] %>
</div>
<% end %>
<%= f.submit "Submit" %>
<% end %>
Also you are not saving the record in your #create method.
def create
#group = Group.new(allowed_params)
if #group.save
# ...
else
render :new
end
end
You should add this in your controller action create
def create
#group = Group.new(allowed_params)
#group.save
end
Group.new doesn't persist model in DB but save after yes.
These were the adds and changes I needed to make it work.
class Group < ApplicationRecord
has_many :group_artists
has_many :singers, -> { where role: "Singer" }, class_name: "GroupArtist"
has_many :guitarists, -> { where role: "Guitarist" }, class_name: "GroupArtist"
accepts_nested_attributes_for :group_artists,
reject_if: proc { |a| a[:artist_id].blank? || a[:role].blank? },
allow_destroy: true
end
class GroupsController < ApplicationController
def new
#group = Group.new
#group.group_artists.build
#artists = Artist.all // for a selection
end
def create
#group = Group.new(allowed_params)
#group.save
end
end
groups/_form.html.erb
<%= form_for #group do |f| %>
<div class="singers">
<%= f.label :singers %>
<%= f.fields_for :group_artists do |ga| %>
<%= ga.collection_select :artist_id, Artist.all, :id, :name %>
<%= ga.hidden_field :role, value: 'Singer' %>
<% end %>
</div>
<div class="guitarists">
<%= f.label :guitarists %>
<%= f.fields_for :group_artists do |ga| %>
<%= ga.collection_select :artist_id, Artist.all, :id, :name %>
<%= ga.hidden_field :role, value: 'Guitarist' %>
<% end %>
</div>
<%= f.submit "Submit" %>
<% end %>
That way you don't have to specify the artist's role if his name's selected in the singers or guitarists labeled div.
That's it. Thanks to max, for guiding me to the right direction.

rails4 collection select with has_many through association and nested model forms

I have a rails4 app. At the moment my collection select only works if I select only one option. Below is my working code. I only have product form. Industry model is populated with seeds.rb. IndustryProduct is only use to connect the other 2 models.
I'd like to know what I have to change in the code to be able to choose more.
I saw some working examples with multiple: true option like (https://www.youtube.com/watch?v=ZNrNGTe2Zqk at 10:20) but in this case the UI is kinda ugly + couldn't pull it off with any of the sample codes. Is there an other solution like having more boxes with one option chosen instead of one box with multiple options?
models:
class Product < ActiveRecord::Base
belongs_to :user
has_many :industry_products
has_many :industries, through: :industry_products
has_many :product_features
accepts_nested_attributes_for :industry_products, allow_destroy: true
accepts_nested_attributes_for :product_features
validates_associated :industry_products
validates_associated :product_features
end
class Industry < ActiveRecord::Base
has_many :industry_products
has_many :products, through: :industry_products
accepts_nested_attributes_for :industry_products
end
class IndustryProduct < ActiveRecord::Base
belongs_to :product
belongs_to :industry
end
_form.html.erb
<%= form_for #product do |f| %>
<%= render 'layouts/error_messages', object: f.object %>
......
<%= f.fields_for :industry_products do |p| %>
<%= p.collection_select :industry_id, Industry.all, :id, :name %>
<% end %>
<%= f.fields_for :product_features do |p| %>
<%= p.text_field :feature, placeholder: "add a feature", class: "form-control" %>
<% end %>
<%= f.submit class: "btn btn-primary" %>
<% end %>
products controller
def new
#product = Product.new
#product.industry_products.build
#product.product_features.build
end
def create
#product = current_user.products.new(product_params)
if #product.save
redirect_to #product
else
render action: :new
end
end
......
def product_params
params.require(:product).permit(....., industry_products_attributes: [:id, :industry_id, :_destroy], industries_attributes: [:id, :name], product_features_attributes: [:feature])
end
Firstly, you could fix your first collection select by using it to set the industry_ids for the #product:
<%= form_for #product do |f| %>
<%= f.collection_select :industry_ids, Industry.all, :id, :name %>
<% end %>
This will allow you to set the collection_singular_ids method, which exists for all has_many associations.
You'd have to back it up in the params method:
#app/controllers/products_controller.rb
....
def product_params
params.require(:product).permit(.... industry_ids: [])
end
A lot more succinct than using nested attributes.
To get that "multiple" selection, you'll want to use the following:
<%= f.collection_select :industry_ids, Industry.all, :id, :name, {}, { multiple: true } %>
Tested & working
--
You may also want to look at collection_check_boxes:
<%= f.collection_check_boxes :industry_ids, Industry.all, :id, :name %>

Rails 4.2 nested form attributes not saving

I cannot seem to get nested attributes to save to the database, though I can see the params in terminal. I am using Rails 4.2.
Here are my models:
class Device < ActiveRecord::Base
belongs_to :hub
has_many :accessories, dependent: :destroy
accepts_nested_attributes_for :accessories,
reject_if: proc { |attributes| attributes['material'].blank? },
allow_destroy: true
end
class Accessory < ActiveRecord::Base
belongs_to :device
end
Here is the controller. I have my device model nested under user and hub model.
class DevicesController < ApplicationController
def edit
#user = User.find_by(params[:user_id])
#hub = Hub.find_by_title(params[:hub_id])
#device = Device.find_by(id: params[:id])
end
def update
#user = User.find_by(params[:user_id])
#hub = Hub.find_by_title(params[:hub_id])
#device = Device.find_by(id: params[:id])
if #device.update_attributes(device_params)
flash[:success] = "update successfully"
redirect_to user_hub_device_path(#user, #hub, #device)
else
render 'edit'
end
end
private
def device_params
params.require(:device).permit(:model, :hub_id, :resolution, :materials, :startcost, :take_online, :delivery_time, :unitcost, :color, :accessories, :accessories_attributes => [:id, :name, :cost, :color, :device_id, :_destroy])
end
end
Finally is my form.
<%= form_for([#user, #hub, #device]) do |f| %>
<fieldset>
<div id="material">
<%= f.fields_for :accessories do |a| %>
<%= render 'devices/accessory', a: a %>
<% end %>
</div>
</fieldset>
The partial:
<div class="row">
<%= a.collection_select :name, Material.all, :material, :material %>
<%= a.text_field :cost, id: "right-label" %>
<%= a.text_field :color, id: "right-label" %>
<%= a.check_box :_destroy %>
</div>
You are whitelisting params[:device][:materials] but you are checking attributes['material'].blank? (note the the s on the end). Which causes the nested attributes to be rejected.

Rails Forms with Nested Attributes from Join Model many_to_many

I GOT TWO PROBLEMS:
-I'm stuck with creating a project which includes nested attributes for :position
-I got it nearly working for editing the project details plus the :position attribute, but the fields_for :assigned_projects ads all the fields for all users who are assigned to the project.
I have 3 Models:
class User < ActiveRecord::Base
has_many :assigned_projects
has_many :projects, :through => :assigned_projects
has_many :created_projects, :class_name => "Project", :foreign_key => :creator_id
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :assigned_projects
has_many :users, :through => :assigned_projects
belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
attr_accessible :name, :controlled, :currency, :creator_id, :assigned_projects
accepts_nested_attributes_for :assigned_projects#, :allow_destroy => true
end
class AssignedProject < ActiveRecord::Base
belongs_to :user, class_name: "User"
belongs_to :project, class_name: "Project"
attr_accessible :project_id, :user_id, :position, :project, :user, :user_attributes
accepts_nested_attributes_for :user
end
Each User can create a Project and is the Projects.creator
Each Project has_many Users through the join model Assigned_Project
Each User can have a different position in the project, so I want to save the :position in the join model AssignedProject.
if a user creates a Project, he should be able to edit the project attributes PLUS the :position attribute of the new join model.
Now the Form field for New.Project and Edit.Project
/project/new.htm.erb
<%= form_for( setup_new_project(#project) ) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.submit "Add Project", class: "" %>
<% end %>
/project/edit.htm.erb
<%= form_for( setup_project(current_project) ) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.submit "Update Project", class: "" %>
<% end %>
And I have the following setup methods as described in this article:
http://www.sitepoint.com/complex-rails-forms-with-nested-attributes/
module FormHelper
def setup_project(project)
project.assigned_projects ||= AssignedProject.new
project
end
def setup_new_project(project)
project.assigned_project = AssignedProject.new
project
end
end
I hope the problem is clear enough.
for creating a new project the current error message is:
undefined method `assigned_project='
15: <%= render 'shared/user_sidebar_menu' %>
16:
17: <div class="span4 offset1">
18: <%= form_for( setup_new_project(#project) ) do |f| %>
19:
20: <%= render 'shared/error_messages', object: f.object %>
21:
UPDATE: added projects_controller.rb
projects_controller.rb
class ProjectsController < ApplicationController
def new
#project = Project.new
end
def create
#project = Project.new(params[:project])
#project.creator = current_user
if #project.save
current_user.assigned_projects.create(project: #project)
redirect_to current_user
else
render 'new'
end
end
end
Update setup_new_project method as below:
def setup_new_project(project)
project.assigned_projects.build ## Updated this line
project
end
Use project.assigned_projects(Notice plural) instead of project.assigned_project(Notice singular WRONG).
User and AssignedProject model are in a 1-M relationship. So, you get dynamic method assigned_projects=(Notice plural), you are getting error as you called assigned_project=(Notice singular) which does not exist.
UPDATE
undefined method each for <AssignedProject:0x007ff7aa55b528>
Use project.assigned_projects.build instead of project.assigned_project = AssignedProject.new.
UPDATE 2
You are approaching this incorrectly. The form helpers are totally not required. All you need to do is update the new and create actions as below:
def new
#project = Project.new
#project.assigned_projects.build
end
and update the form_for in both new and edit view's as below:
<%= form_for(#project) do |f| %>

Resources