I am stumped about this. In a previous commit my forms to submit new records for a particular model worked perfectly, but I must have done something along the way that messed it up because now my controller just renders the 'new' action whenever I submit a form.
The validations are all passing, but for some reason the records are not saving.
I did not write tests during development, which I am regretting now as I am going back and writing them.
But since I am new to rails, I just want to ask about what is the best way to debug this. I've tried looking at my local server log but it does not offer anything helpful. My controller's create action is below:
def create
#product = Product.find(params[:product_id])
#review = #product.reviews.new(params[:review])
current_user.reviews << #review
if #review.save
flash[:notice] = "Successfully created review."
redirect_to current_user
else
render :action => 'new'
end
end
Apologize is this is super vague (I will delete the question if it is)
Also just to note, none of my models are saving now so it seems like an application-wide issue since they were all working at an earlier commit.
In terms of debugging things like this, I recommend two gems: better_errors and pry. Either/Or. Preferably both.
Better Errors
https://github.com/charliesome/better_errors
Just add it to your gemfile and you're done:
group :development do
gem 'better_errors'
gem 'binding_of_caller'
end
Raise an error in your controller, and you'll get dumped into an IRB session right at the point that the exception was raised. Then you can poke around and see what's happening directly.
Pry
http://pryrepl.org/
Again, just install the gem:
gem 'pry-rails'
Then restart your server (use rails s directly - passenger or foreman might screw with this) and call binding.pry from your controller:
def create
#product = Product.find(params[:product_id])
#review = #product.reviews.new(params[:review])
current_user.reviews << #review
binding.pry # <---- You will get dumped right here
if #review.save
flash[:notice] = "Successfully created review."
redirect_to current_user
else
render :action => 'new'
end
end
When you submit the form the server will stop and you should get dumped into a console right at that line. You can then poke around - inspect all the variables, call #review.save to see what happens.
Pry has a ton of features that you'll want to check out while you're in there.
And you can configure better_errors to use pry by default.
Oh, and specs are good :D
1.If you want the product_id to be set on the review, your product should be an existing record. If not you can simply do
Review.new(params[:review])
instead of
#product = Product.new(params[:product])
#review = #product.reviews.new(params[:review])
2.current_user.reviews << #review creates the review record in the database with current_user_id. you don't have to do the #review.save again.
3.Do #review.save! to test. It will raise an exception if there is problem with saving the record.
The sequence in which you're instantiating a new object and then saving it is not quite correct.
Rather than appending your uncommitted #review to current_user.reviews prior to saving it, you should only append it based conditionally on whether or not it saved correctly.
The Rails Way of accomplishing what you're attempting would be something akin to this:
def create
#product = Product.find(params[:product_id])
#review = #product.reviews.build(params[:review])
if #review.save
current_user.reviews << #review
flash[:notice] = "Successfully created review."
redirect_to current_user
else
render :action => :new
end
end
This way, if #review.save returns false, you'll know that something was invalid because the :action => :new will be rendered.
EDIT:
Since you're looking to debug without specs, you might try employing the save! method. Rather than returning false upon failure, it actually throws an exception (which is pretty handy for debugging):
#review.save!
#=> ActiveRecord::RecordInvalid: Validation failed: `reason for validation failure`
Related
I want to save two models in one controller action, or save neither, and return with the validation errors.
Is there a better way than this?
def update
#job = Job.find(params[:id])
#location = #job.location
#job.assign_attributes(job_params)
#location.assign_attributes(location_params)
#job.save unless #job.valid? # gets validation errors
#location.save unless #location.valid? # gets validation errors
if #job.valid? && #location.valid?
#job.save
#location.save
flash[:success] = "Changes saved."
redirect_to edit_job_path(#job)
else
render 'edit'
end
end
New version:
def update
#job = Job.find(params[:id])
#location = #job.location
begin
Job.transaction do
#job.assign_attributes(job_params)
#job.save!(job_params)
#location.assign_attributes(location_params)
#location.save!(location_params)
end
flash[:success] = "Changes saved."
redirect_to edit_job_path(#job)
rescue ActiveRecord::RecordInvalid => invalid
render 'edit'
end
end
Have a look at Active Record Nested Attributes.
Using Nested attributes, you can save associated record attributes through parent.If parent record fails, associated records won't be saved.!
the first thing you'd want to do is delete these two lines
#job.save unless #job.valid? # gets validation errors
#location.save unless #location.valid? # gets validation errors
and only keep the #save in the if statement. because if one of them is valid, but the other isn't, you'll still save the valid one to the db.
To answer your second question, is there a better way to do this? At first blush, it looks like a job for #accepts_nested_attributes_for. However, accepts_nested_attributes_for is somewhat notorious for being difficult to get working (really it just takes a fare amount of tinkering) and what you're currently doing should get you where you're trying to go, so it's up to you.
You can use validates_associated rails helper:
class Model < ActiveRecord::Base
has_one :location
validates_associated :location
end
Then:
if #job.save
#blah
else
#blah
end
Is enough without having to mess with ActiveRecord#Nested_attributes. It's fastest, but less cleaner. Your choice.
Reference:
http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_associated
I realized something quite strange when attempting to upload an image via the paperclip gem for my user model (under the avatar attribute). For some reason there User.update and #user.update_attributes behaves differently. Does anyone know why this is so?
#using #user.update_attributes(user_avatar_params)
def update_profile_pic
#user = User.find(params[:id])
#user.update_attributes(user_avatar_params)
puts #user.avatar_file_name.nil? # prints false as expected
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
#using User.update(#user.id, user_avatar_params)
def update_profile_pic
#user = User.find(params[:id])
User.update(#user.id, user_avatar_params)
puts #user.avatar_file_name.nil? # prints true although successfully saves
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
And here is my strong params in the user_controller.rb
def user_avatar_params
params.require(:user).permit(:avatar)
end
For what it's worth, as of Rails 4.0.2, #update returns false if the update failed, not simply the object which the update failed for. Of further note, #update_attributes is simply an alias of #update now.
ActiveRecord.update has a behavior that may be throwing you off:
Updates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.
http://apidock.com/rails/ActiveRecord/Base/update/class
However the update_attributes will just return false.
Both of these use Model-level validations and so both should save or not save equally. However, the return values will be different.
As #RoaringStones pointed out, the solution is to use
user = User.update(user.id, user_avatar_params)
By the way, #update_attributes gonna be deprecated from Rails 6 (though this is not released yet)
please have a look at
https://github.com/rails/rails/pull/31998
https://github.com/rails/rails/commit/5645149d3a27054450bd1130ff5715504638a5f5
for more details.
I'm implementing a multistep form - as shown in #217 Multistep Forms - Railscasts - and came across an error:
can't dump File
Here are new and create actions:
def new
session[:batch_params] ||= {}
#batch = current_user.batches.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #batch }
end
end
def create
session[:batch_params].deep_merge!(params[:batch]) if params[:batch]
#batch = current_user.batches.build(session[:batch_params])
if #uploaded
#batch.file = #uploaded
end
#batch.current_step = session[:batch_step]
if params[:back_button]
#batch.previous_step
elsif #batch.last_step?
#batch.file = session[:file]
#batch.save
else
#batch.next_step
end
session[:batch_step] = #batch.current_step
if #batch.new_record?
render 'new'
else
session[:batch_step] = session[:batch_params] = nil
flash[:notice] = "Batch was successfully created"
redirect_to #batch
end
end
The problem is: the file needs to be updated in the first step, since I need to read it and get the number of rows, to be used on the second step. So I'm trying to store a file in the session, and since it's not possible to serialize it, I'm getting this error.
How can avoid doing this? I believe I should upload the file on the first step, and then just provide it's url to the following steps; is it correct?
How can I do this?
I suggest you create the record on the first form, and use a state machine to track the steps of the object until you fully build it on the last form. Gems like state_machine will allow you to do validations per step. The only problem with that is that you might (will) end with submissions that weren't completed. You might clean up this periodically.
When you generate a rails scaffold using a command like rails g scaffold Thing is there any way to avoid getting that annoying
respond_to do |format|
format.html # index.html.erb
format.json { render json: #things }
end
stuff in your controller?
I'm trying to teach a class on Rails and I'd like to start by having them generate a scaffold, but with all the json formatting it's much more complicated than it needs to be. I'd be much happier if they could generate a scaffold that created a controller like this:
class ThingsController < ApplicationController
def index
#things = Thing.all
end
def show
#thing = Thing.find(params[:id])
end
def new
#thing = Thing.new
end
def edit
#thing = Thing.find(params[:id])
end
def create
#thing = Thing.new(params[:thing])
if #thing.save
redirect_to #thing, notice: 'Thing was successfully created.'
else
render: "new"
end
end
end
def update
#thing = Thing.find(params[:id])
if #thing.update_attributes(params[:thing])
redirect_to #thing, notice: 'Thing was successfully updated.'
else
render: "edit"
end
end
end
def destroy
#thing = Thing.find(params[:id])
#thing.destroy
redirect_to things_url
end
end
Comment out gem jbuilder in your Gemfile and respond_to blocks won't be generated.
Just clone the file
https://github.com/rails/rails/blob/v5.2.2/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
to your
lib/rails/generators/rails/scaffold_controller/templates/controller.rb
path in your application and customize what you want. Also, you can write your own generators for scaffolding ( http://guides.rubyonrails.org/generators.html ).
I think you'd be missing an opportunity. For one thing, you'd be teaching non-standard Rails, so your students might be confused when they see the normal version in their own installations.
More importantly, the controllers are formatted that way for a reason. Rails puts an emphasis on REST, which encourages access to resources via multiple data formats. Many modern apps are de-emphasizing slower server-rendered html/erb responses in favor of json APIs. I realize this is a little over a year after your OP, and you have limited time in class, just adding some thoughts for anyone who might happen by. I think you could wave your hand over the respond_to and tell them it's setting you up for some future possibilities.
You'll notice that the JSON response is coded directly into the template for the rails generator here:
https://github.com/rails/rails/blob/master/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
I think something to note is that the scaffold generator is really intended to illustrate and moreover educate on how the Rails stack works, it shows how you can edit the controller to provide many different formats to suit your needs!
To avoid duplicating logic, should the create and update actions be the same?
ANy good examples of this?
The create method is responsible for creating that simple blog post, whilst the update method is responsible for updating it.
def create
#blog = Blog.new(params[:blog])
if #blog.save
flash[:notice] = "Saved!"
redirect_to #blog
end
end
def update
#blog = Blog.find(params[:id])
if #blog.update_attributes(params[:blog])
flash[:notice] = "Saved!"
redirect_to #blog
end
end
Not much you could extract out of that besides what to do when the save succeeds / fails.
While create and update are similar, I don't think it worthwhile to make them literally the same. Usually you'll want different flash messages for each. Also, should a validation fail, on create it's usually best to render your new action, while on a failed update, you're more likely to want to render the edit action. These little differences are typically enough so that it's simpler and more readable to have separate create and new methods.
I personally would have an else clause for each of create and update. They would look like:
#create
else
render :action => "new"
end
#update
else
render :action => "edit"
end
The user can still see the error and correct it, but this way I don't need create and update views at all.
If you were to use save! or update_attributes! any validation error would raise an exception, which you'd have to rescue somewhere. If you didn't rescue it, your users would receive a 500 error page every time a model failed to validate. That's why its more conventional to use the non-! save and update_attributes methods.