validates_presence_of not working properly...how to debug? - ruby-on-rails

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.

Related

Passing an id to create review

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. 🍇

Ruby on Rails - HABTM - Inserting data into 2 models in one controller

I'm a beginner in RoR and am having issues on working with some of my models.
Basically I have a habtm relation between a product-ticket-reservation.
A product habtm reservations through tickets and vice-versa.
I also have a Supplier, which has_many :products and has_many :reservations.
What I want to do is after the user selects a supplier and sees it's products, he may then select the products he wants from that supplier.
In that reservations.new I got a form but since after the "submit" action I have to insert data in 2 models, I'm having issues with it.
When I create a reservation, it is supposed to create a reservation entry and a ticket entry at the same time, the ticket entry will have the reservation_id and the product_id as foreign keys.
My Reservations' view:
<%= form_for(#reservation) do |f| %>
Reservation Info
<div id="reservation_top"></div>
<div id="reservation">
<%= f.label :name %><br />
<%= f.text_field :name %>
<%= f.label :surname %><br />
<%= f.text_field :surname %>
(...)
<%= f.hidden_field :supplier_id, :value => #reservation.supplier_id %> #to get the supplier ID
Products:
<%= f.fields_for :tickets do |t| %>
<%= t.select("product_id",options_from_collection_for_select(#products, :id, :name))%>
#I also have another t.select and although this isn't my primary concern, I wanted this t.select option's to change according to what is selected on the previous t.select("product_id"). Something like a postback. How is it done in RoR? I've searched and only found observe_field, but I didn't understand it very much, can you point me in the right direction? thanks
<%end%>
<%= f.label :comments %>
<%= f.text_area :comments %>
<%= f.submit%>
<%end%>
Now i think the problem is in my controller, but I can't understand what to put there, I currently have:
def new
#supplier=Supplier.find(params[:supplier_id])
#reservation = Reservation.new(:supplier_id => params[:supplier_id])
#ticket = Ticket.new(:reservation_id => params[#reservation.id])
#products = Supplier.find(params[:supplier_id]).products
#ticket = #reservation.tickets.build
respond_to do |format|
format.html
format.json { render :json => #reservation }
end
end
def create
#reservation = Reservation.new(params[:reservation])
respond_to do |format|
if #reservation.save
#reservation.tickets << #ticket
format.html { redirect_to #reservation, :notice => 'Reservation Successful' }
else
format.html { render :action => "new" }
format.json { render :json => #reservation.errors, :status => :unprocessable_entity }
end
end
I'm now getting a
Called id for nil, which would mistakenly be 4
Is it because it is trying to create a ticket and it doesn't have the reservation_id?
I've never handled habtm associations before. Any tips?
Thanks in advance,
Regards
Take a look at the POST params for your create action in your log. That will show you exactly what data you have to work with from params when it comes time to save your data.
In
def create
#reservation = Reservation.new(params[:reservation])
respond_to do |format|
if #reservation.save
#reservation.tickets << #ticket
what is #ticket at that point? (There's your nil I believe)
I think it might also be interesting to see what your #reservation and #ticket look like in your new method right before generating the response... log a .inspect of each of those objects to make sure you have what you think you have.
And in a more complicated save like you have, I'd wrap it all in a transaction.

Why can't I build more than one nested attribute here?

this is my form code:
<%= simple_form_for setup_video(#video) do |f| %>
<% f.fields_for :comment_titles do |t| %>
<%= t.input :title, :label => "Comment Title:" %>
<%= t.button :submit, :value => 'Add', :id => 'add_comment_title' %>
<div class='hint'>Let your listeners know what comments you want by adding a guiding title for them. Pose a question, ask for feedback, or anything else!</div>
<% end %>
<% end %>
I have has_many :comment_titles and accepts_nested_attributes_for :comment_titles, :comments in my model. when I create a new comment_title in the form, the old one is replaced. I want an additional one to be built. How can I do this?
Here are the video controller actions:
def new
#video = Video.new
respond_to do |format|
format.js do
render_to_facebox(:partial => 'add_video')
end
end
end
def create
#video = current_user.videos.new(params[:video])
respond_to do |format|
if #video.save
format.html { redirect_to(#video) }
else
format.html { render :action => "new" }
end
end
end
I think this is actually what is needed:
def update
#video = current_user.videos.find(params[:id])
respond_to do |format|
if #video.update_attributes(params[:video])
format.html { redirect_to(#video) }
format.js
else
format.html { render :action => "edit" }
end
end
end
The edit action here will provide a form which will allow you to edit the existing record as well as its nested attributes. This is why it's replacing the existing object.
If you only want people to add new comment titles then I would recommend building a new object in your edit action like this:
def edit
video = current_user.videos.find(params[:id])
video.comment_titles.build
end
Then this will be available as an additional row in your fields_for call. To only make this show new objects:
<% f.fields_for :comment_titles do |t| %>
<% if t.object.new_record? %>
# stuff goes here
<% end %>
<% end %>
However this restricts people to being able to only add new items in an edit action, which may seen counter-intuitive to some users.

Error handeling w/ form_for in associated resource

I can't seem to get the flow to work right here. I have a Ruby on Rails (2.3.9) application. For the purposes of this question we have only a couple of resources. Boxes and Messages.
Box has_many :messages
Message belongs_to :box
I have created a view located at /boxes/1/new_message where I have the below form_for code. I can successfully create a message from this view. The problem arrises when my validations kick in.
In this case, message.body can't be blank and is validated by message.rb. Once this validation happens, it kicks the user over to the Message.new action and upon successfully filling in the message.body the app can no longer find the #box.id to place in message.box_id.
I have tried just about everything I can think of by not sure how to allow a users to receive a validation and still successfully create a message for a box. See my code below for reference.
/views/boxes/new_message.html.erb
<% form_for [#box, Message.new] do |f| %>
<%= f.error_messages %>
<%= f.label :message_title %>
<%= f.text_field (:title, :class => "textfield-message grid_12 alpha") %>
<%= f.label :message_body %>
<%= f.text_area (:body, :class => "textarea-message grid_12 alpha ") %>
<%= f.submit "Add a Message", :class => 'input boxy' %>
<% end %>
messages_controller.rb
def create
#message = Message.new(params[:message])
#box = Box.find(params[:box_id])
#message = #box.messages.build(params[:message])
#message.user = current_user
respond_to do |format|
if #message.save
flash[:notice] = 'Message was successfully created.'
format.html {redirect_to #box }
else
format.html { render :action => "new" }
format.xml { render :xml => #flash.errors, :status => :unprocessable_entity }
end
end
end
def new
#message = Message.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #message }
end
end
I believe your
#box = Box.find(params[:box_id])
should be
#box = Box.find(params[:id])

Rails form_for model in different controller

I have the following model classes:
class Upload < ActiveRecord::Base
...
has_many :reviews, :order => "created_at DESC"
...
end
class Review < ActiveRecord::Base
...
belongs_to :upload
belongs_to :user
validates_presence_of :description
...
end
My upload/show view has a form for to capture a review for the specific upload:
<% form_for(#review) do |f| %>
<%= f.error_messages %>
...
<p>
<%= f.text_area :description, :rows => 5, :cols => 80 %>
</p>
...
<p>
<%= f.submit 'Submit Review' %>
</p>
<% end %>
When the review validation fails how do I display the error messages in the review form that is part of the upload/show view?
My ReviewController does this:
def create
#review = current_user.reviews.new(params[:review])
if #review.save
flash[:notice] = 'Review was successfully created.'
redirect_to( #review.upload )
else
render :action => :new
end
end
Obviously render :action => :new does not work because I need to display the show action of the UploadsController and not the new action of the ReviewsController.
I'm pretty sure there is a simple way to do this, I just can't figure it out!
Your review controller action should be receiving params['upload_id'] to associate the review with its upload, either through the URL (if reviews are a nested route like POST /uploads/1/reviews), or from a hidden field.
You can use render :template to do your redirection:
def create
#review = current_user.reviews.new(params[:review])
#upload = Upload.find(params['upload_id'])
#review.upload = #upload
if #review.save
flash[:notice] = 'Review was successfully created.'
redirect_to( #upload )
else
flash[:error] = 'Review could not be created.'
render :template => 'uploads/show'
end
end
It's also acceptable to just render the form for the review by itself (i.e. the default 'reviews/new') until the form entry is correct instead of showing the whole page for the upload.

Resources