I have to import big CSV tables. My code search for existing objects in database otherwise i create a new instance.
My wish is to add custom error messages if it is necessary after that i have to validate my model.
Here my code example:
def customer_validate
p = Customer.new(customer_params)
p.errors.add(:name, "here my error message")
p.valid?
return p
end
Problem:
All errors get lost after p.valid? How can i combine custom error messages and valid? messages?
You can't. When you run valid? all the existing errors are cleared. There are a few possible workarounds.
One simple possibility is to toggle the order.
def customer_validate
c = Customer.new(customer_params)
c.valid?
c.errors.add(:name, "here my error message")
c
end
and you can check whether the customer is valid using
customer = customer_validate
customer.errors.any?
Another approach would be to attach the errors to a temporary customer object, run valid? on your primary object and then merge the two error lists.
As a side note, do not name the variables p since p is a method in Ruby, you will end up shadowing it.
Or you can use after_validation like below.
after_validation do
errors.add(:name, 'abc')
end
Related
Need help understanding this code, as what to my knowledge I know "<<" append to a collection but here it saves the record correctly, how come it does without calling .save method?
#user.rb
has_many :saved_properties, through: :property_saves, source: :property
#users_controller.rb
def update
if #user.saved_properties << Property.find(params[:saved_property_id])
render plain: "Property saved"
end
In the has_many documentation it says:
Adds one or more objects to the collection by setting their foreign
keys to the collection's primary key. Note that this operation
instantly fires update SQL without waiting for the save or update
call on the parent object, unless the parent object is a new record.
Maybe looking at the source code will help you. This is my trail of searches based on the << method in activerecord:
def <<(*records)
proxy_association.concat(records) && self
end
rails/collection_proxy.rb at 5053d5251fb8c03e666f1f8b765464ec33e3066e · rails/rails · GitHub
def concat(*records)
records = records.flatten
if owner.new_record?
load_target
concat_records(records)
else
transaction { concat_records(records) }
end
end
rails/collection_association.rb at 5053d5251fb8c03e666f1f8b765464ec33e3066e · rails/rails · GitHub
def concat_records(records, should_raise = false)
result = true
records.each do |record|
raise_on_type_mismatch!(record)
add_to_target(record) do |rec|
result &&= insert_record(rec, true, should_raise) unless owner.new_record?
end
end
result && records
end
rails/collection_association.rb at 5053d5251fb8c03e666f1f8b765464ec33e3066e · rails/rails · GitHub
def insert_record(record, validate = true, raise = false)
set_owner_attributes(record)
set_inverse_instance(record)
if raise
record.save!(validate: validate)
else
record.save(validate: validate)
end
end
https://github.com/rails/rails/blob/5053d5251fb8c03e666f1f8b765464ec33e3066e/activerecord/lib/active_record/associations/has_many_association.rb#L32
def insert_record(record, validate = true, raise = false)
ensure_not_nested
if record.new_record? || record.has_changes_to_save?
if raise
record.save!(validate: validate)
else
return unless record.save(validate: validate)
end
end
save_through_record(record)
record
end
https://github.com/rails/rails/blob/5053d5251fb8c03e666f1f8b765464ec33e3066e/activerecord/lib/active_record/associations/has_many_through_association.rb#L38
As you can see, in the end, it does call the save method.
Disclaimer: I'm not that familiar with Rails souce code, but you have interesting question.
In a has_many relationship the link information is saved in the target record. This means that << would have to modify that record in order to add it to the set.
Perhaps intending convenience, ActiveRecord automatically saves these for you when making an assignment if the assignment was successful. The exception is for new records, the record they're being associated with doesn't have any identifier so that has to be delayed. They are saved when the record they're associated with is finally created.
This can be a little confusing, perhaps unexpected, but it's actually the thing you'd want to happen 99% of the time. If you don't want that to happen you should manipulate the linkage manually:
property = Property.find(params[:saved_property_id])
property.user = #user
property.save!
That's basically equivalent but a lot more verbose.
I'm trying to implement a subset of the Repository pattern using Rails and am having some trouble understanding how to pass errors from a class or repository, back to the controller and then in to the view.
For instance, I have a class DomainClass which simply allows users to register a subdomain. There are some rules there - it must be unique, must only contain letters and numbers - what have you.
When one or more model validations fails, I want to pass this value to the view somehow. While passing those errors, I must also return "false" to the controller so it knows that whatever I've tried to do has failed.
This seems very simple. What am I missing here?
Class - if this fails, I should pass the validation error to my controller.
# Create a domain ID and return the newly injected ID.do
# If a new Domain ID couldn't be created for any reason,
# return FALSE.
def create_domain(domain_name)
#domain.domain_name = domain_name
if #domain.save
return #domain.id
else
return false
end
end
Controller - From here, I should return the model's validation error to the view.
# Try to save our user to the database
if new_user = #domain.create_domain(domain_name)
# Do things that are good.
else
# Return model's validation error here.
end
I see two options for designing create_domain in a way that will still make sense when you reimplement it on top of some non-ActiveRecord store. Which one you would use depends on the situations in which you expect to use it.
Define a class to hold all of create_domain's possible return values. This would be a start:
class SaveResult < Struct.new :id, :errors
def succeeded?
errors.empty?
end
end
In create_domain,
return SaveResult.new *(
if #domain.save
#domain.id, []
else
nil, #domain.errors # this is an ActiveModel::Errors, but tell your callers it's a Hash
end
)
Then a caller can do something like
result = #domain.create_domain name
if result.succeeded?
# celebrate
else
# propagate errors to model
end
This design has the disadvantage that a caller has to remember to check whether there are errors. It would work well if most callers have to do something explicitly with the errors if there are any (as is the case above).
Define an exception to be raised when there are errors:
class SaveError < Exception
attr_accessor :errors # again, pretend it's just a hash
def initialize(errors)
self.errors = errors
end
end
In create_domain,
if #domain.save
return #domain.id
else
raise SaveResult, #domain.errors
emd
Then a caller can do something like
begin
new_user_id = #domain.create_domain name
# celebrate
rescue SaveError => e
# propagate errors to model
end
This design has the disadvantage that exception handling is a bit uglier to write than an if/else. It has the advantage that if you can just allow all such exceptions to propagate out of the caller and handle them in one place in ActionController#rescue_from or something like that, callers wouldn't need to write any error handling at all.
hi all what i am trying to do is create a "soft" validation in other words instead of my failing the validation and not saving the data to the DB, id like to have the validation give a warning to the user and allow the user to save faulty data if they so choose. but the validator will give them a warning before.
i want to do somehting like the following:
class MyModel < ActiveRecord::Base
warnings do
validate :warnings_validation
end
def warnings_validation
warnings.add(:name_of_element, "warning message") unless x == x
end
end
my model uses alot of inheritance and so gems like validations_scope dont work any ideas what i can do/use ?
I believe you could inspire yourself from the ActiveModel::Error example to implement the warning feature.
Explanation :
If you look at how the data is validated in Active Record, simply take a look at the valid? method :
def valid?(context = nil)
context ||= default_validation_context
output = super(context)
errors.empty? && output
end
The context and the output is not important, what matter is that the valid? method check if the errors instance variable is empty or not.
In other words, in the previous link I gave you, simply renaming the errors instance variable into warnings should do the trick. You'd have to create a custom validator using rails built-in and then simply call warnings.add(:name, "error") when needed. It should save the records while populating the warnings variable.
see this - custom validation
try this
validate :warnings_validation
def warnings_validation
self.name_of_element.blank?
errors.add(:name_of_element, "warning message") unless x == x
end
end
If a user tries to enter a duplicate entry into a table, they get a full page nasty error
ActiveRecord::RecordNotUnique in Admin::MerchantsController#add_alias
Mysql2::Error: Duplicate entry '2-a' for key 'merchant_id': ..
Is there a way to give a flash message instead like "This record already exists"?
This thread from railsforum can help you
I wouldn't recommend checking for the uniqueness and specifically responding to this validation rule via a flash message, this conventionally should be handled by the form's error messages instead.
Nonetheless, in the controller actions for creating and updated you could wrap the conditional block which decides where to send the user, with a check for uniqueness first:
def create
#merchant = Merchant.new params[:merchant]
if Merchant.where(:merchant_id => #merchant.id).count > 0
flash[:error] = "The merchant id #{#merchant.id} already exists"
render :create # amend this to :update when applying to the update action
else
# your normal generated save and render block
end
end
This isn't the cleanest way of achieving your goal, but I think it'll be easiest to understand.
Would really recommend the model validations and form error messages instead, which if you are usung the generated scaffolding, all you need to do is add a model validation and the form throw out the error messages to the user for you:
# app/models/merchant.rb
class Merchant < ActiveRecord::Base
validates_uniqueness_of :merchant_id
end
I have the following code block:
unless User.exist?(...)
begin
user = User.new(...)
# Set more attributes of user
user.save!
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e
# Check if that user was created in the meantime
user = User.exists?(...)
raise e if user.nil?
end
end
The reason is, as you can probably guess, that multiple processes might call this method at the same time to create the user (if it doesn't already exist), so while the first one enters the block and starts initializing a new user, setting the attributes and finally calling save!, the user might already be created.
In that case I want to check again if the user exists and only raise the exception if it still doesn't (= if no other process has created it in the meantime).
The problem is, that regularly ActiveRecord::RecordInvalid exceptions are raised from the save! and not rescued from the rescue block.
Any ideas?
EDIT:
Alright, this is weird. I must be missing something. I refactored the code according to Simone's tip to look like this:
unless User.find_by_email(...).present?
# Here we know the user does not exist yet
user = User.new(...)
# Set more attributes of user
unless user.save
# User could not be saved for some reason, maybe created by another request?
raise StandardError, "Could not create user for order #{self.id}." unless User.exists?(:email => ...)
end
end
Now I got the following exception:
ActiveRecord::RecordNotUnique: Mysql::DupEntry: Duplicate entry 'foo#bar.com' for key 'index_users_on_email': INSERT INTO `users` ...
thrown in the line where it says 'unless user.save'.
How can that be? Rails thinks the user can be created because the email is unique but then the Mysql unique index prevents the insert? How likely is that? And how can it be avoided?
In this case, you might want to use a migration to create an unique index on a user table key, so that the database will raise an error.
Also, don't forget to add a validates_uniqueness_of validation in your user model.
The validation doesn't always prevent duplicate data (there's a really minimum chance that two concurrent requests are written at the same millisecond).
If you use the validates_uniqueness_of in combination with an index, you don't need all that code.
unless User.exist?(...)
begin
user = User.new(...)
# Set more attributes of user
user.save!
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique => e
# Check if that user was created in the meantime
user = User.exists?(...)
raise e if user.nil?
end
end
becomes
user = User.new(...)
# Set more attributes of user
if user.save
# saved
else
# user.errors will return
# the list of errors
end
Rails validations aren't able to detect race conditions in the database; the solution we use is to also add database constraints.
Here's our brief page of links about this: Rails ActiveRecord Validations: validates_uniqueness_of races