当关联按连接模型的属性排序时,ActiveRecord选择不创建存取方法

时间:2016-08-02 16:58:46

标签: 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

使用以下迁移创建:

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_atupdated_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创建访问者?

1 个答案:

答案 0 :(得分:1)

我认为你有点滥用includesincludes旨在将多个表混合在一起形成一个查询,以减少您需要执行的查询数。如果您只想加入,请使用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

我无法找到让selectincludesorder按照您需要的方式一起工作的方法,抱歉。

这里有一些挥手和猜想。你需要通过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电话相混淆,或者它应该做得对。