我使用的是Ruby 2.1,Rails 4.2和mysql2(0.4.4)。我的应用程序有以下两种型号:
class Batch < ActiveRecord::Base
belongs_to :batch_type
end
class BatchType < ActiveRecord::Base
has_many :batches
end
使用以下迁移创建:
class CreateBatches < ActiveRecord::Migration
def change
create_table :batches do |t|
t.integer :batch_type_id
t.string :title
t.timestamps
end
end
end
class CreateBatchTypes < ActiveRecord::Migration
def change
create_table :batch_types do |t|
t.string :short_name
t.timestamps
end
end
end
我使用以下内容播种数据库:
BatchType.create short_name: 'Type 1'
Batch.create batch_type: BatchType.first
我正在尝试使用名为&#39; duration&#39;的动态创建的属性访问器,这是created_at
和updated_at
的时间戳之间的差异。当我按title
属性订购结果时,访问者可用:
2.1.0 :007 > Batch.order('title').select('batches.*', 'IF(batches.updated_at AND batches.created_at, batches.updated_at - batches.created_at, 0) AS duration').includes(:batch_type).first.duration
Batch Load (0.7ms) SELECT batches.*, IF(batches.updated_at AND batches.created_at, batches.updated_at - batches.created_at, 0) AS duration FROM `batches` ORDER BY title LIMIT 1
BatchType Load (0.7ms) SELECT `batch_types`.* FROM `batch_types` WHERE `batch_types`.`id` IN (1)
=> 0
但是,当我按照已加入模型BatchType
上的属性订购结果时,访问者不可用:
2.1.0 :008 > Batch.order('batch_types.short_name').select('batches.*', 'IF(batches.updated_at AND batches.created_at, batches.updated_at - batches.created_at, 0) AS duration').includes(:batch_type).first.duration
SQL (0.6ms) SELECT batches.*, IF(batches.updated_at AND batches.created_at, batches.updated_at - batches.created_at, 0) AS duration, `batches`.`id` AS t0_r0, `batches`.`batch_type_id` AS t0_r1, `batches`.`batch_status_id` AS t0_r2, `batches`.`title` AS t0_r3, `batches`.`created_at` AS t0_r4, `batches`.`updated_at` AS t0_r5, `batch_types`.`id` AS t1_r0, `batch_types`.`short_name` AS t1_r1, `batch_types`.`created_at` AS t1_r2, `batch_types`.`updated_at` AS t1_r3 FROM `batches` LEFT OUTER JOIN `batch_types` ON `batch_types`.`id` = `batches`.`batch_type_id` ORDER BY batch_types.short_name LIMIT 1
NoMethodError: undefined method `duration' for #<Batch:0x0000010125ede0>
结果的顺序如何影响select
创建访问者?
答案 0 :(得分:1)
我认为你有点滥用includes
。 includes
旨在将多个表混合在一起形成一个查询,以减少您需要执行的查询数。如果您只想加入,请使用joins
:
Batch.order('title')
.select('batches.*', 'IF(batches.updated_at AND batches.created_at, batches.updated_at - batches.created_at, 0) AS duration')
.joins(:batch_type)
.first
.duration
或者,向Batch
添加一个方法,用于计算Ruby中的持续时间并继续使用includes
:
class Batch < ActiveRecord::Base
def duration
updated_at - created_at
end
#...
end
我无法找到让select
,includes
和order
按照您需要的方式一起工作的方法,抱歉。
这里有一些挥手和猜想。你需要通过ActiveRecord源来挖掘真正发生的事情,这是一项非常重要的任务,有龙和其他可怕的野兽。
SELECT中生成的t0_r0
...列名可能是问题的根源:
SELECT batches.*,
IF(batches.updated_at AND batches.created_at, batches.updated_at - batches.created_at, 0) AS duration,
`batches`.`id` AS t0_r0, ...
`batch_types`.`id` AS t1_r0, ...
当你使用includes
时,你告诉ActiveRecord你希望它做一个查询,把所有东西混在一起(因此你在SQL中看到的LEFT JOIN和看起来很滑稽的tN_rM
列别名)然后ActiveRecord必须从组合查询中提取所有不同的模型。一路上它可能完全忘记你的select
,因为它必须以非常特定的格式构建自己的SELECT子句;这里有一个冲突:调用select
告诉AR查看SELECT以找出查询返回的内容以及模型实例上应该有哪些方法,调用includes
告诉AR构建自己的SELECT。
在您的第一个查询中:
Batch.order('title')...
AR不会被强制执行JOIN,因此它不会执行任何操作,并且您的SELECT无损地通过ActiveRecord的内容。在第二个查询中:
Batch.order('batch_types.short_name')...
AR被强制执行JOIN并激活SELECT修改逻辑,将所有内容都横向发送。
includes
有点像黑客,所以它有点脆弱并不奇怪。
我称这是AR中的一个错误,它应该抱怨你的小问题与你的select
电话相混淆,或者它应该做得对。