Ruby ActiveRecord: How to connect two rooms with an exit - ruby

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.

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

How to properly handle multiple model associations to different keys in Rails 4.0.8

I'm having the following problem while trying to model my application, it's basically a functionality to handle bills with multiple people, like sharing the rent or any other similar stuff, the deal is:
I have a user model and a billing model but can't find out how to build the associations.
A user has_many billings and billings belongs_to user but, also the billing has_many users, e.g. Tom registers a bill that is meant to be shared by Tom himself, Betty and Bob.
So that makes Tom the creditor and the other two become debtors in the billing.
I'm kinda lost at this point, how to consolidate these associations, dunno if any more information is needed or if it's clear enough, but will appreciate any help and update with any other information needed.
Thanks in advance,
---EDIT---
I've come to the following approach:
Class User < ActiveRecord::Base
has_many :billings, foreign_key: "creditor_id", dependent: :destroy
end
Class Billing < ActiveRecord::Base
belongs_to :creditor, class_name: "User"
has_many :debts, dependent: :destroy
end
Class Debt < ActiveRecord::Base
belongs_to :billing
has_one :user
end
I also tried to graphically model it for better understanding here: imgur
Would that all be correct?
Don't be afraid to use the options available to you in ActiveRecord. If your model naming is getting cluttered (Billing belongs to a User and has_many Users) then be more specific with the association labels and sort the links with options. The Rails Associations Guide is easy to Google and covers pretty much everything.
class User < ActiveRecord::Base
has_many :billings, :foreign_key => 'creditor_id'
has_many :debts, :through => :debtor_users, :source => :billing
end
class Billing < ActiveRecord::Base
belongs_to :creditor, :class_name => 'User'
has_many :debtors, :through => :debtor_users, :source => :user
end
class DebtorUser < ActiveRecord::Base
belongs_to :billing
belongs_to :user
end
You can extend the Billing model to have multiple creditors also simply by converting the belongs_to association into a has_many :through association following the same pattern as debtors just with a different join model (say CreditorUser).

Active Record has_many through more than one model

Is it possible to access objects more than one model away?
For example let's say I have
class Contact <ActiveRecord:Base
has_many :interactions
end
class Interaction <ActiveRecord:Base
belongs_to :contact
belongs_to :course_presentation
end
class CoursePresentation <ActiveRecord:Base
has_many: interactions
belongs_to :course
end
class Course <ActiveRecord:Base
has_many :course_presentations
end
Right now I know I could write a through relationship via contacts to course presentations and then get all the course related to all the course presentations or I could do
contact.interactions.map{ |i| i.course_presentation.course }
I would like to be able to pull courses related to a contact directly so ... e.g.
contact.courses
Is this possible?
Yes, I believe so. Just add the following:
class Contact < ActiveRecord::Base
has_many :interactions
has_many :course_presentations, through: :interactions
has_many :courses, through: :course_presentations
end

ActiveRecord: Does destroy_all execute callbacks for associated records?

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.

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.

Resources