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)
Related
I have a model called Resource configured with:
class Resource < ActiveRecord::Base
has_many_attached :assets
end
I created an action in my resources_controller.rb as follows:
def delete_asset_attachment
#asset = ActiveStorage::Attachment.find_by(params[:id])
logger.debug "The value of #asset is #{#asset}"
#asset.purge
redirect_to #resource
end
I have a form that shows the resource and loops through the attached assets. Below is the snippet of code doing the loop through the assets:
<% #resource.assets.each do |asset| %>
<%= link_to 'Remove Attachment', delete_asset_attachment_resource_url(#resource, asset.id), method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
The /resources page properly shows the resource along with the attached assets. However, when I try to click the link to delete one of the assets, I receive an error: "undefined method `purge' for nil:NilClass". However, in console I see the attachment exists.
Here is the output from the server console:
Started DELETE "/resources/10/delete_asset_attachment.18" for ::1 at 2019-03-09 17:27:28 -0500
Processing by ResourcesController#delete_asset_attachment as
Parameters: {"authenticity_token"=>"EFZO5V9Bii3dId0I6hn5DajFR5WJYZBc8qPAAi5ppQOFW3cws5I4FjyVP9IlvA+2a2kKUJhobnqd8atG4L3k+g==", "id"=>"10"}
Resource Load (0.1ms) SELECT "resources".* FROM "resources" WHERE "resources"."id" = ? LIMIT ? [["id", 10], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
ActiveStorage::Attachment Load (0.1ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE (10) LIMIT ? [["LIMIT", 1]]
The value of #asset is #<ActiveStorage::Attachment:0x00007f8d7bd4df68>
ActiveStorage::Blob Load (0.2ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ? [["id", 27], ["LIMIT", 1]]
Completed 500 Internal Server Error in 5ms (ActiveRecord: 0.6ms)
NoMethodError - undefined method `purge' for nil:NilClass:
::1 - - [09/Mar/2019:17:27:28 EST] "POST /resources/10/delete_asset_attachment.18 HTTP/1.1" 500 76939
http://localhost:3000/resources/10/edit -> /resources/10/delete_asset_attachment.18
Started POST "/__better_errors/70da0e976a425fce/variables" for ::1 at 2019-03-09 17:27:28 -0500
ActiveStorage::Blob Load (0.2ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ? [["id", 27], ["LIMIT", 11]]
::1 - - [09/Mar/2019:17:27:28 EST] "POST /__better_errors/70da0e976a425fce/variables HTTP/1.1" 200 36499
http://localhost:3000/resources/10/delete_asset_attachment.18 -> /__better_errors/70da0e976a425fce/variables
I've searched for solutions everywhere. The couple that exist on stackoverflow didn't address my issue. There is an incredible lack of specific details and examples in the Rails guide or anywhere else on the web for specifically handling deleting attachments. Would appreciate any help.
UPDATE: Here are my routes.rb:
resources :resources do
get 'listing', :on => :collection
put :sort, on: :collection
member do
delete :delete_asset_attachment
end
end
UPDATE 2: rails routes output
resources GET /resources(.:format) resources#index
POST /resources(.:format) resources#create
new_resource GET /resources/new(.:format) resources#new
edit_resource GET /resources/:id/edit(.:format) resources#edit
resource GET /resources/:id(.:format) resources#show
PATCH /resources/:id(.:format) resources#update
PUT /resources/:id(.:format) resources#update
DELETE /resources/:id(.:format) resources#destroy
I've been able to make this work. Ahhhh. The rush, after hours of frustration.
Piecing things together after reading this article
Deleting ActiveStorage Attachments From the Controller, 3 Ways
I changed my controller code to be this:
def delete_asset_attachment
#resource.assets.find_by(params[:attachment_id]).purge
redirect_to #resource
end
and my form to be this:
<% #resource.assets.each do |asset| %>
<%= asset.filename %>
<%= link_to 'Remove Attachment', delete_asset_attachment_resource_url(#resource, asset.id), method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
I believe the issue was that the line in my old code:
#asset = ActiveStorage::Attachment.find_by(params[:id])
...was only passing the #resource id and the attachment was not being found. The key was changing this line:
#resource.assets.find_by(params[:attachment_id]).purge
...which more properly points to the correct resource and then the specific asset (attachment) to be purged.
Fix the syntax of find_by and use safe ampersand, instead
#asset = ActiveStorage::Attachment.find_by(params[:id])
#asset.purge
try:
#asset = ActiveStorage::Attachment.find_by(id: params[:id])
#asset&.purge
So I want to pass a data from a view that was rendered from an external api to a controller to be able to save the ID to a model/database table.
This is my view:
<h1>Heroes</h1>
<% #response.each do |hero| %>
<div class = "hero_wrap">
<div class = "img">
<img src="https://api.opendota.com<%= hero["img"]%>", id ="hero_img"/>
</div>
<div class = "fave_container">
<%= link_to(image_tag('fave.png', class: "fave_img"), new_hero_path) %>
<%= hero["id"]%>
</div>
<div class = "hero_name">
<%= hero["localized_name"] %>
</div>
</div>
<% end %>
My controller:
# heroes_controller
class HeroesController < ApplicationController
def index
#response = HTTParty.get("https://api.opendota.com/api/heroStats")
end
def create
#hero= Hero.new(params[:id])
#hero.save
redirect_to #hero
end
end
Routes:
Rails.application.routes.draw do
get '/signup', to: 'users#new'
get 'pages/home'
resources :pro_players
devise_for :users
resources :heroes
resources :users
root 'pages#home'
end
My hero model doesn't contain anything yet, I just want to pick the hero id and save it to my database.
this is my web app
server log upon clicking the star icon with link:
Started GET "/heros?custom_hero_id=1&method=post" for 127.0.0.1 at
2018-10-15 09:20:53 +0800
Processing by HerosController#index as HTML
Parameters: {"custom_hero_id"=>"1", "method"=>"post"}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳/home/don/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-
5.2.1/lib/active_record/log_subscriber.rb:98
Rendering heros/index.html.erb within layouts/application
Rendered heros/index.html.erb within layouts/application (42.2ms)
Rendered layouts/_header.html.erb (1.1ms)
Rendered layouts/_footer.html.erb (0.4ms)
Completed 200 OK in 770ms (Views: 83.3ms | ActiveRecord: 0.7ms)
Updated log:
Started POST "/heros?custom_hero_id=21" for 127.0.0.1 at 2018-10-15 10:13:17 +0800
Processing by HerosController#create as HTML
Parameters: {"authenticity_token"=>"PHDEXdDmPhPX+VdloU2y6yONY5HN5wI2lIfOaSbSKj9+RvTO5Ua3QPuTcreLZtGNDFPaSOXhDVyve6J69+1CQQ==", "custom_hero_id"=>"21"}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳/home/don/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.1/lib/active_record/log_subscriber.rb:98
(0.2ms) BEGIN
↳ app/controllers/heros_controller.rb:9
Hero Create (0.4ms) INSERT INTO "heros" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", "2018-10-15 02:13:17.032248"], ["updated_at", "2018-10-15 02:13:17.032248"]]
↳ app/controllers/heros_controller.rb:9
(42.3ms) COMMIT
↳ app/controllers/heros_controller.rb:9
Redirected to http://localhost:3000/heros/10
Completed 302 Found in 59ms (ActiveRecord: 46.9ms)
Started GET "/heros/10" for 127.0.0.1 at 2018-10-15 10:13:17 +0800
AbstractController::ActionNotFound (The action 'show' could not be found for HerosController):
The :id attribute is by default a auto-increment attribute of datatype integer which sets its value under the hood whenever a new instance of the model is created. There are several ways to override the its behavior or setting its value explicitly, but considering that you are fairly new to the technology I don't recommend those for you. Instead I recommend a simpler solution of using another attribute to store hero["id"]
Steps:
1) Generate a migration to create a new column(say custom_hero_id) in the heroes table
rails g migration add_custom_hero_id_to_heroes custom_hero_id:integer
and do rake db:migrate
2) Change
<%= link_to(image_tag('fave.png', class: "fave_img"), new_hero_path) %>
to
<%= link_to(image_tag('fave.png', class: "fave_img"), heroes_path(custom_hero_id: hero["id"]), method: :post) %>
3) Finally change your create action to below
def create
#hero= Hero.create(custom_hero_id: params[:custom_hero_id])
redirect_to #hero
end
Update:
Due to unknown reason, custom_hero_id isn't saving to the DB. Probably due to forbidden attributes error. Try changing it to
def create
#hero = Hero.new(hero_params)
if #hero.save
redirect_to #hero
end
end
private
def hero_params
params.permit(:custom_hero_id)
end
Do you want to store the hero id or it's name ?
I would advise to create a method in your heroe model. Something like
// heroe.rb
def save_api_heroes_to_db(response)
response.each do |hero|
unless hero[:localized_name].exists?
hero.create(id: hero[:id], name: hero[:localized_name], image: hero[:img])
end
end
end
This method will save the heroe id, name and image in your database (unless it already exists).
You just have to call it in your index method, in your heroe controller.
Hope that helps.
I'm working on using form_with in Rails 5.1.3 & Ruby 2.3.0
The best documentation I've found is here
Rails Github Repo on line 533, but it's still unclear to me.
# The parameters in the forms are accessible in controllers according to
# their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
# accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
# respectively.
Code:
# friendships_controller.rb
...
private
def friendship_params
params.require(:friendship).permit(:user_id, :id)
end
# Works
def destroy
Friendship.remove_friend(current_user.id, params[:id].to_i)
end
# Doesn't work
def destroy
Friendship.remove_friend(friendship_params[:user_id].to_i, friendship_params[:id].to_i)
end
# form_with
<%= form_with model: Friendship,
url: user_friendship_path(
user_id: current_user.id
id: other_user.id,
), method: :delete do |f| %>
<button class='button'></button>
<% end %>
I know the naming is a little confusing, I still haven't figured out how to get form_with to map correctly to the route I need.
friendships#destroy located at path
/users/:user_id/friendships/:id(.:format)
resources :users do
resources :friendships
end
Error Message
Started DELETE "/users/1/friendships/8" for 127.0.0.1 at 2018-01-02 03:21:40 +0700
Processing by FriendshipsController#destroy as JS
Parameters: {"utf8"=>"✓", "authenticity_token"=>"fWnYwpNDItctgg3q/TYIXy5VRO55nwQRINCCykMsDPAFBfwJKjMZ1dneNbg 5yFNHaQP+lXR4ViTje6mK+dCmVg==", "user_id"=>"1", "id"=>"8"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.2ms) BEGIN
(0.1ms) COMMIT
Friendship Load (1.0ms) SELECT "friendships".* FROM "friendships" WHERE "friendships"."friend_id" = $1 AND "friendships"."user_id" = $2 ORDER BY "friendships"."id" ASC LIMIT $3 [["friend_id", 1], ["user_id", 8], ["LIMIT", 1]]
SQL (1.2ms) DELETE FROM "friendships" WHERE "friendships"."id" = $1 [["id", 499]]
Friendship Load (0.5ms) SELECT "friendships".* FROM "friendships" WHERE "friendships"."friend_id" = $1 AND "friendships"."user_id" = $2 ORDER BY "friendships"."id" ASC LIMIT $3 [["friend_id", 8], ["user_id", 1], ["LIMIT", 1]]
SQL (0.9ms) DELETE FROM "friendships" WHERE "friendships"."id" = $1 [["id", 498]]
Completed 400 Bad Request in 13ms (ActiveRecord: 4.2ms)
ActionController::ParameterMissing (param is missing or the value is empty: friendship):
app/controllers/friendships_controller.rb:40:in `friendship_params'
app/controllers/friendships_controller.rb:22:in `destroy'
Maybe I shouldn't say it doesn't work, the behavior is how I expect with or without the friendship_params. However, the server logs returning a 400 Bad Request is a red flag to me.
Thanks for any suggestions in advance~!
The route is a nested resource, so you should provide two arguments to the path. you also need to provide the scope to give it the "friendship" key in the params.
<%= form_with scope: :friendship, url: user_friendship_path(current_user, other_user), method: :delete do |f| %>
<button class='button'></button>
<% end %>
This code is little bit confusing, I suggesting the way for simple like change your route like below
resources :users do
resources :friendships
end
Change to
resources :users
resources :friendships
and on the view
<%= link_to "Remove Friend", friendship_path(friend_id), method: :delete, data: {confirm: "Are you sure"}, class: "btn btn-xs btn-danger" %>
and finally the controller
def destroy
#friendship = current_user.friendships.where(friend_id: params[:id]).first
#friendship.destroy
flash[:notice] = "Friend was removed"
redirect_to(my_friends_path)
end
Hope to help
The answer was that the arguments to the form_with don't pass the actually parameters to the key Friendship, we have to pass them manually within the body of the form.
<%= form_with model: Friendship, url:
user_friendship_path(
current_user, other_user
), method: :delete do |f| %>
<%= f.hidden_field :id, value: other_user.id %>
<button class='button'></button>
<% end %>
First post, so i'm a newbie in StackOverflow. I'm trying for several days to make appear a Return button on a page form but only on a specific one.
So, I was advised to use backlink to make it appears.
Here's my code from the form where I want the return button
<% if #backlink.present? %>
<div class="spacer30"></div>
<% if #backlink == 'infos' %>
path = membre_path(menu: 'infos')
<% end %>
<% end %>
<%= link_to "Retour", path, class: "btn-rounded btn-turquoise btn-small" %>
Here's my code controller
def edit
super do |user|
puts "TEST PARAMS BACKLINK #{params[:backlink]}"
#backlink = params[:backlink]
end
end
and my route's :
get 'change_password', to: 'users/registrations#edit'
put 'update' => 'users/registrations#update', :as => 'user_registration'
get 'edit_password', to: 'users/registrations#edit', :as => 'user_edit'
So i should have in my log my PUTS 'TEST PARAMS BACKLINK' but nothing appear, only :
Started GET "/change_password.1?backlink=infos" for ::1 at 2017-10-04 10:07:41 +0200
Processing by Users::RegistrationsController#edit as
Parameters: {"backlink"=>"infos"}
User Load (9.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Rendering users/registrations/edit.html.erb within layouts/application
Rendered users/registrations/edit.html.erb within layouts/application (14.4ms)
Rendered shared/_navbar.html.erb (4.0ms)
Rendered shared/_flashes.html.erb (1.1ms)
Completed 200 OK in 231ms (Views: 217.0ms | ActiveRecord: 9.1ms)
Any ideas why it doesn't work?
Many thanks.
I just had to delete some lines, here's what i changed from my registration controller :
def edit
#backlink = params[:backlink]
super
end
This way, it appears exactly the way I wanted to.
Many thanks :)
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