Rendering Error Messages in Custom Controller Action - ruby-on-rails

I've added the following to my controller's create action:
def create
BatchUser.mass_insert_v2(params[:batch_user][:batch_name], params[:batch_user] [:batch_description], params[:batch_user][:quantity])
redirect_to batch_users_path
end
'mass_insert_v2', in my BatchUser model, starts like this:
def self.mass_insert_v2(batch_name, batch_description, quantity)
#batch_create = BatchUser.create! :batch_name => batch_name, :batch_description => batch_description
...
end
And then goes on to create X user accounts with random usernames and passwords. I've chosen this route because I found raw sql insertion to be faster than using activerecord on its own.
The problem I have is that I'm struggling to render my error messages. For example, batch_name must be present and unique.
I do get an error screen:
ActiveRecord::RecordInvalid in BatchUsersController#create
But no errors show.
Previously, I've had this check in my controller:
respond_to do |format|
if #batch_user.save
....
else
....
But that doesn't seem to work anymore. What can I do to display the errors on the page??

The create! (create with bang!) will throw an exception if the object fails validations. Unless you are planning on catching this exception and handling you might be better off just using create and then checking if the object was created successfully (has an id) and/or has errors.
There are several railsy ways of handling the finding and rendering error messages so you can experiment. However, knowing the following along with the important note above will help get you on track I think:
#batch_user = BatchUser.create(attr_hash) #will give you an object to work with instead of throwing an exception.
If you have an existing object:
#batch_user.valid? #will trigger the validations only and set the object's errors attribute with any relevant data and return a boolean value.
#batch_user.errors #gives you access to the error data (in 3.1 ActiveModel::Errors object)
As far as actually rendering the errors, like I said there are several options. I prefer either putting those error messages (obj.errors.full_messages) in the flash or using something like the dynamic_form plugin.

Related

How to create multiple models in one action in the Right Way?(Ruby on Rails 5)

In fact, this issue keep popping up in my mind when I write the code that there are multiple models in one action.
In create, update action that I found besides using the association build API, otherwise I can't avoid that when one model failed but the others was success and they already exist then I can't redemption or revocation them.
The situation is as follows :
Remove an good from the warehouse when an order is created.
And the order is not actually related to the goods in the warehouse
I provide some simple examples of this issue:
def create
#order = PackageOrder.new(order_params)
if #order.save
stock_record = PackageStockRecord.new(stock_record_params)
if stock_record.save
msg = 'stock_record saving success'
else
$ERROR.error "stock_record: #{stock_record.error_msgs}"
msg = 'stock_record saving failed'
end
redirect_to action: 'index', msgs: msg
else
$ERROR.error "package_order: #{#order.error_msgs}"
flash.now[:error] = 'package_order create failed.'
render 'new'
end
end
From the above example, it can be found that when #order is created or updated successfully, subsequent models can no longer affect or inform it. (except build api
So what I want is to undo the action of previous models and make it be notified when subsequent models was failed in the Right Way. (not by using the build api)
Or is it possible to create a short-lived association?
Is this normal? I mean is this situation inevitable or may be accumulated to cause system errors, or maybe I shouldn't care about it.
Are there relevant documents or procedures that can be learned in this situation?
If you want a model to be affected by changes in another model then that's done through association after all model is just class mapped to the table so you interact with the table in the ruby code.

Rails 2.3.4 Persisting Model on Validation Failure

I have a standard html form post that is being persisted and validated with a rails model on the server side.
For the sake of discussion, lets call this a "car" model.
When
car.save
is invoked the validators fire and set a list of fields in error within the model.
How best can I get an object back that has the fields whose validation failed removed, i.e. such that I don't have to delete the fields on the form the user entered correctly?
The validation patterns they use are great but how do I get a hook to when the validation fails such that I can delete the bogus entries from the model?
I could see manually iterating over the errors list and writing a big case statement but that seems like a hack.
In your controller, render the new action from your create action if validation fails, with an instance variable, #car populated from the user input (i.e., the params hash). Then, in your view, add a logic check (either an if block around the form or a ternary on the helpers, your choice) that automatically sets the value of the form fields to the params values passed in to #car if car exists. That way, the form will be blank on first visit and in theory only be populated on re-render in the case of error. In any case, they will not be populated unless #car is set.
EDIT:
Don't do this (see comments) but if you must here is how:
I ended up using a call to
#car.invalid?
inside of a loop to remove the invalid entries:
params[:car].each do | key, array|
if #car.errors.invalid?(key)
#car[key.to_sym] = ''
end
end
render :action=> 'new'
Note the use of the render here vs. redirect. Getting error messages to persist from the rails model validation across a redirect appears tricky, see:
Rails validation over redirect
I still feel like there should be a helper method for this or a better way.

Rails best practices - Controller or model?

I want to use this piece of code to retrieve a user's list of credit cards on file with Stripe to show on his profile (/users/:id)
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
Thing is, I'm not exactly sure where (in terms of Rails best practices) it fits. My first tought is to put it in the show method of the User controller since it's not really business logic and doesn't fit in the model. I've also looked at helper methods but they seem (from my understanding) to be used strictly when toying around with HTML.
Can any of you Rails experts chime in?
Thanks!
Francis
Good question. Whenever you see an instance variable in rails (starting with a #), it usually is a view/controller bit of code.
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
However looking at the tail end of that
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
This might fit better of in a model, where you can reuse that same line, but have the safety of added error handling and predictable behavior. For example
# user.rb
def stripe_customer_cards
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
rescue Stripe::InvalidRequestError
false # You could use this to render some information in your views, without breaking your app.
end
Also note the use of self. This usually implies use of a Rails model, because calling self in the controller actually refers to the controller, rendering it almost worthless, unless you really know what you are doing.
EDIT
To render an error message, simply write a call to redirect or render, with the alert option.
if #stripe_cards = current_user.stripe_customer_cards
# Your being paid, sweet!
else
# Render alert info :(
render 'my_view', alert: 'This is an alert'
redirect_to other_path, alert: 'Another alert'
end
I also like to make it a point to mention that you should not handle errors just because you can. Don't handle errors you don't expect. If you handle errors you don't expect it will
Confuse users
Make bugs in code harder to fix
Exaggerate the time before an error is recognized
I'd recommend adding a virtual attribute in your User model:
# app/models/user.rb
def cards
Stripe::Customer.retrieve(stripe_customer_id).cards.all # note the spelling of `retrieve`
end
Then, you'd be able to access all a users cards in the following manner:
user = User.first
#=> #<User id:1>
user.cards
#=> [Array of all cards]

Defensively Handling ActiveRecord::RecordNotFound

I'm wondering what a best practice is for defensively handling ActiveRecord throwing a RecordNotFound exception. It seems that the default Rails behavior is to render a 404 error. That works fine when we're dealing with getting content-- it makes intuitive sense. When I'm doing something like creating an associated record and the parent model's record no longer exists, however, throwing a 404 seems more cryptic.
What if I wanted to, instead, return a validation error complaining that the record no longer exists? How might I go about doing this?
You should really never have a 404 error on your website: you should try to catch all errors and handle them gracefully.
It depends where you're getting the error. If you're in a controller, say, in the edit action, you can just use a rescue, perhaps in conjunction with .exists? so that no error is even actually thrown. Something like this:
def edit
if Model.exists?(params[:id])
#model = Model.find(params[:id])
else
flash[:error] = "Unable to find model with ID #{params[:id]}!"
redirect_to models_path
end
end
You can add a model level validation for this as well. Let's say you have a User which belongs to a Group (i.e, it has a group_id). This validation will ensure that not only a group_id is set, but that it maps to a real Group record.
# User.rb
validates_presence_of: group
That way if you're just throwing params in to a .create! or .save! method, the validation will fail and you'll get a standard Rails error message.
But, your controller should then use the non-exception throwing versions (.create and .save) and again you can do something like above to see if the object is valid, then set a flash if it's not.
Use validates_existence gem
https://github.com/perfectline/validates_existence

Why don't people wrap begin/rescue blocks around ActiveRecord.find in Rails?

If a user tries to submit a form or access a service that uses something such as the following underneath the hood:
Model.find(params[:id]) # if model does not exist, throw ActiveRecord::RecordNotFound
If an instance cannot be found, an exception is thrown. Yet I rarely see folks wrap that statement around in a begin/rescue block, even if you create a scaffold, the rails generator does not wrap the find call in begin/rescue blocks.
Is there a reason for that?
I think it's because the most common case is that if you're going after an object by id and it doesn't exist then that is exceptional. The exception will bubble up and rails will handle it as a 404 for you which is usually appropriate.
If it is a situation where the object may or may not exist then either catching the exception or using Model.find_by_id(params[:id]) and checking for a nil object work perfectly well.
One reason may be that error handling logic is passed to rescue handlers in the application.
your controller:
def some_action
#foo = Foo.find!(params[:id])
# Exception will raise here ...
# ...
end
and then specify
rescue_from ActiveRecord::RecordNotFound, :some_method_that_will_render_a_404
(See this for an explanation)
Scaffolds are just shortcuts to get something up-and-running in a short time, but as a rule of thumb scaffolds are not meant to live up to production.
Personally, I've not seen too much code that doesn't do at least basic validation. I'd say it's somewhat a cultural thing: if you don't create a route that would raise an error, you are not obligated to handle it. Obviously this is far from ideal, but I think is not of a priority for many developers. This mainly depends on the business logic: it is usually positively oriented, that is, only contemplates responding to user valid actions.
because it's easier to call:
Model.find_by_id(params[:id])
Which returns nil if no record found
Because you want to fail early. The sooner you find out something's wrong, the sooner you can fix it.
I have seen code like that:
def add_to_cart
begin
product = Product.find(params[:id])
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid product #{params[:id]}")
redirect_to_index("Invalid product")
else
#cart = find_cart
#cart.add_product(product)
end
end

Resources