Trouble updating nested attributes - ruby-on-rails

I'm trying to build a small expense tracking app using Rails 4.1. When a user submits the expense request, it's state is marked as pending by default. The admin has to approve the request. I'm using state_machine gem to do this.
Comments are added from the expense show page using a nested_form_for like this:
<%= nested_form_for (#expense) do |f| %>
<div class="form-group">
<%= f.label :state %><br />
<%= f.collection_select :state, #expense.state_transitions, :event, :human_to_name, :include_blank => #expense.human_state_name, class: "form-control" %>
</div>
<%= f.fields_for :comments, #expense.comments.build do |comment| %>
<div class="form-group">
<%= comment.label :comment%>
<%= comment.text_area :comment, class: "form-control" %>
</div>
<% end %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
The controller looks like:
class ExpensesController < ApplicationController
def new
#expense = Expense.new
#item = #expense.items.build
#comment = #expense.comments.build
end
def show
#expense = Expense.find(params[:id])
#items = Item.where(:expense_id => #expense.id)
end
def update
#expense = Expense.find(params[:id])
if #expense.update(expense_params)
if #expense.state == "approved"
ExpenseMailer.expense_approved(#expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
elsif #expense.state = "rejected"
ExpenseMailer.expense_declined(#expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
end
else
render 'edit'
end
end
private
def expense_params
params.require(:expense).permit(:claim, :department_id, :expense_type_id, :expense_attachment, :state, :notes, items_attributes: [:id, :description, :amount, :issue_date, :_destroy], comments_attributes:[:id, :comment, :expense_id])
end
The problem is, if I add a comment without changing the state from the dropdown, I get the 'state is invalid' error and the edit page is shown. I can get past this by simply hitting the update button. But, the comments aren't created. On the other hand, if I change the state and add a comment, comments are shown without any issue.
The params value for that is:
Started PATCH "/expenses/14" for 127.0.0.1 at 2014-08-15 13:31:40 +0530
Processing by ExpensesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"MAEL2UYzos76NV6/eumHkXcpR2ge09wm6eOGQ+eEGCA=", "expense"=>{"state"=>"", "comments_attributes"=>{"0"=>{"comment"=>"vv"}}}, "commit"=>"Submit", "id"=>"14"}
The expense model with state machine looks like:
state_machine initial: :pending do
state :pending
state :approved
state :rejected
event :approved do
transition [:pending, :rejected] => :approved
end
event :rejected do
transition [:pending, :approved] => :rejected
end
end
Guess I'm making some mistake when it comes to building the comment attributes. Can someone let me know where I have to make changes?
Logger info for rejection:
Started GET "/expenses/17" for 127.0.0.1 at 2014-08-15 16:22:43 +0530
Processing by ExpensesController#show as HTML
Parameters: {"id"=>"17"}
[1m[35mExpense Load (0.2ms)[0m SELECT "expenses".* FROM "expenses" WHERE "expenses"."id" = ? LIMIT 1 [["id", 17]]
[1m[36mItem Load (0.1ms)[0m [1mSELECT "items".* FROM "items" WHERE "items"."expense_id" = ?[0m [["expense_id", 17]]
[1m[35mComment Load (0.2ms)[0m SELECT "comments".* FROM "comments" WHERE "comments"."expense_id" = ? [["expense_id", 17]]
Rendered expenses/show.html.erb within layouts/application (16.2ms)
Completed 200 OK in 45ms (Views: 42.8ms | ActiveRecord: 0.5ms)
Started PATCH "/expenses/17" for 127.0.0.1 at 2014-08-15 16:22:53 +0530
Processing by ExpensesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"MAEL2UYzos76NV6/eumHkXcpR2ge09wm6eOGQ+eEGCA=", "expense"=>{"state"=>"rejected", "comments_attributes"=>{"0"=>{"comment"=>"checking logger for rejected!"}}}, "commit"=>"Submit", "id"=>"17"}
[1m[36mExpense Load (0.2ms)[0m [1mSELECT "expenses".* FROM "expenses" WHERE "expenses"."id" = ? LIMIT 1[0m [["id", 17]]
[1m[35m (0.1ms)[0m begin transaction
[1m[36mSQL (8.1ms)[0m [1mUPDATE "expenses" SET "state" = ?, "updated_at" = ? WHERE "expenses"."id" = 17[0m [["state", "rejected"], ["updated_at", "2014-08-15 10:52:53.030676"]]
[1m[35mSQL (0.2ms)[0m INSERT INTO "comments" ("comment", "created_at", "expense_id", "updated_at") VALUES (?, ?, ?, ?) [["comment", "checking logger for rejected!"], ["created_at", "2014-08-15 10:52:53.040889"], ["expense_id", 17], ["updated_at", "2014-08-15 10:52:53.040889"]]
[1m[36m (4.2ms)[0m [1mcommit transaction[0m
Redirected to http://localhost:3000/expenses
Completed 302 Found in 24ms (ActiveRecord: 12.8ms)
Logger info for approval:
Started GET "/expenses/16" for 127.0.0.1 at 2014-08-15 16:22:30 +0530
Processing by ExpensesController#show as HTML
Parameters: {"id"=>"16"}
[1m[35mExpense Load (0.3ms)[0m SELECT "expenses".* FROM "expenses" WHERE "expenses"."id" = ? LIMIT 1 [["id", 16]]
[1m[36mItem Load (0.2ms)[0m [1mSELECT "items".* FROM "items" WHERE "items"."expense_id" = ?[0m [["expense_id", 16]]
[1m[35mComment Load (0.3ms)[0m SELECT "comments".* FROM "comments" WHERE "comments"."expense_id" = ? [["expense_id", 16]]
Rendered expenses/show.html.erb within layouts/application (167.3ms)
Completed 200 OK in 244ms (Views: 213.7ms | ActiveRecord: 1.1ms)
Started PATCH "/expenses/16" for 127.0.0.1 at 2014-08-15 16:22:41 +0530
Processing by ExpensesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"MAEL2UYzos76NV6/eumHkXcpR2ge09wm6eOGQ+eEGCA=", "expense"=>{"state"=>"approved", "comments_attributes"=>{"0"=>{"comment"=>"checking logger!"}}}, "commit"=>"Submit", "id"=>"16"}
[1m[36mExpense Load (0.2ms)[0m [1mSELECT "expenses".* FROM "expenses" WHERE "expenses"."id" = ? LIMIT 1[0m [["id", 16]]
[1m[35m (0.1ms)[0m begin transaction
[1m[36mSQL (0.5ms)[0m [1mUPDATE "expenses" SET "state" = ?, "updated_at" = ? WHERE "expenses"."id" = 16[0m [["state", "approved"], ["updated_at", "2014-08-15 10:52:41.604580"]]
[1m[35mSQL (0.5ms)[0m INSERT INTO "comments" ("comment", "created_at", "expense_id", "updated_at") VALUES (?, ?, ?, ?) [["comment", "checking logger!"], ["created_at", "2014-08-15 10:52:41.607555"], ["expense_id", 16], ["updated_at", "2014-08-15 10:52:41.607555"]]
[1m[36m (4.0ms)[0m [1mcommit transaction[0m
Redirected to http://localhost:3000/expenses
Completed 302 Found in 17ms (ActiveRecord: 5.3ms)

I don't know why you're getting an error, but I'll give you some ideas for the state_machine / aasm gems
--
State Machines
Since Rails is object-orientated, you have to appreciate how these state machine gems work - they are an extrapolation of the electronics method of setting up a "state machine" (to predicate finite "states" within a circuit):
What I'm trying to demonstrate with this is that by including a state machine in your application, you're actually indicating the state of an object (it's not just another attribute)
Currently, you're treating the state attribute of your Comment model as an attribute, when it can be treated as an object in itself
--
Object
Notice this functionality from the State Machine repo:
Notice how that has nothing to do with the state attribute?
I think you'd be better treating the state method as what it is - a way to influence the state_machine itself. I would do that in several ways:
Set the state "default" state in the state_machine declaration
Validate the state object using OOP principles
#app/models/expense.rb
Class Expense < ActiveRecord::Base
state_machine :state, :initial => :pending do #-> sets the state to "pending" unless specified otherwise
end
end
#app/controllers/expenses_controller.rb
Class ExpensesController < ApplicationController
def update
if #expense.approved?
...
end
end
end
--
Fix
In regards to your being unable to create a comment, I think the problem will be two-fold
Firstly, you're building your comments within the view. Apart from anything, this is bad practice (against MVC) - you'll be best building the associated objects inside your model:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
def self.build id
expense = (id.present?) self.find id : self.new
expense.comments.build
return expense
end
end
This allows you to perform the following:
#app/controllers/expenses_controller.rb
Class ExpensesController < ApplicationController
def new
#expense = Expense.build
end
def edit
#expense = Expense.build params[:id]
end
end
This will basically give your nested comments form the pre-built nested objects required to fire the form for the edit & new methods (so you don't need to call #expense.comments.build in your view)
In regards to the non-saving functionality - I would certainly look at how you're saving the state attribute. I suspect it will be down to you not passing the attribute correctly (IE that you're using an incorrect value for the state param upon default submission)
I would recommend using the following:
Investigate your params from the "default" update
Does the state attribute match your model definition of the attributes?
If it does not, that will be your problem
--
UPDATE
Thanks for the update
Okay, so the problem seems to be that the state value is not being passed if it's default. I think the way to fix this will be to set a default value for the collection_select:
Remove :include_blank => #expense.human_state_name
Replace with <%= f.collection_select :state, #expense.state_transitions, :event, :human_to_name, { selected: #expense.human_state_name}, class: "form-control" %>
Update 2
Since state_machine gives you the ability to track & fire instance methods after a successful transition, you may wish to do the following:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
state_machine :state, :initial => :pending do
state :pending
state :approved
state :rejected
event :approve do
transition [:pending, :rejected] => :approved
end
event :reject do
transition [:pending, :approved] => :rejected
end
after_transition :on => :approved, :do => :send_approval_email
after_transition :on => :rejected, :do => :send_rejection_email
def send_approval_email
ExpenseMailer.expense_approved(self).deliver #-> might need to call outide of state_machine block
end
def send_rejection_email
ExpenseMailer.expense_declined(self).deliver
end
end
end
This will give you the ability to perform the following:
#app/controllers/expenses_controller.rb
Class ExpensesController < ApplicationController
def update
#expense = Expense.find params[:id]
if #expense.update(expense_params)
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
end
end
end
By the way, you need to change your "events" to have different names to your "states". As per my object-oriented references above, you need to be able to call the likes of #object.approve etc

Related

How to set a form for nested attributes with collection selection?

I am trying to configure a form for nested attributes with a selector on RoR, but it seems I am doing it wrong.
I am trying to create a new affaire with nested_attributes contributions.
Each contribution belongs to a skill which needs to be defined.
As there is a limited number of possible skills, I would like to have a selector in my form to select the contribution_skill from a list.
EDIT : I finally made it save the affaire with contributions by setting all the contribution attributes (I used the defaut set in the model definition).
But I don't always want these attributes to be defined, so it is still not the solution.
I add that there is no presence validation on these attributes.
Anyone knows how to save a nested_attribute without all nested_attribute_attributes defined ?
Here is the models definitions :
class Affaire < ApplicationRecord
has_many :contributions
accepts_nested_attributes_for :contributions
end
class Skill < ApplicationRecord
has_many :contributions
end
class Contribution < ApplicationRecord
belongs_to :skill
belongs_to :affaire
end
Controller definition :
def new
#affaire = Affaire.new
2.times {#affaire.contributions.build}
end
def create
#affaire = Affaire.new(affaire_params)
#affaire.save
redirect_to #affaire
end
private
def affaire_params
params.require(:affaire).
permit(:nam, contributions_attributes: [:id, :skill_id])
end
View definition :
<%= form_with model: #affaire do |form| %>
<%= form.label :name, "Name of Affaire" %>
<%= form.text_field :name %>
<%= form.fields_for :contributions do |ff| %>
<%= ff.label :skill_id, "Contribution" %>
<%= ff.collection_select(:skill_id, Skill.all, :id, :name) %>
<% end %>
<%= form.submit "Create affaire" %>
I am expecting to get my affaire created with my contribution and the contribution to have its skill set.
Unfortunately, it goes wrong.
Here iswhat I got in the rails server console :
Started GET "/affaires/new" for ::1 at 2019-09-04 21:06:41 +0200
Processing by AffairesController#new as HTML
Rendering affaires/new.html.erb within layouts/application
Skill Load (0.4ms) SELECT "skills".* FROM "skills"
↳ app/views/affaires/new.html.erb:30
CACHE Skill Load (0.0ms) SELECT "skills".* FROM "skills"
↳ app/views/affaires/new.html.erb:30
Rendered affaires/new.html.erb within layouts/application (20.9ms)
Completed 200 OK in 105ms (Views: 70.3ms | ActiveRecord: 2.2ms)
Started POST "/affaires" for ::1 at 2019-09-04 21:06:48 +0200
Processing by AffairesController#create as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"GCc2wX2C7mWn77eksjdhDp5i1lFoUc2L3MLkOIXmWhavNdxwIynpNTVaRmbrvkKNdRtQxqoXjaInI2MaJtvvFA==", "affaire"=>{"name"=>"test", "contributions_attributes"=>{"0"=>{"skill_id"=>"3"}, "1"=>{"skill_id"=>"1"}}}} (0.3ms) BEGIN
↳ app/controllers/affaires_controller.rb:20
Skill Load (0.3ms) SELECT "skills".* FROM "skills" WHERE "skills"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]]
↳ app/controllers/affaires_controller.rb:20
Skill Load (0.3ms) SELECT "skills".* FROM "skills" WHERE "skills"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/affaires_controller.rb:20
(0.2ms) ROLLBACK
↳ app/controllers/affaires_controller.rb:20
Redirected to http://localhost:3000/affaires
Completed 200 OK in 27ms (ActiveRecord: 2.8ms)
I can't see where is the problem.
Parameters seems ok to me if I refer to rails documentation. I don't get why it rolls back.
Thanks for your help

How to debug simple RoR application

So far, to a very experienced Java EE developer with years of experience in many different languages, I am having real difficulties with Ruby on Rails. I am using: ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15] and Rails 5.0.0. I am following a very simple on-line tutorial on building a private library web application, BUT, in order to learn something, instead of having Books with a linked table of Subjects, I changed Subjects to Authors since many books have the same authors. I am using SQLLite for development and MySQL for production( haven't gotten there yet! ). I find that when you follow exactly the directions in most tutorials, you end up with whatever application you were building. But, IF you deviate in any fashion, things just don't work and it's very hard to figure out what happened. You get error messages ( sometimes ) in the logs that you've got an undefined variable or constant. Normally, you would search for where that variable is used, then be sure you define it or spell it correctly. However, in RoR, that constant doesn't appear anywhere except in the log, if there. RoR, due to its conventions, has either created or assumed that you had such a variable, when in fact, you may have named a "view" folder in the singular instead of the plural. It "invented" a variable to point to that, but it didn't match the pattern, so it fails with very poor error messages.
The server doesn't complain, just does a rollback, and goes on. The log has some unmeaningful message, as per above. I end up spending hours trying different patterns for routes suggested by people, or renaming things, but it's all guesswork.
I enjoy working with frameworks and systems where I understand them. This seems to be a collection of different pieces which parse in yml, yaml, erb, rb, sass, haml, etc. I've tried logging, but to no avail. How do you located simple mistakes?
Here is my "books_controller.rb":
class BooksController < ApplicationController
def list
#books = Book.all
end
def show
#book = Book.find(params[:id])
end
def new
#book = Book.new
#authors = Author.all
end
def create
#book = Book.new(book_params)
if #book.save
logger.debug 'Redirecting to list'
redirect_to :action => 'list'
else
#authors = Author.all
render :action => 'new'
end
end
def edit
#book = Book.find(params[:id])
#authors = Author.all
end
def update
#book = Book.find(params[:id])
if #book.update_attributes(book_params)
redirect_to :action => 'show', :id => #book
else
#authors = Author.all
render :action => 'edit'
end
end
def delete
Book.find(params[:id]).destroy
redirect_to :action => 'list'
end
def show_authors
#author = Author.find(params[:id])
end
def book_params
params.require(:books).permit(:title, :description, :author_id)
end
end
The new.html.erb under app/views/books is:
<h1>Add new book</h1>
<%= form_tag :action => 'create' do %>
<p><label for = "book_title">Title</label>:
<%= text_field 'books', 'title' %></p>
<p><label for = "book_author_id">Author</label>:
<%= collection_select(:book, :author_id, #authors, :id, :name, prompt: true) %></p>
<p><label for = "book_description">Description</label><br/>
<%= text_area 'books', 'description' %></p>
<%= submit_tag "Create" %>
<% end -%>
<%= link_to 'Back', {:action => 'list'} %>
routes.rb is:
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :books
#get 'books/list'
#post 'books/create'
#get 'books/new'
#patch 'books/update'
#get 'books/show'
#get 'books/edit'
#get 'books/delete'
get 'books/show_authors'
get 'authors/list'
post 'authors/create'
get 'authors/new'
patch 'authors/update'
get 'authors/show'
get 'authors/edit'
root :to => 'books#list'
end
When I try to add a new book, I enter the title, select an author, and put in a description and click "Create". It then just returns to the new screen. The console has:
Started GET "/books/new" for ::1 at 2016-08-04 17:18:22 -0400
Processing by BooksController#new as HTML
Rendering books/new.html.erb within layouts/application
Author Load (0.1ms) SELECT "authors".* FROM "authors"
Rendered books/new.html.erb within layouts/application (5.4ms)
Completed 200 OK in 26ms (Views: 21.6ms | ActiveRecord: 0.5ms)
Started POST "/books" for ::1 at 2016-08-04 17:18:28 -0400
Processing by BooksController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"noRmEq8rHE6RLs0cPNrlZoQXq//2sr+SAOSHEFc0U3zqbSJZOSKDmdgwpdm5/nVswItHp4Ken0mjggt47ph46Q==", "books"=>{"title"=>"sdfasdf", "description"=>"asdfasdf"}, "book"=>{"author_id"=>"2"}, "commit"=>"Create"}
(0.1ms) begin transaction
(0.1ms) rollback transaction
Rendering books/new.html.erb within layouts/application
Author Load (0.1ms) SELECT "authors".* FROM "authors"
Rendered books/new.html.erb within layouts/application (2.0ms)
Completed 200 OK in 24ms (Views: 20.3ms | ActiveRecord: 0.2ms)
and the development log has:
Started GET "/books/new" for ::1 at 2016-08-04 17:18:22 -0400
Processing by BooksController#new as HTML
Rendering books/new.html.erb within layouts/application
[1m[36mAuthor Load (0.1ms)[0m [1m[34mSELECT "authors".* FROM "authors"[0m
Rendered books/new.html.erb within layouts/application (5.4ms)
Completed 200 OK in 26ms (Views: 21.6ms | ActiveRecord: 0.5ms)
Started POST "/books" for ::1 at 2016-08-04 17:18:28 -0400
Processing by BooksController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"noRmEq8rHE6RLs0cPNrlZoQXq//2sr+SAOSHEFc0U3zqbSJZOSKDmdgwpdm5/nVswItHp4Ken0mjggt47ph46Q==", "books"=>{"title"=>"sdfasdf", "description"=>"asdfasdf"}, "book"=>{"author_id"=>"2"}, "commit"=>"Create"}
[1m[35m (0.1ms)[0m [1m[36mbegin transaction[0m
[1m[35m (0.1ms)[0m [1m[31mrollback transaction[0m
Rendering books/new.html.erb within layouts/application
[1m[36mAuthor Load (0.1ms)[0m [1m[34mSELECT "authors".* FROM "authors"[0m
Rendered books/new.html.erb within layouts/application (2.0ms)
Completed 200 OK in 24ms (Views: 20.3ms | ActiveRecord: 0.2ms)
Yes, the transaction was rolled back. WHY? How can I get information on what caused the database to "rollback"? The two tables in the database are:
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
t.string :title
t.integer :author_id
t.string :description
t.timestamp :created
t.timestamps
end
end
end
class CreateAuthors < ActiveRecord::Migration[5.0]
def change
create_table :authors do |t|
t.string :name
t.timestamps
end
end
end
class Book < ApplicationRecord
belongs_to :author
validates_presence_of :title
end
class Author < ApplicationRecord
has_many :books
end
I can create a book in rails console as:
b=Book.create :title=>'Test', :author_id=>1, :description=>'Desc'
(0.1ms) begin transaction
Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
SQL (0.3ms) INSERT INTO "books" ("title", "author_id", "description", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["title", "Test"], ["author_id", 1], ["description", "Desc"], ["created_at", 2016-08-04 20:17:40 UTC], ["updated_at", 2016-08-04 20:17:40 UTC]]
(2.4ms) commit transaction
=> #<Book id: 1, title: "Test", author_id: 1, description: "Desc", created: nil, created_at: "2016-08-04 20:17:40", updated_at: "2016-08-04 20:17:40">
I would appreciate input and especially help on understanding why what happened actually happened. It seems that a very simple error is being made, but I can't see it.
------------------ Added after several answers and "guesses" by me.
I changed the form_tag to a form_for as I'll show below.
----new.html.erb------
<%= form_for(#book) do |f| %>
Title: <%= f.text_field :title %><br/>
Author: <%= select("book", "author_id", Author.all.collect{|p| [p.name,p.id]}, prompt: 'Select') %><br/>
Description: <%= f.text_area :description %><br/>
<%= f.submit "Create" %>
<% end -%>
<%= link_to 'Back', {:action => 'list'} %>
I get in the browser:
Validation failed: Author must exist, Title can't be blank
Extracted source (around line #18):
16
17
18
19
20
21
def create
#book = Book.new
if #book.save!
redirect_to :action => 'list'
else
#authors = Author.all
Rails.root: /Users/woo/Development/rails/library
Application Trace | Framework Trace | Full Trace
app/controllers/books_controller.rb:18:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"gi+wVGV3MIlkJsRjO8Ig1cS3YV/OIADSevFJg7ItBesokIiHFDThycTO8/kob+2E1fuPFquFUK+b7bGksWRZGQ==",
"book"=>{"title"=>"Book", "author_id"=>"2", "description"=>"test"},
"commit"=>"Create"}
As far as I can see, book does have a title, and an author_id, and a description. Why "Author must exist, Title can't be blank"?
Try using form_for instead of form_tag in your books/new view. This is the Rails way to create forms for model object.
Check a handy guide on form_for here.
How to debug ....
There are several tools to help debug a Rails application. One you have already discovered: the log file in log/development.log.
Another is Byebug. Add the gem to your Gemfile and insert the following in the create action after the 'else':
require 'byebug'
byebug
then post the form again. The development server will bring up a Byebug console where you can inspect local variables, instance variables, and the stack trace. Try inspecting:
#book.errors
When an ActiveRecord model fails to save it is usually because validations failed. Errors encountered when saving are added to the errors object on the model instance.
The reason for the failure is probably that the form is not passing the expected parameters. By convention, Rails expects attributes for the model to be in a hash where the key is the model name, so params[:book][:title], not params[:title]. See the documentation for the form_for helper for more info.
Thanks for the help. With your suggestions and a lot of guessing from ready various Google sites, I combined them and in the new.html.erb put form_for(#book), and then in the create method of books_controller.rb, I put #book - Book.new(book_params), where book_params is:
def book_params
params.require(:book).permit(:title, :author_id, :description)
end
I'm guessing that this is to handle the strong attribution required by Rails 4 and up. Before I had books as the first argument and since it existed, but book was not filled, I got the weird error. After using form_for with #book as an argument, that set the values from the form into the book hash. Then the params.require with :book as the first argument, looked in that hash to extract title, author_id, and description.
Again, many thanks for the help and I learned about bye bug and save! and so forth. I find information is very sketchy and ofter the version is not mentioned, thus leading one astray many times.

Rails form submitting but not creating

Hey guys not sure what's going on here. I have Movies and Critics on my app. I've set up an assocation between those and Reviews. I'm trying to set up the controller and form to create and destroy Reviews. In the rails console I can create reviews that belong to both just fine and I have tested my controller (may be incorrect though) and it seems to be working, so I think the problem is in my form. Thanks in advance guys. Here's the codeand server logs:
class ReviewsController < ApplicationController
def create
#movie = Movie.find(params[:movie_id])
current_critic.reviews.create(content: params[:content], movie_id: #movie.id)
redirect_to #movie
end
def destroy
#movie = Movie.find(params[:movie_id])
#review = current_critic.reviews.find_by(movie_id: #movie.id)
#review.delete
redirect_to #movie
end
end
form:
<div class="form">
<h1 class="smaller">Write a Review</h1>
<%= form_for(current_critic.reviews.new) do |r| %>
<%= hidden_field_tag :movie_id, #movie.id %>
<ul>
<li>
<%= r.text_area :content, placeholder: "Write your review...", size: "50x10" %>
</li>
<li>
<%= r.submit "Submit Review" %>
</li>
</ul>
<% end %>
</div>
server log after submitting form:
Started POST "/reviews" for 99.39.164.184 at 2015-12-04 20:34:59 +0000
Processing by ReviewsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"d16BVZxzqZY5bQrw9xr2VlWbWjh0Dc7bL6t4OgKQPk1RXWt40acMjtkjXG9DUBBfnA7K06iJDwQzd5YJ0D6c4Q==", "movie_id"=>"2", "review"=>{"content"=>"One last try at writing and submitting a review before I head out"}, "commit"=>"Submit Review"}
Movie Load (2.1ms) SELECT "movies".* FROM "movies" WHERE "movies"."id" = ? LIMIT 1 [["id", 2]]
Critic Load (0.2ms) SELECT "critics".* FROM "critics" WHERE "critics"."id" = ? LIMIT 1 [["id", 1]]
(5.3ms) begin transaction
(0.9ms) commit transaction
Redirected to https://everyones-a-critic-caedbudris.c9users.io/movies/2
Completed 302 Found in 574ms (ActiveRecord: 18.1ms)
EDIT: I implemented strong paramaters for create so the controller is now
def create
#movie = Movie.find(params[:movie_id])
current_critic.reviews.create(review_params)
redirect_to #movie
end
private
def review_params
params.require(:review).permit(:content, :movie_id)
end
And it is now inserting into reviews, but for some reason it's not getting the movie_id passed by the hidden_field_tag. Why is this?
Started POST "/reviews" for 99.39.164.184 at 2015-12-05 21:31:07 +0000
Processing by ReviewsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"OlBIfneoWTvBtIeISTF9ubo9jj06oVyfDd6rswxe7xO+JyGXRvFV4TLD+3xKhBZHRF+eRJAawKUabU7KrLpZow==", "movie_id"=>"2", "review"=>{"content"=>"review review review review review"}, "commit"=>"Submit Review"}
Movie Load (0.3ms) SELECT "movies".* FROM "movies" WHERE "movies"."id" = ? LIMIT 1 [["id", 2]]
Critic Load (0.3ms) SELECT "critics".* FROM "critics" WHERE "critics"."id" = ? LIMIT 1 [["id", 1]]
(0.1ms) begin transaction
SQL (6.4ms) INSERT INTO "reviews" ("content", "critic_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["content", "review review review review review"], ["critic_id", 1], ["created_at", "2015-12-05 21:31:08.185722"], ["updated_at", "2015-12-05 21:31:08.185722"]]
(10.3ms) commit transaction
Redirected to https://everyones-a-critic-caedbudris.c9users.io/movies/2
Completed 302 Found in 157ms (ActiveRecord: 25.6ms)
You should whitelist your parameters, by default rails won't accept any parameters to avoid mass assignment. Proper way is to define a protected block at the bottom of your controller. Like this,
protected
def rating_params
params.require(:rating).permit(:content)
end
And you can use it like
current_critic.reviews.create(rating_params)

rails has_one belongs to form submission error

I have two models and i'm trying to integrate it, i can bring form another model, problem is while submission , id is being updated to null
orbituary model:
class Orbituarysite < ActiveRecord::Base
attr_accessible :birth_place, :death_place, :dob, :dod, :name, :living_place, :orbiturerimage, :salutation, :user_id
belongs_to :user
mount_uploader :orbiturerimage, OrbiturerUploader
has_one :notice_display
end
Notice model
class NoticeDisplay < ActiveRecord::Base
attr_accessible :message, :notice_type, :orbituarysite_id, :posted_by, :notice_event_places_attributes, :notice_event_contacts_attributes
belongs_to :orbituarysite
end
orbituary controller:
def show
#orbituarysite = Orbituarysite.find(params[:id])
if #orbituarysite.notice_display.nil?
#notice_display = #orbituarysite.build_notice_display
end
respond_to do |format|
format.html # show.html.erb
format.json { render json: #orbituarysite }
end
end
in orbituary sites show view:
<% if #orbituarysite.notice_display.nil? %>
<%= render 'notice_displays/form' , :remote => true %>
<% end %>
while i submit the form i get the following problem, i.e, aftter submission i redirect it to orbituary sites page so i get this in server,
Started GET "/orbituarysites/1" for 127.0.0.1 at 2013-09-05 08:58:45 +0530
Processing by OrbituarysitesController#show as HTML
Parameters: {"id"=>"1"}
User Load (1.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Orbituarysite Load (1.8ms) SELECT "orbituarysites".* FROM "orbituarysites" WHERE "orbituarysites"."id" = $1 LIMIT 1 [["id", "1"]]
NoticeDisplay Load (0.8ms) SELECT "notice_displays".* FROM "notice_displays" WHERE "notice_displays"."orbituarysite_id" = 1 LIMIT 1
History Load (0.9ms) SELECT "histories".* FROM "histories" WHERE "histories"."orbituarysite_id" = 1 LIMIT 1
(0.5ms) BEGIN
(0.9ms) UPDATE "histories" SET "orbituarysite_id" = NULL, "updated_at" = '2013-09-05 03:28:46.214455' WHERE "histories"."id" = 2
(25.0ms) COMMIT
Rendered histories/_form.html.erb (125.2ms)
Rendered memories/_form.html.erb (9.1ms)
Rendered condolences/_form.html.erb (14.4ms)
Rendered orbiturer_share_images/_form.html.erb (6.2ms)
Rendered orbituarysites/show.html.erb within layouts/application (162.6ms)
Rendered orbituarysites/_form.html.erb (26.4ms)
Completed 200 OK in 944ms (Views: 504.5ms | ActiveRecord: 64.5ms)
how to change this because get false in
<% if #orbituarysite.notice_display.nil? %>
but in console i get true when it is nil and false when there is content
Please help me to solve this
.nil? does not always guarantee truthiness. Use #orbituarysite.notice_display.present?
Essentially, all nil's return true because in ruby all things are truthy except false and nil.
So you're code will always return TRUE because it will return nil. :-)

Rails 3: validation, presence: true is only validating text field but not radio button

So I'm working on this part of a rails app with nested forms. I am having trouble with getting validation to work. So the parent form is a model to store Questions and the child form is a model to store Answers.
There are 3 different types of questions: number (text field), yes/no (radio buttons), agree/disagree (radio buttons).
I have a simple validation in the answers model: validates :value, presence: true
So for example I create a question of type number, it generates a text field and if I submit it as empty the validation works and the errors are rendered on the page. However, if I pick one of the the other 2 options, which are both radio buttons, I can submit the form without making a selection and the validation doesn't work. I noticed in the console that only the question is inserted into the database, but the answer is not (with the radio button form); normally I would assume that at least there would be nil values passed, but the INSERT query doesn't even show up.
I cheated a little by having a hidden field in the radio button forms, and creating a change handler that sets the value of the radio button to the hidden field whenever the radio button selected is changed. However, I would really like to dig deeper and figure out the real issue, because it's always good to have a back-up in case javascript is disabled.
Answer Model
class Answer < ActiveRecord::Base
attr_accessible :value, :user_id, :meter_id, :question_id
belongs_to :user
belongs_to :question
validates :value, presence: true, :numericality => true
before_save :associate_with_meter_id
before_save :associate_with_user_id
def associate_with_meter_id
self.meter_id = question.user.meter_id
end
def associate_with_user_id
self.user_id = question.user.id
end
end
Question Model
class Question < ActiveRecord::Base
attr_accessible :description, :taxonomy, :user_id, :answers_attributes
belongs_to :user
has_many :answers
accepts_nested_attributes_for :answers
validates :description, presence: { :on => :create }
validates :taxonomy, presence: { :on => :create }
def relevance_score
rand
end
end
Log
Started POST "/questions" for 127.0.0.1 at 2012-06-12 09:21:25 -0400
Processing by QuestionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"knwvfB6q6Q7qoTprc/3R4Et3r13xWzpAB1Iq8FsRndQ=", "question"=>{"description"=>"How are you?", "taxonomy"=>"yesno"}, "submit_button"=>"Ask"}
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 585460615 LIMIT 1
SQL (0.1ms) BEGIN
SQL (4.3ms) INSERT INTO `questions` (`avganswer`, `coeff`, `created_at`, `description`, `pval`, `quality`, `rank`, `responses`, `rsquare`, `skips`, `taxonomy`, `updated_at`, `user_id`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["avganswer", nil], ["coeff", nil], ["created_at", Tue, 12 Jun 2012 13:21:25 UTC +00:00], ["description", "How are you?"], ["pval", 0.0], ["quality", 0.0], ["rank", nil], ["responses", nil], ["rsquare", 0.0], ["skips", nil], ["taxonomy", "yesno"], ["updated_at", Tue, 12 Jun 2012 13:21:25 UTC +00:00], ["user_id", 585460615]]
(0.3ms) COMMIT
SQL (0.0ms) BEGIN
(0.0ms) COMMIT
Redirected to http://localhost:3000/questions
Completed 302 Found in 14ms (ActiveRecord: 0.0ms)
Started GET "/questions" for 127.0.0.1 at 2012-06-12 09:21:25 -0400
Processing by QuestionsController#index as HTML
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 585460615 LIMIT 1
Question Load (1.3ms) SELECT `questions`.* FROM `questions` WHERE `questions`.`user_id` = 585460615
Rendered shared/_error_messages.html.erb (0.0ms)
Rendered questions/_form.html.erb (23.9ms)
(0.5ms) SELECT COUNT(*) FROM `questions`
Rendered questions/index.html.erb within layouts/application (48.8ms)
Question Load (1.6ms) SELECT `questions`.* FROM `questions`
(0.4ms) SELECT COUNT(*) FROM `questions` WHERE `questions`.`user_id` = 585460615
Rendered /Users/gregorygrillone/.rvm/gems/ruby-1.9.3-p194/bundler/gems/gauges-58ad28a906b2/app/views/gauges/_gauge.html.erb (0.1ms)
CACHE (0.0ms) SELECT `questions`.* FROM `questions`
CACHE (0.0ms) SELECT COUNT(*) FROM `questions` WHERE `questions`.`user_id` = 585460615
Completed 200 OK in 72ms (Views: 62.2ms | ActiveRecord: 4.2ms)
Started GET "/assets/application.css" for 127.0.0.1 at 2012-06-12 09:21:25 -0400
Served asset /application.css - 304 Not Modified (0ms)
[2012-06-12 09:21:25] WARN Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true
Started GET "/assets/application.js" for 127.0.0.1 at 2012-06-12 09:21:25 -0400
Served asset /application.js - 304 Not Modified (0ms)
[2012-06-12 09:21:25] WARN Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true
Question controller
class QuestionsController < ApplicationController
respond_to :html, :json
def index
#question = current_user.questions.new
#questions = current_user.questions.all
end
def create
#question = current_user.questions.new(params[:question])
if !params[:update_button]
if #question.valid?
if params[:next_button] || !#question.save
#questions = current_user.questions.all
render 'index'
elsif !params[:next_button] && params[:submit_button] && #question.save
flash[:success] = "Your question and answer have been saved."
respond_with #question, :location => questions_path
end
else
#questions = current_user.questions.all
render 'index'
end
else
#questions = current_user.questions.all
render 'index'
end
end
def next
#question = current_user.unanswered.first
#answer = Answer.new(:question => #question, :user => current_user)
respond_to do |format|
format.js
end
end
end
I think your view code is not creating the form structure properly.
You should be able to confirm your controller code is correct, by writing a functional/controller test that posts the params in the format you expect. (much better/faster feedback than trying to post half your app up to StackOverflow!)
You will be able to create a decent test in less than an hour, and that knowledge will make everything else you do that much better and faster. Trust me, it will be worth the effort of learning how to properly test your code.
Once you prove that you have the controller working properly, you'll most likely find that your view code is not creating the form inputs of the nested answer properly. You can see from the log file that it didn't even attempt to post them.
You might want to separate the create and update actions, the code that checks for different buttons is a bit confusing and makes it hard to understand the 'contract' between the page and the controller. (eg. if next but not update but submit...) That's not causing your problem now, but just something you might want to clean up before it bites you later.

Resources