拆解Rails中的.joins和.where方法

12

我有两个模型 - BannerBannerType

它们的架构如下:

Banner

# Table name: banners
#
#  id             :integer          not null, primary key
#  name           :string(255)
#  image          :string(255)
#  created_at     :datetime         not null
#  updated_at     :datetime         not null
#  url            :string(255)
#  banner_type_id :integer

横幅类型

# Table name: banner_types
#
#  id         :integer          not null, primary key
#  name       :string(255)
#  created_at :datetime         not null
#  updated_at :datetime         not null

Banner 属于 :banner_type,而 BannerType 拥有多个 banners

我在 BannerType 中有两条记录,分别是:

BannerType.all
  BannerType Load (0.3ms)  SELECT "banner_types".* FROM "banner_types" 
 => [#<BannerType id: 1, name: "Featured", created_at: "2012-12-17 04:35:24", updated_at: "2012-12-17 04:35:24">, #<BannerType id: 2, name: "Side", created_at: "2012-12-17 04:35:40", updated_at: "2012-12-17 04:35:40">] 

如果我想查询所有类型为Featured的横幅,可以尝试以下操作:

Banner.joins(:banner_type).where("banner_types.name = ?", 'Featured')

我知道我也可以查询banner_type_id => 1,但这与此特定问题无关。

如果我们分解这个语句,有一些事情对我来说有点令人困惑。

  1. Banner.join(:banner_type) - 将生成NoMethodError: undefined method 'join' for #<Class:0x007fb6882909f0>。为什么没有叫做join的Rails方法,而这是SQL方法的名称?
  2. 为什么要使用Singular banner_type,而不是表名banner_types,比如Banner.joins(:banner_type)。难道我不是在联接Banner和BannerType表(根据Rails约定表示为复数)吗?如果我尝试Banner.joins(:banner_types),会出现以下错误:

    Banner.joins(:banner_types) ActiveRecord::ConfigurationError: Association named 'banner_types' was not found; perhaps you misspelled it?

  3. 为什么where子句需要banner_types而不是banner_type(即复数形式-即表名而不是在joins方法中使用的符号)?如果你在两个地方都使用表名或符号名,似乎更符合直觉。至少为了保持一致性。

  4. 为什么我不能通过关联进行动态查找 - 即如果我可以使用 Banner.find_by_banner_type_name("Featured") 就好了?

希望听听你的想法。

谢谢。

1个回答

9

#1

Banner.join(:banner_type) - 会生成 NoMethodError: undefined method 'join' for #。为什么Rails没有叫做join的方法,而这却是SQL方法名呢?

将其阅读为普通英语更清晰,即“Banner joins banner type”与“Banner join banner type”。我想没有比这更好的理由了。


#2

为什么我要使用Banner.joins(:banner_type),即单数的banner_type,而表名却是banner_types。我不是在连接Banner和BannerType表(Rails约定为复数形式)吗?如果我尝试Banner.joins(:banner_types),则会出现以下错误:

Banner.joins(:banner_types) ActiveRecord::ConfigurationError: Association named 'banner_types' was not found; perhaps you misspelled it?

.joins(:banner_type)中,:banner_type是您正在连接的关系,而不是表。您有

has_one :banner_type

这就是Rails连接的方式。当你将符号传递给.joins时,Rails会按照这种方式工作,如果你传递一个不匹配模型现有关联的符号,则会出现错误并引用该关联。这也解释了为什么可以使用嵌套关联的符号进行多层级的JOIN,详见Rails指南
Category.joins(:posts => [{:comments => :guest}, :tags])

此外,在Rails指南中也有描述,你同样可以向.joins方法传递字符串。
Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')

在上面的例子中,当一个字符串被传递给joins时,使用的是表名addresses,而不是关联名称; 这有助于回答#3

#3

为什么where子句需要banner_types而不是banner_type(即复数版本 - 即表名而不是joins方法中使用的符号?如果在两个位置都使用表名或符号名称,则似乎更直观。至少出于一致性的目的。

经过一些字符串插值后,传递给where方法的字符串(类似于joins或多或少直接传递到最终的SQL查询中(ARel会进行一些操作)name是一个模糊的列(你的bannersbanner_types表都有一个name列),因此需要通过完整路径[TABLE NAME] .[COLUMN NAME]来引用表。例如,如果您在banner_types中有一些color(在banners中也不存在),则没有必要在where方法中使用"banner_types.color =?"; "color =?"也可以正常工作。
请注意,就像在#2中一样,您可以将符号传递给where方法以连接表。
Banner.joins(:banner_type).where(banner_type: [name: 'Featured'])

#4

为什么我不能通过关联进行动态查找 - 例如我想这样做: Banner.find_by_banner_type_name("Featured")?

你不能这样查找是因为ARel不支持,就是这么简单的道理 (在我看来, find_by_banner_type_name 这个方法名相当令人困惑).


哇...感谢您提供这么全面的解决方案 - 但是只想指出另一个人回答并已删除的#4问题的解决方案是:BannerType.find_by_name('Featured').banners。那会有点满足我的最后一个问题,尽管有点反向。我明白您的观点...那可能会有点令人困惑。 - marcamillion
另外,关于#2,我没有明确使用has_one:banner_type。您是说因为Banner belongs_to:banner_type吗?这样实际上就是一个has_one关系? - marcamillion
现在它完美地工作,而且我没有定义 has_one。我只在 BannerType 模型上有 has_many :banners,并且在 Banner 模型上有 belongs_to :banner_type。非常好用。 - marcamillion
有点跑题,但你有使用 acts_as_shopping_cart 的经验吗?我需要一些关于这个问题的帮助 - http://stackoverflow.com/questions/13899686/using-acts-as-shopping-cart-how-do-i-implement-basic-quantity-editing - marcamillion
1
虽然有点晚了,但是关于第一点:joinArray 上的一个方法;由于 ActiveRecord::Relation 将它不知道的任何方法委托给懒加载的记录数组,如果你调用 join,就会触发数组方法。我猜 joins 多了一个 s 的原因是为了 1)避免混淆这两个方法 2)避免掩盖 join 方法(别问我为什么,我不知道在哪种情况下会有用)3)让 Relation 的接口感觉像是一个强化版的数组。 - m_x
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接