: has_many: через ассоциации без имен конвенций? - PullRequest
2 голосов
/ 03 апреля 2011
create_table "friendships", :force => true do |t|
    t.integer  "user_id"
    t.integer  "friend_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "likes", :force => true do |t|
    t.string   "name"
    t.integer  "user_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "users", :force => true do |t|
    t.string   "name"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

А это модели

class User < ActiveRecord::Base

    has_many :friendships
    has_many :friends, :through => :friendships
    has_many :likes
    has_many :friends_likes, :through => :friendships, :source => :likes

end

class Friendship < ActiveRecord::Base

    belongs_to :user
    belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
    has_many :likes, :foreign_key => :user_id, 
end

class Like < ActiveRecord::Base

    belongs_to :user
    belongs_to :friendship

end

Я пытаюсь получить "лайки друзей", но не могу.

"User.find (1).friends_likes "дает этот SQL-запрос

SELECT "likes".* FROM "likes" INNER JOIN "friendships" ON "likes".user_id = "friendships".id WHERE (("friendships".user_id = 1))

, но я думаю, что это должно быть" INNER JOIN "дружба*

как я могу это сделать?спасибо

1 Ответ

1 голос
/ 04 апреля 2011

Самое простое решение, вероятно, заключается в добавлении метода экземпляра friends_likes в модель User, которая создает правильный SQL:

  def likes_of_friends
    friends.includes(:likes).map(&:likes)
  end

.includes(:likes) для повышения производительности, чтобы избежать ситуации с запросом N + 1.

Тогда ваш User.find(1).friends_likes выдаст следующие запросы, предполагая, что у пользователя 1 есть друзья с идентификаторами 2 и 3:

  User Load (0.1ms)  SELECT `users`.* FROM `users` LIMIT 1
  User Load (0.4ms)  SELECT `users`.* FROM `users` INNER JOIN `friendships` ON `users`.id = `friendships`.friend_id WHERE ((`friendships`.user_id = 1))
  Like Load (0.2ms)  SELECT `likes`.* FROM `likes` WHERE (`likes`.user_id IN (2,3))

Если вам действительно нужно все в одном запросе, вы можете написать простой SQL:

Like.find_by_sql("select likes.* from
  likes
    inner join users as friends
      on friends.id = likes.user_id
    inner join friendships
      on friendships.friend_id = friends.id
  where friendships.users_id = 1;
")

Причина, по которой это не так просто, заключается в том, что один пользователь «владеет» дружбой - это односторонняя связь, и, похоже, нет способа связать дружбу с «Другом» (указано friend_id на столе дружбы).

Итак, добавление противоположного направления поможет (странное название в стороне):

class Friendships
  # ...
  has_many :befriendedships, :class_name => "Friendship", :foreign_key => "friend_id"
end

Затем вы можете запросить что-то, что ищете немного проще:

Like.joins(:user => :befriendedships).where(["friendships.user_id = ?", 1])

Это сгенерирует по существу тот же SQL, что и пример find_by_sql.

...