Im attempting to pass user_id and a game_id to create a review. I can get it to work if I just select the game_id in a dropdown, but unable to pass the game_id correctly...
here is my code for the form
<div class="field form-group">
<%= form.number_field :user_id, id: :review_user_id, class:"form-control", value: current_user.id, type: :hidden %>
</div>
<div class="field form-group">
<%= form.number_field :game_id, id: :review_game_id, class:"form-control", value: #game_id, type: :hidden %>
</div>
My Reviews Controller
ReviewsController
....
def new
#review = current_user.reviews.build
end
def create
#review = current_user.reviews.build(review_params)
respond_to do |format|
if #review.save
format.html { redirect_to #review, notice: 'Review was successfully created.' }
format.json { render :show, status: :created, location: #review }
else
format.html { render :new }
format.json { render json: #review.errors, status: :unprocessable_entity }
end
end
end
def review_params
params.require(:review).permit(:reviewed_game, :rating, :game_id, :user_id)
Pretty sure it has to do with something in my Controller, im not passing something correctly. I attempted to do something like this with my GameController:
def create
#game = Game.new(params[:review])
...
end
no luck, Im pretty green to RoR...any help would be great!
Thanks
Welcome to Rails!
Looks to me like you're not setting #game_id anywhere, so that value will be nil, and thus an empty value tag in your HTML. So when the form is submitted, no :game_id will be in your params.
What you're trying to build sounds like a great case for nested resources.
In your config/routes.rb you would have something like
resources :games do
resources :reviews
end
So when you visit a URL like "/games/123/reviews/new", you'll hit the ReviewsController but there'll be a params[:game_id] equal to 123.
Your ReviewsController would look something like this
class ReviewsController < ApplicationController
def new
#game = Game.find(params[:game_id])
#review = current_user.reviews.new
end
def create
#review = current_user.reviews.new(review_params)
#game = Game.find(params[:game_id])
#review.game = #game
if #review.save
# etc.
end
end
Your reviews/new view template would have a form that points to this nested URL.
I believe you can do that with form_with
So, something like:
form_with model: [#game, #review] do |f|
# form code
When you run current_user.reviews.new, the Review instance will automatically have the :user_id attribute assigned. So you don't need to have the user_id floating around in your params, which as max points out, is a security flaw.
Similarly, #review.game = #game should assign the correct game_id.
By taking this approach, the only real information you need from the user is the rating, your routes will handle the Game in question, and Devise's current_user will handle the User.
I hope this helps in some way. Persistence is fruitful. 🍇
Related
Not sure how to title my question...
My intent is that users will have a list of items (and only the current_user can edit this list)
class Item < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :items
end
I have a Users controller (note: I'm also using devise gem, but created a separate Users Controller)
class UsersController < ApplicationController
load_and_authorize_resource
def show
#user = User.find(params[:id])
end
def list
#user.items.build
end
private
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(
items_attributes: [:user_id, :item, :name, :_destroy]).merge(user_id: current_user.id)
end
end
Right now I have set up routing
get 'users/:id/list', to: 'users#list', as: :list
Which gives me this: localhost:3000/users/1/list
Theoretically, this should be the show view... where it'll populate the list of items for user id: 1.
I want to be able to do localhost:3000/list/edit So the user can update this list.
My form in views/users/list.html.erb (I'm using cocoon gem for help with nested forms).
<%= simple_form_for #user do |f| %>
<%= f.simple_fields_for :items do |i| %>
<%= f.text_field :item %>
<%= f.text_field :name %>
<% end%>
<%= link_to_add_association 'add item', f, :items %>
<%= f.button :submit %>
<% end %>
I know my form is in my list.html.erb where it'll populate all the items, but I'm just doing this for testing purposes.
How do I update the form to save into items database and its associated with the current user?
EDIT:
def update
#user = current_user.items.find_by_user_id(current_user.id)
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'Congrats on your successful update!' }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
There is nothing wrong with having a form in a "show" view. If you only want the current_user to view the form, then wrap your form in an if statement:
<% if #user == current_user %>
(form goes here...)
<% end %>
As long as you have an update route/action for users, that form should submit (disclaimer: I've never used the cocoon gem).
Edit: Your form should be fine as is. Add this route to your routes.rb file:
patch 'users/:id', to: 'users#update'
For current user authentication you should use cancan gem
https://github.com/ryanb/cancan
For nested fields related to user nested_form gem is the best option
https://github.com/ryanb/nested_form
I am positive this is the dumbest question but I cannot wrap my head around it.
I have two models in a simple has_one/belongs_to relationship
registration_code.rb
class RegistrationCode < ActiveRecord::Base
has_one :billing_transaction
accepts_nested_attributes_for :billing_transaction
billing_transaction.rb
class BillingTransaction < ActiveRecord::Base
belongs_to :registration_code
In my form I am collecting information for BOTH models using fields_for.
_form.html.erb (truncated example)
<%= form_for #registration_code, :html => {:class => "form", role: "form"} do |f| %>
<%= f.label :registration_code, "Registration Code", :class => "control-label" %>
<%= f.text_field :registration_code %>
<%= f.fields_for #billing_transaction do |bt| %>
<%= bt.label :transaction_amount, "Transaction Amount", :class => "control-label" %>
<%= bt.number_field :transaction_amount %>
<% end %>
<% end %>
In my controller, I have the following.
registration_code_controller.rb
def new
#registration_code = RegistrationCode.new
#billing_transaction = BillingTransaction.new
#billing_transaction.registration_code = #registration_code
end
def create
#registration_code = RegistrationCode.new(registration_code_params)
#billing_transaction = BillingTransaction.new # DOES THIS HAVE TO TAKE PARAMS?
#billing_transaction.registration_code = #registration_code # DO I NEED THIS LINE?
##### THE TROUBLE IS ON THIS NEXT LINE #####
#billing_transaction.transaction_amount = params[:billing_transaction_attributes][:transaction_amount] # THIS NEVER GETS SET! NOT SURE HOW TO ACCESS THE PARAMS
respond_to do |format|
if #registration_code.save && #billing_transaction.save
format.html { redirect_to registration_codes_path, notice: 'Registration code was successfully created.' }
else
format.html { render :new }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def registration_code_params
params.require(:registration_code).permit(:registration_code, :expires_at, billing_transaction_attributes: [:transaction_amount])
end
The params are submitted and I can access the params for the primary model (registration_code) just fine. But I cannot for the life of me figure out how to get the parameters of the "sub" model (billing_transaction) and use them in the controller.
{"utf8"=>"✓",
"authenticity_token"=>"ePpqwJkeTAGMzb5WRWeI6aYCx4xpqvq4rl2m405IbwLdbp9xE0RyPgTZ6NmX8SvCFu94GKrfMfV9PrOkKa1BLg==",
"registration_code"=>{"registration_code"=>"HFmkbQEN",
"expires_at"=>"2015-07-16",
"billing_transaction"=>{"transaction_amount"=>"958.40" }},
"commit"=>"Create Registration Code"}
To access the billing_transaction.transaction_amount, for example, I have tried many variations:
params[:billing_transaction_attributes][:transaction_amount]
params[:billing_transaction][:transaction_amount]
params[#billing_transaction][:transaction_amount]
params[:registration_code][:billing_transaction][:transaction_amount]
No matter what I enter I cannot seem to access that nested array of parameters.
Help. Feeling super dumb right now. Thanks.
A few important changes to your new and create methods as below would solve your problem.
def new
#registration_code = RegistrationCode.new
#billing_transaction = #registration_code.build_billing_transaction
end
def create
#registration_code = RegistrationCode.new(registration_code_params)
respond_to do |format|
if #registration_code.save
format.html { redirect_to registration_codes_path, notice: 'Registration code was successfully created.' }
else
format.html { render :new }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
This is how the nested_attributes gets saved in the DB.
To retrieve the hash of billing transactions:
registration_code_params[:billing_transaction_attributes]
To retrieve the first (and only) key/value pair in the hash:
key, value = registration_code_params[:billing_transaction_attributes].first
puts key #transaction_amount
puts value #958.40
I'm trying to build a small expense tracking app using Rails 4.1. Using devise for authorization. Expense and it's nested attribute, comments belong to a user. The associations are set up in the model and expenses are getting associated with the user. Here's the Expense controller:
class ExpensesController < ApplicationController
def new
#expense = Expense.new
#item = #expense.items.build
##comment = #expense.comments.build
end
def index
#expenses = Expense.all
##items = Item.where(:expense_id => #expense.id)
end
def show
#expense = Expense.find(params[:id])
#items = Item.where(:expense_id => #expense.id)
end
def create
#expense = current_user.expenses.new(expense_params)
respond_to do |format|
if #expense.save
ExpenseMailer.expense_submission(#expense).deliver
format.html { redirect_to #expense, notice: 'Expense Report Submitted.' }
format.json { render :show, status: :created, location: #expense }
else
format.html { render :new }
format.json { render json: #expense.errors, status: :unprocessable_entity }
end
end
end
def edit
#expense = Expense.find(params[:id])
end
def update
#expense = Expense.find(params[:id])
##comment = #expense.comments.build
if #expense.update(expense_params)
#if #comment.save
#ExpenseMailer.comments_added(#expense).deliver
flash[:notice] = "Expense Report Updated"
redirect_to expenses_path
#else
# flash[:notice] = "Expense Report Updated"
#redirect_to expenses_path
##end
else
render 'edit'
end
end
The form from where the comment attributes are built looks like:
<%= nested_form_for (#expense) do |f| %>
<div class="form-group">
<%= f.label :state %><br />
<%= f.select :state, Expense.states, :include_blank => false, 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>
<%= comment.hidden_field :commenter %>
<% end %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
The #comment.commenter = current_user isn't adding the current user id to the database. Should I include it in the expense controller somewhere?
You have to add:
#comment.commenter = current_user
below that if statement. Like this:
def create
#article = Expense.find(params[:expense_id])
if #comment = #expense.comments.create(comment_params)
#comment.commenter = current_user
#comment.save
ExpenseMailer.comments_added(#expense).deliver
redirect_to expenses_path
end
end
And then save the comment again. In your current code you're overwriting the #comment object with the newly created object by doing:
#comment = #expense.comments.create(comment_params)
but you haven't set the commenter on that new object anywhere yet.
Model
I just tried to create better code for your strong params, but I couldn't work out how to include the param in your nested attributes
I would therefore recommend using the inverse_of: method in your Comment model to get it sorted properly:
#app/models/expense.rb
Class Expense < ActiveRecord::Base
belongs_to :user
has_many :comments, inverse_of: :expense
accepts_nested_attributes_for :comments
end
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :expense, inverse_of: :comments
before_create :populate_expense, on: :create
private
def populate_expense
self.commenter_id = self.expense.user_id
end
end
This should work if you're populating the comments from the accepts_nested_attributes_for directive
Comments
I don't understand why you've created two create actions for both your expenses and comments controllers - the controller action is meant to be independent of the Model
What I'm trying to say is that if you think the comments#create controller action will be invoked by your nested attribute creation, you'd be mistaken - it is only invoked when you send a request to it through the Rails router :)
If you're creating Comments and Expenses separately, you'll be able to use these two different actions; but they won't be invoked by each other. Only Model methods can be invoked by the controller (you shouldn't be calling other controller methods)
If you wanted to create a Comment from the expenses#show page, here's how you'd set it up:
#config/routes.rb
resources :expenses do
resources :comments #-> domain.com/expenses/:expense_id/comments/new
end
#app/controllers/expenses_controller.rb
Class CommentsController < ApplicationController
def new
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new
end
def create
#expense = Expense.find params[:expense_id]
#comment = #expense.comments.new(comment_params)
#comment.save
end
private
def comment_params
params.require(:comment).permit(:comment, :params).merge(commenter_id: current_user.id)
end
end
This will work if you wanted to create a comment from the expenses#show page. If you do this, you need to ensure you are calling the comments#new / comments#create actions, rather than those of the expenses controller
Rails controller:
class VenuesController < ApplicationController
def new
#venue = Venue.new
end
def create
#venue = Venue.new(params[:venue])
if #venue.save
redirect_to root_path
end
end
def update
redirect_to search_path
end
end
Rails form:
<%= form_for(#venue) do |f| %>
<%= f.text_field :foursquare_id %>
<%= f.submit "Save" %>
<% end %>
"foursquare_id" is a column in the "venues" table. usually i import a foursquare id from foursquare but i'm typing in text for testing purposes. i am being redirected to "root_path" before even being given a chance to type into the form.
what is my controller/form missing? thank you in advance
The template form should be used by the new action with new.html.erb as the filename. And you should be going to /venues/new to fill out the form.
The create action is used to submit the completed form, which is why you are getting redirected. You should also modify create to handle a model that couldn't save:
def create
#venue = Venue.new(params[:venue])
if #venue.save
redirect_to root_path
else
render :action => :new
end
end
Or you can use the shorthand for this:
def create
#venue = Venue.new(params[:venue])
#venue.save
respond_with #venue, :location => root_path
end
Your form for creating a new venue should be in venues/new.html.erb which will call the Create action in your controller upon form submit. You shouldn't have a create view in this scenario.
In my Review model, I have the following:
class Review < ActiveRecord::Base
belongs_to :vendor
belongs_to :user
has_many :votes
validates_presence_of :summary
end
I submit a new entry as follows in the URL:
vendors/9/reviews/new
The new.html.erb contains a form as follows:
<%= error_messages_for 'review' %>
<h1>New review for <%= link_to #vendor.name, #vendor%></h1>
<% form_for(#review, :url =>vendor_reviews_path(#vendor.id)) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :summary %><br />
<%= f.text_area :summary, :rows=>'3', :class=>'input_summary' %>
<%= f.hidden_field :vendor_id, :value => #vendor.id %>
</p>
<p>
<%= f.submit 'Submit Review' %>
</p>
<% end %>
When I leave the field for :summary blank, I get an error, not a validation message:
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.name
Extracted source (around line #3):
1: <%= error_messages_for 'review' %>
2:
3: <h1>New review for <%= link_to #vendor.name, #vendor%></h1>
I don't understand what is happening, it works if :summary is populated
def new
#review = Review.new
#vendor = Vendor.find(params[:vendor_id])
#review = #vendor.reviews.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #review }
end
end
def create
#review = Review.new(params[:review])
##vendor = Vendor.find(params[:vendor_id]) #instantiate the vendor from the URL id -- NOT WOKRING
##review = #vendor.reviews.build #build a review with vendor_id -- NOT working
#review = #current_user.reviews.build params[:review]#build a review with the current_user id
respond_to do |format|
if #review.save
flash[:notice] = 'Review was successfully created.'
format.html { redirect_to review_path(#review) }
format.xml { render :xml => #review, :status => :created, :location => #review }
else
format.html { redirect_to new_review_path(#review) }
format.xml { render :xml => #review.errors, :status => :unprocessable_entity }
end
end
end
My guess is that when it fails it is going to redirect_to new_review_path(#review) and so doesn't know the vendor it. How can I redirect to vendor/:vendor_id/reviews/new instead?
You probably don't have #vendor member variable set - but to fix this, it would be more correct to use not #vendor directly, but through your #review variable instance.
If you are creating new review, you already have #review member variable created, and you simply are populating fields in it - so, you need to set the vendor for #review (unless it's optional)... it would be more correct to use #review.vendor.name instead.
(If vendor is optional, then you obviously must catch all vendor.nil? cases.)
What code do you have in the new and create actions in your ReviewsController?
I suspect that your new Review is failing validation because the summary field is blank and then when the form is redisplayed on validation failure, the #vendor instance variable is nil.
You need to make sure that #vendor is assigned a value for both code paths.
I think you need to render :action => 'new' instead of your redirect_to new_review_path(#review). This will keep your error_messages on the #review object. By redirecting you are losing the old object and creating a new one.
As others has said, you also need to make sure you re-populate the #vender variable in your create method before rendering the view.
PS. I like to use the ardes resources_controller plugin for bog standard controller actions like these, makes life a lot easier for me and it handles nested resources really well.