Rails validation for has_many association

I am having problems with has_many relationship checks where children exist but the parent is not. However, when creating / saving a parent object, I want certain children (with certain attributes) to be already saved.

There is a Parent object that has_many Child objects. Child objects are first stored in the database and therefore do not have a reference to the parent. Association structure:

 Parent - has_many :children Child - someProperty: string - belongs_to: parent 

For example, there are three children:

 #1 {someProperty: "bookmark", parent: nil} #2 {someProperty: "history", parent: nil } #2 {someProperty: "window", parent: nil } 

A parent is valid only if it contains children with someProperty history and window .

I set the parent element inside the controller as:

 p = Parent.new(params[:data]) for type in %w[bookmark_id history_id window_id] if !params[type].blank? p.children << Child.find(params[type]) end end // save the parent object p now p.save! 

When children are assigned to a parent with << , they are not saved immediately because the parent identifier does not exist. And for a parent to be saved, he must have at least these two children. How can I solve this problem? Any input is welcome.

+4
source share
2 answers

Not sure why you need to do this, but anyway, how about this?

 class Parent < ActiveRecord::Base CHILDREN_TYPES = %w[bookmark_id history_id window_id] CHILDREN_TYPES.each{ |c| attr_accessor c } has_many :children before_validation :assign_children validate :ensure_has_proper_children private def assign_children CHILDREN_TYPES.each do |t| children << Child.find(send(t)) unless send(t).blank? end end def ensure_has_proper_children # Test if the potential children meet the criteria and add errors to :base if they don't end end 

Controller:

 ... p = Parent.new(params[:data]) p.save! ... 

As you can see, I have moved all the logic to model in the first place. Then there is a two-step process for saving children. First, we assign the children to the parents, and then check if they meet the required criteria (insert your logic there).

Sorry for being short. I will answer any additional questions if necessary.

+5
source

Firstly, if you want children to be saved without a parent identifier, then there is no point in doing this

  p = Parent.new(params[:data]) for type in %w[bookmark_id history_id window_id] if !params[type].blank? p.children << Child.find(params[type]) end end 

whole goal

  p.children << some_child 

consists in attaching a parent identifier to a child object that you are not executing here because the parent does not exist yet.

Another thing is if you just want to make sure that the parent has a child, and if you create child and parent elements together, you can use the transaction block around the parent and child creation, which will ensure that the parent is a child, for example

  transaction do p = create_parent p.children << child1 p.children << child2 end 

Thus, within the framework of the transaction, if at some stage the code crashes, it will roll back the entire transaction db, i.e. you will have either one parent with 2 children, or nothing if this is the final condition you are looking for.

EDIT: since you cannot create a parent if it does not have 2 children, instead

 p = Parent.new(params[:data]) for type in %w[bookmark_id history_id window_id] if !params[type].blank? p.children << Child.find(params[type]) end end 

do

  children = [] for type in %w[bookmark_id history_id window_id] if !params[type].blank? children << Child.find(params[type]) end end if children.size >= 2 p = Parent.create!(params[:data]) children.each {|child| p.children << child} end 

It makes sense

+1
source

Source: https://habr.com/ru/post/1300145/


All Articles