Related
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.
I just noticed in my database that inside the Receipts table it is not showing the proper recipient_id for the receiver_id column. I am working between the Questions, Conversations and Receipts table with the Mailboxer gem.
For example here's the issue:
A new entry in the Questions table appears with the
id 552, sender_id 1, and recipient_id 2.
2 new entrys is then made in the Receipts table that's associated with the Questions entry just created (creates 2 entrys by default, one for recipient and other for sender). The details for the
first entry is id 547 and receiver_id 552.
The second entry id is 548 and receiver_id 1.
As you can see for the first entry the receiver_id is being copied from the Questions table id. It should be transferring the Questions table recipient_id instead.
I have no idea at how to fix this.
Questions controller:
def create
#question = Question.new(params[:question])
if #question.save
#message = current_user.send_message(#question, #question.question, "You have a question from #{#question.sender_id}")
redirect_to :back, notice: 'Your question was saved successfully. Thanks!'
else
render :new, alert: 'Sorry. There was a problem saving your question.'
end
end
end
Question model:
acts_as_messageable
attr_accessible :answer, :question, :sender_id, :recipient_id, :conversation_id
belongs_to :user
belongs_to :sender,:class_name => 'User',:foreign_key => 'sender_id'
belongs_to :recipient,:class_name => 'User',:foreign_key => 'recipient_id'
belongs_to :message
belongs_to :conversation
show.html.slim:
center
.message_div
= form_for Question.new, class: 'question_form form-horizontal', role: 'form' do |f|
.form-group
= f.text_field :question, {:placeholder => 'Please add your question...',class:'form-control'}
= f.hidden_field :sender_id, :value => current_user.id
= f.hidden_field :recipient_id, :value => #user.id
= f.submit 'Ask Question', class: 'btn btn-primary'
schema.rb:
create_table "receipts", force: true do |t|
t.integer "receiver_id"
t.string "receiver_type"
t.integer "notification_id", null: false
t.boolean "is_read", default: false
t.boolean "trashed", default: false
t.boolean "deleted", default: false
t.string "mailbox_type", limit: 25
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "receipts", ["notification_id"], name: "index_receipts_on_notification_id", using: :btree
add_foreign_key "receipts", "notifications", name: "receipts_on_notification_id"
Log:
Started POST "/questions" for 127.0.0.1 at 2014-08-08 17:00:12 -0400
Processing by QuestionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"nYOcY69oD6WcfNGEJcKGpyupA0CNbYnAJz0SQ+dKtEk=", "question"=>{"question"=>"Stack Overflow can you help me out?", "sender_id"=>"3", "recipient_id"=>"2"}, "commit"=>"Ask Question"}
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`auth_token` = 'HkI7Lm4fJYHHf5LHEcMtvA' LIMIT 1
(0.2ms) BEGIN
SQL (0.3ms) INSERT INTO `questions` (`created_at`, `question`, `recipient_id`, `sender_id`, `updated_at`) VALUES ('2014-08-08 21:00:12', 'Stack Overflow can you help me out?', 2, 3, '2014-08-08 21:00:12')
(0.4ms) COMMIT
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 3 ORDER BY `users`.`id` ASC LIMIT 1
(0.1ms) BEGIN
SQL (0.3ms) INSERT INTO `conversations` (`created_at`, `subject`, `updated_at`) VALUES ('2014-08-08 21:00:12', 'You have a question from 3', '2014-08-08 21:00:12')
SQL (0.3ms) INSERT INTO `notifications` (`attachment`, `body`, `conversation_id`, `created_at`, `sender_id`, `sender_type`, `subject`, `type`, `updated_at`) VALUES (NULL, 'Stack Overflow can you help me out?', 419, '2014-08-08 21:00:12', 3, 'User', 'You have a question from 3', 'Message', '2014-08-08 21:00:12')
SQL (0.3ms) INSERT INTO `receipts` (`created_at`, `mailbox_type`, `notification_id`, `receiver_id`, `receiver_type`, `updated_at`) VALUES ('2014-08-08 21:00:12', 'inbox', 459, 577, 'Question', '2014-08-08 21:00:12')
(0.4ms) COMMIT
(0.1ms) BEGIN
SQL (0.4ms) INSERT INTO `receipts` (`created_at`, `is_read`, `mailbox_type`, `notification_id`, `receiver_id`, `receiver_type`, `updated_at`) VALUES ('2014-08-08 21:00:12', 1, 'sentbox', 459, 3, 'User', '2014-08-08 21:00:12')
(0.4ms) COMMIT
(0.1ms) BEGIN
(0.1ms) COMMIT
Redirected to http://localhost:3000/users/user
Completed 302 Found in 562ms (ActiveRecord: 62.3ms)
When you call send_message in your controller, you pass #question as a recipient. It means that the first receipt is indeed built with the question_id as receiver_id. But from what I understand, you want the question's recipient to be sent the message. Simply replace your send_message call with:
current_user.send_message(#question.recipient, #question.question, "You have a question from #{#question.sender_id}")
What's happening behind the scene
The Receipt table from mailboxer can be considered as a proof that a notification has been sent. In your model, by adding
acts_as_messageable
You include the Messageable module described in the mailboxer documentation, including the following send_message method:
def send_message(recipients, msg_body, subject, sanitize_text=true, attachment=nil, message_timestamp = Time.now)
# message = ...
# message building stuff happening
message.deliver false, sanitize_text
end
When you call that method from your controller, it calls the deliver method on this newly created message. Here's the interesting bits of that method, extracted from the mailboxer documentation:
def deliver(reply = false, should_clean = true)
# ... some stuff
#Receiver receipts
temp_receipts = recipients.map { |r| build_receipt(r, 'inbox') }
#Sender receipt
sender_receipt = build_receipt(sender, 'sentbox', true)
temp_receipts << sender_receipt
if temp_receipts.all?(&:save!)
# some other stuff
end
sender_receipt
end
As you can see temp_receipts = recipients.map { |r| build_receipt(r, 'inbox') } builds a receipt for every recipient of the message.
The second interesting line sender_receipt = build_receipt(sender, 'sentbox', true) builds a receipt for the sender. Which in your case is the current_user.
Hope it helps.
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
I have a activerecord model with a validates :body, presence: true. When a form is filled and submited, I get {:body=>["can't be blank"]} even though the body is not actually blank. If I remove the validation from the model, and re-submit, the record is successfully created. Why is the validation saying the body field is blank when it is not blank.
The controller.
class SqlTemplatesController < ApplicationController
def create
#sql_template = SqlTemplate.new(sql_template_params)
respond_to do |format|
if #sql_template.save
format.html { redirect_to #sql_template, notice: 'Sql template was successfully created.' }
end
end
private
def sql_template_params
params.require(:sql_template).permit(:body, :path, :format, :locale, :handler, :partial)
end
end
The Model
class SqlTemplate < ActiveRecord::Base
validates :body, :path, presence: true
validates :format, inclusion: Mime::SET.symbols.map(&:to_s)
validates :locale, inclusion: I18n.available_locales.map(&:to_s)
validates :handler, inclusion: ActionView::Template::Handlers.extensions.map(&:to_s)
def to_liquid
SqlTemplateDrop.new(self)
end
def body=(text)
if self[:handler] == 'liquid'
#template = Liquid::Template.parse(text)
self[:body] = Marshal.dump(#template)
end
end
def render(options = {})
template.render options
end
private
def template
return #body unless #body.nil?
#body = Marshal.load(self[:body])
end
end
In the rails console If create a new record and set the body field to either a string body => "message body" or a liquid markup eg body => "{{body}}, it will raise the error {:body=>["can't be blank"]} but if remove the validation they work.
irb(main):016:0> a = SqlTemplate.create(:body => "{{body}", path => "mail/liquid_database_template", :format => "html", :locale => "en", :handler => "liquid", :partial => false)
(0.1ms) begin transaction
(0.1ms) rollback transaction
irb(main):016:0> a.errors.messages
=> {:body=>["can't be blank"]}
If I remove the validation and submit a form, it all works, as shown below:
Started POST "/sql_templates" for 127.0.0.1 at 2014-03-11 15:28:14 +0000
Processing by SqlTemplatesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"GVsRbsCKSlcEMiL1AzXE0tT8LBCNhhoK6wSGzvnB80A=", "sql_template"=>{"body"=>"{{body}}", "path"=>"customer_mail/liquid_database_template", "format"=>"html", "locale"=>"en", "handler"=>"liquid", "partial"=>"0"}, "commit"=>"Create Sql template"}
#<SqlTemplate id: nil, body: nil, path: "customer_mail/liquid_database_template", format: "html", locale: "en", handler: "liquid", partial: false, created_at: nil, updated_at: nil>
(0.1ms) begin transaction
SQL (9.4ms) INSERT INTO "sql_templates" ("created_at", "format", "handler", "locale", "path", "updated_at") VALUES (?, ?, ?, ?, ?, ?) [["created_at", "2014-03-11 15:28:14.869619"], ["format", "html"], ["handler", "liquid"], ["locale", "en"], ["path", "customer_mail/liquid_database_template"], ["updated_at", "2014-03-11 15:28:14.869619"]]
(621.4ms) commit transaction
Redirected to http://localhost:3000/sql_templates/7
Completed 302 Found in 662ms (ActiveRecord: 630.9ms)
Started GET "/sql_templates/7" for 127.0.0.1 at 2014-03-11 15:28:15 +0000
Processing by SqlTemplatesController#show as HTML
Parameters: {"id"=>"7"}
SqlTemplate Load (3.1ms) SELECT "sql_templates".* FROM "sql_templates" WHERE "sql_templates"."id" = ? LIMIT 1 [["id", 7]]
Rendered sql_templates/show.html.erb within layouts/application (11.4ms)
Completed 200 OK in 52ms (Views: 46.0ms | ActiveRecord: 3.1ms)
If I add the validation back and submit it fails as shown below:
Started POST "/sql_templates" for 127.0.0.1 at 2014-03-11 14:34:22 +0000
ActiveRecord::SchemaMigration Load (0.3ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by SqlTemplatesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"GVsRbsCKSlcEMiL1AzXE0tT8LBCNhhoK6wSGzvnB80A=", "sql_template"=> {"body"=>"{{body}}", "path"=>"customer_mail/liquid_database_template", "format"=>"html", "locale"=>"en", "handler"=>"liquid", "partial"=>"0"}, "commit"=>"Create Sql template"}
#<SqlTemplate id: nil, body: nil, path: "customer_mail/liquid_database_template", format: "html", locale: "en", handler: "liquid", partial: false, created_at: nil, updated_at: nil>
(0.2ms) begin transaction
(0.2ms) rollback transaction
Rendered sql_templates/_form.html.erb (32.6ms)
Rendered sql_templates/new.html.erb within layouts/application (57.1ms)
Completed 200 OK in 208ms (Views: 143.0ms | ActiveRecord: 1.4ms)
Your body setter is getting called before the setter for handler. So self[:handler] will be nil when it goes into the body= method
You can try one of these
i) Change the order of your hash
a = SqlTemplate.create(:handler => "liquid", :body => "{{body}", path => "mail/liquid_database_template", :format => "html", :locale => "en", :partial => false)
ii) Set the body later after setting the handler
a = SqlTemplate.new(:handler => "liquid", path => "mail/liquid_database_template", :format => "html", :locale => "en", :partial => false)
a.body = "{{body}}"
a.save
I resolved it myself. The problem was with the setter method for the body field. Normally rails automatically defines this but if it is being overridden to do some extra things which is the case here, I still needed to set self[:body] = text at the top before everything else. Note that text is the value passed in to the field through the form. Without that validates presence will fail.
def body=(text)
self[:body] = text
end
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. :-)