I have this newbie error when i want to upvote a "hack" :
ActionController::ParameterMissing at /hacks/6/upvote
param is missing or the value is empty: vote
With Request parameters exemple :
{"_method"=>"post", "authenticity_token"=>"r+fYieTQDsD6fuonr3oe0YEzkzBXH1S8k6bDENS0wCVr3LEpxGA4mps5saM4RQLvBNDVzsm2zXpGm9TKe3ZIYA==",
"controller"=>"hacks", "action"=>"upvote", "id"=>"6"}
I don't understand why my #vote do not appear in parameters...
Controller hacks_controller.rb
class HacksController < ApplicationController
skip_before_action :authenticate_user!, only: [:upvote]
def upvote
#vote = Vote.new(vote_params)
#hack = Hack.find(params[:id])
# raise
#vote.hack = #hack
if #vote.save
redirect_to root_path
else
p 'Problème de #vote.save !'
end
end
private
def vote_params
params.require(:vote).permit(:hack_id, :user_id)
end
end
Model Vote.rb
class Vote < ApplicationRecord
belongs_to :user
belongs_to :hack
validates :hack, presence: true
end
Thanks !
The Rails strong parameters are meant as mass assignment protection and are not suited to this case.
To create an additional CRUD method properly you can just add the additional route to resources:
resources :hacks do
post :upvote
delete :downvote
end
Note that we are using POST not GET as this is a non-idempotent operation.
You also don't need to pass any parameters. :hacks_id will be present in the path and you should fetch the current user id from the session and not the request parameters.
Passing a user id via the parameters is a really bad practice as its very trivial to spoof by using just the web inspector.
class HacksController < ApplicationController
before_action :set_hack!, except: [:new, :index, :create]
# POST /hacks/:hack_id/upvote
def upvote
#vote = #hack.votes.new(user: current_user)
if #vote.save
redirect_to #hack, success: 'Vote created'
else
redirect_to #hack, error: 'Vote could not be created'
end
end
# DELETE /hacks/:hack_id/downvote
def downvote
#vote = #hack.votes.where(user: current_user).first!
#vote.destroy
redirect_to #vote, success: 'Vote deleted'
end
private
# this will raise ActiveRecord::RecordNotFound if
# the id or hack_id param is not valid. This triggers a 404 response
def set_hack!
if params[:id].present?
Hack.find(params[:id])
else
Hack.find(params[:hack_id])
end
end
end
Then in your view you can create the links / buttons like so:
<% if current_user && #hack.votes.where(user: current_user) %>
<%= button_to 'Downvote', hack_downvote_path(#hack), method: :delete %>
<% else %>
<%= button_to 'Upvote', hack_upvote_path(#hack), method: :post %>
<% end %>
Related
I want to pass params from event/id(show page) to my order_controller.
I use simple_form_for to pass event.id and promocode that input by user
#event.show.html.haml
= simple_form_for order_url, url: orders_path(#event, :promocode), method: :post do |f|
= f.hidden_field :event_id, params: {id: #event.id}
= f.input :promocode, value: :promocode, class: 'form-control', placeholder: "Enter your PromoCode"
= f.submit 'APPLY PromoCode'
IDK if a need hidden_field to pass event_id
#order_controller
class OrdersController < ApplicationController
before_action :order, only: %i[show]
def index
#orders = Order.all.order(created_at: :desc).page(params[:page]).per(5)
end
def show; end
def create
#order = Order.create(title: event.title, user_id: current_user.id, event_id: event.id, order_amount: event.price, order_currency: event.currency)
if !promo.nil?
redirect_to_order
elsif #order.save
redirect_to checkout_create_path(id: #order.id)
else
redirect_to event, alert: 'Something went wrong, try again later'
end
end
def redirect_to_order
promo_validate
order_amount_promo_code = #order.order_amount - promo.promo_code_amount
order.update(order_amount: order_amount_promo_code)
redirect_to #order
end
def promo_validate
if promo.present? && promo.promo_code_amount.positive? && promo.promo_code_currency == event.currency
promo.update(order_id: #order.id)
else
redirect_to event, alert: "This PromoCode is invalid or Your PromoCode Currency doesn't match with Event"
end
end
private
def promo
#promo ||= PromoCode.find_by(uuid: params[:promocode])
end
def event
#event ||= Event.find(params[:id])
end
def order
#order ||= Order.find(params[:id])
end
def order_params
params.require(:order).permit(:title, :event_id, :promocode, :event)
end
end
I'm using methods def event and def promo to take this params from view.
Also my routes look like this.
resources :events
resources :orders
I would nest the route:
resources :events do
resources :orders, shallow: true
end
This creates an explicit relationship between the two resources that can be seen by just looking at the URL. To create a order tied to an even you send a POST request to /events/:event_id/orders.
class EventsController
def show
# ..
#order = #event.orders.new
end
end
= simple_form_for [#event, #order] do |f|
= f.input :promocode, value: :promocode, class: 'form-control', placeholder: "Enter your PromoCode"
= f.submit 'APPLY PromoCode'
class OrdersController < ApplicationController
# POST /events/:id/orders
def create
#event = Event.find(params[:event_id])
#order = #event.orders.new(title: #event.title, user: current_user order_amount: #event.price, order_currency: #event.currency)
begin
#promo = PromoCode.find_by!(uuid: params[:order][:promocode])
rescue ActiveRecord::RecordNotFound
#order.errors.add(:promocode, 'is invalid')
end
if #order.save
redirect_to checkout_create_path(id: #order.id)
else
redirect_to #event, alert: 'Something went wrong, try again later'
end
end
# ...
end
Other then that your handling of promo codes is very iffy. Instead of monkying around and deducting the rebate from the "amount" by updating the record you should store both the original sales price and the rebate and then calculate the total at checkout - which should also be stored separately. Not doing so amounts to pretty dismal record keeping and might get you in trouble - when it comes to money always play it safe.
hoping someone can help. I've been searching through other "param is missing" questions, but can't seem to figure out what's wrong.
In my routes file I have this nested resource "actions"
resources :jobs do
resources :actions
end
The associated models. Ignore "action_reference". That's something else.
class Job < ActiveRecord::Base
has_many :actions
end
class Action < ActiveRecord::Base
belongs_to :job
belongs_to :action_reference
end
And I'm trying to create a new action by making a POST request using button_to
Here's the ActionsController
class ActionsController < ApplicationController
before_action :set_job
before_action :set_action, only: [:show, :edit, :update]
# GET /jobs/:job_id/actions/:id
def show
end
# GET /jobs/:job_id/actions/new
def new
#action = Action.new
end
# GET /jobs/:job_id/actions/:id/edit
def edit
end
# POST /jobs/:job_id/actions/
def create
#action = #job.actions.create(action_params)
if #action.save
flash[:success] = "Next step successfully added."
redirect_to jobs_path
else
flash[:danger] = #action.errors.full_messages.join(", ")
redirect_to new_job_action_path
end
end
# PATCH to /jobs/:job_id/actions/:id
def update
if #action.update(action_params)
flash[:success] = "Next step successfully updated."
redirect_to jobs_path
else
flash[:danger] = #action.errors.full_messages.join(", ")
redirect_to edit_job_action_path
end
end
private
def set_job
#job = Job.find(params[:job_id])
end
def set_action
#action = Action.find(params[:id])
end
def action_params
params.require(:action).permit(:action_reference_id, :job_id, :completed_at, :next_action_date)
end
end
And here's my button_to
<%= button_to answer[:text], post_action_jobs_path(#job), method: "post", params: { action: { action_reference_id: answer[:action_reference_id], job_id: #job_id , completed_at: answer[:action_completed_at], next_action_date: answer[:next_action_date] } }, type: "button", class: "btn btn btn-info btn-block" %>
I know the problem has something to do with the arguments I'm passing to the post_action_jobs_path in the view or the ones I'm passing to action_params in the controller, but I can't figure it out.
When I run this I get the error:
undefined method `permit' for "create":String Did you mean? print
I saw some thread a little while ago saying something about "action" being a reserved word in Rails, so you have to use something else, but if that's true I'm not sure how to go about that.
Any help would be greatly appreciated :)
Yes unfortunately this is due to "action" being an existing method inside rails controllers. It is used to get the name of the action that has been called. In this case "action" will equal the string "create".
One thing you could do would be to rename you Action model to JobAction and use params.require(:job_action)
Sadly I couldn't seem to find away around this, so I renamed my "actions" table and replaced every reference to "action" with a different word "step". Now it works!
I have a very straight-forward task to fulfil --- just to be able to write comments under posts and if the comments fail validation display error messages on the page.
My comment model uses a gem called Acts_as_commentable_with_threading, which creates a comment model after I installed.
On my post page, the logic goes like this:
Posts#show => display post and a form to enter comments => after the comment is entered, redisplay the Post#show page which has the new comment if it passes validation, otherwise display the error messages above the form.
However with my current code I can't display error messages if the comment validation fails. I think it is because when I redisplay the page it builds a new comment so the old one was erased. But I don't know how to make it work.
My codes are like this:
Comment.rb:
class Comment < ActiveRecord::Base
include Humanizer
require_human_on :create
acts_as_nested_set :scope => [:commentable_id, :commentable_type]
validates :body, :presence => true
validates :first_name, :presence => true
validates :last_name, :presence => true
# NOTE: install the acts_as_votable plugin if you
# want user to vote on the quality of comments.
#acts_as_votable
belongs_to :commentable, :polymorphic => true
# NOTE: Comments belong to a user
belongs_to :user
# Helper class method that allows you to build a comment
# by passing a commentable object, a user (could be nil), and comment text
# example in readme
def self.build_from(obj, user_id, comment, first_name, last_name)
new \
:commentable => obj,
:body => comment,
:user_id => user_id,
:first_name => first_name,
:last_name => last_name
end
end
PostController.rb:
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def show
#post = Post.friendly.find(params[:id])
#new_comment = Comment.build_from(#post, nil, "", "", "")
end
end
CommentsController:
class CommentsController < ApplicationController
def create
#comment = build_comment(comment_params)
respond_to do |format|
if #comment.save
make_child_comment
format.html
format.json { redirect_to(:back, :notice => 'Comment was successfully added.')}
else
format.html
format.json { redirect_to(:back, :flash => {:error => #comment.errors}) }
end
end
end
private
def comment_params
params.require(:comment).permit(:user, :first_name, :last_name, :body, :commentable_id, :commentable_type, :comment_id,
:humanizer_answer, :humanizer_question_id)
end
def commentable_type
comment_params[:commentable_type]
end
def commentable_id
comment_params[:commentable_id]
end
def comment_id
comment_params[:comment_id]
end
def body
comment_params[:body]
end
def make_child_comment
return "" if comment_id.blank?
parent_comment = Comment.find comment_id
#comment.move_to_child_of(parent_comment)
end
def build_comment(comment_params)
if current_user.nil?
user_id = nil
first_name = comment_params[:first_name]
last_name = comment_params[:last_name]
else
user_id = current_user.id
first_name = current_user.first_name
last_name = current_user.last_name
end
commentable = commentable_type.constantize.find(commentable_id)
Comment.build_from(commentable, user_id, comment_params[:body],
first_name, last_name)
end
end
comments/form: (this is on the Posts#show page)
<%= form_for #new_comment do |f| %>
<% if #new_comment.errors.any? %>
<div id="errors">
<h2><%= pluralize(#new_comment.errors.count, "error") %> encountered, please check your input.</h2>
<ul>
<% #new_comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% end %>
I would instead use nested routes to create a more restful and less tangled setup:
concerns :commentable do
resources :comments, only: [:create]
end
resources :posts, concerns: :commentable
This will give you a route POST /posts/1/comments to create a comment.
In your controller the first thing you want to do is figure out what the parent of the comment is:
class CommentsController < ApplicationController
before_action :set_commentable
private
def set_commentable
if params[:post_id]
#commentable = Post.find(params[:post_id])
end
end
end
This means that we no longer need to pass the commentable as form parameters. Its also eliminates this unsafe construct:
commentable = commentable_type.constantize.find(commentable_id)
Where a malicous user could potentially pass any class name as commentable_type and you would let them find it in the DB... Never trust user input to the point where you use it to execute any kind of code!
With that we can start building our create action:
class CommentsController < ApplicationController
before_action :set_commentable
def create
#comment = #commentable.comments.new(comment_params) do |comment|
if current_user
comment.user = current_user
comment.first_name = current_user.first_name
comment.last_name = current_user.last_name
end
end
if #comment.save
respond_to do |format|
format.json { head :created, location: #comment }
format.html { redirect_to #commentable, success: 'Comment created' }
end
else
respond_to do |format|
format.html { render :new }
format.json { render json: #comment.errors, status: 422 }
end
end
end
private
# ...
def comment_params
params.require(:comment).permit(:first_name, :last_name, :body, :humanizer_answer, :humanizer_question_id)
end
end
In Rails when the user submits a form you do not redirect the user back to the form - instead you re-render the form and send it as a response.
While you could have your CommentsController render the show view of whatever the commentable is it will be quite brittle and may not even provide a good user experience since the user will see the top of the post they where commenting. Instead we would render app/views/comments/new.html.erb which should just contain the form.
Also pay attention to how we are responding. You should generally avoid using redirect_to :back since it relies on the client sending the HTTP_REFERRER header with the request. Many clients do not send this!
Instead use redirect_to #commentable or whatever resource you are creating.
In your original code you have totally mixed up JSON and HTML responses.
When responding with JSON you do not redirect or send flash messages.
If a JSON POST request is successful you would either:
Respond with HTTP 201 - CREATED and a location header which contains the url to the newly created resource. This is preferred when using SPA's like Ember or Angular.
Respond with HTTP 200 - OK and the resource as JSON in the response body. This is often done in legacy API's.
If it fails do to validations you should respond with 422 - Unprocessable Entity - usually the errors are rendered as JSON in the response body as well.
Added.
You can scrap your Comment.build_from method as well which does you no good at all and is very idiosyncratic Ruby.
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def show
#post = Post.friendly.find(params[:id])
#new_comment = #post.comments.new
end
end
Don't use line contiuation (\) syntax like that - use parens.
Don't:
new \
:commentable => obj,
:body => comment,
:user_id => user_id,
:first_name => first_name,
:last_name => last_name
Do:
new(
foo: a,
bar: b
)
Added 2
When using form_for with nested resources you pass it like this:
<%= form_for([commentable, comment]) do |f| %>
<% end %>
This will create the correct url for the action attribute and bind the form to the comment object. This uses locals to make it resuable so you would render the partial like so:
I'm assuming your form_for submits a POST request which triggers the HTML format in CommentsController#create:
def create
#comment = build_comment(comment_params)
respond_to do |format|
if #comment.save
make_child_comment
format.html
format.json { redirect_to(:back, :notice => 'Comment was successfully added.')}
else
format.html
format.json { redirect_to(:back, :flash => {:error => #comment.errors}) }
end
end
end
So, if #comment.save fails, and this is an HTML request, the #create method renders create.html. I think you want to render Posts#show instead.
Keep in mind that if validations fail on an object (Either by calling save/create, or validate/valid?), the #comment object will be populated with errors. In other words calling #comment.errors returns the relevant errors if validation fails. This is how your form is able to display the errors in #new_comment.errors.
For consistency, you'll need to rename #new_comment as #comment in the posts#show action, otherwise you'll get a NoMethodError on Nil::NilClass.
TL;DR: You're not rendering your form again with your failed #comment object if creation of that comment fails. Rename to #comment in posts, and render controller: :posts, action: :show if #comment.save fails from CommentsController#create
I have figured out the answer myself with the help of others here.
The reason is that I messed up with the JSON format and html format (typical noobie error)
To be able to display the errors using the code I need to change two places ( and change #comment to #new_comment as per #Anthony's advice).
1.
routes.rb:
resources :comments, defaults: { format: 'html' } # I set it as 'json' before
2.
CommentsController.rb:
def create
#new_comment = build_comment(comment_params)
respond_to do |format|
if #new_comment.save
make_child_comment
format.html { redirect_to(:back, :notice => 'Comment was successfully added.') }
else
commentable = commentable_type.constantize.find(commentable_id)
format.html { render template: 'posts/show', locals: {:#post => commentable} }
format.json { render json: #new_comment.errors }
end
end
end
So I am in the process of setting up a forum and everything is setup/working well except for my replies are not appearing on the thread "show" page. After checking the rails console, I see they are saving but the user_id and discussion_id are no. The user_id is always nil and the discussion_id is always 0. The discussion threads were easier to setup but with having these replies, I obviously seem to be having an issue. Here are my snippets of code:
class PostsController
# ...
before_filter :authenticate_user!
before_filter :set_discussion, only: [:new, :create, :destroy]
def create
#post = #discussion.post.new(create_params) do |post|
post.user = current_user
end
if #post.save
redirect_to #discussion, notice: "It has been posted!"
else
render :new
end
end
def destroy
#post = #discussion.posts.find(params[:id])
#post.destroy
flash.notice = "Deleted"
redirect_to discussion_path(#discussion)
end
private
def create_params
params.require(:post).permit(:reply)
end
def set_discussion
#discussion = Discussion.friendly.find(params[:id])
end
end
class DiscussionsController
def show
#discussion = Discussion.friendly.find(params[:id])
#post = Post.new
render :layout => 'discussion'
end
end
Partial rendered to reply:
<h2>Reply</h2>
<%= form_for [ #discussion, #post ] do |f| %>
<p>
<%= f.label :reply, "Reply" %><br/>
<%= f.text_field :reply %>
</p>
<p>
<%= f.submit 'Submit' %>
</p>
<% end %>
Partial rendered to show replies in on discussion page:
<h3><%= post.user.first_name %></h3>
<%= post.reply %>
Posted: <%= post.created_at.strftime("%b. %d %Y") %></p>
<p><%= link_to "Delete Comment", [post.discussion, post], data: {confirm: "Are you sure you wish to delete?"}, method: :delete, :class => "post_choices" %></p>
Just want to mention that I also have the correct associations between the three models (User, Discussion, Post). If there is more code needed, please let me know. I appreciate it very much for any information that may be helpful =)
Joe
EDIT
class User < ActiveRecord::Base
has_many :articles
has_many :discussions
has_many :posts
# ...
end
class Discussion
belongs_to :user
has_many :posts
extend FriendlyId
friendly_id :subject, use: :slugged
end
class Post
belongs_to :discussion
belongs_to :user
end
I could post the entire user model if needed but its all validations/devise aspects =P The other two I listed all of the contents in the models.
Edit 2
Thanks to Max, the user_id returns correctly in the console but still not the discussions. Going go dig around a bit more with the recent changes to see what else =)
There are a few issue you need to deal with.
First you should ensure that Devise is actually authorizing your controller action.
class PostsController < ApplicationController
before_filter :authenticate_user!
end
Otherwise current_user will return nil if there is no signed in user. And I'm
guessing that you do not want un-authenticated users to be able to create posts.
Also if you have a nested route you most likely want to check that the discussion actually
exists before trying to add posts.
class PostsController
before_filter :authenticate_user!
before_filter :set_discussion, only: [:new, :create, :destroy]
private
# Will raise an ActiveRecord::NotFoundError
# if the Discussion does not exist
def set_discussion
#discussion = Discussion.friendly.find(params[:id])
end
end
When you are creating resources be careful not to query the database needlessly.
This especially applies to CREATE and UPDATE queries which are expensive.
def create
#post = Post.create(post_params) # INSERT INTO 'users'
#post.discussion_id = params[:discussion_id]
#post.user = current_user
#post.save # UPDATE 'users'
flash.notice = "It has been posted!"
redirect_to discussions_path(#post.discussion)
end
Also you are not even checking if the record was created successfully.
So lets put it all together:
class PostsController
before_filter :authenticate_user!
before_filter :set_discussion, only: [:new, :create, :destroy]
def new
#post = #discussion.post.new
end
def create
# new does not insert the record into the database
#post = #discussion.post.new(create_params) do |post|
post.user = current_user
end
if #post.save
redirect_to #discussion, notice: "It has been posted!"
else
render :new # or redirect back
end
end
def destroy
#post = #discussion.posts.find(params[:id])
#post.destroy
flash.notice = "Deleted"
redirect_to discussion_path(#discussion)
end
private
def create_params
# Only permit the params which the user should actually send!
params.require(:post).permit(:reply)
end
# Will raise an ActiveRecord::NotFoundError
# if the Discussion does not exist
def set_discussion
#discussion = Discussion.friendly.find(params[:id])
end
end
I'm running into what I think is a routing error. I was creating a shopping cart but somehow I'm missing the required key [:product_id]. I believe it may be beginners mistake not placing the right code somewhere.
No route matches {:action=>"add", :controller=>"carts", :product_id=>nil} missing required keys: [:product_id]
app/views/stores/index.html.erb:34:in block in _app_views_stores_index_html_erb__2030225986820803304_70197415655620
app/views/stores/index.html.erb:3:in _app_views_stores_index_html_erb__2030225986820803304_70197415655620
Carts_Controller.rb
class CartsController < ApplicationController
def show
cart_ids = $redis.smembers current_user_cart
#cart_products = Product.find(cart_ids)
end
def add
$redis.sadd current_user_cart, params[:product_id]
render json: current_user.cart_count, status: 200
end
def remove
$redis.srem current_user_cart, params[:product_id]
render json: current_user.cart_count, status: 200
end
private
def current_user_cart
"cart#{current_user.id}"
end
def carts_params
params.require(:cart).permit(:product_id)
end
end
Cart.rb
class Cart < ActiveRecord::Base
belongs_to :user
end
Products_Controller.rb
class ProductsController < ApplicationController
def index
#products = Product.all
#order_item = current_order.order_items.new
end
end
Routes.rb
resource :cart, only: [:show] do
put 'add/:product_id', to: 'carts#add', as: :add_to
put 'remove/:product_id', to: 'carts#remove', as: :remove_from
end
Stores/index.html
<%=link_to "", class: "button", data: {target: #cart_action, addUrl: add_to_cart_path(#product_id), removeUrl: remove_from_cart_path(#product_id)} do%>
<%=#cart_action%>
When you are doing add_to_cart_path(#product_id) it sends the param as :id and you want it to be :product_id.
You can fix this two ways.
Way #1
In your button/link_to change
add_to_cart_path(#product_id)
to:
add_to_cart_path(product_id: #product_id)
Way #2
You can update your controller and route to expect and use the param id instead of product_id