Errors raised twice in when inherited model object validation fails - ruby-on-rails

Subclass validations as well as superclass validations implemented. How to discard superclass validations in subclass?
My code:
class a < ActiveRecord::Base
validates_presence_of :price
end
class a2 < a
validates_presence_of :price
end
When I am creating a object for a2, using following command:
x = a2.new
x.save
the following errors are displayed:
x.errors.full_messages
=> ['price can't be blank','price can't be blank']
How can I resolve this, so that validations of superclass are ignored.

The two error messages are expected according to your code.
What you did on inherited model is to add one more validation to the validation chain, instead of overwrite it.

I tried using "Add to errors" unless errors.added? which works fine.

Related

Why does a child model failing validation on destroy blow up accepts_nested_attributes_for?

We have an accepts_nested_attributes_for with a dependent: destroy that works fine with the _destroy param arg.
We added a validation on the child, and that works as expected.
But when we combine the two the save on the parent throws an unhandled error instead of returning false.
class Foo < ActiveRecord::Base
accepts_nested_attributes_for :bars, allow_destroy: true
...
end
class Bar < ActiveRecord::Base
before_destroy :can_do?
def can_do?
unless yeah_sure
errors.add(:base, I18n.t("the.translation"))
false
end
end
...
end
The bar_spec tests yeah_sure in both cases, with errors being empty or present (and the correct message is in there).
When I stepped through the rails portion there are 3 levels of catch, rollback/cleanup, and release in active_support and transaction.
I also tried to rescue in the controller, both method level and a begin block, and neither of those trapped the error, which is strange.
Any idea why foo.save is throwing an error instead of returning false?
Rails 4.2.10
You can use the inverse_of to run the validation on nested attributes.
Also, you can reject the attributes using reject_if block
please refer the following link.

Rails how can I get all custom validate methods that defined in a model

I have a Model called Person.
class Person < ActiveRecord::Base
validate_presence_of :name
validate :check_card_number
def check_card_number
errors.add(:card_number, "Card Number can't be blank") if card_number.blank?
-----
# some other stuffs here
----
end
end
If I am calling the below method I am getting the validator class name
Person.validators_on(:name) => [ActiveModel::Validations::PresenceValidator]
but for
Person.validators_on(:card_number) => []
How can I check I am validating the presence of card number?
validate method actually inserts a method(which is passed as an argument) into a callback chain. Because, it doesn't use any defined validators from ActiveModel. So, it can be accessed like so:
Person.send(:get_callbacks, :validate).detect{ |cb| cb.filter == :check_card_number }
However, condition in method "card_number.blank?" looks from presence of card_number attribute. Which in my guess would be a good fit for:
validate_presence_of :name, :card_number
Try this:-
ModelName._validate_callbacks.to_a.reject { |validation| validation.filter.to_s.starts_with?('validate_associated_records') }
Here 'reject' is used to ignore some default validations.
I doubt this is possible. Just tried Person.validators but it does not return custom validation methods.
If you really want validators_on and validators to return all validators, you can create a custom ActiveModel::Validator class and register it in your model with validates_with.

Invalidating parent model save through child before_save callback

I have two models, a Parent and a Child (as outlined below). The child model has a before_save callback to handle some external logic, and if it encounters any errors, the callback invalidates that model being saved.
class Parent < ActiveRecord::Base
has_one :child
accepts_nested_attributes_for :child
validates :child, :presence => true
validates_associated :child
end
class Child < ActiveRecord::Base
belongs_to :parent
before_save :external_logic
validates :parent, :presence => true
def external_logic
begin
# Some logic
rescue
#Invalidate child model
errors.add(:base, "external logic failed")
return false
end
end
end
The problem that I'm running into is that the Child model instance is created as through the nested attributes of the Parent model. When the external logic fails, I want the child model AND the parent model to not be saved, but instead the parent model is being saved on its own. How can I achieve this?
Please note, I am aware of validation callbacks, but they are not suitable in this case. The child model callback has to be a before_save.
EDIT #1
I already know about transactions, and don't consider someone telling me "hey, wrap it around a transaction externally" to be a valid response. This question is explicitly about how to solve this issue through a before_save call.
Why I can't use validations on create - as mentioned in the comments, the external bit of logic needs to be guaranteed to run ONLY before a database save. Validation calls can happen multiple times with or without altering the database record, so that's an inappropriate place to put this logic.
EDIT #2
Ok, apparently having the before_save return false does prevent the parent being saved. I have verified that through the console and actually inspecting the database. However, my rspec tests are telling me otherwise, which is just odd. In particular, this is failing:
describe "parent attributes hash" do
it "creates new record" do
parent = Parent.create(:name => "name", :child_attributes => {:name => "childname"})
customer.persisted?.should be_false
end
end
Could that be an rspec/factory_girl bit of weirdness?
EDIT #3
The test error is because I'm using transactional fixtures in Rspec. That was leading to tests that incorrectly tell me that objects were being persisted in the database when they really weren't.
config.use_transactional_fixtures = true
Okay so your problem is with the ActiveRecord::Callbacks order.
As you can see on the linked page first validation is processed and if validation was successful then before_save callbacks are run. before_save is a place where you can assume every validation passed so you can manipulate a bit data or fill a custom attribute based on other attributes. Things like that.
So what you could do is just say for the Child model:
validate :external_logic and just remove the before_save :external_logic callback.
It's equivalent with what you want to do. When a Parent instance is created it will just error out if the Child object fails to validate, which will happen in your :external_logic validation method. This is a custom validation method technique.
After OP update:
Still you can use :validate method. You can set it to only run on create with:
validate :external_logic, :on => :create.
If you are running into issue that you need this to run on update as well, that is the default behavior. Validations are run on .create and .update only.
OR If you want to stick to before_save:
The whole callback chain is wrapped in a transaction. If any before callback method returns exactly false or raises an exception, the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception.
I see you did return false so it should work as expected. How do you use Parent.create! method? What are the arguments there?
Make sure you are using it like (supposing .name is an attribute of Parent and Child):
Parent.create!{
:name => 'MyParent'
# other attributes for Parent
:child_attributes => {
:name => 'MyChild'
# other attributes for Child
}
}
This way it both the Parent and Child object will be created in the same transaction, so if your before_save method returns false Parent object will be rolled back.
OR
If you cannot use this format you could just try using pure transactions (doc, example in guides):
Parent.transaction do
p = Parent.create
raise Exception if true # any condition
end
Anything you do inside of this transaction will be rolled back if there is an exception raised inside the block.

How to run validations of sub-class in Single Table Inheritance?

In my application, I have a class called Budget. The budget can be of many types.. For instance, let's say that there are two budgets: FlatRateBudget and HourlyRateBudget. Both inherit from the class Budget.
This is what I get so far:
class Budget < ActiveRecord::Base
validates_presence_of :price
end
class FlatRateBudget < Budget
end
class HourlyRateBudget < Budget
validates_presence_of :quantity
end
In the console, if I do:
b = HourlyRateBudget.new(:price => 10)
b.valid?
=> false
b.errors.full_messages
=> ["Quantity can't be blank"]
As, expected.
The problem is that the "type" field, on STI, comes from params.. So i need to do something like:
b = Budget.new(:type => "HourlyRateBudget", :price => 10)
b.valid?
=> true
Which means that rails is running validations in the super-class instead of instantiating the sub class after I set up the type.
I know that is the expected behaviour, since I'm instantiating a class that dosen't need the quantity field, but I wonder if there is anyway to tell rails to run the validations for the subclass instead of the super.
You could probably solve this with a custom validator, similar to the answer on this question: Two models, one STI and a Validation However, if you can simply instantiate the intended sub-type to begin with, you would avoid the need for a custom validator altogether in this case.
As you've noticed, setting the type field alone doesn't magically change an instance from one type to another. While ActiveRecord will use the type field to instantiate the proper class upon reading the object from the database, doing it the other way around (instantiating the superclass, then changing the type field manually) doesn't have the effect of changing the object's type while your app is running - it just doesn't work that way.
The custom validation method, on the other hand, could check the type field independently, instantiate a copy of the appropriate type (based on the value of the type field), and then run .valid? on that object, resulting in the validations on the sub-class being run in a way that appears to be dynamic, even though it's actually creating an instance of the appropriate sub-class in the process.
I've done something similar.
Adapting it to your problem:
class Budget < ActiveRecord::Base
validates_presence_of :price
validates_presence_of :quantity, if: :hourly_rate?
def hourly_rate?
self.class.name == 'HourlyRateBudget'
end
end
For anyone looking for example code, here's how I implemented the first answer:
validate :subclass_validations
def subclass_validations
# Typecast into subclass to check those validations
if self.class.descends_from_active_record?
subclass = self.becomes(self.type.classify.constantize)
self.errors.add(:base, "subclass validations are failing.") unless subclass.valid?
end
end
Instead of setting the type directly set the type like that... Instead, try:
new_type = params.fetch(:type)
class_type = case new_type
when "HourlyRateBudget"
HourlyRateBudget
when "FlatRateBudget"
FlatRateBudget
else
raise StandardError.new "unknown budget type: #{new_type}"
end
class_type.new(:price => 10)
You could even transform the string into its class by:
new_type.classify.constantize but if it's coming in from params, that seems a bit dangerous.
If you do this, then you'll get a class of HourlyRateBudget, otherwise it'll just be Budget.
Better yet, use type.constantize.new("10"), however this depends on that the type from params must be correct string identical to HourlyRateBudget.class.to_s
I also required the same and with the help of Bryce answer i did this:
class ActiveRecord::Base
validate :subclass_validations, :if => Proc.new{ is_sti_supported_table? }
def is_sti_supported_table?
self.class.columns_hash.include? (self.class.inheritance_column)
end
def subclass_validations
subclass = self.class.send(:compute_type, self.type)
unless subclass == self.class
subclass_obj= self.becomes(subclass)
self.errors.add(:base, subclass_obj.errors.full_messages.join(', ')) unless subclass_obj.valid?
end
end
end
Along the lines of #franzlorenzon's answer, but using duck typing to avoid referencing class type in the super class:
class Budget < ActiveRecord::Base
validates_presence_of :price
validates_presence_of :quantity, if: :hourly_rate?
def hourly_rate?
false
end
end
class HourlyRateBudget < Budget
def hourly_rate?
true
end
end

Rails validating virtual attributes

I this model:
class Bunny < ActiveRecord::Base
attr_accessor :number
validates_presence_of :number
validates_numericality_of :number
end
Whenever I submit a form to create this model I get the following error:
undefined method `number_before_type_cast' for #<Bunny:0x103624338>
I fixed the problem by adding this method to my Bunny model:
def number_before_type_cast
number
end
I don't like it, but I suppose it will work until someone posts a better solution.
Rails generates the FIELDNAME_before_type_cast in the model for each field. It stores the value from the form as a String before it's converted (cast) in this case to a number (it might be a date for example). This cast occurs before save, but after validation.
So when validation occurs before that cast is performed it has to use the "before type cast" value to get the value. Since this is not generated for your attribute, it fails.

Resources