ActiveRecord: Does destroy_all execute callbacks for associated records? - activerecord

I have four models:
class Order < WebDatabase
has_many :shipments
class Shipment < WebDatabase
belongs_to :order
has_many :line_items, :order => "id", :dependent => :destroy
class LineItem < WebDatabase
belongs_to :shipment
has_many :line_item_messages, :dependent => :destroy
class LineItemMessage < WebDatabase
belongs_to :line_item
So if I did #order.shipments.destroy_all, would there be a chain of destroys where #order.shipments.line_items are destroyed and #order.shipments.line_items.line_item_messages are destroyed too because of the :dependent => :destroy on each of the models?

Yes.
from the docs (emphasis is my own)
destroy_all(conditions = nil)
public
Destroys the records matching conditions by instantiating each record and calling its destroy method. Each object’s callbacks are executed (including :dependent association options and before_destroy/after_destroy Observer methods). Returns the collection of objects that were destroyed; each will be frozen, to reflect that no changes should be made (since they can’t be persisted).
Note: Instantiation, callback execution, and deletion of each record can be time consuming when you’re removing many records at once. It generates at least one SQL DELETE query per record (or possibly more, to enforce your callbacks). If you want to delete many rows quickly, without concern for their associations or callbacks, use delete_all instead.

Related

Papertrail tracks create but not delete through association

I have a table of Industries and am keeping tracking of it's competitors which are also industries. This is through a mapping table industry_competitors which has industry_id and competitor_id. I want papertrail to track associations and dissociations of industry competitors.
class Industry < ApplicationRecord
has_many :industry_competitors, dependent: :destroy
has_many :competitors, through: :industry_competitors
end
class IndustryCompetitor < ApplicationRecord
has_paper_trail
belongs_to :industry
belongs_to :competitor, class_name: "Industry"
end
My controller code is as such.
competitors = ::Industry.where(id: params[:competitor_ids])
#industry.competitors = competitors
#industry.save
Every time the entire competitor list is passed. If I try to disassociate a few competitor (by not passing the ids to the controller) from the industry a 'Delete' query is fired.
DELETE FROM `industry_competitors` WHERE `industry_competitors`.`industry_id` = 4559 AND `industry_competitors`.`competitor_id` = 4564
I suspect because activerecord calls 'delete' and not 'destroy' this papertrail callbacks are not triggered hence the changes are not tracked.
If there a way to call delete explicitly (with minimal code changes). Or is there a way for papertrail to track delete?
Adding this patch can get it to work.
module HasManyThroughAssociationPatch
def delete_records(records, method)
method ||= :destroy
super
end
end
ActiveRecord::Associations::HasManyThroughAssociation.prepend(HasManyThroughAssociationPatch)
Credits: https://github.com/westonganger/paper_trail-association_tracking

Issues with Polymorphic/STI in Ruby

We're having a problem with polymorphism & STI in Ruby
Our database has two tables: 'account' and 'list'. 'list' has columns 'account_id', 'type', 'description'.
Our classes look like so:
class Account < ActiveRecord::Base
has_many :lists
has_many :subscription_lists
has_many :email_lists
end
class List < ActiveRecord::Base
belongs_to :account
end
class SubscriptionList < List
end
class EmailList < List
end
Inside Account, methods email_lists and subscription_lists work exactly as expected. Our goal is we want to be able to call lists which will return an array of all lists. Currently, that doesn't work, nor does self.lists
Oddly, Account.find(self.id).lists DOES give us an array of all the lists associated.
What gives? How do we fix this?
You can use List.all which will return an ActiveRecord::Relation of all List objects, regardless of type.
Additionally, you can use #instance_variable.lists on any instance variable of Account
What's more, you could use a query on a class method to accomplish the job, like so, which will also return an ActiveRecord::Relation:
List.where('account_id = ?', id)
Lastly, your Account association with List should not include the children of List:
class Account < ActiveRecord::Base
has_many :lists, inverse_of: :account, dependent: :destroy
end

Ruby ActiveRecord: How to connect two rooms with an exit

I am making a small text-adventure, and I want to use ActiveRecord as Object Relational Mapping.
What I'm having trouble with, is understanding how to connect two rooms together using an exit.
The following facts are a given:
A room can have multiple exits
An exit can be in different directions (it has a 'direction' field). Also, it might have other parameters such as 'locked', etc. that I want to add later.
An exit connects up two rooms.
However, now I am stuck:
What I have so far
class Room < ActiveRecord::Base
has_many :exits
has_many :neighbours, through: :exits
end
class Exit < ActiveRecord::Base
belongs_to :room, dependent: :destroy
belongs_to :room_dest, foreign_key: "room_dest_id", class_name: "Room", dependent: :destroy
end
but this is incomplete. room.neighbours, for example, is not working at all.
What mostly baffles me is how to make exits work two-ways: If I add an exit on one room, it won't be in the room.exits list in the other room.
What works is: (given an exit connecting room1 and room2)
room1.first.exits.first.room_dest (this is room2)
But room2.exits is empty, and room1.neighbours shows a list containing only itself.
How is this done properly?
To get room.neighbours working, I believe you would first need to change
belongs_to :room_dest, foreign_key: "room_dest_id", class_name: "Room", dependent: :destroy
to
belongs_to :neighbour, foreign_key: "room_dest_id", class_name: "Room"
Note that I removed the dependent: option because you probably don't want to destroy Rooms when Exits are deleted. You want dependent: :destroy on the has_many relationship with exits.
Now we've really solidified the one-way binding of Exit. But if you think about it, isn't "Exit" just one-way by definition? While this seems limiting at first, you can utilize this to define "entrances" of the Room. That is your connection from the neighbour to the original Room. Something like:
has_many :entrances, class_name: "Exit", foreign_key: "room_dest_id"
Or you could define a method that queries Exits and checks if either room_id or room_dest_id is the Room ID. In this case I would rename "Exit" class to be something more generic. Unfortunately I can't think of any built-in AR association that would do this multi-key association for you. It wouldn't really work right because things like association.build/create would not know which of the keys to set. But it's a relatively simple method or set of methods that could still return a scope which you could operator on:
has_many :connections, dependent: :destroy # For ease of creating connection, not as useful for querying them
def exits
Connection.where(["room_id = :id OR dest_room_id = :id", id: self.id])
end
def neighbours
exits.map do |conn|
conn.room_id == self.id ? conn.dest : conn.source
end
end
You could make the query more complex if you actually want some of the Connections to be one-way. Or write other methods that build on this since it is not executed immediately. You could still chain .first(), another .where(), etc on it.

Rails ActiveRecord has_many issue

I have a User that has many tasks. This is a predefined set of tasks. Each task has a list of requirements that need to be completed for the task to be finished. I have set up 2 has_many through relationships. Here is my code.
class User < ActiveRecord::Base
has_many :user_tasks
has_many :tasks, through: :user_tasks
end
class UserTask < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class Task < ActiveRecord::Base
has_many :user_tasks
has_many :users, through: :user_tasks
has_many :task_requirements
has_many :requirements, through: :task_requirements
end
class TaskRequirement < ActiveRecord::Base
belongs_to :task
belongs_to :requirement
end
class Requirement < ActiveRecord::Base
has_many :task_requirements
has_many :requirements, through: :tasks
end
This part seems fairly simple, but now I want to capture the date that each requirement is completed and a boolean of whether it is completed or not for each User. I originally tried to put it in the TaskRequirement table, but of course then it was updated for all users, which is not the behavior that I want. So I have 2 questions.
1. Where do I put these fields?
2. what is the best way to access them?
I assume they will go in a join table, but there is no easy way to access the information in the join tables.
Thanks again for all the help.
If each user completes the task requirements independently of whether other users have completed the task, then you need a UserRequirement table to store the associated data items -- the boolean and the completion date.
However, if each requirement has to be completed multiple times by each user, once for every task with which it is associated, then you'd need a join between User and TaskRequirement, or UserTask and Requirement, or User and Task and Requirement.

Ordering by complex structure in Rails

I have a model called Event that has many EventRegistrations, which belongs to a person.
Also the EventRegistration has many EventAtendees, which belongs to a person too.
I want to order in Rails all the people related to a event, which means:
- Person associated to an event registration
- Person associated to an event atendee which is associated to a registration..
Any help?
As I understand it, you have the following models and associations:
Event
has_many :event_registrations
EventAttendee
belongs_to :event_registration
belongs_to :person
Person
has_many :event_registrations
has_many :event_attendees
EventRegistration
belongs_to :person
belongs_to :event
Now, as to your actual question. You say you want to 'order all the people related to an event'
I don't actually see how 'ordering' (a.k.a sorting) enters into this.
To get all users associated with an event, I recommend adding some :through associations:
Event
has_many :event_attendees, :through => :event_registrations
has_many :people, :through => :event_attendees
Then, when you have an event object, you can simply call .people on it, and it'll just work.
Here is how you would order by the person's last name, assuming you have all your ActiveRecord associations set up correctly.
event_registrations = EventRegistrations.find(:all)
ordered_event_registrations = event_registrations.sort{|registration_a,registration_b|registration_a.people.lastname<=>registration_b.people.lastname}

Resources