Mysql 当关系按联接模型的属性排序时,ActiveRecord select不会创建访问器方法

Mysql 当关系按联接模型的属性排序时,ActiveRecord select不会创建访问器方法,mysql,ruby-on-rails,ruby,activerecord,rails-activerecord,Mysql,Ruby On Rails,Ruby,Activerecord,Rails Activerecord,我使用的是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 我试图使用一个名为“duration”的动态创建的属性访问器,它是创建的时间戳和更新的时间戳之间的差异。当我按title属性对结果排序

我使用的是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
我试图使用一个名为“duration”的动态创建的属性访问器,它是创建的时间戳和更新的时间戳之间的差异。当我按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上的属性对结果排序时,访问器不可用:

结果的顺序如何影响select创建访问器?

我认为您有点滥用了。includes旨在将多个表合并到一个查询中,以减少需要执行的查询数量。如果您只想加入,请使用:

或者,向Batch添加一个方法,该方法使用Ruby计算持续时间,并继续使用includes:

当您使用时,您告诉ActiveRecord您希望它执行一个查询,将所有内容混合在一起,因此您在SQL中看到左连接和外观有趣的tN_rM列别名,然后ActiveRecord必须从组合查询中提取所有不同的模型。一路上,它可能会完全忘记您的select,因为它必须以非常特定的格式构建自己的select子句;这里有一个冲突:调用select告诉AR查看select以确定查询返回的内容以及模型实例上应该提供的方法,调用INCLUDE告诉AR构建自己的select

在第一个查询中:

Batch.order('title')...
AR不会被迫加入,所以它不会加入,而您的选择会毫发无损地通过ActiveRecord的内部。在第二个查询中:

Batch.order('batch_types.short_name')...
AR被强制执行一个连接,这将激活SELECT mangling逻辑,该逻辑将所有内容发送到侧面

includes有点像黑客,所以它有点脆弱也就不足为奇了


我把这称为AR中的一个bug,它应该抱怨你把它的小脑袋和你的select调用搞混了,或者它应该做得很好。

如何动态创建一个动态访问器?简而言之,“Rails magic”:它在本文档中有介绍:-如果指定了一个别名,则可以从生成的ObjectsHanks访问它!我刚开始尝试用“连接”代替“包含”,这就解决了我的问题。我仍然有一个问题,那就是我对“包含”和“连接”之间的区别的理解有点动摇,但我会仔细阅读。我只是使用了includes,因为当我开始实现排序时,在现有代码中使用了includes。执行摘要:当您想要避免多个查询以满足关系时,请使用includes;当您想要在一个查询中访问多个表时,请使用join。如果你在火上放足够的汽油,它可能会熄灭。
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>
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
class Batch < ActiveRecord::Base
  def duration
    updated_at - created_at
  end
  #...
end
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, ...
Batch.order('title')...
Batch.order('batch_types.short_name')...