我有三种模式:
class Book < ActiveRecord::Base
has_many :collections
has_many :users, :through => :collections
end
class User < ActiveRecord::Base
has_many :collections
has_many :books, :through => :collections
end
class Collection < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
我正在尝试显示这些图书的列表,并且有一个链接可以添加或删除用户的集合。我无法弄清楚这样做的最佳语法。
例如,如果我执行以下操作:
控制器
class BooksController < ApplicationController
def index
@books = Book.all
end
end
查看
...
<% if book.users.include?(current_user) %>
...
或显然是反...
...
<% if current_user.books.include?(book) %>
...
然后为每本书发送查询以检查包含哪些内容?这很浪费。我在考虑将用户或集合添加到Book.all上的:include,但我不确定这是最好的方法。实际上,我所需要的只是book对象,只是一个布尔列,表明当前用户是否在他们的集合中有书,但我不知道如何对查询进行论证以便这样做。
提前感谢您的帮助。
-Damien
答案 0 :(得分:1)
我创建了一个gem(select_extra_columns),用于在ActiveRecord查找器中返回join / calculated / aggregate列。使用此gem,您将能够在一个查询中获取书籍详细信息和标记indicating if the current user has the book
。
在您的用户模型中注册select_extra_columns
功能。
class Book < ActiveRecord::Base
select_extra_columns
has_many :collections
has_many :users, :through => :collections
end
现在在您的控制器中添加以下行:
@books = Book.all(
:select => "books.*, IF(collections.id, 1, 0) AS belongs_to_user",
:extra_columns => {:belongs_to_user => :boolean},
:joins => "LEFT OUTER JOIN collections
ON book.id = collections.book_id AND
collections.user_id = #{current_user.id}"
)
现在,您可以执行以下操作。
book.belongs_to_user?
答案 1 :(得分:0)
你将需要2个SQL查询和基于O(1)的查找(可能不相关,但这是原则)来检查他们是否有这本书。
初次通话。
@books = Book.all
@user = User.find(params[:id], :include => :collections)
接下来,您将要将用户拥有的书籍写入哈希以进行恒定时间查找(如果人们不会有很多书籍,只需要做一个数组。包含?很好)。
@user_has_books = Hash.new
@user.collections.each{|c|@user_has_books[c.book_id] = true}
在显示屏上:
@books.each do |book|
has_book = @user_has_books.has_key?(book.id)
end
我不会在用户对象上缓存book_ids,因为如果你因为某种原因(即memcached或队列)开始序列化你的用户对象,那么走这条路会会产生一些有趣和意想不到的后果。
编辑:加载中间集合而不是双重装书。
答案 2 :(得分:0)
基本上,您需要进行一次调用以获取图书信息,并使用布尔标志指示当前用户是否拥有该图书。 ActiveRecord查找器不允许您从另一个表返回连接结果。我们通过做一个技巧来解决这个问题。
在Book
模型中添加此方法。
def self.extended_book
self.columns # load the column definition
@extended_user ||= self.clone.tap do |klass|
klass.columns << (klass.columns_hash["belongs_to_user"] =
ActiveRecord::ConnectionAdapters::Column.new(
"belongs_to_user", false, "boolean"))
end # add a dummy column to the cloned class
end
在您的控制器中使用以下代码:
@books = Book.extended_book.all(
:select => "books.*, IF(collections.id, 1, 0) AS belongs_to_user",
:joins => "LEFT OUTER JOIN collections
ON book.id = collections.book_id AND
collections.user_id = #{current_user.id}"
)
现在,您可以执行以下操作。
book.belongs_to_user?
<强>解释强>
在extended_book
方法中,您要创建Book类的副本,并向哈希添加虚拟列belongs_to_user
。在查询期间,不会拒绝额外的连接列,因为它存在于columns_hash中。您应该仅使用extended_book
进行查询。
如果将它用于CRUD操作,DB将抛出错误。
答案 3 :(得分:-1)
在关联上使用exists?
,因为它是直接SQL调用。未加载关联数组以执行这些检查。
books.users.exists?(current_user)
这是Rails执行的SQL。
SELECT `users`.id FROM `users`
INNER JOIN `collections` ON `users`.id = `collections`.user_id
WHERE (`users`.`id` = 2) AND ((`collections`.book_id = 1)) LIMIT 1
在上面的SQL中current_user
id = 2且book
id为1
current_user.books.exists?(book)
这是Rails执行的SQL。
SELECT `books`.id FROM `books`
INNER JOIN `collections` ON `books`.id = `collections`.book_id
WHERE (`books`.`id` = 3) AND ((`collections`.user_id = 4)) LIMIT 1
在上面的SQL current_user
中,id = 4和book
id为3
有关详细信息,请参阅exists?
关联中:has_many
方法的documentation。
修改:我已提供其他信息以验证我的回答。
答案 4 :(得分:-1)
我首先在User模型中创建一个实例方法,用于“缓存”其集合中的所有Book ID:
def book_ids
@book_ids ||= self.books.all(:select => "id").map(&:id)
end
每个控制器请求只执行一次SQL查询。然后在User模型上创建另一个实例方法,该方法将book_id作为参数,并检查它是否包含在他的图书集中。
def has_book?(book_id)
book_ids.include?(book_id)
end
然后,当你遍历书籍时:
<% if current_user.has_book?(book.id) %>
该控制器请求只有2个SQL查询:)