Как я могу сделать many-many отношения с той же моделью в направляющих?
Например, каждое сообщение подключено ко многим сообщениям.
Существует несколько видов отношений «многие ко многим»; Вы должны задать себе следующие вопросы:
Это оставляет четыре различные возможности. Я разберусь с ними внизу.
Для справки: документация Rails по теме . Есть раздел под названием «Многие ко многим», и, конечно, документация по самим методам класса.
Это наиболее компактный код.
Начну с этой базовой схемы для ваших публикаций:
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
Для любых отношений «многие ко многим» вам нужна таблица объединения. Вот схема для этого:
create_table "post_connections", :force => true, :id => false do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
end
По умолчанию Rails будет называть эту таблицу комбинацией имен двух таблиц, к которым мы присоединяемся. Но это обернулось бы как посты _ посты
в этой ситуации, поэтому я решил взять пост _ соединения
вместо.
Здесь очень важно : id = > false
, чтобы опустить столбец id
по умолчанию. Rails хочет, чтобы столбец везде , за исключением в таблицах соединения для , имел _ и _ принадлежит _ ко _ многим
. Это будет громко жаловаться.
Наконец, обратите внимание, что имена столбцов также являются нестандартными (не post _ id
), чтобы предотвратить конфликт.
Теперь в вашей модели вам просто нужно рассказать Rails об этих нестандартных вещах. Это будет выглядеть следующим образом:
class Post < ActiveRecord::Base
has_and_belongs_to_many(:posts,
:join_table => "post_connections",
:foreign_key => "post_a_id",
:association_foreign_key => "post_b_id")
end
И это должно просто сработать! Ниже приведен пример сеанса IRB, выполняемого через скрипт/консоль
:
>> a = Post.create :name => 'First post!'
=> #<Post id: 1, name: "First post!">
>> b = Post.create :name => 'Second post?'
=> #<Post id: 2, name: "Second post?">
>> c = Post.create :name => 'Definitely the third post.'
=> #<Post id: 3, name: "Definitely the third post.">
>> a.posts = [b, c]
=> [#<Post id: 2, name: "Second post?">, #<Post id: 3, name: "Definitely the third post.">]
>> b.posts
=> []
>> b.posts = [a]
=> [#<Post id: 1, name: "First post!">]
При назначении связи post
будут созданы записи в таблице post _ connections
.
Некоторые вещи, которые следует отметить:
a.posts = [b, c]
вывод b.posts
не включает первую запись. PostConnection
. Обычно модели не используются для связи имеет _ и _ принадлежит _ ко _ многим
. По этой причине доступ к дополнительным полям невозможен. Прямо сейчас... У вас есть постоянный пользователь, который сегодня сделал сообщение на вашем сайте о том, как угри вкусны. Этот полный незнакомец приходит на ваш сайт, подписывается и пишет ругательный пост о неумении обычного пользователя. Ведь угри - вымирающий вид!
Таким образом, вы хотите уточнить в своей базе данных, что публикация B является ругательным раритетом на публикации A. Для этого вы хотите добавить поле категории
в ассоциацию.
То, что нам больше не нужно, это имеет _ и _ принадлежит _ ко _ многим
, но комбинация имеет _ много
, принадлежит _
, имеет _ много...,: через = >...
и дополнительную модель для таблицы соединений. Эта дополнительная модель дает нам возможность добавлять дополнительную информацию в саму ассоциацию.
Вот еще одна схема, очень похожая на приведенную выше:
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
create_table "post_connections", :force => true do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
t.string "category"
end
Обратите внимание, как в этой ситуации post _ connections
имеет столбец id
. (Параметр no : id = > false
отсутствует.) Это необходимо, поскольку для доступа к таблице будет использоваться обычная модель ActiveRecord.
Я начну с модели PostConnection
, потому что это просто:
class PostConnection < ActiveRecord::Base
belongs_to :post_a, :class_name => :Post
belongs_to :post_b, :class_name => :Post
end
Здесь происходит только : класс _ имя
, что необходимо, потому что Rails не может сделать вывод из post _ a
или post _ b
, что мы имеем дело с Почтой здесь. Мы должны сказать это прямо.
Теперь модель Post
:
class Post < ActiveRecord::Base
has_many :post_connections, :foreign_key => :post_a_id
has_many :posts, :through => :post_connections, :source => :post_b
end
С первой имеет _ связь
, мы говорим модели присоединиться post _ connections
на posts.id = post_connections.post_a_id
.
Со второй ассоциацией мы говорим Rails, что мы можем достичь других постов, которые связаны с этой, через нашу первую ассоциацию post _ connections
, а затем post _ b
association PostConnection
.
Не хватает еще одной вещи , и это то, что мы должны сообщить Rails, что PostConnection
зависит от сообщений, которым он принадлежит. Если один или оба из post _ a _ id
и post _ b _ id
были NULL
, то это соединение мало что скажет нам, не так ли? Вот как мы делаем это в нашей модели Post
:
class Post < ActiveRecord::Base
has_many(:post_connections, :foreign_key => :post_a_id, :dependent => :destroy)
has_many(:reverse_post_connections, :class_name => :PostConnection,
:foreign_key => :post_b_id, :dependent => :destroy)
has_many :posts, :through => :post_connections, :source => :post_b
end
Помимо незначительного изменения синтаксиса, здесь различаются две реальные вещи:
имеет _ много: post _ connections
имеет дополнительный параметр : dependent
. Со значением : уничтожить
, мы говорим Rails, что, как только этот пост исчезнет, он может пойти вперед и уничтожить эти объекты. Альтернативное значение, которое вы можете использовать здесь, это : удалить _ все
, что быстрее, но не вызовет никаких крючков уничтожения, если вы используете их. has _ many
для обратных соединений, которые связали нас через post _ b _ id
. Таким образом, Рейлз может уничтожить и их. Обратите внимание, что здесь необходимо указать : class _ name
, поскольку имя класса модели больше нельзя вывести из : reverse _ post _ connections
. Установив это, я приведу еще один сеанс irb через скрипт/консоль
:
>> a = Post.create :name => 'Eels are delicious!'
=> #<Post id: 16, name: "Eels are delicious!">
>> b = Post.create :name => 'You insensitive cloth!'
=> #<Post id: 17, name: "You insensitive cloth!">
>> b.posts = [a]
=> [#<Post id: 16, name: "Eels are delicious!">]
>> b.post_connections
=> [#<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>]
>> connection = b.post_connections[0]
=> #<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>
>> connection.category = "scolding"
=> "scolding"
>> connection.save!
=> true
Вместо того, чтобы создавать ассоциацию, а затем настройку категорию отдельно, вы также можете просто создать PostConnection и сделать с ним:
>> b.posts = []
=> []
>> PostConnection.create(
?> :post_a => b, :post_b => a,
?> :category => "scolding"
>> )
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> b.posts(true) # 'true' means force a reload
=> [#<Post id: 16, name: "Eels are delicious!">]
Мы также можем манипулировать ассоциациями post _ connections
и reverse _ post _ connections
; он будет точно отражать в ассоциации постов
:
>> a.reverse_post_connections
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> a.reverse_post_connections = []
=> []
>> b.posts(true) # 'true' means force a reload
=> []
В обычной имеет ассоциации _ и _ принадлежит _ к _ многим
, ассоциация определяется в обеих задействованных моделях . И ассоциация является двунаправленной.
Но в данном случае существует только одна модель Post. И ассоциация указывается только один раз. Именно поэтому в данном конкретном случае ассоциации являются однонаправленными.
То же самое справедливо для альтернативного метода с имеет _ много
и модель для таблицы соединения.
Это лучше всего видно, если просто обратиться к ассоциациям из irb и посмотреть на SQL, который Rails генерирует в файле журнала. Вы найдете что-то вроде следующего:
SELECT * FROM "posts"
INNER JOIN "post_connections" ON "posts".id = "post_connections".post_b_id
WHERE ("post_connections".post_a_id = 1 )
Чтобы сделать ассоциацию двунаправленной, мы должны найти способ сделать Rails OR
вышеперечисленные условия с post _ a _ id
и post _ b _ id
реверсированными, поэтому он будет выглядеть в обоих направлениях.
К сожалению, единственный способ сделать это, о котором я знаю, довольно хакерский. Необходимо вручную указать SQL, используя параметры имеет _ и _ принадлежит _ к _ многим
, например : finder _ sql
, : delete _ sql
и т.д. Это не красиво. (Я тоже открыт для предложений. Кто угодно?)