Unpermitted parameters , nested forms - Ruby On Rails - ruby-on-rails

I have follow Michael Hartl's tutorial and now I want to add photos to microposts. But when I submit microposts, the image isn't saved in the database and in the dvlpmt log I can read unpermitted parameters :image
I have look in other posts and did what they advice but it doesn't work for me.
MODELS
micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
has_one :photo
default_scope -> { order('created_at DESC') }
validates :content, presence: true, length: { maximum: 140 }
validates :user_id, presence: true
end
photo.rb
class Photo < ActiveRecord::Base
belongs_to :micropost
has_attached_file :image
end
CONTROLLERS
photos_controller.rb
class PhotosController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy, :index]
def create
#photo = Photo.new(photo_params)
respond_to do |format|
if #photo.save
format.html { redirect_to #photo, notice: 'Photo was successfully created.' }
format.json { render action: 'static_pages/home', status: :created, location: #photo }
else
format.html { render action: 'static_pages/home' }
format.json { render json: #photo.errors, status: :unprocessable_entity }
end
end
end
private
def photo_params
params.require(:photo).permit(:image)
end
end
microposts_controller.rb
class MicropostsController < ApplicationController
before_action :signed_in_user, only: [:create, :destroy]
def create
#micropost = current_user.microposts.build(micropost_params)
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
private
def micropost_params
params.require(:micropost).permit(:content, photo_attributes: [:photo_id, :image])
end
end
VIEWS
_micropost_form.html.erb
I dont know how to construct well this form, I've tried a lot of different formulations
<%= form_for(#micropost) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.fields_for :image, :html => { :multipart => true } do |builder| %>
<%= f.file_field :image, :f => builder %>
<% end %>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
And when I want to submit my form, it is ok, but the column "photo_id" is empty for the table micropost. And photo isn't saved in the database.
Server log
Started POST "/microposts" for 127.0.0.1 at 2013-11-27 15:06:06 +0100
Processing by MicropostsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"<my_private_token>", "micropost"=>{"content"=>"Hello world", "image"=>#<ActionDispatch::Http::UploadedFile:0x00000003401a48 #tempfile=#<Tempfile:/tmp/RackMultipart20131127-4010-1r6qt80>, #original_filename="paint.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"micropost[image]\"; filename=\"paint.jpg\"\r\nContent-Type: image/jpeg\r\n">}, "commit"=>"Post"}
[1m[36mUser Load (0.2ms)[0m [1mSELECT "users".* FROM "users" WHERE "users"."remember_token" = '342df8f039f7f40698b3691f77d2539dc8b9c101' LIMIT 1[0m
Unpermitted parameters: image
[1m[35m (0.1ms)[0m begin transaction
[1m[36mSQL (0.4ms)[0m [1mINSERT INTO "microposts" ("content", "created_at", "updated_at", "user_id") VALUES (?, ?, ?, ?)[0m [["content", "Hello world"], ["created_at", Wed, 27 Nov 2013 14:06:06 UTC +00:00], ["updated_at", Wed, 27 Nov 2013 14:06:06 UTC +00:00], ["user_id", 101]]
[1m[35m (145.1ms)[0m commit transaction
Redirected to http://localhost:3000/
Completed 302 Found in 153ms (ActiveRecord: 145.7ms)
I am completely stuck right now, thank you if you can find something that can help me where to find the solution!!!

You need to use accepts_nested_attributes_for in your Micropost class.
class Micropost < ActiveRecord::Base
belongs_to :user
has_one :photo
accepts_nested_attributes_for :photo
default_scope -> { order('created_at DESC') }
validates :content, presence: true, length: { maximum: 140 }
validates :user_id, presence: true
end
It also looks like your form was associating the image with your micropost, not with the micropost's photo. Take a look at the Ruby docs for fields_for. Can you try changing part of your form to this?
<%= f.fields_for :photo, :html => { :multipart => true } do |photo_fields| %>
<%= photo_fields.file_field :image %>
<% end %>
If you do this, you'll need to make sure you call #micropost.build_photo in the controller action that renders the view.
I don't think you need to list photo_id in your strong parameters in microposts_controller.rb. That should get set once the records save to the database.
You can also read the Rails Guides on building complex forms. The guide talks about how to set up your model, view, and controller to handle nested attributes. Their description of strong parameters may be helpful to check out, if you haven't yet.

Related

Rails 7.0.4- form_for - collection_select with multiple options in edit action

I have three tables:
Staff
Staff_locations
Locations
Business case: Staff can work in multiple locations. Association between Staff and Location is done through staff_locations table. While creating Staff entry I am choosing locations that he/she belongs to. This is working fine.
But I have a problem with correct display of collection_select in the edit action. It is displayed as many times as many entries matching staff_id there are in the staff_locations table.
I can't figure out how to fix that and I didn't find any good hint anywhere so far.
models
class Staff < ApplicationRecord
has_many :visits, dependent: :destroy
has_many :work_schedules
has_many :customers, through: :visits
has_many :staff_locations, dependent: :destroy
has_many :locations, through: :staff_locations
accepts_nested_attributes_for :staff_locations, allow_destroy: true
def staff_locations_attributes=(staff_locations_attributes)
staff_locations_attributes.values[0][:location_id].each do |loc_id|
if !loc_id.blank?
staff_location_attribute_hash = {};
staff_location_attribute_hash['location_id'] = loc_id;
staff_location = StaffLocation.create(staff_location_attribute_hash)
self.staff_locations << staff_location
end
end
end
end
class StaffLocation < ApplicationRecord
belongs_to :staff
belongs_to :location
validates :staff_id, :location_id, uniqueness: true
end
class Location < ApplicationRecord
has_many :staff_locations
has_many :staffs, through: :staff_locations
end
staffs_controller
class StaffsController < ApplicationController
before_action :set_staff, only: %i [ show edit update destroy ]
def index
#staffs = Staff.all
end
def show
end
def new
#staff = Staff.new
#staff.staff_locations.build
end
def create
#staff = Staff.new(staff_params)
if #staff.save
redirect_to #staff
else
render :new, status: :unprocessable_entity
end
end
def edit
end
def update
respond_to do |format|
if #staff.update(staff_params)
format.html { redirect_to #staff, notice: 'Staff was successfully updated.' }
format.json { render :show, status: :ok, staff: #staff }
else
format.html { render :edit }
format.json { render json: #staff.errors, status: :unprocessable_entity }
end
end
end
def destroy
end
private
def staff_params
params.require(:staff).permit(:first_name, :last_name, :status, :staff_type, staff_locations_attributes: [:location_id => [] ])
#due to multiple select in the new staff form, staff_locations_attributes needs to contain Array of location_ids.
#Moreover check Staff model's method: staff_locations_attributes. It converts staff_locations_attributes into hashes.
end
def set_staff
#staff = Staff.find(params[:id])
end
end
form partial
<%= form_for(#staff) do |form| %>
<div>
<% if params["action"] != "edit" %>
<%= form.fields_for :staff_locations do |staff_location_form| %>
<%= staff_location_form.label :location_id, 'Associated Locations' %><br>
<%= staff_location_form.collection_select :location_id, Location.all, :id, :loc_name, {include_blank: false}, {:multiple => true } %>
<% end %>
<% else %>
<%= form.fields_for :staff_locations do |staff_location_form| %>
<%= staff_location_form.label :location_id, 'Associated Locations' %><br>
<%= staff_location_form.collection_select :location_id, Location.all, :id, :loc_name, {selected: #staff.locations.map(&:id).compact, include_blank: false}, {:multiple => true} %>
<% #debugger %>
<% end %>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
UPDATE
After changes suggested by #Beartech, update method works fine. However new action stopped working. Below I am pasting what I captured while submitting form to create one entry in Staff table and two associated entries in Staff_locations table.
Before saving objetct to the DB, I checked in the console:
#staff
#staff.location_ids
staff_params
After that I did save. I don't understand reason why it ends up with FALSE status.
14| ##staff.staff_locations.build
15| end
16|
17| def create
18| #staff = Staff.new(staff_params)
=> 19| debugger
20|
21| respond_to do |format|
22| if #staff.save
23| format.html { redirect_to #staff, notice: 'Staff was successfully created.' }
=>#0 StaffsController#create at ~/rails_projects/dentysta/app/controllers/staffs_controller.rb:19
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/rails_projects/dentysta/vendor/bundle/ruby/3.0.0/gems/actionpack-7.0.4/lib/action_controller/metal/basic_implicit_render.rb:6
# and 75 frames (use `bt' command for all frames)
(ruby) #staff
#<Staff:0x00007f2400acb2e8 id: nil, first_name: "s", last_name: "dd", status: "Active", staff_type: "Doctor", created_at: nil, updated_at: nil>
(ruby) #staff.location_ids
[4, 5]
(ruby) staff_params
#<ActionController::Parameters {"first_name"=>"s", "last_name"=>"dd", "status"=>"Active", "staff_type"=>"Doctor", "location_ids"=>["", "4", "5"]} permitted: true>
(ruby) #staff.save
TRANSACTION (0.1ms) begin transaction
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
StaffLocation Exists? (0.1ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
StaffLocation Exists? (0.1ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 4], ["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
CACHE StaffLocation Exists? (0.0ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
StaffLocation Exists? (0.3ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 5], ["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
TRANSACTION (0.1ms) rollback transaction
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
false
(rdbg) c # continue command
TRANSACTION (0.1ms) begin transaction
↳ app/controllers/staffs_controller.rb:22:in `block in create'
StaffLocation Exists? (0.2ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
StaffLocation Exists? (0.1ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 4], ["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
CACHE StaffLocation Exists? (0.0ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
StaffLocation Exists? (0.2ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 5], ["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
TRANSACTION (0.1ms) rollback transaction
↳ app/controllers/staffs_controller.rb:22:in `block in create'
Rendering layout layouts/application.html.erb
Rendering staffs/new.html.erb within layouts/application
Location Count (0.1ms) SELECT COUNT(*) FROM "locations"
↳ app/views/staffs/_form.html.erb:36
Location Load (0.1ms) SELECT "locations".* FROM "locations"
↳ app/views/staffs/_form.html.erb:36
Rendered staffs/_form.html.erb (Duration: 18.5ms | Allocations: 2989)
Rendered staffs/new.html.erb within layouts/application (Duration: 21.7ms | Allocations: 3059)
Rendered layout layouts/application.html.erb (Duration: 24.6ms | Allocations: 4054)
Completed 422 Unprocessable Entity in 2302301ms (Views: 30.1ms | ActiveRecord: 1.8ms | Allocations: 174939)
Edit Important: Using a multi-select may have unintended user interface issues. When you use the code below the multi-select for an existing record will load with the existing associated Locations highlighted as selections. If you don't touch that form element and then save the form, they will remain associated. But the entire multi-select list may not display at once. And if the person can not see all of the selected elements they may click on one and that will unselect all the others, thus deleting those associations when the record saves. I have edited the answer to add size: to the HTML attributes. This will show all of the options so they can see which are selected and what happens when they click on one (the deselecting of all others requiring a shfit/option select to get them reselected). I would consider if this is the correct interface element for what you are doing. You may want to consider collection_check_boxes as the correct UI element for this as they will have to purposely unselect any they want to get rid of and won't have to reselect them every time they add or remove one location.
Took me a while to remember how to do this. It's because you are focusing on the join table. Normally that is what you would do when you WANT multiple form fields. But you are actually looking to leverage the has_many relationship.
Remember, your accepts_nested_attributes_for give you a method of location_ids= which lets you set those locations just by passing the IDs. Rails will take care of making the associations using the join model.
In your console try:
#staff = Staff.first
# returns a staff object
#staff.locations
#returns an array of location objects due to the has_many
#staff.location_ids
# [12, 32]
#staff.location_ids = [12, 44, 35]
#this will update the joined locations to those locations by id. If any current locations are not in that array, they get deleted from the join table.
change your strong params from:
params.require(:staff).permit(:first_name, :last_name, :status,
:staff_type, staff_locations_attributes: [:location_id => [] ])
to:
params.require(:staff).permit(:first_name, :last_name, :status,
:staff_type, :location_ids => [] )
In your form you just want ONE form element, built using methods on #staff:
<%= f.label :locations %><br />
<%= f.collection_select :location_ids, Location.all, :id, :name,{selected: #staff.location_ids,
include_blank: false}, {:multiple => true, size: Location.all.count } %>
So this works since .location_ids is a valid method on #staff, Location.all returns a collection of all locations, then the two symbols (:id and :name) are both valid methods for a single location object. Then in the selected... you are just using the same .location_ids to grab the ones that already exist to mark them as selected.
I'd forgotten how to do this, it's been a while. Once I remembered it was so easy.
For those who will be struggling with similar case in the future, I am pasting what works for me right now. #Beartech thanks once again for your help. It saved me a lot of time.
models
class Staff < ApplicationRecord
has_many :visits, dependent: :destroy
has_many :work_schedules
has_many :customers, through: :visits
has_many :staff_locations, dependent: :destroy
has_many :locations, through: :staff_locations
accepts_nested_attributes_for :staff_locations, allow_destroy: true
end
class StaffLocation < ApplicationRecord
belongs_to :staff
belongs_to :location
end
class Location < ApplicationRecord
has_many :staff_locations
has_many :staffs, through: :staff_locations
end
staffs_controller
class StaffsController < ApplicationController
before_action :set_staff, only: %i[ show edit update destroy ]
def index
#staffs = Staff.all
end
def show
#debugger
end
def new
#staff = Staff.new
end
def create
#staff = Staff.new(staff_params)
debugger
respond_to do |format|
if #staff.save!
format.html { redirect_to #staff, notice: 'Staff was successfully created.' }
format.json { render :show, status: :ok, staff: #staff }
#redirect_to #staff
else
format.html { render :new, status: :unprocessable_entity, notice: 'Somthing went wrong' }
format.json { render json: #staff.errors, status: :unprocessable_entity }
#render :new, status: :unprocessable_entity
end
end
end
def edit
end
def update
respond_to do |format|
if #staff.update(staff_params)
format.html { redirect_to #staff, notice: 'Staff was successfully updated.' }
format.json { render :show, status: :ok, staff: #staff }
else
format.html { render :edit }
format.json { render json: #staff.errors, status: :unprocessable_entity }
end
end
end
def destroy
end
private
def staff_params
params.require(:staff).permit(:first_name, :last_name, :status, :staff_type, :location_ids => [] )
end
def set_staff
#staff = Staff.find(params[:id])
end
end
_form partial
<%= form_for(#staff) do |form| %>
<div>
<%= form.label :first_name %><br>
<%= form.text_field :first_name %>
<% #staff.errors.full_messages_for(:first_name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :last_name %><br>
<%= form.text_field :last_name %>
<% #staff.errors.full_messages_for(:last_name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :staff_type %><br>
<%= form.collection_select :staff_type, Staff.valid_types, :to_s, :to_s, {include_blank: false}, {:multiple => false} %>
<% #staff.errors.full_messages_for(:staff_type).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :status %><br>
<%= form.collection_select :status, Staff.valid_statuses, :to_s, :to_s, {include_blank: false}, {:multiple => false} %>
<% #staff.errors.full_messages_for(:status).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :locations %><br />
<%= form.collection_select :location_ids, Location.all, :id, :loc_name,{selected: #staff.location_ids, include_blank: false}, {:multiple => true, size: Location.all.count } %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>

Rails Nested Attributes with has_many through association not creating

I am trying to provide a way to generate a new object ("List") in one model with a new associated object in another ("Item") using a has_many through relationship (through "Groupings"). I was able to get the form working fine but can't figure out what I'm missing in order to finish the creation process correctly.
Rails v. 5.1.2, Ruby v. 2.4.1
lists_controller.rb
def new
#list = current_user.lists.new
3.times { #list.items.build }
end
def create
#list = current_user.lists.new(list_params)
respond_to do |format|
if #list.save
format.html { redirect_to #list, notice: 'List was successfully created.' }
format.json { render :show, status: :created, location: #list }
else
format.html { render :new }
format.json { render json: #list.errors, status: :unprocessable_entity }
end
end
end
private
def set_list
#list = List.find(params[:id])
end
def correct_user
#list = current_user.lists.find_by(id: params[:id])
redirect_to lists_path, notice: "Not authorized to edit this list"
if #list.nil?
end
def list_params
params.require(:list).permit(:title, {
item_attributes: [
:id, :title, :url
]})
end
items_controller.rb
def new
#item = Item.new
end
private
def set_item
#item = Item.find(params[:id])
end
def item_params
params.require(:item).permit(:title, :body, :url, :created,
:list_ids => [])
end
end
list.rb model
has_many :groupings, :dependent => :destroy
has_many :items, :through => :groupings
accepts_nested_attributes_for :items,
reject_if: ->(attrs) { attrs['title'].blank? || attrs['url'].blank? }
item.rb model
has_many :groupings, :dependent => :destroy
has_many :lists, :through => :groupings
validate :has_lists?
accepts_nested_attributes_for :lists
attr_writer :list_titles
after_save :assign_lists
def list_titles
#list_titles || lists.map(&:title).join(' ')
end
private
def assign_lists
if #list_titles
self.lists = #list_titles.split(/\,/).map do |title|
if title[0..0]==" "
title=title.strip
end
title=title.downcase
List.find_or_create_by_title(title)
end
end
end
def has_lists?
errors.add(:base, 'This item needs to be assigned to a list before it can be saved.') if self.lists.blank?
end
grouping.rb model
belongs_to :item
belongs_to :list
accepts_nested_attributes_for :item, :list
Lists Form
<%= form_with(model: list, local: true) do |f| %>
<% if list.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(list.errors.count, "error") %> prohibited this list from being saved:</h2>
<ul>
<% list.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %>
<%= f.text_field :title, id: :list_title %>
</div>
<div>
<p><strong>Items:</strong></p>
<%= f.fields_for :items do |item| %>
<div>
<%= item.label :title %>
<%= item.text_field :title %>
<%= item.label :url %>
<%= item.text_field :url %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Sample Console Output
Started POST "/lists" for 127.0.0.1 at 2017-09-19 13:12:53 -0700
Processing by ListsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Y6rszWVUXDIVymuoBkXwvkw1pVbyC6mffiWIZzr7PVd1NT9JJi6rD72k5Fh2qU5Q5tEd0qn6bFYMSJnz2TgjAA==", "list"=>{"title"=>"Websites", "items_attributes"=>{"0"=>{"title"=>"Google", "url"=>"www.google.com"}, "1"=>{"title"=>"Yahoo", "url"=>"www.yahoo.com"}, "2"=>{"title"=>"Bing", "url"=>"www.bing.com"}}}, "commit"=>"Create List"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 2], ["LIMIT", 1]]
Unpermitted parameter: :items_attributes
(0.1ms) BEGIN
SQL (0.9ms) INSERT INTO "lists" ("title", "created_at", "updated_at", "user_id") VALUES ($1, $2, $3, $4) RETURNING "id" [["title", "Websites"], ["created_at", "2017-09-19 20:12:53.458577"], ["updated_at", "2017-09-19 20:12:53.458577"], ["user_id", 2]]
(0.3ms) COMMIT
Redirected to http://localhost:3000/lists/24
Completed 302 Found in 7ms (ActiveRecord: 1.6ms)
I'm still learning, clearly - but after trying all kinds of related hints on this forum I couldn't figure this one out. Thanks for any help!
You're almost there but there is an error being reported in your console logs: Unpermitted parameter: :items_attributes.
Change item_attributes to items_attributes in your list_params:
def list_params
params.require(:list)
.permit(:title, items_attributes: [:id, :title, :url])
end
You have some mistakes in your syntax when you define your params. It should be like this: (items instead item and you don't need {})
def list_params
params.require(:list).permit(:title,
items_attributes: [:id, :title, :url])
end

Using cocoon gem, but can't save data after first input field

I've tried to implement cocoon in my application, but having trouble saving data after the first input field.
I tried following the example, but no luck.
I'm able to save the first field into database, but nothing happens on the other fields when I add more.
_form.html.erb
<%= simple_form_for(#project) do |f| %>
<%= f.input :project_name %>
<%= f.hidden_field :user_id %>
<div id="tasks">
<%= f.simple_fields_for :tasks do |g| %>
<%= render 'task_fields', :f => g %>
<% end%>
<%= link_to_add_association 'add task', f, :tasks %>
</div>
<%= f.button :submit %>
<% end %>
_task_fields.html.erb
<li class="control-group nested-fields">
<div class="controls">
<%= f.label :task %>
<%= f.text_field :task %>
</div>
<%= link_to_remove_association "remove task", f %>
</li>
Controller
params.require(:project).permit(
:user_id, :project_name,
tasks_attributes: [:id, :task, :_destroy])
And I added:
//= require cocoon
in application.js
Project Model
class Project < ActiveRecord::Base
belongs_to :user
has_many :tasks
accepts_nested_attributes_for :tasks, :reject_if => :all_blank, allow_destroy: true
end
Task Model
class Task < ActiveRecord::Base
belongs_to :project
end
I think I got this correct? I'm able to click on "add task" link and a new field pops up, but those new fields doesn't save.
EDIT from POST in console
Processing by ProjectsController#create as HTML
Parameters: {
"utf8"=>"✓",
"authenticity_token"=>"blah",
"project"=>{"project_name"=>"cocoon test",
"tasks_attributes"=>{
"0"=>{"task"=>"fix this!",
"_destroy"=>"false"
}}},
"commit"=>"Create Project"}
(0.2ms) begin transaction
SQL (1.5ms) INSERT INTO "projects" ("project_name", "created_at", "updated_at") VALUES (?, ?, ?) [["project_name", "cocoon test"], ["created_at", "2015-07-15 03:26:09.444377"], ["updated_at", "2015-07-15 03:26:09.444377"]]
SQL (0.4ms) INSERT INTO "tasks" ("task", "project_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["task", "fix this!"], ["project_id", 8], ["created_at", "2015-07-15 03:26:09.450324"], ["updated_at", "2015-07-15 03:26:09.450324"]]
(8.4ms) commit transaction
Redirected to http://localhost:3000/projects/8
Completed 302 Found in 24ms (ActiveRecord: 10.4ms)
EDIT 2 posting project controller methods create and new
def create
#project = Project.new(project_params)
respond_to do |format|
if #project.save
format.html { redirect_to #project, notice: 'Project was successfully created.' }
format.json { render :show, status: :created, location: #project }
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
def new
#project = Project.new
#project.tasks.build
end

Rails 4, double-nested models and strong parameters

I am fairly new to Ruby on Rails and began with rails 4 right away.
I have sucessfully nested a Recipe and a Ingredient model so that they can be added in the same form. Next I want to nest quantity within ingredient so that that aswell can be added within the same form. Everything seems to be working fine up until when the quantity of the ingredient is about to be inserted in the database and from this i believe there is something wrong with the strong params in the recipes_controller. But i will post the full code below.
I am using simple_form for the forms.
Thankful for any help!
Here are my models:
class Recipe < ActiveRecord::Base
has_many :comments, dependent: :destroy
has_many :ingredients, dependent: :destroy
accepts_nested_attributes_for :ingredients, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
validates :title, presence: true
validates :desc, presence: true
end
class Ingredient < ActiveRecord::Base
belongs_to :recipe
has_many :quantities, dependent: :destroy
accepts_nested_attributes_for :quantities, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
end
class Quantity < ActiveRecord::Base
belongs_to :ingredient
end
Here is the recipes_controller
class RecipesController < ApplicationController
def new
#recipe = Recipe.new
3.times do
ingredient = #recipe.ingredients.build
1.times {ingredient.quantities.build }
end
end
def create
#recipe = Recipe.new(params[:recipe].permit(:title, :desc, ingredients_attributes: [:id, :recipe_id, :name, :_destroy, quantities_attributes: [:id, :ingredient_id, :amount, :unit, :_destroy]]))
if #recipe.save
redirect_to #recipe
else
render "new"
end
end
def show
#recipe = Recipe.find(params[:id])
end
def edit
#recipe = Recipe.find(params[:id])
end
def update
#recipe = Recipe.find(params[:id])
if #recipe.update(params[:recipe].permit(:title, :desc))
redirect_to #recipe
else
render 'edit'
end
end
def destroy
#recipe = Recipe.find(params[:id])
#recipe.destroy
redirect_to recipes_path
end
def index
#recipes = Recipe.all
end
private
def post_params
params.require(:recipe).permit(:title, :desc, ingredients_attributes: [:id, :recipe_id, :name, :_destroy, quantities_attributes: [:id, :ingredient_id, :amount, :unit, :_destroy]])
end
end
Then i use simple form to create a form for recipe, ingredient and quantity through partials.
_form:
<%= simple_form_for #recipe do |f| %>
<%= f.error_notification %>
<%= f.input :title %>
<%= f.input :desc %>
<%= f.simple_fields_for :ingredients do |builder| %>
<%= render "ingredient_fields", :f => builder %>
<% end %>
<p class="links">
<%= link_to_add_association 'add ingredient', f, :ingredients %>
<p class="links">
<%= f.error :base%>
<%= f.submit %>
<% end %>
Which renders from _ingredients_fields:
<div class="nested-fields">
<%= f.input :name, label: "Ingredient" %>
<%= f.simple_fields_for :quantities do |builder| %>
<%= render "quantities_fields", :f => builder %>
<% end %>
<%= link_to_remove_association "remove", f %>
</div>
which renders from _quantities_fields: [EDITED]
<div class="nested-fields">
<%= f.input :amount %>
<%= f.input :unit %>
</div>
Trying to add new recipes result in the following log statement:
Started POST "/recipes" for 127.0.0.1 at 2013-10-29 14:15:40 +0100
Processing by RecipesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"t6LKgDLwAxaU9xo2ipyCM+j1yfVF9WrI8AoGTX+gRkw=", "recipe"=>{"title"=>"Pancakes", "desc"=>"Tasty", "ingredients_attributes"=>{"0"=>{"name"=>"Milk", "quantities_attributes"=>{"0"=>{"amount"=>"1", "unit"=>"Cup"}}, "_destroy"=>"false"}}}, "commit"=>"Create Recipe"}
[1m[35m (0.1ms)[0m begin transaction
[1m[36mSQL (3.5ms)[0m [1mINSERT INTO "recipes" ("created_at", "desc", "title", "updated_at") VALUES (?, ?, ?, ?)[0m [["created_at", Tue, 29 Oct 2013 13:15:40 UTC +00:00], ["desc", "Tasty"], ["title", "Pancakes"], ["updated_at", Tue, 29 Oct 2013 13:15:40 UTC +00:00]]
[1m[35mSQL (0.4ms)[0m INSERT INTO "ingredients" ("created_at", "name", "recipe_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Tue, 29 Oct 2013 13:15:40 UTC +00:00], ["name", "Milk"], ["recipe_id", 27], ["updated_at", Tue, 29 Oct 2013 13:15:40 UTC +00:00]]
[1m[36m (7.8ms)[0m [1mcommit transaction[0m
Redirected to http://www.projectcookbook.dev/recipes/27
Completed 302 Found in 22ms (ActiveRecord: 11.7ms)
You're using similar render for _quantities and _ingredients partials, which is wrong. In _quantities_field you don't need
<%= f.simple_fields_for :quantities do |builder| %>
<%= render "quantities_fields", :f => builder %>
<% end %>
AND should adjust
<%= f.input :name, label: "Quantity" %>
in _quantities_fields.
UPD
I think the problem is in :reject_if-clause at Ingredient model. It should be
:reject_if => lambda { |a| a[:amount].blank? }
bc here you specify conditions for Quantity, not for Ingredient
On code styling:
1) In controller it's better to use relevant name of private method for strong parameters: recipe_params instead of post_params and then use it for creation of new Recipe #recipe = Recipe.new(recipe_params)
2) Current associations between Recipe, Ingredient and Quantity will lead to Ingredient duplication in case two Recipes use similar one. The reason is belongs_to, which define single association. Try another approach (bellow).
BTW. recently I've answered on the similar question. Check it out: How do I reference an existing instance of a model in a nested Rails form?
I thing you are missing in this part
<%= simple_form_for #recipe do |f| %>
it should be
<%= simple_nested_form_for #recipe do |f| %>

Nested form not saving in rails 3.1

Noob question, I'm sure, but I can't seem to find my mistake. SymptomSets are saving w/ the proper user_id, but the nested symptoms disappear. Note that the user model structure is identical to that in the Rails Tutorial (save that it has_many :symptom_sets)
Models:
class SymptomSet < ActiveRecord::Base
attr_accessible :symptoms, :symptoms_attributes
belongs_to :user
has_many :symptoms, :dependent => :destroy
accepts_nested_attributes_for :symptoms, allow_destroy: true
end
class Symptom < ActiveRecord::Base
attr_accessible :name, :duration, :symptom_set_id
belongs_to :symptom_set
end
Controller:
class SymptomSetsController < ApplicationController
before_filter :signed_in_user, only: [:create, :new]
def new
#symptom_set = SymptomSet.new
3.times do
symptom = #symptom_set.symptoms.build
end
end
def create
#symptom_set = current_user.symptom_sets.build(params[:symptom_sets])
if #symptom_set.save
flash[:success] = "Symptoms submitted!"
redirect_to root_url
else
render 'static_pages/home'
end
end
And the View:
<%= simple_form_for #symptom_set, :html => { :class => 'form-inline' } do |f| %>
<%= f.fields_for :symptoms do |builder| %>
<%= render 'symptom_fields', f: builder %>
<% end %>
<div class="actions"><%= f.submit %></div>
<% end %>
And the partial:
<%= f.input :name,
:collection=> ["Cough", "Fever", "Headache", "Lethargy"],
label: "Symptom",
prompt: "Select a symptom",
:input_html => { :class => "span3" }%>
<%= f.input :duration,
:collection => 1..14,
label: "Duration",
prompt: "How many days?" %>
finally, the rails server console outputs the following:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"s7ksuk40M2r76Nq4PGEEpTpkCECxFniP4TtpfSHszQk=", "symptom_set"=>{"symptoms_attributes"=>{"0"=>{"name"=>"Cough", "_destroy"=>"false", "duration"=>"2"}, "1
"=>{"name"=>"Fever", "_destroy"=>"false", "duration"=>"2"}, "2"=>{"name"=>"", "_destroy"=>"1", "duration"=>""}}}, "commit"=>"Create Symptom set"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" ='OH6_nuvySNjd6AbTuDunsw' LIMIT 1
(0.1ms) BEGIN
SQL (0.4ms) INSERT INTO "symptom_sets" ("created_at", "updated_at", "user_id") VALUES ($1, $2, $3)
RETURNING "id" [["created_at", Tue, 05 Feb 2013 21:12:07 UTC +00:00], ["updated_at", Tue, 05 Feb 20
13 21:12:07 UTC +00:00], ["user_id", 1]]
(1.1ms) COMMIT
I'd try changing:
#symptom_set = current_user.symptom_sets.build(params[:symptom_sets])
to:
#symptom_set = current_user.symptom_sets.new(params[:symptom_sets])
I don't know if build would work there.
And also checking the params on the terminal log if it is called symptom_sets and if it's sending the parameters of nested form attributes.
EDIT:
I Really think your param's name would be symptom_set in singular. check that.

Resources