Rails Nested Form doesn't update. strong_params are setted right - ruby-on-rails

Nested form (with simple_form gem) creates the record, but doesn't want to update it.
Checklist has many questions.
Question belongs to one checklist.
All strong params are setted.
So, in controller:
...
before_action :set_checklist, only: [:show, :edit, :update, :destroy]
...
def new
#checklist = Checklist.new
#checklist.questions.build
end
def create
#checklist = Checklist.new(checklist_params)
if #checklist.save
redirect_to checklists_url, notice: 'Checklist was successfully created.'
...
def edit
end
def update
if #checklist.update(checklist_params)
redirect_to #checklist, notice: 'Checklist was successfully updated.'
else
render :edit
end
end
...
private
def set_checklist
#checklist = Checklist.find(params[:id])
end
def checklist_params
params
.require(:checklist)
.permit(:title, :description,
questions_attributes: Question.attribute_names.map(&:to_sym).push(:_destroy))
end
in view form:
= simple_form_for(#checklist) do |f|
= f.error_notification
.form-inputs
= f.input :title
= f.input :description
...
%tbody.questions
= f.simple_fields_for :questions do |builder|
= render 'question_fields', f: builder
...
in _question_fields:
%tr.nested-fields
%td
= link_to_remove_association "remove", f, class: 'btn btn-primary btn-xs'
%td
= f.input :title, label: false
%td
= f.input :description, label: false
in checklist model:
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions,
allow_destroy: true,
reject_if: proc { |att| att['title'].blank? }
in question model:
belongs_to :checklist, optional: true
Thank you

%tr.nested-fields
%td
= link_to_remove_association "remove", f, class: 'btn btn-primary btn-xs'
%td
= f.input :title, label: false
%td
= f.input :description, label: false
your partial is missing = f.hidden_field :id and = f.hidden_field :_destroy

Related

Rails nested form Unpermitted parameter

When i create or edit product, my merchant_product_detail will not be created or edit due to
Unpermitted parameter: merchant_id. But other like id and price are able to pass strong parameters, only merchant_id could not pass it. Kindly help me why my merchant_id is not permit in this case?
params return in binding.pry mode
"product"=>
{"name"=>"fewrgvers",
"description"=>"",
"product_categories_attributes"=>{"0"=>{"id"=>""}},
"merchant_product_details_attributes"=>
{"0"=>{"merchant_id"=>["2"], "id"=>"", "price"=>"123"}}
"
product_params return
Unpermitted parameter: merchant_id
=> {"name"=>"fewrgvers",
"description"=>"",
"merchant_product_details_attributes"=>{"0"=>{"id"=>"", "price"=>""}}
product.rb
has_many :merchant_product_details
accepts_nested_attributes_for :merchant_product_details, reject_if: proc { |attributes| attributes['merchant_id'].blank? }
has_many :merchants, through: :merchant_product_details
merchant.rb
has_many :merchant_product_details
has_many :products, through: :merchant_product_details
accepts_nested_attributes_for :merchant_product_details
merchant_product_detail.rb
belongs_to :product
belongs_to :merchant
product_controller.rb
def new
#product = Product.new
#product.merchant_product_details.build
end
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to root_path, notice: 'Product was successfully created.' }
else
format.html { render :new }
end
end
end
end
def update
respond_to do |format|
if #product.update_attributes(product_params)
format.html { redirect_to root_path, notice: 'Product was successfully updated.' }
else
format.html { render :edit }
end
end
end
params.require(:product).permit(:name, :description,
merchant_product_details_attributes: [:id, :merchant_id, :price]
_form.html.erb
<%= form_for(#product, :url => path, html: { class: "form-horizontal", role: "form" }) do |f| %>
<%= f.fields_for :merchant_product_details do |builder| %>
<div class="form-group row">
<%= builder.label :merchant_id, class: "col-md-2 control-label" %>
<div class="col-md-8">
<%= builder.select :merchant_id, Merchant.all.collect {|x| [x.name, x.id]}, {include_hidden: false} ,prompt: "Select something", multiple: true, class: "select2" %>
<%= builder.hidden_field :id %><br>
<%= builder.label :price %>
<%= builder.number_field :price %>
</div>
<% end %>
The problem is the multiple: true in the form field for merchant_id. This means that the param will be an array, as it could be multiple merchant ids.
If this is what you want then I would recommend changing the name to merchant_ids and allow an array like this:
params.require(:product).permit(:name, :description,
merchant_product_details_attributes: [:id, :price, merchant_ids: []])
Having a look at your model relations I think you only want to have one id though, in which case it should be enough to remove the multiple: true in the select.
<%= builder.select :merchant_id, Merchant.all.collect {|x| [x.name, x.id]}, {include_hidden: false}, prompt: "Select something", class: "select2" %>

Rails nested routes for create method smart_listing

I have two models: apartment and room. Apartment has_many rooms, and rooms belongs_to apartment. I use smart_listing gem as ajax form. I show my table in edit_apartment_path
= render 'rooms/index' # index is partial
And I add this to my apartment_controller
def edit
#rooms = smart_listing_create :rooms,
Room.where(apartment_id: params[:apartment_id]),
partial: "rooms/list"
end
Now I must set paths for my form
= simple_form_for object, url: object.new_record? ? apartment_rooms_path : apartment_room_path(id: object),
remote: true, html: {class: "form-horizontal"} do |f|
= f.input :title
= f.button :submit
I can edit my created room, but I can't create new room in apartment. My error:
ActionController::UrlGenerationError - No route matches {:action=>"edit", :apartment_id=>nil, :controller=>"rooms", :id=>#<Room id: 83, title: "dawawd">, created_at: "2016-02-11 10:36:30", updated_at: "2016-02-11 10:36:30", apartment_id: 4>} missing required keys: [:apartment_id]:
My routes
resources :apartments do
resources :rooms
end
Propably smart_listing not support nested routes. Anyone have idea? :)
here's simple example of nested routes with smart_listing. I think that should cover the subject.
I used Rails 4.2, ruby 2.2.0, smart_listing 1.1.2
config/routes.rb
resources :users do
resources :bios
end
root 'users#index'
models/user.rb
class User < ActiveRecord::Base
has_one :bio
accepts_nested_attributes_for :bio, allow_destroy: true
scope :like, ->(args) { where("email like '%#{args}%' OR name like '%#{args}%' OR surname like '%#{args}%'")}
end
models/bio.rb
class Bio < ActiveRecord::Base
belongs_to :user
end
controllers/users_controller.rb
class UsersController < ApplicationController
include SmartListing::Helper::ControllerExtensions
helper SmartListing::Helper
before_action :set_user, only: [:update, :destroy]
def index
users_scope = User.all.includes(:bio)
users_scope = users_scope.like(params[:filter]) if params[:filter]
# #users = smart_listing_create :users, users_scope, partial: "users/list", page_sizes: [5, 7, 13, 26]
#users = smart_listing_create(:users, users_scope, partial: 'users/list',
sort_attributes: [
[:name, 'name'],
[:surname, 'surname'],
[:email, 'email'],
[:city, 'bio.city'],
[:birthday, 'bio.birthday']
],
default_sort: { start_at: 'desc' }
)
end
def new
#user = User.new
#user.build_bio
end
def create
#user = User.new(user_params)
#user.save
end
def edit
#user = User.includes(:bio).find(params[:id])
#user.bio.build if #user.bio.nil?
end
def update
#user.update(user_params)
end
def delete
end
def destroy
#user.destroy
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:name, :surname, :email, :bio_attributes => [:birthday, :city])
end
end
views/users/index.html.haml
= smart_listing_controls_for(:users, {class: "form-inline text-right"}) do
.form-group.filter.input-append
= text_field_tag :filter, '', class: "search form-control",
placeholder: "Search...", autocomplete: :off
= smart_listing_render :users
views/users/_list.html.haml
- unless smart_listing.empty?
%table.table.table-striped
%thead
%th= smart_listing.sortable "Name", :name
%th= smart_listing.sortable "Surname", :surname
%th= smart_listing.sortable "Email", :email
%th= smart_listing.sortable "City", :city
%th= smart_listing.sortable "Birthday", :birthday
%tbody
- smart_listing.collection.each do |o|
%tr.editable{data: {id: o.id}}
= smart_listing.render object: o, partial: "users/user", locals: {object: o}
= smart_listing.item_new colspan: 6, link: new_user_path
= smart_listing.paginate
= smart_listing.pagination_per_page_links
- else
%p.warning No users
views/users/_user.html.haml
%td= object.name
%td= object.surname
%td= object.email
%td= object.bio.city
%td= object.bio.birthday
%td.actions= smart_listing_item_actions [ {name: :edit, url: edit_user_path(object)}, {name: :destroy, url: user_path(object), confirmation: "Are you sure you want to delete this?"}]
views/users/_form.html.haml
%td{colspan: 6}
= form_for object, url: object.new_record? ? users_path : user_path(object),
remote: true, html: {class: "form-horizontal"} do |f|
%p
Name:
= f.text_field :name
%p
Surname:
= f.text_field :surname
%p
Email:
= f.text_field :email
= f.fields_for :bio do |ff|
%p
Birthday
= ff.date_field :birthday
%p
City
= ff.text_field :city
= f.submit "Save", class: "btn btn-primary"
%button.btn.btn-link.cancel Cancel
views/users/create.js.erb
<%= smart_listing_item :users, :create, #user, #user.persisted? ? "users/user" : "users/form" %>
views/users/edit.js.erb
<%= smart_listing_item :users, :edit, #user, "users/form" %>
views/users/destroy.js.erb
<%= smart_listing_item :users, :destroy, #user %>
views/users/index.js.erb
<%= smart_listing_update(:users) %>
views/users/new.js.erb
<%= smart_listing_item :users, :new, #user, "users/form" %>
views/users/update.js.erb
<%= smart_listing_item :users, :update, #user, #user.valid? ? "users/user" : "users/form" %>
you should receive to form #apartment also, and in this case if your #room.persisted? form recive request to edit, else to create:
= simple_form_for [#apartment, #room], remote: true, html: {class: "form-horizontal"} do |f|
= f.input :title
= f.button :submit

How to validate a child object attributes when self-reference association?

I have a one model 'Task' and have two entities - 'tasks' and 'subtasks' with self-reference association.
class Task < ActiveRecord::Base
has_many :subtasks, class_name: 'Task', foreign_key: 'parent_id', dependent: :destroy
belongs_to :parent, class_name: 'Task'
accepts_nested_attributes_for :subtasks, allow_destroy: true
validates :title, presence: true, length: { minimum: 3 }
validates :priority, presence: true, numericality: { only_integer: true }, length: { is: 1 }
validates_associated :subtasks
end
And i use one controller - TasksController.
class TasksController < ApplicationController
before_action :find_task, only: [:show, :edit, :update, :destroy, :run, :complete]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_task
def run
#task.run!
redirect_to :back
end
def complete
#task.complete!
redirect_to :back
end
def index
#tasks = Task.all
end
def show
end
def new
#task = Task.new
end
def edit
end
def create
#task = Task.create(task_params)
if #task.errors.empty?
redirect_to tasks_path, notice: "Task created!"
else
render 'new', notice: "Invalid input!"
end
end
def update
#task.update_attributes(task_params)
if #task.errors.empty? || :subtasks_attributes?
redirect_to #task
else
render 'edit'
end
end
def destroy
#task.destroy
if #task.parent_id?
redirect_to #task.parent
else
redirect_to tasks_path
end
end
private
def task_params
params.require(:task).permit(:title, :description, :scheduled, :deadline, :priority, :project, subtasks_attributes: [:title, :priority])
end
def find_task
#task = Task.find(params[:id])
end
def invalid_task
redirect_to tasks_path, notice: "Invalid task!"
end
end
I wanna create subtasks on task show page:
- #task.subtasks.each do |subtask|
- if subtask.in_work?
=> link_to 'Complete', complete_task_path(subtask), method: :put
- else
=> link_to 'Run', run_task_path(subtask), method: :put
=> subtask.title
=> link_to 'Edit', edit_task_path(subtask)
= link_to 'Delete', [subtask], method: :delete, data: { confirm: 'Are you sure?' }
= simple_form_for #task do |t|
= t.simple_fields_for :subtasks, #task.subtasks.build do |f|
.form-inputs
= f.input :title
= f.hidden_field :priority, value: #task.priority
.form-actions
= f.button :submit, "Add a subtask"
Now on the task show page i can create a subtask with valid attributes and can't create a subtask with invalid attributes, but i do not get validation message.
How can i fix it?
Ty and sorry for my English.
PS:
i don't know why, but errors exist inside controller and doesn't exist inside view
#project.update_attributes(project_params)
puts #project.errors.full_messages
if #project.errors.empty? || :tasks_attributes?
redirect_to #project
puts #project.errors.full_messages
(0.0ms) begin transaction
(0.0ms) rollback transaction
Tasks title can't be blank
Tasks title is too short (minimum is 3 characters)
Redirected to http://localhost:3000/projects/3
Tasks title can't be blank
Tasks title is too short (minimum is 3 characters)
Completed 302 Found in 6ms (ActiveRecord: 0.2ms)
You should add the errors messages to the view too:
= simple_form_for #task do |t|
= t.simple_fields_for :subtasks, #task.subtasks.build do |f|
#error message added here
- if #task.subtasks.errors.any?
%ul.errors
- #task.subtasks.errors.full_messages.each do |msg|
%li= msg
.form-inputs
= f.input :title
= f.hidden_field :priority, value: #task.priority
.form-actions
= f.button :submit, "Add a subtask"
EDIT
You have a _form partial in you application, change that code to this
= simple_form_for #task do |f|
- if #task.errors.any?
ul.errors
- #task.errors.full_messages.each do |msg|
=> msg
= f.input :title
= f.input :description
= f.input :scheduled
= f.input :deadline
= f.input :priority, collection: [["None", 0], ["High", 3], ["Medium", 2], ["Low", 1]], selected: ["None"]
= f.button :submit

Dont display fields nested attributes with using gem cocoon

i have model poll with nested attributes poll_items:
class Poll < ActiveRecord::Base
ITEMS_COUNT_MAX = 5
attr_accessible :from_date, :question, :results_hidden, :to_date, :owner_id, :poll_items_attributes
belongs_to :owner, polymorphic: true
has_many :poll_items, dependent: :destroy
has_many :poll_votes, through: :poll_items
accepts_nested_attributes_for :poll_items, allow_destroy: true,
reject_if: proc { |attributes| attributes['answer'].blank? }
#validates :owner, :question, :from_date, :to_date, presence: true
#validate :validate_duplicate, on: :create
validate :validate_max_poll_items, on: :update
after_initialize :defaults
...................................................................
model pollItem
attr_accessible :answer
attr_readonly :poll_votes_count
belongs_to :poll
has_many :poll_votes, dependent: :destroy
has_many :users, through: :poll_votes
validates :poll, :answer, presence: true
validate :validate_duplicate, on: :create
scope :editable, lambda { |u|
unless u.moderator?
where(:poll.owner.user_id => u.id).where(:created_at.gt Settings.edit_delay.minutes.ago)
end
}
private
def validate_duplicate
errors.add(:base, :duplicate) unless poll.poll_items.where(answer: answer).empty?
end
end
Poll_controller:
class PollsController < ApplicationController
before_filter :authenticate_user!
before_filter :set_poll, only: [:show, :edit, :update, :destroy]
before_filter :find_owner, only: [:new, :create]
def new
#poll = Poll.new
end
def create
#poll = Poll.new params[poll_params]
##poll.owner = #owner
respond_to do |format|
if #poll.save
format.html { redirect_to [:edit, #poll], notice: 'Опрос был успешно создан.' }
else
format.html { render :new }
end
end
end
def show
end
def edit
#poll.poll_items.build
end
def update
if #poll.editable?(current_user)
if #poll.update_attributes params[:poll]
respond_to do |format|
format.html { redirect_to [:edit, #poll], notice: 'Опрос был успешно обновлен.' }
end
else
respond_to do |format|
format.html { render :edit, alert: #poll.errors }
end
end
end
end
def destroy
#poll.destroy
respond_to do |format|
format.html { redirect_to #owner, notice: 'Опрос был успешно удален.' }
end
end
private
def poll_params
params.require(:poll).permit(:from_date, :question, :results_hidden, :to_date,
:owner_id, poll_items_attributes: [:id, :unswer, :_destroy])
end
protected
def set_poll
#poll = Poll.find(params[:id])
#owner = #poll.owner
end
def find_owner
#owner = case
when params[:post_id]
Post.visible(current_user).find(params[:post_id])
when params[:blog_post_id]
BlogPost.with_privacy(current_user).find(params[:blog_post_id])
end
end
end
I installed gem cocoon:
next i create view new.html.haml:
%h1= title "Новый опрос"
= simple_form_for #poll do |f|
= f.error_messages header_message: nil
= f.input :question, disabled: !#poll.editable?(current_user), input_html: { class: 'input-block-level' }
= f.input :results_hidden, as: :boolean, inline_label: 'Скрыть результаты до окончания опроса', label: false
= f.input :from_date, as: :datetime, input_html: { class: 'poll_date' }
= f.input :to_date, as: :datetime, input_html: { class: 'poll_date' }
.items-index
%h3#poll-items Варианты ответа (не больше пяти)
= f.simple_fields_for :poll_items do |poll|
= render 'poll_items_fields', f: poll
= link_to_add_association 'Добавить еще вариант', f, :poll_items
.form-actions
= f.button :submit, 'Опубликовать опрос', class: 'btn-bg'
%p
Вернуться к посту:
= link_to #owner
poll_items_fields.html.haml:
.poll_row
.poll_item
= f.input :answer, input_html: { class: 'ctrlenter expanding' }, label: false, placeholder: 'Введите вариант ответа'
= link_to_remove_association "удалить", f
I open page new for creating new poll, but only field for new name quation poll and button "create poll". No fields for adding poll_items. I thinkm dont render form poll_items_fields.html.haml. But why? How fix?
If I understood correctly, your new method should look like this
def new
#poll = Poll.new
#poll.poll_items.build
end
And also you have unswer instead of answer in poll_items_attributes, so fix that too.
def poll_params
params.require(:poll).permit(:from_date, :question, :results_hidden, :to_date, :owner_id, poll_items_attributes: [:id, :answer, :_destroy])
end
Update:
You should also remove attr_accessible :from_date, :question, :results_hidden, :to_date, :owner_id, :poll_items_attributes from your Poll model as Rails 4 uses strong parameters.
And also this line #poll = Poll.new params[poll_params] in create method should look like this #poll = Poll.new(poll_params)

Rails 4 - Nested Routes and Form Parameters

When I'm trying to create a new "participation" in my Rails4 application and it seems like there is something wrong with my parameters. Actually this is not causing any problems in application (or I didn't notice it) but still I would like to fix it. You can see 2 "examination_id" parameters one of them is null and the other one is equal to 1.
REQUEST PARAMETERS:
{"utf8"=>"✓",
"authenticity_token"=>"XZ71eV0zxrnTBilzvEtLlHLwoAb+qKdDfxOHjrAHUPg=",
"participation"=>
{
"language_preference"=>"Türkçe",
"exam_center_preference"=>"1",
"disability"=>"1",
"user_id"=>"1",
"examination_id"=>""
},
"commit"=>"Sınava Başvur",
"examination_id"=>"1"
}
routes.rb:
resources :examinations do
resources :participations do
member do
get :update_profile_information
end
end
end
participation.rb:
class Participation < ActiveRecord::Base
belongs_to :user
belongs_to :examination
before_save :verification_key_generator
end
participations_controller.rb:
class ParticipationsController < ApplicationController
before_filter :authenticate_user!
before_action :set_participation, only: [:show, :edit, :update, :destroy]
before_filter :get_examination
def get_examination
#examination = Examination.find(params[:examination_id])
end
def index
#participations = #examination.participations
end
def show
#participation = #examination.participations.find(params[:id])
end
def new
#participation = Participation.new
end
def create
#participation = #examination.participations.new(participation_params)
#participation.user = current_user
respond_to do |format|
if #participation.save
format.html { redirect_to [#examination, #participation], notice: 'Başvuru işlemi başarıyla tamamlandı!' }
format.json { render action: 'show', status: :created, location: [#examination, #participation] }
else
render 'new'
format.html { render action: 'new' }
format.json { render json: #participation.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #participation.update(participation_params)
format.html { redirect_to [#examination, #participation], notice: 'Başvurunuz Başarıyla Güncellendi!' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: participation.errors, status: :unprocessable_entity }
end
end
end
private
def set_participation
#participation = Participation.find(params[:id])
end
def participation_params
params.require(:participation).permit(:user_id, :examination_id, :payment_status, :language_preference, :exam_center_preference, :disability)
end
end
app/views/participations/_form.html.erb:
<%= simple_form_for([#examination, #participation], html:{class: "well"}) do |f| %>
<%= f.input :user_id, :as => :hidden, :input_html => { :value => current_user.id } %>
<%= f.input :examination_id, as: :hidden %>
<%= f.input :language_preference, collection: ["Türkçe", "İngilizce", "Rusça"], label: 'Sınav Dili Tercihi' %>
<%= f.input :exam_center_preference, collection:ExamCenter.all, label: 'Sınav Merkezi Seçiniz', label_method: :city %>
<%= f.input :disability, inline_label: 'Yardımcı İstiyorum', label: false %>
<%= f.button :submit, "Sınava Başvur" %>
<% end %>
app/views/participations/new.html.erb:
<%= simple_form_for([#examination, #participation]) do |f| %>
<%= f.error_notification %>
<%= f.input :language_preference, collection: ["Türkçe", "İngilizce", "Rusça"], label: 'Sınav Dili Tercihi' %>
<%= f.input :exam_center_preference, collection:ExamCenter.all, label: 'Sınav Merkezi Seçiniz', label_method: :city %>
<%= f.input :disability, inline_label: 'Yardımcı İstiyorum', label: false %>
<%= f.input :user_id, :as => :hidden, :input_html => { :value => current_user.id } %>
<%= f.input :examination_id, as: :hidden %>
<%= f.button :submit, "Sınava Başvur" %>
<% end %>
sa
When you're using
<%= simple_form_for([#examination, #participation], html:{class: "well"}) do |f| %>
to generate the form, it will set the action to be /examinations/[examination_id]/participations so the routes/action will know the examination_id from the url itself.
So, you don't need to pass examination_id separately as hidden field that you're setting as
<%= f.input :examination_id, as: :hidden %>
Once you remove this hidden field the request parameters will look like:
{"utf8"=>"✓",
"authenticity_token"=>"XZ71eV0zxrnTBilzvEtLlHLwoAb+qKdDfxOHjrAHUPg=",
"participation"=>
{
"language_preference"=>"Türkçe",
"exam_center_preference"=>"1",
"disability"=>"1",
"user_id"=>"1"
},
"commit"=>"Sınava Başvur",
"examination_id"=>"1"
}

Resources