I have 3 model here : NewWord, VerbForm and AdjForm.
In NewWord model , I have a column word_type stored type of word: Adj Noun Verb Phrase GenericWord
Each NewWord may have 1 VerbForm or 1 AdjForm
Class NewWord < ApplicationRecord
has_one :adj_form, dependent: :destroy
has_one :verb_form, dependent: :destroy
accepts_nested_attributes_for :adj_form, allow_destroy: true
accepts_nested_attributes_for :verb_form, allow_destroy: true
def self.types
%w(Adj Noun Verb Phrase GenericWord)
end
end
class NewWord::AdjForm < ApplicationRecord
belongs_to :new_word
end
class NewWord::VerbForm < ApplicationRecord
belongs_to :new_word
end
I use this form to create a word alongside with it forms
<%= simple_form_for new_word, remote: true do |f| %>
<div class="error_section"></div>
<%= f.input :word %>
<%= f.input :kanji_version %>
<%= f.input :word_type, collection: NewWord.types %>
<%= f.simple_fields_for :verb_form do |v| %>
<%= v.input :verb_type %>
<%= v.input :dictionary_form %>
# Other fields
<% end %>
<%= f.simple_fields_for :adj_form do |a| %>
<%= a.input :adj_type %>
# Other fields
<% end %>
<%= f.button :submit %>
<% end %>
My idea here is when user select word_type from dropdown, I can use Javasript to hide or show fields for AdjForm or VerbForm, or both. Then at submit, I only save AdjForm if new word's word_type is 'Adj', or VerbForm if word_type is 'Verb'.
So, how can I achieve this ? Since nested object saved automatically when I run this in new word create method: #new_word.save. ?
I have try reject_if but it only returns params for nested object only!
accepts_nested_attributes_for :adj_form, allow_destroy: true, reject_if: :not_adj
def not_adj(att)
att['new_word']['word_type'] != 'Adj' # Found out "att" here only has attributes of AdjForm , not NewWord !
end
In the controller, before saving, check the word_type value and discard the params that you dont want to save.
new_words_controller.rb
def create
prepare_params
#new_word = NewWord.new(new_word_params)
if #new_word.save
# ...
else
# ...
end
end
private
def prepare_params
params.delete[:verb_form] if params[:word_type] == "Adj"
params.delete[:adj_form] if params[:word_type] == "Verb"
params
end
This assumes you have whitelisted the parameters for new_word and its associations.
Related
I have 3 models, a Property has_many units and each unit belongs to a Property. A Unit has_one unit_amenity and a unit_amenity belongs to a unit. The issue is that I need to nest the unit_amenity form which is 2 levels down in the new property form and I'm getting an Unpermitted parameter error. I also see just "unit_amenity"=>{"heat"=>"1"} in the logs instead of "unit_amenity_attributes"=>{"heat"=>"1"}.
Property.rb
class Property < ApplicationRecord
has_many :units, dependent: :destroy
accepts_nested_attributes_for :units, allow_destroy: true
end
Unit.rb
class Unit < ApplicationRecord
belongs_to :property
has_one :unit_amenity, dependent: :destroy
accepts_nested_attributes_for :unit_amenity, allow_destroy: true
end
Unit_amenity.rb
class UnitAmenity < ApplicationRecord
belongs_to :unit
end
I have nested the unit_amenity params within the unit params in properties_controller.rb
# Only allow a list of trusted parameters through.
def property_params
params.require(:property).permit(:city, :state, :zip_code,
units_attributes: [ :id, :_destroy, :unit_number, :bedrooms, :bathrooms, :property_id,
unit_amenity_attributes: [ :id, :_destroy, :unit_id, :heat, :air_conditioning ] ])
end
end
The form has a nested fields_for for unit_amenity
<%= form_with(model: property) do |form| %>
<div class="col-span-6">
<%= form.label :city %>
<%= form.text_field :city %>
</div>
....
<%= form.fields_for :unit_amenity do |amenity_field| %>
<div>
<%= amenity_field.check_box :heat, class: '', type: 'checkbox', id: 'heat' %>
<%= amenity_field.label :heat, class: "text-sm font-medium text-gray-700" %>
</div>
....
<% end %>
<% end %>
The new action of the properties_conroller.rb
def new
#property = Property.new
units = #property.units.build # has_many association
units.build_unit_amenity # has_one association
end
You need to nest the calls to fields for:
<%= form_with(model: property) do |form| %>
# ...
<%= form.fields_for :units do |unit_field| %>
<%= unit_field.fields_for :unit_amenity do |amenity_field| %>
# ...
<% end %>
<% end %>
Aparat from that you should also remove property_id and unit_id from the parameters whitelist. Never have the "parent id" in the params whitelist.
They are assigned when you create nested records and could actually be used to create records belonging to another property if a malicous user felt like it.
I am trying to make a player character generator. I have a form that hopefully will allow me to attach skills with their values to a character sheet model. I made models like this:
class CharacterSheet < ApplicationRecord
has_many :character_sheet_skills, dependent: :destroy
has_many :skills, through: :character_sheet_skills
belongs_to :user
accepts_nested_attributes_for :skills
end
class Skill < ApplicationRecord
has_many :character_sheet_skills, dependent: :destroy
has_many :character_sheets, through: :character_sheet_skills
attr_reader :value
end
class CharacterSheetSkill < ApplicationRecord
belongs_to :skill
belongs_to :character_sheet
end
Character sheet model holds data about player character and skill model has all skills available in game. In CharacterSheetSkill I'd like to store the skills that the player chooses for his character together with an integer field setting the skill value.
When opening form, I already have a full list of skills in database. All I want to do in form is create a character sheet that has all of these skills with added value. I tried using "fields_for" in form, but I couldn't really get that to work. Right now it looks like this:
<%= simple_form_for [#user, #sheet] do |f| %>
<%= f.input :name %>
<%= f.input :experience, readonly: true, input_html: {'data-target': 'new-character-sheet.exp', class: 'bg-transparent'} %>
...
<%= f.simple_fields_for :skills do |s| %>
<%= s.input :name %>
<%= s.input :value %>
<% end %>
<% end %>
How can I make that form so it saves character sheet together with CharacterSheetSkills?
A better idea here is to use skills as a normalization table where you store the "master" definition of a skill such as the name and the description.
class CharacterSheetSkill < ApplicationRecord
belongs_to :skill
belongs_to :character_sheet
delegate :name, to: :skill
end
You then use fields_for :character_sheet_skills to create rows on the join table explicitly:
<%= f.fields_for :character_sheet_skills do |cs| %>
<fieldset>
<legend><%= cs.name %></legend>
<div class="field">
<%= cs.label :value %>
<%= cs.number_field :value %>
</div>
<%= cs.hidden_field :skill_id %>
</fieldset>
<% end %>
Instead of a hidden fields you could use a select if you want let the user select the skills.
Of course nothing will show up unless you "seed" the inputs:
class CharacterSheetController < ApplicationController
def new
#character_sheet = CharacterSheet.new do |cs|
# this seeds the association so that the fields appear
Skill.all.each do |skill|
cs.character_sheet_skills.new(skill: skill)
end
end
end
def create
#character_sheet = CharacterSheet.new(character_sheet_params)
if #character_sheet.save
redirect_to #character_sheet
else
render :new
end
end
private
def character_sheet_params
params.require(:character_sheet)
.permit(
:foo, :bar, :baz,
character_sheet_skill_attributes: [:skill_id, :value]
)
end
end
I have a few models in my project : Request, Work, Car and Employee. Work is an intermediate model between Request and Car/Employee.
Here are the associations:
Request
has_many :works, dependent: :destroy
def performers
works.map {|x| x.performer}
end
Work
belongs_to :request
belongs_to :performer, polymorphic: true
Car
has_many :works, as: :performer
has_many :requests, through: :works, as: :performer
Employee
has_many :works, as: :performer
has_many :requests, through: :works, as: :performer
View used to create works:
<%= form_for([#request, #work]) do |f| %>
<%= (f.collection_select :performer_id, Employee.all, :id, :name) if #request.type == "StaffRequest" %>
<%= (f.collection_select :performer_id, Car.all, :id, :select_info) if #request.type == "CarRequest" %>
<%= f.submit 'OK' %>
<% end %>
Work controller
def new
#work = #request.works.new
end
def create
#work = #request.works.new(work_params)
end
def work_params
params.require(:work).permit(:performer_id, :request_id)
end
The problem is that my performer_type column is always empty, it does not save the class name. What can be the problem? Any ideas?
It's empty because you did't pass it, you should add a hidden field for you form:
<%= form_for([#request, #work]) do |f| %>
<% if #request.type == "StaffRequest" %>
<%= (f.hidden_field :performer_type, value: "Employee") %>
<%= (f.collection_select :performer_id, Employee.all, :id, :name) %>
<% elsif #request.type == "CarRequest" %>
<%= (f.hidden_field :performer_type, value: "Car") %>
<%= (f.collection_select :performer_id, Car.all, :id, :select_info) %>
<% end %>
<%= f.submit 'OK' %>
<% end %>
Beside :performer_id, you have to pass the :performer_type also, one way to do this is via the form select_tag :
def create
#work = #request.works.new(work_params)
end
def work_params
# use :performer if you use :performer as in the select form field
params.require(:work).permit(:performer, :request_id)
# OR
# use :performer_id & :performer_type if you also use the hidden field
params.require(:work).permit(:performer_id, :performer_type, :request_id)
end
There is a good example (for Rails 4.2) of using a single select form field for polymorphic so you can write like:
<%= f.grouped_collection_select :global_performer, [ Car, Employee ], :all, :model_name, :to_global_id, :name %>
How to create grouped select box in Rails for polymorphic association using Global ID?
I am using Rails 4.
In my project, include nested form for has_many relationship. From UI point of view, I got it. But nested form values are not inserting into database.
class Newspaper < ActiveRecord::Base
has_to :newspaper_categories, :dependent_destroy => true
accepts_nested_attributes_for :newspaper_categories, :allow_destroy => true, :reject_if => :all_blank
end
class NewspaperCategory < ActiveRecord::Base
belongs_to :newspaper
end
Newspaper form contents like,
<%= nested_form_for(#newspaper) do |f| %>
# Newspaper form fields
# Include `Newspaper category` form from the file.
<%= f.fields_for :newspaper_categories do |nc|%>
<%= render "newspaper_category" %>
<% end %>
# For add new form using JS
<%= f.link_to_add "Add New", :newspaper_categories %>
<%= f.submit %>
<% end %>
In my Newspaper Controller,
# add build in new method,
def new
#newspaper = Newspaper.new
#newspaper.newspaper_categoried.build
end
# In params set task_attributes,
def newspaper_params
params.require(:newspaper).permit(:name, :logo, task_attributes[:cat_link, :_destroy])
end
Where I goes wrong, still i'm confusing to insert
Update this
params.require(:newspaper).permit(:name, :logo, {newspaper_categories_attributes: [ :_destroy, :category_id, :rss_link, :image_url]})
Currently, an Item belongs_to a Company and has_many ItemVariants.
I'm trying to use nested fields_for to add ItemVariant fields through the Item form, however using :item_variants does not display the form. It is only displayed when I use the singular.
I have check my associations and they seem to be correct, could it possibly have something to do with item being nested under Company, or am I missing something else?
Thanks in advance.
Note: Irrelevant code has been omitted from the snippets below.
EDIT: Don't know if this is relevant, but I'm using CanCan for Authentication.
routes.rb
resources :companies do
resources :items
end
item.rb
class Item < ActiveRecord::Base
attr_accessible :name, :sku, :item_type, :comments, :item_variants_attributes
# Associations
#-----------------------------------------------------------------------
belongs_to :company
belongs_to :item_type
has_many :item_variants
accepts_nested_attributes_for :item_variants, allow_destroy: true
end
item_variant.rb
class ItemVariant < ActiveRecord::Base
attr_accessible :item_id, :location_id
# Associations
#-----------------------------------------------------------------------
belongs_to :item
end
item/new.html.erb
<%= form_for [#company, #item] do |f| %>
...
...
<%= f.fields_for :item_variants do |builder| %>
<fieldset>
<%= builder.label :location_id %>
<%= builder.collection_select :location_id, #company.locations.order(:name), :id, :name, :include_blank => true %>
</fieldset>
<% end %>
...
...
<% end %>
You should prepopulate #item.item_variants with some data:
def new # in the ItemController
...
#item = Item.new
3.times { #item.item_variants.build }
...
end
Source: http://rubysource.com/complex-rails-forms-with-nested-attributes/
try this way
in your item controller new action write
def new
...
#item = # define item here
#item.item_variants.build if #item.item_variants.nil?
...
end
and in item/new.html.erb
<%= form_for #item do |f| %>
...
...
<%= f.fields_for :item_variants do |builder| %>
...
<% end %>
...
...
<% end %>
for more see video - Nested Model Form