ActiveRecord 多对多我应该制作另一个 table 来保存关系信息吗?
ActiveRecord many to many should I make another table to hold the relation information?
我有 albums
和 artists
table 应该尽可能多地相关:每个专辑可以由多个艺术家拥有,每个艺术家可以有多个专辑。但是我需要知道画师是主要画师还是贡献者。
目前我只使用 albums
table 中的 artists
列将艺术家 ID 保存在分号分隔的字符串中(格式:3;6;343;56;1)。主要艺术家的 id 应该首先出现,其余的只是贡献者。
目前,我通过使用 .where
和 LIKE
关键字查询访问艺术家在专辑中的贡献。然后根据 this answer.
过滤数组结果以排除前缀(艺术家 ID)
@albums_ = Album.where("artists LIKE :prefix", prefix: "%#{params[:id]}%")
@albums_contribute_to = @albums_.select { |album| !album.artists.start_with?("#{params[:id]}") }
有没有更有效的方法来做到这一点?
使用连接table是典型的情况。如果你想建立那种关系。你正在做的事情只会在很长的 运行 中产生问题,因为你必须将这些信息保存在内存中,这也很慢。
在 ruby 和 rails 中,该关系需要以下内容:
class Album < ApplicationRecord
has_many :album_artists
has_many :artists, through: :album_artists
end
class AlbumArtist < ApplicationRecord
belongs_to :artist
belongs_to :album
end
class Artist < ApplicationRecord
has_many :album_artists
has_many :albums, through: :album_artists
end
如果您想在 rails 上使用 ruby 避免这样做,您必须实施自定义查询,但规则是相同的,您必须创建某种连接 table.
我真的不认为您需要一个单独的模型来加入 Album
和 Artist
模型。因此,您可以使用来自 ActiveRecord
并用于 传递多对多 关系的 has_and_belong_to_many
(HABTM) 方法。您还需要一个 table 来连接这两个模型,您可以通过迁移轻松创建一个。
我强烈建议您阅读这篇文章 article。这对你真的很有帮助。
试了一下,主要的想法是在连接 table 上添加一些附加信息(在我的示例中,我称之为 primary
)。然后阅读 docs 了解如何告诉 ActiveRecord
使用它们。
# setup active record
require 'active_record'
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
# some tables (migrations / schema)
ActiveRecord::Schema.define do
create_table(:artists) { |t| t.string :name}
create_table(:albums) { |t| t.string :name }
create_table :appearances do |t|
t.integer :artist_id
t.integer :album_id
t.boolean :primary, default: false # <-- join table specifies who is primary
end
end
# some models
class Artist < ActiveRecord::Base
has_many :appearances
has_many :albums, through: :appearances
end
class Appearance < ActiveRecord::Base
belongs_to :artist
belongs_to :album
end
class Album < ActiveRecord::Base
# three associations to appearances
has_many :all_appearances, class_name: 'Appearance'
has_many :primary_appearances, -> { where primary: true }, class_name: 'Appearance'
has_many :featured_appearances, -> { where primary: false }, class_name: 'Appearance'
# three associations to artists
has_many :all_artists, through: 'all_appearances', source: 'artist'
has_many :primary_artists, through: 'primary_appearances', source: 'artist'
has_many :featured_artists, through: 'featured_appearances', source: 'artist'
end
# some artists
dre = Artist.create! name: 'Dr. Dre'
dogg = Artist.create! name: 'Snoop Dogg'
slim = Artist.create! name: 'Eminem'
# some albums
weed = Album.create! name: 'The Chronic 2001',
primary_artists: [dre],
featured_artists: [dogg, slim]
show = Album.create! name: 'The Eminem Show',
primary_artists: [slim],
featured_artists: [dre]
# it understands the associations
weed.all_artists.pluck :name # => ["Dr. Dre", "Snoop Dogg", "Eminem"]
weed.primary_artists.pluck :name # => ["Dr. Dre"]
weed.featured_artists.pluck :name # => ["Snoop Dogg", "Eminem"]
# might be nice to add similar scoped associations to get from Artist to Album
weed # => #<Album id: 1, name: "The Chronic 2001">
.primary_artists # => #<ActiveRecord::Associations::CollectionProxy [#<Artist id: 1, name: "Dr. Dre">]>
.first # => #<Artist id: 1, name: "Dr. Dre">
.albums # => #<ActiveRecord::Associations::CollectionProxy [#<Album id: 1, name: "The Chronic 2001">, #<Album id: 2, name: "The Eminem Show">]>
.last # => #<Album id: 2, name: "The Eminem Show">
.primary_artists # => #<ActiveRecord::Associations::CollectionProxy [#<Artist id: 3, name: "Eminem">]>
.first # => #<Artist id: 3, name: "Eminem">
同意 Joshua 和 Przemek 的观点,这是一个简单的解决方案。下面是如何使用它:
class Appearance < ActiveRecord::Base
belongs_to :artist
belongs_to :album
# has attribute "primary"
end
class Artist < ActiveRecord::Base
has_many :appearances
has_many :albums, through: :appearances
end
class Album < ActiveRecord::Base
has_many :appearances
has_many :artists, through: :appearances
end
# create album with 2 artists
Album.create(
name: "foo_bar",
appearances: [
Appearance.new(artist: Artist.create(name: "foo"), primary: true),
Appearance.new(artist: Artist.create(name: "bar"))
]
)
# list artists
album = Album.first
album.appearances.order("piramary desc").each do |appearance|
puts "#{appearance.artist.name} #{appearance.primary? ? "(Primary)" : ""}"
end
我有 albums
和 artists
table 应该尽可能多地相关:每个专辑可以由多个艺术家拥有,每个艺术家可以有多个专辑。但是我需要知道画师是主要画师还是贡献者。
目前我只使用 albums
table 中的 artists
列将艺术家 ID 保存在分号分隔的字符串中(格式:3;6;343;56;1)。主要艺术家的 id 应该首先出现,其余的只是贡献者。
目前,我通过使用 .where
和 LIKE
关键字查询访问艺术家在专辑中的贡献。然后根据 this answer.
@albums_ = Album.where("artists LIKE :prefix", prefix: "%#{params[:id]}%")
@albums_contribute_to = @albums_.select { |album| !album.artists.start_with?("#{params[:id]}") }
有没有更有效的方法来做到这一点?
使用连接table是典型的情况。如果你想建立那种关系。你正在做的事情只会在很长的 运行 中产生问题,因为你必须将这些信息保存在内存中,这也很慢。
在 ruby 和 rails 中,该关系需要以下内容:
class Album < ApplicationRecord
has_many :album_artists
has_many :artists, through: :album_artists
end
class AlbumArtist < ApplicationRecord
belongs_to :artist
belongs_to :album
end
class Artist < ApplicationRecord
has_many :album_artists
has_many :albums, through: :album_artists
end
如果您想在 rails 上使用 ruby 避免这样做,您必须实施自定义查询,但规则是相同的,您必须创建某种连接 table.
我真的不认为您需要一个单独的模型来加入 Album
和 Artist
模型。因此,您可以使用来自 ActiveRecord
并用于 传递多对多 关系的 has_and_belong_to_many
(HABTM) 方法。您还需要一个 table 来连接这两个模型,您可以通过迁移轻松创建一个。
我强烈建议您阅读这篇文章 article。这对你真的很有帮助。
试了一下,主要的想法是在连接 table 上添加一些附加信息(在我的示例中,我称之为 primary
)。然后阅读 docs 了解如何告诉 ActiveRecord
使用它们。
# setup active record
require 'active_record'
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
# some tables (migrations / schema)
ActiveRecord::Schema.define do
create_table(:artists) { |t| t.string :name}
create_table(:albums) { |t| t.string :name }
create_table :appearances do |t|
t.integer :artist_id
t.integer :album_id
t.boolean :primary, default: false # <-- join table specifies who is primary
end
end
# some models
class Artist < ActiveRecord::Base
has_many :appearances
has_many :albums, through: :appearances
end
class Appearance < ActiveRecord::Base
belongs_to :artist
belongs_to :album
end
class Album < ActiveRecord::Base
# three associations to appearances
has_many :all_appearances, class_name: 'Appearance'
has_many :primary_appearances, -> { where primary: true }, class_name: 'Appearance'
has_many :featured_appearances, -> { where primary: false }, class_name: 'Appearance'
# three associations to artists
has_many :all_artists, through: 'all_appearances', source: 'artist'
has_many :primary_artists, through: 'primary_appearances', source: 'artist'
has_many :featured_artists, through: 'featured_appearances', source: 'artist'
end
# some artists
dre = Artist.create! name: 'Dr. Dre'
dogg = Artist.create! name: 'Snoop Dogg'
slim = Artist.create! name: 'Eminem'
# some albums
weed = Album.create! name: 'The Chronic 2001',
primary_artists: [dre],
featured_artists: [dogg, slim]
show = Album.create! name: 'The Eminem Show',
primary_artists: [slim],
featured_artists: [dre]
# it understands the associations
weed.all_artists.pluck :name # => ["Dr. Dre", "Snoop Dogg", "Eminem"]
weed.primary_artists.pluck :name # => ["Dr. Dre"]
weed.featured_artists.pluck :name # => ["Snoop Dogg", "Eminem"]
# might be nice to add similar scoped associations to get from Artist to Album
weed # => #<Album id: 1, name: "The Chronic 2001">
.primary_artists # => #<ActiveRecord::Associations::CollectionProxy [#<Artist id: 1, name: "Dr. Dre">]>
.first # => #<Artist id: 1, name: "Dr. Dre">
.albums # => #<ActiveRecord::Associations::CollectionProxy [#<Album id: 1, name: "The Chronic 2001">, #<Album id: 2, name: "The Eminem Show">]>
.last # => #<Album id: 2, name: "The Eminem Show">
.primary_artists # => #<ActiveRecord::Associations::CollectionProxy [#<Artist id: 3, name: "Eminem">]>
.first # => #<Artist id: 3, name: "Eminem">
同意 Joshua 和 Przemek 的观点,这是一个简单的解决方案。下面是如何使用它:
class Appearance < ActiveRecord::Base
belongs_to :artist
belongs_to :album
# has attribute "primary"
end
class Artist < ActiveRecord::Base
has_many :appearances
has_many :albums, through: :appearances
end
class Album < ActiveRecord::Base
has_many :appearances
has_many :artists, through: :appearances
end
# create album with 2 artists
Album.create(
name: "foo_bar",
appearances: [
Appearance.new(artist: Artist.create(name: "foo"), primary: true),
Appearance.new(artist: Artist.create(name: "bar"))
]
)
# list artists
album = Album.first
album.appearances.order("piramary desc").each do |appearance|
puts "#{appearance.artist.name} #{appearance.primary? ? "(Primary)" : ""}"
end