Rails Nested Attributes with has_many through association not creating - ruby-on-rails

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

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 5 Independent View Nested Resource - param is missing or the value is empty

I have a nested resource called PracticeQuestion, which is a child of PracticeQuiz. I want users to be able to go through one question at a time when they are at a PracticeQuiz. For example: foo.com/practice_quizzes/1/practice_questions/1..n
I got the practice quizzes working, but when I try to add a new practice question, I get a rails error that says that the param is missing or empty, but I don't see what i'm doing wrong. Please help
practice_quiz.rb
class PracticeQuiz < ApplicationRecord
belongs_to :user, optional: true
validates :user, presence: true
has_many :practice_questions, dependent: :destroy
end
practice_question.rb
class PracticeQuestion < ApplicationRecord
belongs_to :user, optional: true
belongs_to :practice_quiz
end
practice_questions_controller.rb
class PracticeQuestionsController < ApplicationController
before_action :set_practice_question, only: [:show, :edit, :update, :destroy]
def index
#practice_questions = PracticeQuestion.all
end
def show
end
# GET /practice_questions/new
def new
#practice_quiz = PracticeQuiz.friendly.find(params[:practice_quiz_id])
#practice_question = PracticeQuestion.new
end
# GET /practice_questions/1/edit
def edit
end
def create
#practice_quiz = PracticeQuiz.friendly.find(params[:practice_quiz_id])
#practice_question = PracticeQuestion.new(practice_question_params)
respond_to do |format|
if #practice_question.save
format.html { redirect_to #practice_question, notice: 'Practice question was successfully created.' }
format.json { render :show, status: :created, location: #practice_question }
else
format.html { render :new }
format.json { render json: #practice_question.errors, status: :unprocessable_entity }
end
end
end
private
def set_practice_question
#practice_question = PracticeQuestion.find(params[:id])
end
def practice_question_params
params.require(:practice_question).permit(:question, :explanation, :flagged)
end
end
views/practice_questions/_form.html.erb
<%= form_with(url: practice_quiz_practice_questions_path, local: true) do |form| %>
<% if practice_question.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(practice_question.errors.count, "error") %> prohibited this practice_question from being saved:</h2>
<ul>
<% practice_question.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :question %>
<%= form.text_field :question %>
</div>
<div class="field">
<%= form.label :explanation %>
<%= form.text_field :explanation %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
routes.rb
resources :practice_quizzes do
resources :practice_questions
end
I set the practice_questions controller's new method to find the id of the parent resource, but I get this error. I'm pretty sure I'm following rails naming conventions fine too.
ActionController::ParameterMissing at /practice_quizzes/23535/practice_questions
param is missing or the value is empty: practice_question
Update: here's the results from the rails server window
Processing by PracticeQuestionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"7dpxBB7jjZWzicCYWGA8yeTPSc9UeaqNDOavKQai2vMISryPBiMZ9Zo4LLS3DgZQI8IJc7rLh2TXd9Fj8PAjiA==", "question"=>"235235", "explanation"=>"25235232", "commit"=>"Save ", "practice_quiz_id"=>"23535"}
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
PracticeQuiz Load (0.9ms) SELECT "practice_quizzes".* FROM "practice_quizzes" WHERE "practice_quizzes"."slug" = $1 ORDER BY "practice_quizzes"."id" ASC LIMIT $2 [["slug", "23535"], ["LIMIT", 1]]
Completed 400 Bad Request in 102ms (ActiveRecord: 1.7ms)
ActionController::ParameterMissing - param is missing or the value is empty: practice_question:
app/controllers/practice_questions_controller.rb:76:in `practice_question_params'
app/controllers/practice_questions_controller.rb:30:in `create'
Update 2:
views/practice_questions/new.html.erb
<h1>New Practice Question</h1>
<%= render 'form', practice_question: #practice_question %>
Here
def practice_question_params
params.require(:practice_question).permit(:question, :explanation, :flagged)
end
you are using rails strong parameters. See this answer https://stackoverflow.com/a/30826895/2627121
Basically, params.require(:practice_question) means that you must have practice_question parameter.
Here
Parameters: {"utf8"=>"✓", "authenticity_token"=>"", "question"=>"235235", "explanation"=>"25235232", "commit"=>"Save ", "practice_quiz_id"=>"23535"}
you have question and explanation as root parameters, when according to your strong parameters declaration you must have
"practice_question" => { "question"=>"235235", "explanation"=>"25235232" }
You should edit form fields to have name as practice_question[question]

Cannot create answer through word by using accepts_nested_attributes_for

I am newbie on RoR. I try to create answer through word controller by using accepts_nested_attributes_for but when I click submit button, I get nothing and button got disable.
Here is my code.
word.rb
class Word < ApplicationRecord
belongs_to :category
belongs_to :exam
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :answers
has_many :exam_words, dependent: :destroy
scope :alphabet, ->{order :content}
end
answer.rb
class Answer < ApplicationRecord
belongs_to :wordscope :alphabel, ->{order "content"}
validates :content, presence: true
end
new.html.erb
<% provide(:title, "Create word" )%>
<h1></h1>
<%= form_for #word do |f| %>
<%= f.label :word_content %>
<%= f.text_field :content, class: "form-control" %>
<%= f.fields_for :answers do |answer| %>
<%= answer.label :answer_content %>
<%= answer.text_area :content, class: "form-control" %>
<%= answer.label :is_correct %>
<%= answer.check_box :is_correct %>
<%end%>
<%= f.submit "create", class: "btn btn-primary"%>
<%end%>
words_controller.rb
class WordsController < ApplicationController
before_action :load_category, except: [:show, :new]
def index
#words = #category.words.includes(:answers).paginate(page: params[:page])
end
def new
#word = Word.new
#word.answers.new
end
def show
#word = Word.find_by_id(params[:id])
session[:w_id] = #word.id
end
def create
#word = #category.words.new(word_params)
#word.category_id = session[:cat_id]
#word.exam_id = 1
if #word.save
redirect_to category_path(session[:cat_id])
end
end
def destroy
#word = Word.find(params[:id])
if #word.present?
#word.destroy
end
redirect_to :back
end
def edit
#word = Word.find(params[:id])
end
def update
#word = Word.find(params[:id])
if #word.update_attributes(word_params)
flash[:success] = "Updated"
redirect_to category_path(session[:cat_id])
else
render 'edit'
end
end
private
def word_params
params.require(:word).permit :content,
answers_attributes: [:id, :content, :is_correct]
end
def load_category
#category = Category.find_by id: session[:cat_id]
unless #category
flash[:danger] = t "category_not_found"
redirect_to categories_path
end
end
end
This is what i get in server in terminal
Started POST "/words" for 127.0.0.1 at 2016-11-29 14:52:26 +0000
Processing by WordsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"qr4qCFAh6omymmgJK7aOjZO0HFtkyT3uwB0KGl6EL60MOipdCFgS0l+XwEi7adhPt1uF1TL2RdRGwsK79FX3iw==", "word"=>{"content"=>"new", "answers_attributes"=>{"0"=>{"content"=>"moi", "is_correct"=>"1"}}}, "commit"=>"create"}
Category Load (0.9ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.2ms) begin transaction
Exam Load (0.3ms) SELECT "exams".* FROM "exams" WHERE "exams"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.4ms) rollback transaction
No template found for WordsController#create, rendering head :no_content
Completed 204 No Content in 48ms (ActiveRecord: 2.7ms)
This is what show in my local web
==============SOLVED========================
I just find out how to solve, I forgot optional: true in answer.rd.
class Answer < ApplicationRecord
belongs_to :word, optional: true
scope :alphabel, ->{order "content"}
validates :content, presence: true
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

Unpermitted parameters , nested forms - 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.

Resources