Rails - remove object if has no childs - ruby-on-rails

I'm struggling with the following problem: how to force object to destroy when it has no childs (or more precisly last child got destroyed). I tried following soltions:
Adding counter cache to the parent and destroing him if it is equal to 0, but then i couldn't create it using Factory Girl it was immediately destroyed.
Last solution from Delete Orphaned Parent, unfortunatelly it won't work with destroy_all - I'm getting stack overflow.
I cannot find any other up-to-date solution. Any one have any idea how can it be done?

One way to handle this is in an after_destroy callback on the the child object. Here's an example with two models named Parent and Child:
class Parent < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :parent
after_destroy: :destroy_orphaned_parent
def destroy_orphaned_parent
parent.destroy if parent.children.empty?
end
end
This solution works fine with destroy_all and will work for creating Parent records with no children in FactoryGirl.

I would suggest the following:
(1) Child model should not be able to call parent_model.destory, which is against model design pattern.
(2) In parent model after_update callback, check if children.count > 0. destroy if no associations found.

Related

self join association undefined method for nil:NilClass

I have a model, Item, that comes in three types, implemented using Single Table Inheritance. Item has a tree hierarchy that is represented using a has_many :through relationship for both the parents (called groups) and the children (called sub_items). One of the subclasses (we'll call it ItemA) must have only one parent at all times (but the other subclasses can have 0 or more parents). I couldn't figure out how to implement validations that would enforce the rules of the tree hierarchy, so I left them out.
ItemA has a helper method, parent, to get its parent. Sometimes this method will raise undefined method 'first' for nil:NilClass. It doesn't always happen, but it does in certain situations.
Situation 1
I am using jqGrid to list all the ItemAs. I use sorting functionality for some of the columns, including the parent. The grid would initially load the ItemAs successfully, showing that they all had parents as they should. However, when I tried to sort the parent column, I would get the error as if they suddenly disappeared. It went away when I removed includes(:groups) from the sorting method. I don't understand why that would help, but I figured the problem was solved. Until...
Situation 2
I am testing my app with Rspec, Factory Girl, and Selenium. In one of my tests, I call the parent method on an instance of ItemA created using Factory Girl. It raises the error. My tests that use the parent method indirectly don't fail. For example, the parent method is called on the index page for ItemA, and many tests visit that page without problem. This situation has not been resolved. My app no longer has any calls to includes, so that isn't part of it this time.
Relevant Code
ItemA
class ItemA < Item
# This method is called in Situation 1
def self.sort_by_parent sort_order
all.sort_by(&:parent_name).tap do |i|
i.reverse! if sort_order == :desc
end
end
def parent
groups.take
end
def parent_name
parent.component_name
end
end
Item
class Item < ActiveRecord::Base
has_many :item_groups, foreign_key: 'sub_item_id', dependent: :destroy
has_many :groups, through: :item_groups
has_many :group_items, class_name: 'ItemGroup', foreign_key: 'group_id', dependent: :destroy
has_many :sub_items, through: :group_items
end
update_spec
feature 'ItemA editing', js: true do
given!(:item_a) { create(:item_a) }
given!(:parent) { create(:item_b) }
scenario 'when parent', focus: true do
itemas_page = ItemAsPage.visit # This is a custom page object
# Situation 2 occurs here
itemas_page.edit_parent item_a.parent_name, parent.component_name
expect(itemas_page).to have_item_a_with parent.component_name
end
end
Why is the parent method sometimes reading as nil, and how do I get it to always produce a value?
EDIT: As I'm changing my code, I'm coming across more situations that cause this error. I checked the source. Here's a snapshot of ActiveRecord::FinderMethods:
module ActiveRecord::FinderMethods
def take(limit = nil)
limit ? limit(limit).to_a : find_take
end
private
def find_take
if loaded?
#records.first
else
#take ||= limit(1).to_a.first
end
end
end
For debugging purposes, I modified the parent method to look like this:
def parent
groups.tap {|g| puts '#records: ' + g.instance_variable_get(:#records).inspect }.take
end
#records is nil. I tried changing it to groups.reload.take to get #records to load, but it's not working. I'm using groups.limit(1).to_a.first right now, which is working, but I'm curious to find out what sort of bug in my app is causing this problem.
It turns out that despite calling reload, Rails still uses the cached version of the query. The same problem is described here. This fixes it:
class ItemA < Item
def parent
ActiveRecord::Base.uncached { groups.take }
end
end

rails creating default child when parent doesn't have children and not showing it when parent has children

is there any trick to create default child when a parent doesn't have children and not showing it when parent has children ? an after_create callback doesn't solve the problem by itself alone.now if user creates 3 children and you call "parent.children" the result not only contains those 3 children, but also the auto-created one.
UPDATE: I need the auto-created child only when there are no other children and when there are, I don't want the auto-created one to be among the result of "parent.children"
Example:
i have a product model having many variants.a customer can order a variant and so we need a variant. but sometimes the admin don't want to add variant and thinks that a product without variant it enough.so we need a default variant to let customers to order.maybe 2 month later, the admin comes and add a variant and from this time, we don't want a default one anymore. i was looking for a nice and clean solution for this problem
after_create should work just fine, assuming you associated any children before saving the parent:
class ParentModel < ActiveRecord::Base
has_many :child_models
after_create :ensure_child_exists
def ensure_child_exists
child_models.create(default: true) unless child_models.exists?
end
end
If you then need to remove this child if new ones are added at some later point, you'll need to have a 'default' flag on the child to identify for removal, then add a callback to trigger the default child removal.
class ChildModel < ActiveRecord::Base
attr_accessible :default
belongs_to :parent
after_create :remove_default_if_unneeded
scope :default, -> { where(default: true) }
def remove_default_if_unneeded
parent.child_models.default.destroy_all unless self.default?
end
end
Just call a method like this in the after_create callback.
def create_child_if_needed
Child.create(:parent_id => self.id) unless self.children.any?
end
This will only create the Child object if there isnt any child in the children association.

Setting up a parent and child model but child has it's own columns

I am trying to set up a structure with a parent that has some attributes and children models that inherit those attributes as well as maintain their own.
Ideally I'd like a setup of
class Parent
attr_accessible :some_attribute, some_attribute2, some_attribute3
end
class Child < Parent
attr_accessible :some_child_attr, :some_other_child_attr
end
class OtherChild < Parent
attr_accessible :something, :something_else
end
In that I can access the columns through the children but also have specific children data attached to the given model. I've been looking at STI and Polymorphic Associations but I want to avoid putting in has_many for every type of child. Is this possible?
Of course you can. That's how inheritance works. That's the beauty of OOP.
Just put 'has_many' in Parent class and all children have this.
I have been able to solve my problem using the https://github.com/hzamani/acts_as_relation gem. It seems what I was looking for was called Multi Table Inheritance. Once I got that setup, I was able to create children models with the same attributes as the parent as well as their own individual ones without having to specify every child as a polymorphic association in the parent.

after_destroy callback of child saves dependency of parent, causes issues when parent is destroyed

I have a situation where I would like to update a dependency of a parent object after another object has been destroyed. Here is an example of the class hierarchy:
class Parent < ActiveRecord::Base
has_one :info, :dependent => :destroy
has_many :conditions, :dependent => :destroy
....
end
class Info < ActiveRecord::Base
belongs_to :parent
def recalculate
# Do stuff
end
....
end
class Condition < ActiveRecord::Base
belongs_to :parent
has_one :condition_detail
after_destroy :update_info
def update_info
parent.info.recalculate
parent.info.save(:validate => false)
end
....
end
The problem is that when the parent is destroyed, it destroys the condition, which then sets off the after_destroy callback and saves the info object after it was already destroyed. So after the parent is destroyed info still exists. If I don't bypass validations, the save will silently fail, which I don't want. And using save! raises an exception.
The callback on Condition has to be an after_destroy because otherwise the recalculate method on Info won't have the correct representation of the state of the relationships to compute what it needs to.
I feel like I need a way to bypass callbacks when the parent is destroyed, but I don't think that's possible. I can't use dependent => delete_all because that will not destroy the children of Condition. I tried seeing if there was a way I could tell if the parent had destroy called on it and use that info to bypass the save in the after_destroy, but that didn't seem to work either.
Any help would be greatly appreciated, thanks!
I see there being 2 options:
Don't use a after_destroy callback on Condition, but rather expect the info to be recalculated by whoever is destroying Condition. This is the cleanest, because you're decoupling two separate intents: object destruction and calculation. You can see where this would be more helpful if, say, someday you want to destroy 2 conditions at once and only recalculate after both are destroyed. You cannot do this with a callback. It also aligns more closely with the Law of Demeter - the caller of Condition.destroy calling info.recalculate is better than Condition calling parent.info.recalculate.
If you really want to package this behavior in Condition, create a #destroy_and_recalculate function that is called instead of just #destroy with a sort-of-hidden callback. It's more obvious to callers that you are going to kick off a recalculation.
Remove :dependent=>destroy on the parent's :condition association, and replace it with your own before_destroy callback on Parent that will cause condition to be destroyed without callbacks.
In Condition, I would create this method, say, #destroy_without_callbacks, and in it destroy Condition's children, then cause condition to delete itself.
The functionality of :dependent=>destroy is great, but with cycles like this, I think the clearest method by far is to make it very explicit what you're doing by taking away some of the magic and managing the object and process lifecycle more explicitly.

Does an ActiveRecord object that has_many children know when a child belongs_to it?

I have an object model like:
class parent < ActiveRecord::Base
has_many: children
end
class child < ActiveRecord::Base
belongs_to: parent
end
My question is, if I do something like:
a_parent = Parent.create
child = Child.create(parent: a_parent)
Is there a clean way for the parent to be aware of the new child object so I can do something (like have the parent send out a birth announcement)? I'd rather not have the child do it (they can't even spell, let alone afford stamps). :)
I can imagine using an observer, or an after_create in the child to call a public method on the parent (calling parent.look_at_me). Ideally I'd like something that I define in the parent. Any other ideas?
You can use Association callbacks
class Parent
has_many :children, :after_add => :send_birth_announcement
def send_birth_announcement(child)
# ...
end
end
This would work in cases like this:
a_parent.children << a_child
a_parent.children.create(child_attributes)
However, ORM is leaky abstraction, so it won't be hard to miss such callback. It's always possible to create a child without instantiating parent, so perhaps after_create in child isn't that bad of an idea.
The basic answer is yes, the parent knows about its children. This is done by having a parent_id field in the child table. Whenever a parent wants to find its children all it need do is ask that table for all children with the right parent id.
This is a fundamental concept of relational databases, well worth reading more about to make sure you understand what is happening when you use model associations.
Edit:
A slight code correction for your example
a_parent = Parent.create
child = Child.create(:parent => a_parent)
You need to make sure you do the appropriate migration. See here. Given you've done that, the code above will automatically associate a_parent to its child.

Resources