Reputation: 33
I have a table "films" whose schema looks like this:
create_table "films", force: :cascade do |t|
t.integer "tmdb_id", null: false
t.string "language_iso_639_1", null: false
t.string "title", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["tmdb_id", "language_iso_639_1"], name: "index_films_on_tmdb_id_and_language_iso_639_1", unique: true
end
As you can see the tmdb_id itself is not unique, only the combination of the tmdb_id and the language_iso_639_1 together. So far this is working as expected and lets me create records like these:
[#<Film:0x0000000108156630
id: 1,
tmdb_id: 12,
language_iso_639_1: "en",
title: "Finding Nemo",
created_at: Fri, 15 Apr 2022 14:58:43.128785000 UTC +00:00,
updated_at: Fri, 15 Apr 2022 14:58:43.128785000 UTC +00:00>,
#<Film:0x00000001081564f0
id: 2,
tmdb_id: 12,
language_iso_639_1: "de",
title: "Findet Nemo",
created_at: Fri, 15 Apr 2022 14:58:52.563142000 UTC +00:00,
updated_at: Fri, 15 Apr 2022 14:58:52.563142000 UTC +00:00>,
#<Film:0x0000000108156428
id: 3,
tmdb_id: 12,
language_iso_639_1: "fr",
title: "Le Monde de Nemo",
created_at: Fri, 15 Apr 2022 14:59:03.318667000 UTC +00:00,
updated_at: Fri, 15 Apr 2022 14:59:03.318667000 UTC +00:00>]
Now I want to create another table "film_backdrops" to save backdrop-images for the specific films. Those images do not differ for different languages, so only the tmdb_id is important.
My (probably to be revised) scheme for this table currently looks like this:
create_table "film_backdrops", force: :cascade do |t|
t.integer "tmdb_id", null: false
t.string "file_path", null: false
t.float "vote_average"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
And an entry would look like this, for example:
#<FilmBackdrop:0x0000000108a3e798
id: 1,
tmdb_id: 12,
file_path: "/bla/bla/image.png",
vote_average: 9.8,
created_at: Fri, 15 Apr 2022 15:00:59.635324000 UTC +00:00,
updated_at: Fri, 15 Apr 2022 15:00:59.635324000 UTC +00:00>
I want to link these two tables so that the same FilmBackdrop records are returned for films with the same tmdb_id and irrespective of the language_iso_639_1 field, like this:
Film.find_by(tmdb_id: 12, language_iso_639_1: "en").film_backdrops # => #<FilmBackdrop:0x0000000107b445d0
Film.find(tmdb_id: 12, language_iso_639_1: "de").film_backdrops # => #<FilmBackdrop:0x0000000107b445d0
Film.find(tmdb_id: 12, language_iso_639_1: "fr").film_backdrops # => #<FilmBackdrop:0x0000000107b445d0
The other way around should also work:
FilmBackdrop.find(1).films # => #<Film:0x00000001077cd348, #<Film:0x00000001077cd280 , #<Film:0x00000001077cd1b8
Since I don't need the primary key "id" of Film for this and "tmdb_id" of Film alone is not unique either, no approach I have tried so far has been successful.
Is it even possible to get it done without creating a third "inbetween" table?
Upvotes: 0
Views: 96
Reputation: 101821
What I'm guess that your doing here is localizing films and this solution feels very backwards and overcomplicated.
Rails doesn't really do composite primary keys / foreign keys as thats not something thats widely supported among databases. There is a gem for composite primary keys but it is built on hacking ActiveRecord internals so I would consider if this is something you really want to inflict on your app.
I would instead do it like so:
class Film
has_many :film_localizations
has_many :film_backdrops
end
# rails g model film_localization film:belongs_to language_iso_639_1:string title:string
class FilmLocalization
belongs_to :film
end
If you want to use an externally derived UUID instead of an auto-incrementing integer or generated UUID for the films table thats perfectly fine.
But your models should have a single unique primary key which other tables can use to refer to it or you're going to have a very difficult time as you're fighting the entire ORM / conventions.
Delegation can be used if you want a FilmLocalization
to behave like a Film
.
There are also ready made solutions for translating model data such as Mobility.
Upvotes: 0
Reputation: 11099
Assuming no changes to your provided schema, the only thing you need to do is specify the foreign_key
and primary_key
options on your associations:
class Film
has_many :film_backdrops, foreign_key: :tmdb_id, primary_key: :tmdb_id
end
class FilmBackdrop
has_many :films, foreign_key: :tmdb_id, primary_key: :tmdb_id
end
Note that while this will work for your purposes, it's non-standard in Rails. So some functionality, like the inverse
associations, will not be supported.
Upvotes: 0
Reputation: 52336
It sounds like you're missing a model in which the value of tmdb_id
is a unique key.
This would have a has_many
association to Film
, and, I think, has_many
to FilmBackdrop
.
Then Film
and FilmBackdrop
would each has_many
of the other through this model.
Upvotes: 1