Skip to content

Instantly share code, notes, and snippets.

@johnnyshields
Last active April 12, 2017 06:39
Show Gist options
  • Save johnnyshields/dd06c089d887acf984f0cfcb67d0b8fd to your computer and use it in GitHub Desktop.
Save johnnyshields/dd06c089d887acf984f0cfcb67d0b8fd to your computer and use it in GitHub Desktop.
mongoid-relations-proposal.md

Proposal for Better Mongoid Relation Schema

There is opportunity to simplify Mongoid's relation definitions, increase intuitiveness, and at the same time make them more powerful.

One particular issue I'm facing is over-usage of has_and_belongs_to_many ... inverse_of: nil. The problem with this is that the inverse side of the relationship doesn't trigger any callbacks, which leads to out-of-sync data. It would be more natural to express this as belongs_to_many and has_many as per below.

Definitions

Given two models, each can have the one of the following verbs to define a relation, so long as at least one side is belongs:

  • belongs_to_one (= belongs_to)
  • belongs_to_many
  • has_one
  • has_many

belongs_to implies that this Model stores the foreign key in its fields

has implies that this Model does NOT store the foreign key in its fields

has_and_belongs_to_many will NO LONGER be used under this new schema. In ActiveRecord, has_and_belongs_to_many is actually a shortcut for using a join table (equivalent to has_many ... :through) and is different than the Mongoid usage anyway, therefore, by dropping it and using different verbs we're not "breaking" compatibility with AR per-se.

Worked Examples

This will enable the following canonical cases of relations:

1) One-to-One - One-sided

(already supported in Mongoid)

County.belongs_to_one :leader
Leader.has_one :country

Country - leader_id
Leader  - id

2) One-to-One - Two-sided

County.belongs_to_one :leader
Leader.belongs_to_one :country

Country - id country_id
Leader  - id country_id

3) One-to-Many - One-sided case A

(already supported in Mongoid)

Dog.belongs_to_one :owner
Owner.has_many :dogs

Dog   - owner_id
Owner - id

4) One-to-Many - One-sided case B

Dog.has_one :owner
Owner.belongs_to_many :dogs

Dog   - id
Owner - dog_ids

5) One-to-Many - Two-sided (A + B)

Dog.belongs_to_one :owner
Owner.belongs_to_many :dogs

Dog   - id, owner_id
Owner - id, dog_ids

6) Many-to-Many - One-sided

(can be approximated with has_and_belongs_to_many ... inverse_of: nil, but the has_many side won't get callbacks)

Student.belongs_to_many :subjects
Subject.has_many :students

Student - subject_ids
Subject - id

7) Many-to-Many - Two-sided

(already supported in Mongoid)

Student.belongs_to_many :subjects
Subject.belongs_to_many :students

Student - id, subject_ids
Subject - id, student_ids

8) Many-to-Many via Join Table**

** Not really needed, just adding for sake of completeness

Student.many_to_many :subjects, MyApp.Subject, join_through: "student_subjects"

Student         - id
StudentSubjects - subject_id, student_id
Subject         - id
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment