Hi.
I’d appreciate advice and feedback, as I’m new to rails (and Ruby and mostly just a junior): am I missing something or doing it right? Are there better ways? I also have a few specific questions down the line (near the code).
I have a person
and a relationship
model. The idea is that I can create any kind of relationship between two people, i.e. they’re neighbors, significant others, colleagues, etc.
Some kinds of relationships such as friends
, parents
, and such are known and are accessible through Person
, others are simply accessible via Person.relationships
.
I’m using postgres
as database.
The migrations.
create_people.rb
class CreatePeople < ActiveRecord::Migration[6.1]
def change
create_table :people do |t|
t.string :name
t.timestamps
end
end
end
(this model hasn’t been fleshed out fully, since my first objective was to make the relationships work.)
create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[6.1]
def change
create_table :relationships do |t|
t.integer :person_id # Person
t.integer :with_id # Person
t.string :kind
t.timestamps
end
add_index :relationships, %i[person_id kind]
add_index :relationships, %i[person_id with_id kind], unique: true
end
end
- naming advice:
with_id
– is there a better or railsy way of naming the otherperson
? - should I use t.reference for
:person_id
and:with_id
? - are the indexes proper, or should I do something differently?
The models.
person.rb
# frozen_string_literal: true
class Person < ApplicationRecord
has_many :relationships
has_many :friends, -> { where("relationships.kind": 'friend') }, through: :relationships, source: 'with'
has_many :neighbors, -> { where("relationships.kind": 'neighbor') }, through: :relationships, source: 'with'
end
User specified kinds of relationships, i.e. not per se application specific are accessed via person.relationships
. The relations friends
and neighbors
were examples to test with. Known relationships will be friends
, significant_others
, parents
, children
, etc. but the user may wish to specify their landlord.
-
I considered splitting up
kind
into 2 fields:kind
andinverse_kind
so I could easily mapparent/child
relationships and allow end users to specify their custom mappings such aslandlord/tenant
. For data such as friends/neighbors, the inverse would just be the same.I figured it would save the extra row for the inverse relationship and instead just populate another column. Any thoughts? I’m concerned it may make querying the data more difficult.
-
Talking about querying data. Is the way I’m using scopes recommended? The scope does also work on inserting data, i.e. “some_person.friends << some_other_person” executes:
# some_person.friends << some_other_person
INSERT INTO "relationships"
("person_id", "with_id", "kind", "created_at", "updated_at")
VALUES
($1, $2, $3, $4, $5)
RETURNING
"id" [
["person_id", 1],
["with_id", 2],
["kind", "friend"],
["created_at", "2021-08-09 00:33:37.507950"],
["updated_at", "2021-08-09 00:33:37.507950"]
]
-
If not splitting up
kind
into 2 columns (as specified in the first item in this list) what would be the recommended way of creating inverse relationship behavior for known kinds?For instance,
parent.children << child
should then also dochild.parents << parent
This can be achieved by defining a method onPerson
such as:add_child
but I’m curious if there isn’t a better way. I was wondering if there was a way I could modify the behavior to run a single transaction to do both actions for the child and the parent. I haven’t found a proper way, a single transaction would mean, if one fails: both fail.I looked at model hooks, but I’ve been a bit confused on how I could actually ‘hook’ into the transaction itself.
-
Is there a way I can create custom validations for known relationships types/kinds? Such as that a parent should be older than a child. Or should I just use methods such as:
add_child/parent
and do it in there? -
I want to prevent my models from being infested with all sorts of relationship type specific code, so I’m wondering if the best way is to create specific models / relationships for known kinds that have specific requirements/validations (child/parent) - but - I also want to keep things as DRY as possible.
relationship.rb
# frozen_string_literal: true
class Relationship < ApplicationRecord
belongs_to :person
belongs_to :with, class_name: 'Person'
end
- Thoughts?
Thank you, I look forward to hearing your thoughts/suggestions/answers/anything really.
– Xander.