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.
Related
Using check boxes to update the nested form I can't update the tables. I received following message:
Unpermitted parameter: :category
ActionController::Parameters
{"name"=>"Flux Capacitor", "price"=>"19.55"} permitted: true
I have tried different ways to fix this through the permitted params, including a :category parameter, like so:
def product_params
params.require(:product).permit(:id, :name, :price, :category, categories_attributes: [:id, :name, :category], categorizations_attributes: [:id, :product_id, :category_ids, :category])
end
My models
class Product < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categories, reject_if: proc {|attributes| attributes['name'].blank?}
accepts_nested_attributes_for :categorizations
end
class Categorization < ApplicationRecord
belongs_to :product, inverse_of: :categorizations
belongs_to :category, inverse_of: :categorizations
end
class Category < ApplicationRecord
has_many :categorizations
has_many :products, through: :categorizations, inverse_of: :category
end
class ProductsController < ApplicationController
def edit
#categories = Category.all
end
def new
#product = Product.new
end
def create
#product = Product.new(product_params)
if #product.save
flash[:notice] = 'Product succesfully created'
redirect_to products_path
else
flash[:notice] = 'Product was not created'
render 'edit'
end
end
def update
if #product.update(product_params)
flash[:notice] = "Product succesfully updated"
redirect_to products_path
else
flash[:notice] = 'Product was not updated'
render 'edit'
end
end
app/view/products/edit.html.erb
<%= simple_form_for(#product) do |f| %>
<%= f.input :name %>
<%= f.input :price %>
<%= f.simple_fields_for #product.categories do |cats| %>
<%= cats.collection_check_boxes :ids, Category.all, :id, :name, collection_wrapper_tag: :ul, item_wrapper_tag: :li %>
<% end %>
<%= f.button :submit %>
<% end %>
This seems like something that is common enough that rails and/or simple_form, should provide in a more built-in way to do this. Am I missing something obvious?
If I am understanding you correctly you should be able to do this without the use of accepts_nested_attributes_for or simple_fields_for. Try something like this:
<%= simple_form_for(#product) do |f| %>
<%= f.input :name %>
<%= f.input :price %>
<%= f.association :categories, as: :check_boxes %>
<%= f.button :submit %>
<% end %>
your strong params should look something like this:
def product_params
params.require(:product).permit(:id, :name, :price, { category_ids: [] }])
end
I'm trying to create a form where a user can answer questions and submit to a results page, however, when I am trying to pass the AnsweredQuestions attributes through the Quiz controller it's telling me it's an 'unknown attribute'.
Error i'm receving: unknown attribute 'answered_questions_attributes' for AnsweredQuestion.
Quiz.rb:
class Quiz < ApplicationRecord
validates :title, presence: true, length: { maximum: 50 }
has_many :questions, dependent: :destroy
has_many :answered_questions, through: :questions, dependent: :destroy
accepts_nested_attributes_for :answered_questions, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :questions, reject_if: :all_blank, allow_destroy: true
end
AnsweredQuestion.rb:
class AnsweredQuestion < ApplicationRecord
belongs_to :user
belongs_to :question
belongs_to :answer
belongs_to :quiz
end
Quiz controller:
def show
#quiz = Quiz.find(params[:id])
#questions = Question.all
#answered_questions = current_user.answered_questions.build
#quiz.answered_questions.build
end
def create
#quiz = Quiz.new(show_params)
if #quiz.save
flash[:success] = "You have created a new quiz!"
redirect_to #quiz
else
render 'new'
end
end
def post_answered_questions
#answered_question = current_user.answered_questions.build(show_params)
if #answered_question.save
flash[:success] = "You have completed the quiz!"
redirect_to results_quiz_path(params[:quiz][:id])
else
render ''
end
end
private
def user_completed_quiz
if(current_user.answered_questions.pluck(:quiz_id).uniq.include?(params[:id].to_i))
redirect_to quizzes_path
end
end
def show_params
params.require(:quiz).permit(:title, answered_questions_attributes: [:id, :answer_id, :question_id, :user_id, :quiz_id], questions_attributes: [:id, :question_title, :quiz_id, :done, :_destroy, answers_attributes: [:id, :answer_title, :question_id, :quiz_id, :correct_answer, :_destroy]])
end
end
show.html.erb (in quizzes):
<%= form_for(#quiz, url: post_answered_questions_quizzes_path, method: "POST") do |f| %>
<%= #quiz.title %>
<%= f.hidden_field :id, :value => #quiz.id %>
<% #quiz.questions.each do |question| %>
<%= f.fields_for :answered_questions do |answer_ques| %>
<h4><%= question.question_title %></h4>
<%= answer_ques.hidden_field :question_id, :value => question.id %>
<%= answer_ques.hidden_field :quiz_id, :value => #quiz.id %>
<%= answer_ques.select(:answer_id, options_for_select(question.answers.map{|q| [q.answer_title, q.id]})) %>
<% end %>
<% end %>
<%= submit_tag %>
<% end %>
UPDATED:
show.html.erb (quizzes):
<%= form_for(#answered_questions, url: answered_questions_path, method: "POST") do |f| %>
<%= #quiz.title %>
<%= f.hidden_field :id, :value => #quiz.id %>
<% #quiz.questions.each do |question| %>
<%= f.fields_for :answered_questions do |answer_ques| %>
<h4><%= question.question_title %></h4>
<%= answer_ques.hidden_field :question_id, :value => question.id %>
<%= answer_ques.hidden_field :quiz_id, :value => #quiz.id %>
<%= answer_ques.select(:answer_id, options_for_select(question.answers.map{|q| [q.answer_title, q.id]})) %>
<% end %>
<% end %>
<%= submit_tag %>
<% end %>
AnsweredQuestion controller:
class AnsweredQuestionsController < ApplicationController
def show
#answered_question = AnsweredQuestion.new
end
def create
#answered_question = current_user.answered_questions.build(answered_params)
binding.pry
if #answered_question.save
flash[:success] = "You have completed the quiz!"
redirect_to results_quiz_path(params[:quiz][:id])
else
render ''
end
end
def edit
#answered_questions = AnsweredQuestion.find(params[:id])
end
def destroy
AnsweredQuestion.find(params[:id]).destroy
flash[:success] = "Answered quiz deleted"
redirect_to answered_questions_url
end
private
def answered_params
params.require(:answered_questions).permit(:question_id, :answer_ids, :user_id, :quiz_id, :id, :_destroy)
end
end
It looks like you are accepting nested attributes for Quiz but using the nested attributes in AnsweredQuestion instead of in Quiz. You need to add accepts_nested_attributes_for in the other model and in show_params it should be quiz_attributes instead of answered_questions_attributes. Although you are using show_params twice, once over one model and the second one over the other, so you may need two params. Otherwise you'll break the other one.
I have a form I would like to create that populates the corresponding model form for, and a fields_for that populates a has many through table.
The plan.rb model:
class Plan < ApplicationRecord
has_many :plan_materials
has_many :materials, through: :plan_materials
accepts_nested_attributes_for :materials
end
The materials.rb model:
class Material < ApplicationRecord
has_many :plan_materials
has_many :plans, through: :plan_materials
end
And the PlanMaterial model:
class PlanMaterial < ApplicationRecord
belongs_to :plan
belongs_to :material
end
This is what I have in the plan form:
<%= form_for #plan do |form| %>
<div class="form-group">
<%= form.label :name %>
<%= form.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :description %>
<%= form.text_field :description, class: 'form-control' %>
</div>
<%= form.fields_for :materials, plan.materials.build do |material_fields| %>
<%= material_fields.text_field :title %>
<% end %>
<%= form.submit %>
<% end %>
I have created fields_for forms before but never trying to input the information for the form being created, and the id of the material they are selecting, in a new table from that same form.
I am creating this through a plan.materials.build which I realize could possibly be the wrong way to go about doing this because I don't think it would be building that in the plan_materials table.
class PlansController < ApplicationController
def index
#plans = Plan.all
end
def new
#plan = Plan.new
#plan.materials.build
end
def create
#plan = Plan.new(plan_params)
respond_to do |format|
if #plan.save
format.html { redirect_to plan_path, notice: 'Plan has been created' }
else
format.html { render :new, notice: 'There was an error saving your plan' }
end
end
end
private
def plan_params
params.require(:plan).permit(:name, :description, :grade_id, :subject_id, :unit_id, materials_attributes: [:title])
end
end
So, just to recap, I would like to create a plan, and be able to add materials to that plan. I need to have that plan created in the plans table, along with the id of the plan that was created and the id of the material that was added in that form. How do I go about doing this?
administrator.rb:
class Administrator < ActiveRecord::Base
has_one :administrator_role, dependent: :destroy
has_one :role, through: :administrator_role
end
role.rb:
class Role < ActiveRecord::Base
has_many :administrator_roles
has_many :administrators, through: :administrator_roles
end
administrator_role.rb:
class AdministratorRole < ActiveRecord::Base
belongs_to :administrator
belongs_to :role
end
in view for "new" action administrator_controller:
<%= form_for #administrator do |f| %>
<%= render 'shared/errors', object: #administrator %>
<div class="form-group">
<%= f.label :role_id, "Роль:" %>
<%= f.collection_select(:role_id, #roles, :id, :name) %>
</div>
...
<%= f.submit 'Save', class: 'btn btn-primary btn-lg' %>
<% end %>
administrator_controller.rb:
class AdministratorsController < ApplicationController
def new
#administrator = Administrator.new
#roles = Role.all
end
def create
#administrator = Administrator.new(administrators_params)
if #administrator.save
flash[:success] = "Account registered!"
redirect_to root_path
else
render :new
end
end
...
private
def administrators_params
params.require(:administrator).permit(:login, :password, :password_confirmation, :role_id)
end
end
when you open the page get the error:
undefined method `role_id' for #<Administrator:0x007f6ffc859b48>
Did you mean? role
How to fix it? if I put in place role_id a role, when you create administrator will get the error:
ActiveRecord::AssociationTypeMismatch (Role(#69964494936160) expected, got String(#12025960)):
You have to rewrite the form as below:
<%= form_for #administrator do |f| %>
<%= render 'shared/errors', object: #administrator %>
<div class="form-group">
<%= f.fields_for :role do |role_form| %
<%= role_form.label :role_id, "Роль:" %>
<%= role_form.select(:id, #roles.map { |role| [role.name, role.id] }) %>
<% end %>
</div>
...
<%= f.submit 'Save', class: 'btn btn-primary btn-lg' %>
<% end %>
You also need to add 1 line which enables the nested form logic as:
class Administrator < ActiveRecord::Base
has_one :administrator_role, dependent: :destroy
has_one :role, through: :administrator_role
accepts_nested_attributes_for :role
end
And also change the controller like:
class AdministratorsController < ApplicationController
#....
private
def administrators_params
params.require(:administrator).permit(
:login, :password,
:password_confirmation,
role_attributes: [ :id ]
)
end
end
When you are using has_one association, you get the below method, but not association_id=, and that is what error is saying.
association(force_reload = false)
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
I'm new to Nested Forms. I'm building a form where Companies can create Promos as under a few Categories. I keep getting undefined method "model_id" for Class. My codes and exact error messages are as below :-
Error Message:-
undefined method `category_id' for #<Company:0x007fd43828b6f8>
<%= f.collection_select :category_id, Category.all, :id, :name, {include_blank: 'Choose Category'}, required: true %>
<%= render partial: 'promo', locals: {f: f}%>
Models :-
class Company < ActiveRecord::Base
has_many :users, :through => :company_customers
has_many :company_users
has_many :categories, :through => :category_companies
has_many :category_companies
accepts_nested_attributes_for :category_companies
accepts_nested_attributes_for :categories s
end
class CompanyCategory < ActiveRecord::Base
belongs_to :company
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :company_categories
has_many :companies, :through => :company_categories
has_many :promos
accepts_nested_attributes_for :company_categories
end
class Promo < ActiveRecord::Base
belongs_to :category
end
Controller :-
class CompaniesController < ApplicationController
def new
#company = Company.new
end
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
format.html { redirect_to #company, notice: 'Company was successfully created.' }
format.json { render :show, status: :created, location: #company }
else
format.html { render :new }
format.json { render json: #company.errors, status: :unprocessable_entity }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_company
#company = Company.find(params[:id])
end
def company_params
params.require(:company).permit(:company_name, :registration_no, :address, :phone_no, :outlet_location, company_categories_attributes: [:id, :company_id, :category_id, category_attributes:[:id, :name, promo_attribute:[:id, :name, :description]]])
end
end
View:-
<%= form_for(:company) do |f| %>
<div class="medium-6 columns">
<%= f.collection_select :category_id, Category.all, :id, :name, {include_blank: 'Choose Category'}, required: true %>
<%= render partial: 'promo', locals: {f: f}%>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
_promo.html.erb
<%= f.fields_for :promos do |promo| %>
<h4 class="text-center">Promotions to be offered</h4><br>
<div class="row">
<div class="medium-6 columns">
<%= f.text_field :name, placeholder: "Name", required: true %>
</div>
<div class="medium-6 columns">
<%= f.text_field :description, placeholder: "Description", required: true %>
</div>
</div>
<% end %>
<p><%= f.link_to_add "Add More Promos", :promos %> </p>
Appreciate the help. Many Thanks !
From the docs, the collection_select has the following use:
collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
They have this example:
class Post < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :posts
def name_with_initial
"#{first_name.first}. #{last_name}"
end
end
Which would be used like this:
collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true)
So, when you do:
f.collection_select :category_id, Category.all, :id, :name, {include_blank: 'Choose Category'}, required: true
I think maybe you're missing the object argument? Like, where they have :post.