I have two models, one is the parent of the other, and the parent accepts_nested_attributes_for and validates_associated the children.
However, some of my validations have an :if that needs to check one of the properties of the parent.
I was thinking that I could do something like this:
validates_presence_of :blah, :if => Proc.new{|thing| thing.parent.some_value.present?}
However, the 'parent' relationship doesn't appear to be setup at the time of validation (I would assume the children get instantiated and validated first.
Therefore is there any way of doing what I'm thinking of? Is it possible?
You can use before_update or before_create callbacks as per your need like this..
def before_update
self.errors.add("Error Message") if self.parent.some_value.present?
return false if self.errors.count > 0
end
def before_create
self.errors.add("Error Message") if self.parent.some_value.present?
return false if self.errors.count > 0
end
This kind of validation should work:
validates_associated :children
But it won't
The reason is, as far as I understand, beause using acceptes_nested_attributes_for is creating nested objects straight to database via one transaction without passing any children validations.
What you can do here: write your own validation in parent model and validate creating children objects.
Use the :inverse_of option for the association on the parent, so the children will have a reference to the parent when they are built.
class Parent < ActiveRecord::Base
has_many :children, :inverse_of => :parent
accepts_nested_attributes_for :children
end
class Child < ActiveRecord::Base
belongs_to :parent
end
p = Parent.new :children_attributes => { 0 => { :child_attribute => 'value' } }
p.children.first.parent #=> shouldn't be nil anymore
Related
How can I create a parent model only if its children models fit some validations alltogether like the sum of their attributes equal come value, etc?
I have an parent model as:
class Foo < ApplicationRecord
has_many :bars, dependent: :destroy
accepts_nested_attributes_for :bars, allow_destroy: true
end
And the child model as:
class Bar < ApplicationRecord
belongs_to :foo
end
Is there a right way of doing it? Where should I validate the children models? Also how could I test it with rspec? Like this?
before do
#foo = create(:foo)
#bar = create(:bar, value: 30, foo_id: #foo.id)
end
Rails offers validates_associated, which will ensure the associated records are valid. If the associated records are invalid, the parent record will not be saved.
In Foo:
class Foo < ApplicationRecord
has_many :bars, dependent: :destroy
accepts_nested_attributes_for :bars, allow_destroy: true
validates_associated :bars
end
In Bar:
class Bar < ApplicationRecord
belongs_to :foo
include ActiveModel::Validations
validates_with BarValidator
end
In BarValidator, which is a custom validator:
class BarValidator < ActiveModel::Validator
def validate(record)
record.errors.add :some_error, 'Some message' unless condition_met?
end
end
You've stated:
only if its children models fit some validations alltogether like the sum of their attributes equal come value, etc?
which is somewhat ambiguous. If you truly do need to calculate the sum of children then you can add a validator to the parent which maps through the children and appends an error on failure to meet conditional:
In Foo, (or preferably, a validator):
validate :children_condition
def children_condition
errors[:base] << "Some message" if bars.map(&:attribute).sum != expected_minimum_value
end
Important notes from the documentation on validates_associated:
WARNING: This validation must not be used on both ends of an association. Doing so will lead to a circular dependency and cause infinite recursion.
NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association is both present and guaranteed to be valid, you also need to use validates_presence_of.
As for:
Also how could I test it with rspec?
I would create a test suite of valid parent, valid child && valid parent, invalid child, etc and expect the Model.count to have increased by the expected amount (which would be zero in the latter example).
I've run into a gotcha when trying to create a parent and child at the same time. I would think an exception should be raised when the associated parent fails to save, but it doesn't.
class Child < ApplicationRecord
belongs_to :parent
validates_presence_of :parent, :name
end
class Parent < ApplicationRecord
has_one :child
validates_presence_of :name
end
Notice the child saves and their is no visibility into the save issue with the parent:
parent = Parent.new
parent.valid? # => false
child = Child.create!(name: 'something', parent: parent) # => true
child.parent_id # => nil
child.reload.valid? # => false
An an invalid child has been created. Why is the create! method not calling create! on the parent as well so an exception is raised?
Its worth noting that When the same process is followed, but instead we start from the parent, we get the behavior I expect:
child = child.new
child.valid? # => false
parent = Parent.create!(name: 'something', child: child) # => ActiveRecord::Exception
parent.valid? # => false
I know some work arounds (e.g. validates_associated :parent on the child), but I'm trying to understand why Rails is behaving the way it is.
Rails doesn't know you're trying to save them at the same time. If you want to save them both at once, try nested attributes.
I would expect this to do what you want:
class Child < ApplicationRecord
belongs_to :parent
validates_presence_of :parent
end
class Parent < ApplicationRecord
has_many :children
validates_presence_of :name
accepts_nested_attributes_for :children, allow_destroy: true
end
parent = Parent.new children: [child]
parent.save!
Why would the exception be raised? You are setting the parent and as far as the child is concerned, the parent exists (passing the validation) and it will not call the create! on the parent and IMO nor should it.
Your workaround is to validate on the parent_id instead of the parent or create the child via parent.children.create!. The latter will raise ActiveRecord::RecordNotSaved: You cannot call create unless the parent is saved if the parent is not persisted
change children to
class Child < ApplicationRecord
belongs_to :parent
validates_presence_of :parent, :id
end
It is as #AbM said you should put the constraint on some field, This will give you ActiveRecord::RecordInvalid: Validation failed: Id can't be blank exception.
On Rails 4.1.1.
I have a model, User and a model Comment (example models).
User has_many :comments.
Take this controller code:
#user = User.find(1)
#user.comments.new({ body: "Hello World!" })
#user.save
This causes a validation error on my user object, because the comment has not been persisted to the database.
I have a situation in which I don't want the comment to persist to the database, but exist in memory so that #user.comments has the additional "record" show up in my view, but doesn't actually exist (as a preview).
For now I'm just removing the entry from the array in the controller, but I'd like to prevent these errors from happening in the future by having a more global method, maybe in a before_save hook.
Is this a common problem? Am I experiencing a design pattern that's not "the rails way"?
to save child along with parent..your parent model should have accepts_nested_attributes_for
in user.rb
accepts_nested_attributes_for :comments
Note that the :autosave option is automatically enabled on every association that #accepts_nested_attributes_for is used for.
so now..you can add/update parent/child very easily
params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'
It also allows you to update the avatar through the member:
params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
member.update params[:member]
member.avatar.icon # => 'sad'
If you are not using ActionView form helpers, then the accepts_nested_attributes_for is not needed at all. That simply checks for nested attributes in the params to autoload and save the association. What you really need is autosave and inverse_of on the has_many/has_one relation when wanting to save both the parent and child association:
class Parent < ApplicationRecord
has_one :child, inverse_of: :parent, autosave: true
end
class Child < ApplicationRecord
belongs_to :parent
end
Now you will be able to both CREATE and UPDATE the association along with the parent as so:
parent.child.assign_attributes(attrs)
I'm struggling since some hours in order to make validations of nested attributes work in my rails app. A small caveat is that I have to validate nested attributes dynamically based off of their parent's attributes, as the amount of info required changes over time according to where in the process the parent is.
So here's my setup: I have a parent with many different associated models and I want to validate subsequently nested attributes of those every time I save the parent. Given the fact that validations change dynamically, I had to write a custom validation method in the model:
class Parent < ActiveRecord::Base
attr_accessible :children_attributes, :status
has_many :children
accepts_nested_attributes_for :children
validate :validate_nested_attributes
def validate_nested_attributes
children.each do |child|
child.descriptions.each do |description|
errors.add(:base, "Child description value cant be blank") if description.value.blank? && parent.status == 'validate_children'
end
end
end
end
class Child < ActiveRecord::Base
attr_accessible :descriptions_attributes, :status
has_many :descriptions
belongs_to :parent
accepts_nested_attributes_for :descriptions
end
In my controller I call update_attributes on the parent when I want to save. Now the problem is that, apparently, rails runs the validations against the database and not against the object that was modified by the user or the controller. So what might happen is that a child's value is erased by a user and the validations will pass, while later validations will not pass because the item in the database is not valid.
Here's a quick example of this scenario:
parent = Parent.create({:status => 'validate_children', :children_attributes => {0 => {:descriptions_attributes => { 0 => {:value => 'Not blank!'}}}})
#true
parent.update_attributes({:children_attributes => {0 => {:descriptions_attributes => { 0 => {:value => nil}}}})
#true!! / since child.value.blank? reads the database and returns false
parent.update_attributes({:children_attributes => {0 => {:descriptions_attributes => { 0 => {:value => 'Not blank!'}}}})
#false, same reason as above
The validation works for first-level associations, e.g. if a Child has a 'value' attribute, I could run a validation the way I do. The problem is with deep associations that apparently cannot be validated before saving.
Could anyone point me in the right direction of how to solve this? The only way I currently see is by saving records, validating them afterwards and deleting / reverting them if the validation fails, but I am honestly hoping for something more clean.
Thank you all in advance!
SOLUTION
So it turns out I was running validations on deep nested models by referencing those directly in the custom validation, this way:
class Parent < ActiveRecord::Base
[...]
has_many :descriptions, :through => :children
[...]
def validate_nested_attributes
descriptions.each do |description|
[...]
end
end
end
Which for some reason leads to the problems I was having above. Thanks Santosh for testing my example code and reporting it was working, this pointed me in the right direction to figure this out.
For future reference, the code in the original question works for this sort of dynamic, deeply-nested validations.
I think you should use validates_associated for this
with the following validation in Child
validates :value, :presence => true, :if => "self.parent.status == 'validate_children'"
I'm trying to access my parent model in my child model when validating. I found something about an inverse property on the has_one, but my Rails 2.3.5 doesn't recognize it, so it must have never made it into the release. I'm not sure if it's exactly what I need though.
I want to validate the child conditionally based on parent attributes. My Parent model has already been created. If the child hasn't been created when I update_attributes on the parent, then it doesn't have access to the parent. I'm wondering how I can access this parent. It should be easy, something like parent.build_child sets the parent_id of the child model, why is it not doing it when building the child for accepts_nested_attributes_for?
For Example:
class Parent < AR
has_one :child
accepts_nested_attributes_for :child
end
class Child < AR
belongs_to :parent
validates_presence_of :name, :if => :some_method
def some_method
return self.parent.some_condition # => undefined method `some_condition' for nil:NilClass
end
end
My form is standard:
<% form_for #parent do |f| %>
<% f.fields_for :child do |c| %>
<%= c.name %>
<% end %>
<% end %>
With an update method
def update
#parent = Parent.find(params[:id])
#parent.update_attributes(params[:parent]) # => this is where my child validations take place
end
I had basically the same problem with Rails 3.2. As suggested in the question, adding the inverse_of option to the parent's association fixed it for me.
Applied to your example:
class Parent < AR
has_one :child, inverse_of: :parent
accepts_nested_attributes_for :child
end
class Child < AR
belongs_to :parent, inverse_of: :child
validates_presence_of :name, :if => :some_method
def some_method
return self.parent.some_condition # => undefined method `some_condition' for nil:NilClass
end
end
I had a similar problem: Ruby on Rails - nested attributes: How do I access the parent model from child model
This is how I solved it eventually; by setting parent on callback
class Parent < AR
has_one :child, :before_add => :set_nest
accepts_nested_attributes_for :child
private
def set_nest(child)
child.parent ||= self
end
end
You cannot do this because in-memory child doesn't know the parent its assigned to. It only knows after save. For example.
child = parent.build_child
parent.child # => child
child.parent # => nil
# BUT
child.parent = parent
child.parent # => parent
parent.child # => child
So you can kind of force this behavior by doing reverse association manually. For example
def child_with_inverse_assignment=(child)
child.parent = self
self.child_without_inverse_assignment = child
end
def build_child_with_inverse_assignment(*args)
build_child_without_inverse_assignment(*args)
child.parent = self
child
end
def create_child_with_inverse_assignment(*args)
create_child_without_inverse_assignment(*args)
child.parent = self
child
end
alias_method_chain :"child=", :inverse_assignment
alias_method_chain :build_child, :inverse_assignment
alias_method_chain :create_child, :inverse_assignment
If you really find it necessary.
P.S. The reason it's not doing it now is because it's not too easy. It needs to be explicitly told how to access parent/child in each particular case. A comprehensive approach with identity map would've solved it, but for newer version there's :inverse_of workaround. Some discussions like this one took place on newsgroups.
check these sites, maybe they'll help you...
Rails Nested Attributes Association Validation Failing
accepts_nested_attributes_for child association validation failing
http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
it seems, rails will assign parent_id after child validation succeeds.
(as parent has an id after it's saved)
maybe worth trying this:
child.parent.some_condition
instead of self.parent.some_condition ... who knows...