mark_for_destruction in before_save - ruby-on-rails

Whats wrong with this before_save-callback?
class Order < ActiveRecord::Base
has_many :line_items, :dependent => :destroy, :inverse_of => :order
accepts_nested_attributes_for :line_items
attr_accessible :line_items_attributes
before_save :mark_line_items_for_removal
def mark_line_items_for_removal
line_items.each do |line_item|
line_item.mark_for_destruction if line_item.quantity.to_f <= 0
end
end
end
When one of the line_items are marked for destruction, no line_item will be saved.
However the parent Order object does get saved.
Returning true does not make a difference...
about mark_for_destruction: http://apidock.com/rails/v3.1.0/ActiveRecord/AutosaveAssociation/mark_for_destruction
and why that instead of ":allow_destroy => true"? see here:
http://weblogs.manas.com.ar/spalladino/2010/03/15/deleting-children-with-accepts_nested_attributes_for-in-rails/

I believe you need to set the :autosave => true option for your has_many definition.
As stated, here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
"If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object. If false, never save or destroy the associated objects. By default, only save associated objects that are new records."

Related

Why relative position of associations and callbacks in a rails model affecting the results

I have a model Indent which has many indent_items and has many shipments through indent_items.
class Indent < ActiveRecord::Base
before_destroy :check_for_shipments # WORKS HERE
has_many :indent_items, inverse_of: :indent, :dependent => :destroy
has_many :shipments, :through => :indent_items
# before_destroy :check_for_shipments # DOESN"T WORK HERE
private
def check_for_shipments
# Should not be allowed to delete if there are any shipments.
if self.shipments.count > 0
errors.add(:base, "Cannot delete indent because shipments are there.")
return false
end
end
end
I guess it may be because if callback is mentioned after the association, all indent items gets marked for deletion and the shipment count check returns zero always.
But this shouldn't happen. Or may be I am missing something here. I don't know.
I am using rails 3.2.8.
This is the desired behaviour of rails and if you want that before_destroy callback is executed before the shipment has destroyed(because of dependent: :destroy option) then you have to use the prepend option on the before_destroy callback.
e.g.
class Indent < ActiveRecord::Base
has_many :indent_items, inverse_of: :indent, :dependent => :destroy
has_many :shipments, :through => :indent_items
before_destroy :check_for_shipments, prepend: true #this will work
private
def check_for_shipments
# Should not be allowed to delete if there are any shipments.
if self.shipments.count > 0
errors.add(:base, "Cannot delete indent because shipments are there.")
return false
end
end
end
And for the further reference you can read from here.

:dependent => :delete on belongs_to doesn't delete owner object

I have been checking options of belongs_to method and testing following behavior in Rails 3.2.7
As per above link the :dependent option states that
If set to :destroy, the associated object is destroyed when this
object is. If set to :delete, the associated object is deleted without
calling its destroy method.
As I understand the Author should be removed if Post is removed in following case:
class Post < ActiveRecord::Base
belongs_to :author, :dependent => :delete
end
class Author < ActiveRecord::Base
attr_accessible :name
has_one :post
before_destroy :log_author_removal
private
def log_author_removal
logger.error('Author is getting removed')
end
end
In console:
> Post.first
Post Load (0.4ms) SELECT "posts".* FROM "posts" LIMIT 1
=> #<Post id: 5, title: "Post 5", author_id: 3>
> p.delete
SQL (197.7ms) DELETE FROM "posts" WHERE "posts"."id" = 5
=> #<Post id: 5, title: "Post 5", author_id: 3>
> Author.find(3)
Author Load (0.5ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 3]]
=> #<Author id: 3, name: "Author 3">
However calling p.destroy deletes associated author.
Am I misunderstood above quoted statement?
Yes, calling delete generally skips all callbacks that either you or rails set on destroying the record. These include callbacks like before_destroy and also destroying associated records.
Therefore if you call p.delete it will not do anything with the associated records.
When you call p.destroy it will:
Call the before_destroy callback if set.
Delete the object.
If you set :dependent => :delete, it will simply delete the Author object. If you set it to :destroy it will repeat this whole process for the author object (callback & destroying its related records if applicable).
Call the after_destroy callback if set.
From what I understand :
:dependent => :destroy will trigger association.destroy if you call destroy on the object.
:dependent => :delete will trigger association.delete if you call destroy on the object.
In both cases, you have to call destroy on the parent object. The difference lies in th methos that is called on the child object. If you don't want to trigger destroy filters on the child object use :dependent => :delete. If you do want them, use :dependent => :destroy.
By quickly taking a look at the source here : https://github.com/rails/rails/blob/357e288f4470f484ecd500954fd17fba2512c416/activerecord/lib/active_record/associations/builder/belongs_to.rb#L68
We see that calling dependent will just create an after_destroy on the parent model, calling either delete or destroy on the child object. But in both cases, it creates an after_destroy.
belongs_to association support both :delete and :destroy for :dependent.
you can refer below link
http://apidock.com/rails/v4.0.2/ActiveRecord/Associations/ClassMethods/belongs_to
calling delete skips all callbacks like before_destroy and as well won't delete associated records for association object as well.
Example
class Order < ActiveRecord::Base
has_one :project, :dependent => :delete
has_many :resources, :dependent => :delete
end
class Project < ActiveRecord::Base
belongs_to :order, :dependent => :delete
end
In above code,if project has been destroyed then order will as well deleted but resources in order won't delete
but if we use
belongs_to :order, :dependent => :destroy
then resources attached with orders as well deleted on project destroy.
belongs_to :author, :dependent => :delete
Should be:
belongs_to :author, :dependent => :destroy
:destroy and :delete behave differently in ActiveRecord, delete bypasses validations and AR associations, thus associated objects are not being removed.
belongs_to association can't support the :delete for :depedent. It supports only :destroy. Please refer this link http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html.

accepts_nested_attributes_for with polymorphic association

Is there any way to halt saving of child before parent.
I am using accepts_nested_attributes_for with polymorphic association.
I used multiple options validates_presence_of :parent_id , validates_assoicated :parent but none are working.
For example, I do have a class
Class Person
include HasPhoneNumbers
..
end
module HasPhoneNumbers
def self.included(kclass)
kclass.has_many :phone_numbers, :as => :callable, :dependent => kclass == Person ? :destroy : :nullify
end
klass.accepts_nested_attributes_for :phone_numbers, :reject_if => lambda {|pn| pn.keys.any?{|k| k.to_sym != :id && pn[k].blank?} }
end
class PhoneNumber
belongs_to :callable, :polymorphic => true
end
So while saving person due to validation in person object, it was not saving. However, child(phone_number) was saving. So I need to restrict it to not save child(phone_number) before parent(person) saves.
I did try multiple options using validates_presence_of and validates_associated, but none are working for me.
#person = Person.new(params[:person])
ActiveRecord::Base.transaction do
person.save!
end
Wrapping your saves within a transaction should roll back the phone number save if the person fails validation.
Reference: ActiveRecord Transactions

Having trouble with :dependent => :destroy and Instance variables

I have a project with many items; and it's :dependent => :destroy.
I'm trying to tell rails when calling callbacks (specifically the after_destroy of Item), to run ONLY if the Item is destroyed "alone", but all of the project is NOT being destroyed.
When the whole project is being destroyed, I actually don't need this after_destroy method (of Item) to run at all.
I don't want to do :dependent => :delete since the Item has many other associations connected to it (with :dependent => :destroy).
It works for me only with class variable, but I wish it would had worked with an instance variable:
class Project < ActiveRecord::Base
has_many :items, :dependent => :destroy
before_destroy :destroying_the_project
def destroying_the_project
# this is a class variable, but I wish I could had #destroying_me
# instead of ##destroying_me.
##destroying_me = true
end
def destroying_the_project?
##destroying_me
end
end
class Item < ActiveRecord::Base
belongs_to :project
after_destroy :update_related_statuses
def update_related_statuses
# I with I could had return if project.destroying_the_project?
# but since the callback gets the project from the DB, it's another instance,
# so the instance variable is not relevant here
return if Project::destroying_the_project?
# do a lot of stuff which is IRRELEVANT if the project is being destroyed.
# this doesn't work well since if we destroy the project,
# we may have already destroyed the suites and the entity
suite.delay.suite_update_status
entity.delay.update_last_run
end
end
The other option I can think of is remove the :dependent => :destroy and manually handle the destroy of the items inside the Project after_destroy method, but it seems too ugly as well, especially since Project has many item types with :dependent => :destroy that would have to shift to that method.
Any ideas would be appreciated
I hope that's not the best solution, but at least it works and doesn't introduce any global state via class variables:
class Project < ActiveRecord::Base
has_many :items
before_destroy :destroying_the_project
def destroying_the_project
Rails.logger.info 'Project#destroying_the_project'
items.each &:destroy_without_statuses_update
end
end
class Item < ActiveRecord::Base
belongs_to :project
after_destroy :update_related_statuses,
:unless => :destroy_without_statuses_update?
def update_related_statuses
Rails.logger.info 'Item#update_related_statuses'
end
def destroy_without_statuses_update
#destroy_without_statuses_update = true
destroy
end
def destroy_without_statuses_update?
!!#destroy_without_statuses_update
end
end
If you don't need to use callbacks when deleting the whole project, you could use delete_all instead of destroy:
Rails :dependent => :destroy VS :dependent => :delete_all

My "has_many through" join model has nil reference after saving

I'm trying to create an object and adding an existing object to a "has_many through" association, but after saving my object the reference to my newly created object is set to nil in the join model.
To be specific, I'm creating a Notification object and adding a pre-existing Member object to the Notification.members association. I'm using nested resources and I'm invoking the notification controller's new function using the following relative URL:
/members/1/notifications/new
After filling out the form and submitting, the create function is called, and from what I understand from the Rails Associations guide, section 4.3.3 "When are Objects Saved?", the members associations should be created in the database when the new notification object is saved:
"If the parent object (the one declaring the has_many association) is unsaved (that is, new_record? returns true) then the child objects are not saved when they are added. All unsaved members of the association will automatically be saved when the parent is saved."
After creating the notification object, the following record was created in the database:
select id, notification_id, notifiable_type, notifiable_id from deliveries;
1|<NULL>|Member|1
I worked around the problem by saving the notification object before adding the member object to the association. At first this seemed to be an ok solution for now, but I soon discovered that this has it's downsides. I don't want to save the notification without it's member association since I then have to write workarounds for my callbacks so that they don't start performing tasks on the not yet valid notification object.
What am I doing wrong here? All tips are appreciated. :D
Models
class Notification < ActiveRecord::Base
has_many :deliveries, :as => :notifiable
has_many :members, :through => :deliveries, :source => :notifiable, :source_type => "Member"
has_many :groups, :through => :deliveries, :source => :notifiable, :source_type => "Group"
end
class Member < ActiveRecord::Base
has_many :deliveries, :as => :notifiable
has_many :notifications, :through => :deliveries
end
class Delivery < ActiveRecord::Base
belongs_to :notification
belongs_to :notifiable, :polymorphic => true
end
# Group is not really relevant in this example.
class Group < ActiveRecord::Base
has_many :deliveries, :as => :notifiable
has_many :notifications, :through => :deliveries
end
Controller
class NotificationsController < ApplicationController
def create
#notification = Notification.new(params[:notification])
#member = Member.find(params[:member_id])
#notification.members << #member
respond_to do |format|
if #notification.save
...
end
end
end
end
After posting a bug report, I got som help from one of the Rails gurus. In short, it's impossible to get this working the way I thought.
I decided to proceed with slightly more controller code, seems to be working just fine:
def create
#notification = Notification.new(params[:notification])
#member = Member.find(params[:member_id])
respond_to do |format|
if #notification.save
#member.notifications << #notification
#member.save
...

Resources