I have a situation where a user can create multiple records at the same time, so my controller action looks like this:
if Unit.create multiple_unit_params.values
redirect_to units_path
else
render :new
end
The validation can fail for any one of the records. So how am I supposed to render errors in the view if I don't know what record validation failed since there are multiple records?
One way to conquer this is to use the create! method that raises exceptions. The exception object contains information on the model that failed.
The pattern I tend to use looks like this:
def create
#unit = Unit.new(multiple_unit_params.values)
#unit.save!
redirect_to(units_path)
rescue ActiveRecord::RecordInvalid => e
# ... Deal with exception
end
If you're creating multiple records you may want to encapsulate that in a transaction so you don't end up with some created, some uncreated. It'll be an all-or-none thing. Exceptions automatically unwind transactions.
Related
Failed to replace tickets because one or more of the new records could
not be saved.
My Form model has_many tickets. A ticket cannot have a form if it has a particular flag set. When i go to test this by trying to assign a ticket to an existing form, it crashes rather than pay attention to my validations.
Since its RailsAdmin, I see its just calling update_attribute (which apparently saves nested objects) before doing the error handing of checking if the Form object saves or not.
Is there any known way to get RailAdmin to gracefully catch this validation exception long enough to do its normal error handling for the Form itself failing validation? It's all part of the automatic stuff handling edits/new of an arbitrary object.
I'm using Rails 4.2.0 and RailsAdmin 1.1.1
Edit for Guilermo:
I have models on rails admin that fail to save because the nested object was invalid. Are you using the nested form field on rails admin? Could you show the rails admin configuration code on the form model and the validation? I'd be happy to help with that.
Whoever created this code used simple default things, the following is the relevant line inside the RailsAdmin config initializer.
edit do
include_all_fields
end
Observed behavior is the standard RailsAdmin field where you can search for objects, or pick from a drop down, and select one or more of them to be attached to the current object.
The nested object IS invalid (and as a result the parent object is as well). The problem is that rather than returning the parent object as invalid, the system crashes because the nested object is invalid.
The RailsAdmin code appears to call update_attribute (which throws an uncaught error), and then actually does the save! (along with crash check). I am basing this on the following code:
https://github.com/sferik/rails_admin/blob/master/lib/rails_admin/config/actions/edit.rb
Specifically:
#object.set_attributes(params[#abstract_model.param_key])
#authorization_adapter && #authorization_adapter.attributes_for(:update, #abstract_model).each do |name, value|
#object.send("#{name}=", value)
end
changes = #object.changes
if #object.save
#auditing_adapter && #auditing_adapter.update_object(#object, #abstract_model, _current_user, changes)
respond_to do |format|
format.html { redirect_to_on_success }
format.js { render json: {id: #object.id.to_s, label: #model_config.with(object: #object).object_label} }
end
else
handle_save_error :edit
end
It crashes at #object.set_attributes.
You could avoid this problem by not showing the tickets that do not have that flag on your model edit form.
You can do this like this:
edit do
field :tickets do
associated_collection_scope do
proc { |scope| scope.where(flag: false) }
end
end
end
Unfortunately this means that you'll have to specify which fields will be shown and you won't be able to use
include_all_fields
You could use the include_fields method for a nicer syntax to do that
include_fields [:tickets, :other_field, :etc]
I am trying to figure out a graceful way to handle a ActiveRecord::RecordNotUnique exception globally for all my ActiveRecord code. I know about the validates_uniqueness_of validation but I want to rescue the exception directly as I have a constraint on the database in order to avoid bad data due to race conditions. I also don't want to create a bunch of custom methods that directly handle the exception every time I want to save or update an object where this constraint can be violated.
I would prefer not to monkey patch ActiveRecord methods like save() but I am beginning to think that achieving graceful exception handling for all ActiveRecord objects in my code might require that. Below is some code that demonstrates what a solution would look like:
class Photo < ActiveRecord::Base
belongs_to :post
def save(*args)
super
rescue ActiveRecord::RecordNotUnique => error
errors[:base] << error.message
false
end
end
While this works if I call save directly on a Photo object it won't work if I save the object through another model using accepts_nested_attributes_for with validates_associated.
Any help would be greatly apprecaited.
Thanks
Update
The desired outcome is to handle the exception and just add a key/value pair to the object's errors hash and then display form errors back to the user telling them that the email has been taken.
This is covered in the Action Controller Overview Rails Guide. In short, you can use the rescue_from method to register a handler for exceptions. If you use it in ApplicationController then it'll be inherited by all other controllers.
Here's the example from the Guide:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
render plain: "404 Not Found", status: 404
end
end
Go take a look for more information and an additional example.
What I was looking for was the inverse_of option when defining the association on each of the models. What inverse_of does is it causes rails to use the in memory instance of the associated object as opposed to going to the db to fetch the record. I created a save method in the Photo model that looks like this:
def save(*args)
super
rescue ActiveRecord::RecordNotUnique => error
post.errors[:base] << "You can only have one photo be your header photo"
false
end
In the rescue block, when I call post.errors I am getting the unsaved, associated post object rather than rails looking for one in the db based on photo.post_id which at this point is nil because the photo object is invalid which caused the post not to be persisted to the db.
Here are the docs for inverse_of
http://guides.rubyonrails.org/association_basics.html#bi-directional-associations
My gem activerecord-transactionable allows you to do all of the following (a la carte):
rescue
retry with alternate logic (switch from create to find, for example)
add errors to the record that failed to save
use locking
use nested transactions
handle different kinds of database errors in different ways
I am using Ruby on Rails 4.1. I have a "nested" model and in its controller I would like to make the RESTful create action to handle cases when one or more than one records are submitted. That is, my controller create action is:
def create
#nester = Nester.find(:nester_id)
#nesters_nested_objects = #nester.nested_objects.build(create_params)
if #nnesters_ested_objects.save
# ...
else
# ...
end
end
def create_params
params.require(:nesters_nested_object).permit(:attr_one, :attr_two, :attr_three)
end
I would like it to handle both cases when params contain data related to one object and when it contains data related to more than one object.
How can I make that? Should I implement a new controller action (maybe called create_multiple) or what? There is a common practice in order to handling these cases?
Well, if you insist on creating those records aside from their nest, I can propose to go with something like this (it better be a separate method really):
def create_multiple
#nest = Nester.find(params[:nester])
params[:nested_objects].each do |item|
#nest.nested.new(item.permit(:attr_one, :attr_two, :attr_three))
end
if #nest.save
....
else
....
end
end
I want to check that a user is included in a group. In an attempt to do this, I have the following statement:
user_id = current_user.id
unless (group.user.find(user_id))
redirect_to different_path
end
Looking at the docs, "A failure to find the requested object raises a ResourceNotFound
exception if the find was called with an id." How do I write the unless statement so that it operates properly?
If you defined a relation between group and users, you can call current_user.group.present? This will return false if there is no related object.
You can handle the redirect as part of Exception handling
redirect_to path if group.user.find(user_id)
rescue ResourceNotFound
redirect_to differenct_path
end
alternatively and probably a better way would be to build your logic around
user.groups.include?(group)
I'm writing an import routine that will allow a user to upload a CSV file to load their database. Each row of the CSV corresponds to a model.
I'm using FasterCSV to read the file and split the data into individual models, which is working great. I'm just having trouble deciding on the best approach to handle errors.
Right now I have this going, but it really seems wrong to me:
def import(collection)
begin
self.transaction do
collection.collect{|object| object.save!}
end
rescue ActiveRecord::RecordInvalid => invalid
return false
end
return true
end
Is there a better way to save a collection of models?
Using an exception in a loop is going to cause all kinds of headaches for you when you want to track down the problematic record. What you might do is try and save them all, but report on those with errors:
def import(collection)
failed = nil
transaction do
failed = collection.reject { |r| r.save }
unless (failed.empty?)
raise ActiveRecord::Rollback
end
end
failed
end
This presumes you're interested in having a look at the errors. If any records fail, they will be returned in an Array. Otherwise you'll get a nil, which means no errors.
If you don't care, you can always just do a quick and dirty save:
def import(collection)
transaction do
collection.each(&:save!)
end
end
This will pop an ActiveRecord::RecordInvalid exception for the first failure.
For this problem I think the better approach is yours. Maybe not all the records in the csv aré going yo validare, and those that not doesnt matter (log them in order to know the errors)