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.
Related
I have an app that helps a school to track students attendance to sports practices and games, I have all my index actions to render html, csv and xls formats and EVERYTHING GOES WELL. I have a special report that uses several model relations to complete, I am not required to render csv (I think this will be too complex to implement the to_csv method, I don't even have a clue where to put it, but this is not my problem), Now I created the equipos_controller#forma_rep method and a related view with a form to get the report's parameters, as you can see in the routes and controller code, it works fine when the report action renders the default HTML as you can see in the following log, parameters from the 'forma_rep.html.erb' form are in the params array..
Started POST "/equipos/reporte_asist" for 127.0.0.1 at 2017-01-05 18:37:51 -0600
Processing by EquiposController#reporte_asist as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"IP1O2bSgkGcSaUn5Sf9Tnp30yzxfP10+cA0h/1+XudoR7W8SoP6xveP3fwJpLFTvyRaBFdtqsqz5pCfYID5b5Q==", "entrenador"=>"1", "inicio"=>"2016-12-12", "final"=>"2016-12-20", "commit"=>"Crear Reporte"}
... most SQL ommited
Rendering equipos/reporte_asist.html.erb within layouts/application
Rendered equipos/reporte_asist.html.erb within layouts/application (69.4ms)
Rendered layouts/_shim.html.erb (0.5ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Rendered layouts/_header.html.erb (5.9ms)
Rendered layouts/_footer.html.erb (1.1ms)
Completed 200 OK in 210ms (Views: 165.2ms | ActiveRecord: 5.6ms)
But when I click the "Excel" link :
Started POST "/equipos/reporte_asist.xls" for 127.0.0.1 at 2017-01-05 18:37:56 -0600
Processing by EquiposController#reporte_asist as XLS
Parameters: {"authenticity_token"=>"oYVjNfxN5Qxt9FHC6PpeU0wQenD3p+otaxcGts1kZRuQlUL+6 BPE1pxqZznIKVkiGPIwWXPyBb/ivgCRss2HJA=="}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Equipo Load (0.2ms) SELECT "equipos".* FROM "equipos" WHERE "equipos"."user_id" = ? [["user_id", 1]]
Completed 500 Internal Server Error in 11ms (ActiveRecord: 0.4ms)
NoMethodError (undefined method `<<' for nil:NilClass):
app/controllers/equipos_controller.rb:96:in `block in reporte_asist'
app/controllers/equipos_controller.rb:95:in `reporte_asist'
I can see that the parameters are not complete when I click the Excel link in the html file, how can I send them again? my routes work fine with the html version and the #index action renders fine in all formats, please help me.
Here is all code involved.
Routes:
resources :categorias
get '/equipos/forma_rep'
post '/equipos/reporte_asist', to: 'equipos#reporte_asist', as: 'reporte_asist'
resources :equipos
resources :players
app/controllers/equipos_controller.rb
def index
#equipos = Equipo.paginate(page: params[:page])
respond_to do |format|
format.html
format.csv { send_data #equipos.to_csv }
format.xls
end
end
# GET /equipos/forma_rep
def forma_rep
#equipo = Equipo.new
#entrenadores = User.all
end
# PUT /equipos/reporte_asist
def reporte_asist
if params[:entrenador]
#entrenador = User.find(params[:entrenador].to_i)
inicio = Time.parse(params[:inicio])
final = Time.parse(params[:final])
#equipos = #entrenador.equipos
#eventos = reporte(#entrenador.id, inicio, final)
else
#entrenador = current_user
#equipos = #entrenador.equipos
#equipos.each do |equi|
#eventos << equi.eventos
end
end
respond_to do |format|
format.html
format.xls
end
end
I have created the app/views/equipos/reporte_asist.xls.erb file with the XML directives as in..
<?xml version="1.0"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40">
<Worksheet ss:Name="Asistencias">
<Table>
<Row>
<Cell><Data ss:Type="String">Entrenador:</Data></Cell>
<Cell><Data ss:Type="String"><%= #entrenador.name %></Data> </Cell>
</Row>
....etc.
And this is the link I have in the app/views/equipos/reporte_asist.html.erb
<p>
Descargar:
<%= link_to "Excel", reporte_asist_path(format: "xls"), method: :post %>
</p>
Of course I have defined the Mime:Type.register in the config/initializers/mime_types.rb and requested the 'csv' library in my config/application.rb . I am using Rails 5.0.0.1 and Ruby 2.3.1 ...
This is the code that collects report parameter from user, it is inside the app/views/equipos/forma_rep.html.erb that posts to the reporte_asist.html.erb:
<h1>Reporte de Asistencias</h1>
<%= form_tag(reporte_asist_path) do %>
<%= label_tag(:entrenador, "Entrenador:") %>
<%= select_tag :entrenador, options_from_collection_for_select(#entrenadores, "id", "name"), prompt: "Seleccione el entrenador", class: 'form-control' %>
<%= label_tag(:inicio, "Fecha inicial de reporte:") %>
<%= date_field_tag :inicio, class: 'form-control' %>
<%= label_tag(:final, "Fecha final de reporte:") %>
<%= date_field_tag :final, class: 'form-control' %>
<%= submit_tag "Crear Reporte", class: "btn btn-default" %>
<% end %>
</div>
Initially this is the code that sends the report parameters, but once in the report view I cannot (and don't want to) re-render the form, that's why I put the link_to to the same controller but trying to render the excel
Add the parameters you want to POST to your reporte_asist_path helper, like this:
reporte_asist_path(format: 'xls', entrenador_id: #entrenador.id)
More information can be found here:
http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for
Also note that while using POST method for links is supported by Rails, it relies on JavaScript. If the user has JavaScript disabled, the request will fall back to GET method. So it's safer to use forms.
I'm following Michael Hartl tutorial about Rails 5 ActionCable, to build a simple chat, and a problem is occurring inside my create action in MessageController:
def create
message = current_user.messages.build(message_params)
if message.save
ActionCable.server.broadcast 'room_channel',
content: message.content,
username: message.user.username
head :ok
end
end
It goes until head :ok, and after that, display an empty HTML page. The log is:
Processing by MessagesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"h9UsB78aX8mO8Nyn8eZEUuDzAOxL4V7UCMwNuTfwALliMv7OCkcUIeR4/xXIcvPjwq9H1bphjn6G96G+0VYisw==", "message"=>{"content"=>"oi"}, "commit"=>"Send"}
User Load (1.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Message Load (2.1ms) SELECT "messages".* FROM "messages" ORDER BY "messages"."created_at" DESC LIMIT ? [["LIMIT", 50]]
(0.3ms) begin transaction
SQL (9.3ms) INSERT INTO "messages" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["content", "oi"], ["user_id", 1], ["created_at", 2016-09-19 02:12:58 UTC], ["updated_at", 2016-09-19 02:12:58 UTC]]
(9.1ms) commit transaction
[ActionCable] Broadcasting to room_channel: {:content=>"oi", :username=>"alice"}
Completed 200 OK in 69ms (ActiveRecord: 22.0ms)
RoomChannel transmitting {"content"=>"oi", "username"=>"alice"} (via streamed from room_channel)
Finished "/cable/" [WebSocket] for 10.0.2.2 at 2016-09-19 02:12:58 +0000
RoomChannel stopped streaming from room_channel
(After that, I can refresh the page and the message is displayed properly)
Why that is happening?
My RoomChanel class:
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
My room.coffee
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
alert data.content
I know the answer is accepted already, but this solution worked for me without having to remove head :ok, maybe it helps somebody out.
Normally in the development environment people are running localhost:3000 but i was running on a different port, localhost:3030.
And this case i had to add an allowed request origin for that port, like so:
config.action_cable.allowed_request_origins = [
'http://localhost:3030'
]
to my /config/environments/development.rb
Following a sugestion, I'm adding as answer what I did to get around this problem. It doesn't answer why that happens, but solve the problem.
The solution is: remove head :ok.
Aparently it's unecessary, because if I add a test whit assert_response :success to the action, it'll pass.
I'm following Michael's tutorial for ActionCable and had the same problem.
I think this was due to the format of the form partial _anything.html.erb.
app/views/messages/_message_form.html
As you go through the tutorial, you're upgrading to Action Cable features. But in the _message_form partial, it is still with the usual HTTP request. So after you broadcast a message using the messages_controller.rb, the browser render an empty page because the form was already rendered.
app/views/messages/_message_form.html, Originally:
<div class="message-input">
<%= form_for(#message) do |f| %>
<%= f.text_area :content %>
<%= f.submit "Send" %>
<% end %>
</div>
So, you have to add the remote: true to the form, like this:
<div class="message-input">
<%= form_for(#message, remote: true) do |f| %>
<%= f.text_area :content %>
<%= f.submit "Send" %>
<% end %>
</div>
Adding the remote: true makes an JS request (Ajax, in this case)
Quoting Michael:
You can verify by submitting a message that it appears as desired, but in
fact this is a cheat because the form from Listing 6 is currently submitting
an ordinary HTTP POST request, which causes the page to refresh. In order
to use Action Cable, we need to change the form to submit a remote request
that submits an Ajax request without refreshing the page. We can do this by
including the remote: true option in the call to form_for.
Note that I'm doing everything in localhost:3000
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)
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
This is my first app in Rails 4, but I'm not sure whether Rails 4 is the problem.
I have nested resources as follows:
resources :made_games do
resources :made_game_instances
end
When I try to save a new made_game_instance this is what's happening in the log:
Started POST "/made_games/11/made_game_instances" for 127.0.0.1 at 2013-09-10 12:03:55 -0700
Processing by MadeGameInstancesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jEN2syjftjRtf3DBnijtp7gNVUEFrI+HYTUs+HFgo5M=", "made_game_instance"=>{"new_word1"=>"bluesky"}, "commit"=>"Create Made game instance", "made_game_id"=>"11"}
MadeGame Load (122.7ms) SELECT "made_games".* FROM "made_games" WHERE "made_games"."id" = $1 LIMIT 1 [["id", "11"]]
(14.0ms) BEGIN
SQL (215.9ms) INSERT INTO "made_game_instances" ("created_at", "made_game_id", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["created_at", Tue, 10 Sep 2013 19:03:55 UTC +00:00], ["made_game_id", 11], ["updated_at", Tue, 10 Sep 2013 19:03:55 UTC +00:00]]
(5.7ms) COMMIT
Redirected to http://localhost:3000/made_games/11/made_game_instances/5
Completed 302 Found in 458ms (ActiveRecord: 358.3ms)
You can see that the params hash contains the hash where the new_game_instance attribute :new_word1 is assigned the value "bluesky." What I cannot figure out is why this assignment does not appear in the SQL that is subsequently generated when the new 'made_game_instances' object is created.
Additional information
Since this is Rails 4, in order to whitelist all the parameters (at least at this stage in development), I have used permit! in the params private method at the bottom of the controllers for both made_games and made_game_instances.
The made_games controller:
class MadeGamesController < ApplicationController
def new
#made_game = MadeGame.new
end
def create
#made_game = MadeGame.new(made_game_params)
if #made_game.save
flash[:notice] = "Here you go!"
redirect_to #made_game
else
flash[:notice] = "Something about that didn't work, unfortunately."
render :action => new
end
end
def show
#made_game = MadeGame.find(params[:id])
end
private
def made_game_params
params.require(:made_game).permit!
end
end
Here is a link to the github repo: https://github.com/keb97/madlibs/tree/users_making
The form used to create a new made_game_instance is:
<%= simple_form_for [#made_game, #made_game_instance] do |f| %>
<p>
<%= f.input :new_word1, label: #made_game.word1.to_s %>
</p>
<%= f.button :submit %>
<% end %>
I should also note that there is one form for made_game, and a separate form for made_game_instance, rather than a nested form, so I do not believe this is an issue of accepts_nested_attributes_for or fields_for.
In your made_games_instance_controller.rb
this line...
#made_game_instance = #made_game.made_game_instances.build(params[:made_game_instance_params])
should actually be...
#made_game_instance = #made_game.made_game_instances.build(made_game_instance_params)
There is no params hash entry with a symbol key :made_game_instance_params