Is there a way to do this in Rails without mass assignment? - ruby-on-rails

Members create votes that both belong to them and to another model, Issues. Currently I'm doing this with a hidden form and passing the appropriate parameters. Here's the code on the issues index view:
<%= form_for(#vote) do |f| %>
<%= f.hidden_field "issue_id", :value => issue.id %>
<%= f.hidden_field "member_id", :value => session[:member_id] %>
<%= f.hidden_field "type", :value => :Upvote %>
<%= f.label issue.upvotes_count(issue.id) %>
<%= submit_tag "Up", :class => 'up-vote' %>
<% end %>
This doesn't seem ideal as it leaves issue_id and member_id open to mass assignment. Is there a better way to do this with a button_to tag or something?
Here's the controller code:
class VotesController < ApplicationController
#GET
def new
#vote = Vote.new
end
# POST
def create
#vote = Vote.new(params[:vote])
#vote.member_id = current_member
if #vote.save
redirect_to issues_path
else
redirect_to issues_path, notice: "you must be logged in to vote"
end
end
end
and
class IssuesController < ApplicationController
# GET
def index
#issues = Issue.find(:all)
#vote = Vote.new
end
# GET
def show
#issue = Issue.find(params[:id])
respond_to do |format|
format.html
format.js
end
end
end

Use scope in the controller:
#issue = Issue.find(params[:issue_id])
#vote = #issue.votes.new(params[:vote])
#vote.save
and do not pass member_id and issue_id to hidden fields.
If you have proper nested RESTful routes you should be able to get params[:issue_id] directly.

If issue and member_id are available before you vote.save! in the controller, you can set them manually there.

Normally you get values like member_id from current_user in the controller rather than passing it via form parameters. How you have it currently does expose you to mass-assignment.

Do members have to login before voting? If so, then you don't need to include member_id as a hidden field because you can grab current_user in the controller and this will provide good protection since there wouldn't be any advantage for a member to hack issue_id or type.

Related

undefined method `commentable' for #<ActiveRecord::Associations::CollectionProxy []> (accesing commentable id and type via polymorphic association)

I tried to fix this issue for a day but cannot find solution.
What I am doing now is to render the comment form
<%= form_for #comment, remote: true do |form|%>
<%= form.hidden_field :question_id, value: #question.id%>
<%= form.hidden_field :commentable_id, :value => #question.comments.commentable.id %>
<%= form.hidden_field :commentable_type, :value => #question.comments.commentable.class.name %>
<% end %>
I just dont know how to access the commetable_id and type that was created with the comment controller to record the id of the comment and the type of the commenting model (e.g. client or lawyer)
# GET /questions/1 or /questions/1.json
def show
set_question
#comment = Comment.new
#comments = Comment.all
end
# GET /questions/1 or /questions/1.json
def show
set_question
#comment = Comment.new
#comments = Comment.all
end
resources :questions do
resources :comments do
end
end
As the error message says, you can't call commentable on #question.comments - an association. Pass an array to the form_for method with the commentable (Question) object and the comment object. You don't need to set any hidden fields.
<%= form_for [#question, #comment] do |f| %>
<div><%= f.label :content %></div>
<div><%= f.text_area :content %></div>
<div><%= f.submit 'Post' %></div>
<% end %>
Replace content with the field where you're storing the comment's content.
This should generate a form tag with an action attribute of /questions/1/comments which upon submission is processed by CommentsController#create.
class CommentsController < ApplicationController
before_action :set_commentable
def create
#comment = #commentable.comments.new(comment_params)
if #comment.save
redirect_to #commentable, notice: 'Comment created'
else
render :new
end
end
private
def set_commentable
# e.g. request.path => '/questions/1/comments'
resource, id = request.path.split('/')[1, 2] # ['questions', '1']
#commentable = resource.singularize.classify.constantize.find(id)
end
def comment_params
params.require(:comment).permit(:content)
end
end
In the set_commentable method, the commentable type and its id are detected from the request path. Since resource is 'questions', resource.singularize.classify.constantize returns the Question model. The commentable object is then found using the find method. The CommentsController#create method creates the comment and redirects to the commentable object which is the question show page (/questions/:id). If there's an error, it renders the new view (you have to create views/comments/new.html.erb to render the form with errors).
You are using question.comments which indicates has many relation, So it will return you an active record association array, So if you want to find the commentable id, you need to take single record from the array. For example if you want first comment commentable id, then use
#question.comments.first.commentable.id
If each comment has different commentable id, you need to iterate through loop.
If you have some factor to apply condition, then apply the condition
#question.comments.where('your condition').first.commentable.id
If you use above code, still you will get the array, so I have used .first. Use a uniq value in where condition, so you will have only single record with that value.

Why is my form not saving? form_for question

I have this form. I am new to rails and I am trying to write a simple ecommerce site. This is the only part not working. (It worked 2 days ago I sear)
<%= form_tag line_items_path do%>
<%binding.pry%>
<%= hidden_field_tag :lite_item, :order_id, #order.id%>
<%= hidden_field_tag :line_item, :menu_item_id, #menu_item.id%>
<%= number_field_tag :line_item, :quantity, 1 %>
<%= submit_tag "Add to Cart"%>
<% end %>
It gives params that look like:
#<ActionController::Parameters {"authenticity_token"=>"VECKnS5SBot1rCyekepPXZa7TyTYkfFi0KdNRTB617ZnelmQo8Lkz_cJmQ8nAmCHUdDlPu1mpkhrPvMKysfjew", "order_id"=>"1", "menu_item_id"=>"1", "quantity"=>"1", "commit"=>"Add to Cart", "controller"=>"line_items", "action"=>"create"} permitted: false>
The controller for the view looks like this:
class MenusController < ApplicationController
def index
#menu_items = MenuItem.all
end
def show
#menu_item = MenuItem.find(params[:id])
#line_items = current_order.line_items.build
end
end
The form is really going through the line_items controller
def create
binding.pry
#line_item = LineItem.create(line_item_params)
if #line_item.save
#order.line_item_id = #line_item.id
#order.save
redirect_to cart_path(#current_cart), notice: "Item added to cart."
else
redirect_to menu_path(#menu_item), alert: "Item did not add to cart."
end
end
With strong params like this
def line_item_params
params.require(:line_item).permit(:menu_item_id, :quantity, :order_id)
end
It should use the line_items_path POST>
If anything else is needed just ask. Thanks in advance.
There are a lot of problems here.
The signature is hidden_field_tag(name, value = nil, options = {}). So the parameters you would actually be creating with that form is:
{
"lite_item" => "order_id", # check your spelling...
"line_item" => "quantity"
}
Oops. And that not even going to happen as <%= number_field_tag :line_item, :quantity, 1 %> will raise since you're passing an integer where the method expects a hash.
If you really have to create the inputs manually you would want:
<%= hidden_field_tag "line_item[order_id]", #order.id %>
But since you actually have a model there is no reason why you should be using form_tag instead of form_for(#line_item) or form_with(model: #line_item).
<%= form_for(#line_item) do |form| %>
<%= form.hidden_field :order_id %>
<%= form.hidden_field :menu_item_id %>
<%= form.number_field :quantity %>
<%= form.submit_tag "Add to Cart"%>
<% end %>
The controller should also use the correct pluralization for the instance variable:
def show
#menu_item = MenuItem.find(params[:id])
#line_item = current_order.line_items.build
end
Your create method is also pretty questionable. All you should need is:
def create
# use .new not .create
#line_item = LineItem.new(line_item_params)
if #line_item.save
redirect_to cart_path(#current_cart), notice: "Item added to cart."
else
redirect_to menu_path(#menu_item), alert: "Item did not add to cart."
end
end
I have no idea why you think you need to update #order here. Your controller should just really be adding a row to what is essentially a join table.

Rails 5, Couldnt't find User without and ID . Can you pass an Object through a form?

I'm trying to code the ability for a User to add a Skill (belongs to User, Technology and Level) to it's skillset.
I have a simple_for_for in skills/new.html.erb:
<%= simple_form_for [ #user, #skill ] do |f| %>
<p>Select Tech</p>
<div>
<%= f.collection_select :technology_id, #techs, :id, :name%>
</div>
<p>Select Level</p>
<div>
<%= f.collection_select :level_id, #levels, :id, :name%>
</div>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
Skills Controller: the New action is passing a list of Technology and Level objects to the simple_form_for so the options the user can select are a list of all Technologies and Levels (say, Tech: Ruby, Level: Junior).
class SkillsController < ApplicationController
before_action :authenticate_user!
def index
#skills = policy_scope(Skill)
end
def new
#techs = Technology.all
#levels = Level.all
#skill = Skill.new
#user = current_user
authorize #skill
end
def create
skill = Skill.new(skill_params)
authorize skill
if skill.save
redirect_to projects_path
else
render :action => "new", #user => params[:user_id], #skill => skill
end
end
private
def skill_params
params.require(:skill).permit(:user_id, :technology_id, :level_id)
end
end
I'm having a couple problems:
The else clause is not sending #user and #skill correctly to the form after it fails to save the new Skill, but I don't know if the syntax is correct.
Actual problem: Is it ok for the form to pass the technology and level id's associated to the option selected rather than the object itself? I've tried manually finding all the Skill components manually like this which renders a "Couldn't find User without and ID" error:
def create
skill = Skill.new()
technology = Technology.find(skill_params[:technology_id])
level = Level.find(skill_params[:level_id])
user = User.find(skill_params[:user_id])
skill.user = user
skill.technology = technology
skill.level = level
authorize skill
if skill.save
redirect_to projects_path
else
render :action => "new", #user => user, #skill => skill
end
end
private
def skill_params
params.require(:skill).permit(:technology_id, :level_id, :user_id)
end
First make sure you have setup an association from user to skills which you can build new records off of:
class User
has_many :skills
end
If you application just lets users add skills to their own account you should just fetch the current user from the session:
def create
#skill = current_user.skills.new(skill_params)
authorize #skill
if #skill.save
redirect_to projects_path
else
render :new
end
end
Note that this removes the need for nesting the form and route completely:
resources :skills
<%= simple_form_for #skill do |f| %>
# ...
<% end %>
In your create action you can set the #user and #skill before trying to create/save it and than you can just render new in the else clause:
def create
#user = current_user
#skill = Skill.new(skill_params)
authorize #skill
if #skill.save
redirect_to projects_path
else
render :new
end
end
And yes you can pass the ID's because the ID's is what will be saved in the DB.
The reason for the error Couldn't find a user without an id, is probably because the skill_params doesn't have the user_id. You get the user_id with the current_user helper method and not through the form.

Parameter in find method

I'm following a guide to Ruby on Rails and there is something I don't understand. I have this model called Comment which belongs_to another two models, called User and Book.
This model's controller, Comments has the following create action:
def create
book = Book.find(params[:comment][:book_id])
comment = book.comments.build(comment_params)
comment.user = current_user
if comment.save
redirect_to comment.book
end
end
comment_params is just this:
def comment_params
params.require(:comment).permit(:body)
end
This create action is called when clicking the "Submit" button from this form, which is a partial called _comments located in the books view folder and rendered in the books' show action:
<%= form_for #book.comments.build do |f| %>
<%= f.hidden_field :book_id, value: #book.id %>
<%= f.text_area :body %>
<%= f.submit "Submit" %>
<% end %>
#book is indeed defined in books#show.
I don't understand why I have to pass the [:comment] parameter to the Book.find method in order to find the book. I thought just the [:book_id] would suffice.
You're not actually passing the :comment parameter but rather accessing the :book_id that's nested in the :comment hash. Your params look something like:
{
:comment => {
:book_id => 1
}
}
If you simply passed params[:book_id] you would get back nil.
If book_id is a field in comments table you don't need to retrieve the book. Just do
def create
comment = Comment.new(comment_params)
comment.user = current_user
if comment.save
redirect_to comment.book
end
end
Also, if the User model has a comments association, and in this action you are sure that a current_user is set, you can do
def create
comment = current_user.comments.build(comment_params)
if comment.save
redirect_to comment.book
end
end

Submit Form and Create action for a Polymorphic Association

I received help Setting up a polymorphic association
I am now having trouble implementing the submit form and create action. A user just needs to follow and unfollow each model.
In my Follow Controller
class FollowsController < ApplicationController
before_filter :find_supports
def create
#relation = find_supports
#follow = #relation.find(params[:follow])
current_user.follows.follow!(#follow)
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
def destroy
#follows = Follow.find(params[:id]).followable
current_user.unfollow!(#follows)
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
private
def find_supports
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
else
nil
end
end
end
end
In my Follow Form, which is rendered at on my polymorphic models
EDIT
<%= form_for #cause.follows.build(params[:follow]) do |f| %>
<div><%= f.hidden_field :followable_id %></div>
<div><%= f.hidden_field :followable_type %></div>
<div><%= f.hidden_field :follower_id %></div>
<div class="create-button"><%= f.submit "Follow" %></div>
<% end %> <!-- I now get the values for followable_id and followable_type, but it
wont get grab the follower_id and I get an error in the create action -->
and in my User Model, I have these 3 methods
def following?(follow)
follows.find_by_followable_id(follow)
end
def follow!(followable)
follows.create![] ##I have tried many different params here
end
def unfollow!(followable)
follows.destroy(params[:followable_id]).destroy
end
I cannot get the follower_id, followable_id and followable_type to save properly. Any suggestions?
Thanks in advance, I have spent many hours with this.
Here is how I solved my issues. The answer is using AJAX and Devise helper method current_user
Form Partial
<%= form_for #relation.supports.build(params[:support]), :remote => true do |f| %>
<div><%= f.hidden_field :supporter_id, :value => current_user.id %></div>
<div><%= f.hidden_field :supportable_id %></div>
<div><%= f.hidden_field :supportable_type %></div>
<%= f.submit "Support", :class => "support-button" %>
<% end %>
My Controller
class SupportsController < ApplicationController
before_filter :find_relation
def create
#relation.supports.create!(params[:support])
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
def destroy
#relation.supports.find(params[:id]).destroy
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
private
def find_supports(instance_params)
class_name = instance_params["supportable_type"].classify.constantize
class_name.find(instance_params["supportable_id"])
end
def find_relation
#relation = find_supports(params[:support])
end
end
Also, add a before_filter in all the controllers that render the form to find the correct object. This will allow you use the same form partial with all your polymorphic classes.
def find_supports
#relation = YourClass.find(params[:id])
end

Resources