解构 Rails .joins 和 .where 方法
Posted
技术标签:
【中文标题】解构 Rails .joins 和 .where 方法【英文标题】:Deconstructing Rails .joins & .where methods 【发布时间】:2012-12-04 06:24:06 【问题描述】:我有两个模型 - Banner
和 BannerType
。
他们的架构如下所示:
横幅
# 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 belongs_to :banner_type
和 BannerType has_many :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
进行查询,但这与这个特定问题密切相关。
如果我们分解那个陈述,有几件事让我有点困惑。
Banner.join(:banner_type)
- 会生成 NoMethodError: undefined method 'join' for #<Class:0x007fb6882909f0>
为什么没有名为 join
的 Rails 方法,而那是 SQL 方法名称?
当表名是banner_types
时,为什么我要使用Banner.joins(:banner_type)
即单数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?
为什么where
子句需要banner_types
而不是banner_type
(即复数形式- 即表名而不是joins
方法中使用的符号?看起来会更多如果您在两个地方都使用表名或在两个地方都使用符号名,那么直观。至少,为了保持一致性。
为什么我不能通过关联进行动态查找 - 即如果我能做到 Banner.find_by_banner_type_name("Featured")
就好了?
很想听听你的想法。
谢谢。
【问题讨论】:
【参考方案1】:#1
Banner.join(:banner_type) - 会生成 NoMethodError: undefined method 'join' for # 为什么没有 Rails 方法调用 join 而那是 SQL 方法名?
用简单的英语阅读“横幅加入横幅类型”与“横幅加入横幅类型”时会更清楚。我不确定还有更多的原因。
#2
为什么我做 Banner.joins(:banner_type) 即当表名是banner_types时单数banner_type。我没有加入 Banner 和 BannerType 表(Rails 约定表示为复数)。如果我尝试 Banner.joins(:banner_types) 这是我得到的错误:
Banner.joins(:banner_types) ActiveRecord::ConfigurationError: 未找到名为“banner_types”的关联;也许你拼错了?
在.joins(:banner_type)
中,:banner_type
是您要加入的关系,而不是表。你有
has_one :banner_type
这就是 Rails 加入的原因。这就是当您将 Symbol 传递给 .joins
时 Rails 的工作原理,这也是为什么当您传递与模型的任何现有关联不匹配的 Symbol 时错误指的是关联。
这也是为什么您可以使用嵌套关联的符号来深度处理JOIN
的多个级别,如in the Rails Guide 所述
Category.joins(:posts => [:comments => :guest, :tags])
还描述了in the Rails Guide,您也可以将字符串传递给.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
是一个不明确的列(您的banners
和banner_types
表都有一个name
列),因此需要通过其完整路径[TABLE NAME].[COLUMN NAME]
引用该表。例如,如果您在banner_types
中有一些color
列(在banners
中也不存在),则无需在您的where
中将其用作"banner_types.color = ?"
方法; "color = ?"
可以正常工作。
注意,就像在 #2 中一样,您可以将符号传递给 where
方法以用于 JOIN
'd 表。
Banner.joins(:banner_type).where(banner_type: [name: 'Featured'])
#4
为什么我不能通过关联进行动态查找 - 即如果我可以做到 Banner.find_by_banner_type_name("Featured") 会很好?
你不能做这样的查找,因为它在 ARel 中不受支持,它真的很简单(IMO,像 find_by_banner_type_name
这样的方法名称相当混乱)。
【讨论】:
哇...感谢这个全面的解决方案 - 但只是指出其他人回答的 #4 的一个解决方案已被删除:BannerType.find_by_name('Featured').banners
。这有点满足我的最后一个问题,虽然有点倒退。我确实明白你的意思……这可能会让人有点困惑。
另外...re: #2,我没有明确的has_one :banner_type
。你这么说是因为Banner belongs_to :banner_type
?所以实际上是has_one
关系?
在没有定义 has_one
关联的情况下,查询(连接)是否也能正常工作?我从来没有尝试过。我也很好奇您是否有没有明确定义的原因。
它现在完美运行,我没有定义has_one
。我在BannerType
模型上只有has_many :banners
,在Banner
模型上只有belongs_to :banner_type
。像魅力一样工作。
迟到了,但至于#1:join
是Array
上的一个方法;由于ActiveRecord::Relation
将它不知道的任何方法委托给底层延迟加载的Array
记录,如果您调用join
,您将点击数组方法。我猜joins
加上一个额外的s
的原因是1)避免方法之间的混淆2)避免掩盖join
方法(不要问我为什么,我不知道在哪种情况下它会有用) 3) 让Relation
的界面感觉就像是类固醇上的数组以上是关于解构 Rails .joins 和 .where 方法的主要内容,如果未能解决你的问题,请参考以下文章
Rails - 传递给 #or 的关系必须在结构上兼容。不兼容的值:[:joins]