`is_a` relationship type

It would be great to have an is_a relationship, meaning each row in the source resource matches one and exactly one row in the destination resource, and has a matching primary key. Perhaps there is already an idiom for this?
23 Replies
ZachDaniel
ZachDaniel3y ago
Yep! You can do has_one Which is at least an approxamation something like:
has_one :something, Something do
allow_nil? false
destination_attribute :id
source_attribute :id
end
has_one :something, Something do
allow_nil? false
destination_attribute :id
source_attribute :id
end
\ ឵឵឵
\ ឵឵឵OP3y ago
Ah, I was actually thinking of it for the opposite case; in order to be able to provide inheritance.
ZachDaniel
ZachDaniel3y ago
well, has_one is just the name, but should be functionally equivalent if you're pointing at the same primary key
\ ឵឵឵
\ ឵឵឵OP3y ago
The issue I think is the underlying table structure. All Journals, Books and Magazines are Publications and other resources may be happy to interact with any sort of Publication, but others might care only about Journals or Books. Another standard pattern is to dump all the attributes into one table and add a type column—and the associated validation headache—but this is not ideal in some cases. Sharing by definition the same primary key has benefits in these cases. In Postgres this is references, same in Ecto.
ZachDaniel
ZachDaniel3y ago
Well, if you do belongs_to :something, Something it will create a foreign key to that thing when generating migrations And if you make it belongs_to, :something, Something, allow_nil?: false then you're guaranteed for that other thing to exist
\ ឵឵឵
\ ឵឵឵OP3y ago
If I add primary_key :something_id, will it produce references?
ZachDaniel
ZachDaniel3y ago
🤔 I'm not sure I understand its the belongs_to that makes it add references to migrations There is another option that you can do with resources:
defmodule Foo do
...

resource do
base_filter expr(type == :foo)
end

postgres do
table "things"
repo Repo
end

attributes do
attribute :foo_only_thing, :string, allow_nil?: false
attribute :type, :atom, default: :foo
end
end
defmodule Foo do
...

resource do
base_filter expr(type == :foo)
end

postgres do
table "things"
repo Repo
end

attributes do
attribute :foo_only_thing, :string, allow_nil?: false
attribute :type, :atom, default: :foo
end
end
Then you can make as many resources that do that same thing as you want and they will all share the same table (and the migration generator will generate migrations with the superset of all columns for that table)
\ ឵឵឵
\ ឵឵឵OP3y ago
Right, in various SQL flavors, and definitely Postgres, you can do them simultaneously:
create table somethings (
id integer primary key
);
create table someotherthings (
id integer primary key references somethings
);
create table somethings (
id integer primary key
);
create table someotherthings (
id integer primary key references somethings
);
ZachDaniel
ZachDaniel3y ago
Yeah, so:
belongs_to :something, Something do
source_attribute :id
define_attribute? false
end
belongs_to :something, Something do
source_attribute :id
define_attribute? false
end
will give you what you want in that case i.e "don't automatically define the attribute" and "use the attribute called id as the foreign key" and then you will get a table w/ a primary key that references another table
\ ឵឵឵
\ ឵឵឵OP3y ago
Ok, great! No primary_key required?
ZachDaniel
ZachDaniel3y ago
well, I guess it depends
\ ឵឵឵
\ ឵឵឵OP3y ago
I didn't think it was implied by belongs_to.
ZachDaniel
ZachDaniel3y ago
You can do
uuid_primary_key :id

...

belongs_to ...
uuid_primary_key :id

...

belongs_to ...
the snippet I gave you was assuming you were already defining the primary key elsewhere but yeah if you want it to actually define the id attribute and make it a primary key, then:
belongs_to :something, Something do
source_attribute :id
primary_key? true
end
belongs_to :something, Something do
source_attribute :id
primary_key? true
end
\ ឵឵឵
\ ឵឵឵OP3y ago
They should be constrained to be identical by the database, simultaneously foreign key and primary key. Ah, great.
ZachDaniel
ZachDaniel3y ago
Yeah, no matter how you cut it you will never get multiple attributes called :id its just wether or not you want it defined explicitly or implicitly
\ ឵឵឵
\ ឵឵឵OP3y ago
Specifically implicitly. ^^ I don't want it to have a separate id.
ZachDaniel
ZachDaniel3y ago
sorry, thats not what I meant You're going to have to do the work to make the attribute equal the value that you want
attributes do
attribute :id, :uuid, primary_key?: true, allow_nil?: false
end

relationships do
belongs_to :something, Something do
source_attribute :id
define_attribute? false
end
end
attributes do
attribute :id, :uuid, primary_key?: true, allow_nil?: false
end

relationships do
belongs_to :something, Something do
source_attribute :id
define_attribute? false
end
end
\ ឵឵឵
\ ឵឵឵OP3y ago
The database shouldn't allow creation of rows in the child table without a matching row in the parent, by the foreign key (references) constraint.
ZachDaniel
ZachDaniel3y ago
that and
relationships do
belongs_to :something, Something do
source_attribute :id
primary_key? true
allow_nil? false
end
end
relationships do
belongs_to :something, Something do
source_attribute :id
primary_key? true
allow_nil? false
end
end
the two snippets I just showed are equivalent what I mean by "explicit" vs "implicit" is just "does the :id attribute appear in the attributes block or not"
\ ឵឵឵
\ ឵឵឵OP3y ago
This one looks right.
ZachDaniel
ZachDaniel3y ago
not talking about the underlying database structure they do exactly the same thing
\ ឵឵឵
\ ឵឵឵OP3y ago
Ah yeah, not in comparison to each other, the ones from before. Cool, thanks very much!
ZachDaniel
ZachDaniel3y ago
no problem 😄

Did you find this page helpful?