Rails: avoid recursive destroy callbacks - ruby-on-rails

I have two models:
class Car < ActiveRecord::Base
has_many :adverts, :dependent => :destroy
end
class Advert < ActiveRecord::Base
belongs_to :car
# Destroy the car, if there is no more adverts left
after_destroy do
self.car.destroy unless self.car.adverts.exists?
end
end
Now this works well when calling advert.destroy, but when calling car.destroy, things end up in a recursive loop!
PS. rails 4.1.8

Related

How cause association callback in rails models

How to track in the models this command
=> order = Order.create
=> order.items << Item.first // this command
if i have such models:
class Order < ApplicationRecord
has_many :order_items
has_many :items, through: :order_items
end
class Item < ApplicationRecord
has_many :order_items
has_many :orders, through: :order_items
end
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :item
end
I try use after_add for example, but I did not succeed.
For example my task:
In controller`s (OrderController) method create:
def create
#order = Order.create(order_params)
#order.items << Item.find(params[:id])
end
And i have that models Order or Item track this (when i add item to order) and print me message in console (for example)
Have a look at the Rails Guides about Association Callbacks. There is, for example, an after_add callback.
# in your Order model
has_many :items, after_add: :track_item_added
private
def track_item_added(item)
# your tracking code, for example
Rails.logger.debug("Item ##{item.id} added to order ##{id}")
end

Cart validations for maximum items

I have 2 models, cart and line_item:
cart.rb & line_item.rb
class Cart < ActiveRecord::Base
has_many :line_items, dependent: :destroy
belongs_to :user
class LineItem < ActiveRecord::Base
belongs_to :cart
belongs_to :user
application_controller.rb
def current_cart
Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
cart = current_user.cart.create
session[:cart_id] = cart.id
cart
end
How can I add validations to my cart so that user can only add 5 items maximum into their cart? At the moment I have this code but it is not working?
def maximum_items_not_more_than_5
if line_items.count > 5
errors.add(:line_items, "must be less than 5")
end
end
Here is a way, I would try :
class LineItem < ActiveRecord::Base
belongs_to :cart, validate: true # enables validation
Then inside the Cart model, write your own custom validation like :
class Cart < ActiveRecord::Base
has_many :line_items, dependent: :destroy
validate :maximum_items_not_more_than_5 # using custom validation
private
def maximum_items_not_more_than_5
if line_items.count > 5
errors.add(:base, "must be less than 5")
end
end
Why is line_item belonging to user?? Surely it would be item:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :carts
end
#app/models/cart.rb
class Cart < ActiveRecord::Base
belongs_to :user
has_many :line_items, inverse_of: :cart
has_many :items, through: :line_items
validate :max_line_items
private
def max_line_items
errors.add(:tags, "Too many items in your cart!!!!!!") if line_items.size > 5
end
end
#app/models/line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :cart, inverse_of: :line_items
belongs_to :item #-> surely you want to specify which item is in the cart?
end
#app/models/item.rb
class Item < ActiveRecord::Base
has_many :line_items
has_many :carts, through: :line_items
end
Validation
This is certainly in the realms of validation, specifically a custom method:
#app/models/model.rb
class Model < ActiveRecord::Base
validate :method
private
def method
## has access to all the instance attributes
end
end
I also put inverse_of into the mix.
You can see how this works here: https://stackoverflow.com/a/20283759/1143732
Specifically, it allows you to call parent / child objects from a particular model, thus allowing you to call validations & methods residing in those files.
In your case, it may be prudent to add validations to the line_item model -- specifying individual quantities or something. You can call the validations in this model directly from your cart model by setting the correct inverse_of

Queue of callbacks Rails

class Post < ActiveRecord::Base
has_many :comments, dependent: :destroy
before_destroy :post_method1
after_destroy :post_method2
end
class Comment < ActiveRecord::Base
belongs_to :post
after_destroy :comment_method
end
How will the callbacks and destroy methods be ordered in the two models after the post.destroy like (if post.comments.any? = true)???

validate destruction of has_many association?

Models:
class Factory < ActiveRecord::Base
has_many :factory_workers
has_many :workers, through: :factory_workers
end
class FactoryWorkers < ActiveRecord::Base
belongs_to :factory
belongs_to :worker
before_destroy :union_approves?
private
def union_approves?
errors.add(:proletariat, "is never destroyed!")
false
end
end
class Worker < ActiveRecord::Base
has_many :factory_workers
has_many :factorys, through: :factory_workers
end
If I attempt to update a Factory's list of Workers via Factory, and that leads to the destruction of some FactoryWorker associations, I hope that the before_destroy hook is called, but this does not seem to be the case.
Example
Factory.create(name: 'communist paradise', worker_ids: [1, 2])
Factory.find_by(name: 'commnist paradise').update(worker_ids: [1])
# before_destroy hook is not called, proletariat must riot!
How can I ensure the before_destory hook is called when updating a record's associations?
Found what I needed: ActiveRecord provides a before_remove method on associations, so I just need to rejigger as follows:
class Factory < ActiveRecord::Base
has_and_belongs_to_many :workers, before_remove: :union_approves?
private
def union_approves?
...
end
end

after_touch callback is fired multiple times

I have a hierarchy of models, for which I'm trying to cascade a :touch event.
class Category < ActiveRecord::Base
has_many :posts
after_touch :do_stuff
def do_stuff
# do stuff...
end
end
class Post < ActiveRecord::Base
belongs_to :category, :touch => true
has_many :comments, :dependent => :destroy
end
class Comment < ActiveRecord::Base
belongs_to :post, :touch => true
end
I have a form for Post which creates a new Comment through nesteed_attributes. When this event occurs the after_touch method on the Category class is fired 4 times in quick succession (milliseconds apart) and I'm at a bit of a loss to understand why.
In my minds-eye the callback should only be fired once for the transaction? I also notice that in events such as destroying a post the callback is fired for the post, and each of the comments which is destroyed, resulting in many-many calls.
Is this normal behaviour? Is it expected? Is there a way around this? Is this a bug in Rails?
class Category < ActiveRecord::Base
has_many :posts
after_touch :do_stuff
def do_stuff
# do stuff...
end
end
class Post < ActiveRecord::Base
belongs_to :category
has_many :comments, :dependent => :destroy
end
class Comment < ActiveRecord::Base
belongs_to :post
end
posts_controller
def update
...
if #post.update
#post.category.touch
else
...
end
end
Seems that this is 'by design' within Rails at the moment. There also doesn't seem to be any immediate plan to change this, so for the moment if you're planning on using :touch and only require a single callback, you need to look at another option.
https://github.com/rails/rails/issues/8759

Resources