【问题标题】:Can't group by result of a named function无法按命名函数的结果分组
【发布时间】:2015-07-25 09:17:23
【问题描述】:

我在尝试按命名函数的结果进行分组时收到NoMethodError: undefined method relation' for <Arel::Nodes::NamedFunction:0x7633>

class Appointment < ActiveRecord::Base
  scope :group_by_date, -> { group(date_column) }

  def self.date_column
    Arel::Nodes::NamedFunction.new(:date, [Appointment.arel_table[:start_time]], 'date')
  end
end

Appointment.group_by_date.count
# results in NoMethodError: undefined method `relation' for #<Arel::Nodes::NamedFunction:0x7633>

这似乎完全合理,所以我不确定它为什么会产生错误。正如this SO answer 所示,我很确定这在早期版本的rails 中是可能的。

我希望得到类似于以下 sql 的东西:

SELECT date(appointments.start_time) AS date, COUNT(appointments.*) AS count
FROM appointments
GROUP BY date(appointments.start_time)

有没有办法让它工作?有没有办法将Arel::Nodes::NamedFunction 转换为Arel::Attributes::Attribute

这显然是对解决方案的幼稚尝试(没有奏效):

function = Arel::Nodes::NamedFunction.new('date', [Appointment[:start_time]])
attr = Arel::Attributes::Attribute.new(function)
Appointment.group(attr).to_sql
# NoMethodError: undefined method `table_alias' for #<Arel::Nodes::NamedFunction:0x4b8>

我真的不想重新使用Appointment.group( 'date(appointments.start_time)' ),因为我做了很多范围合并和连接,字符串会造成严重破坏,因为它们并不总是适用于正确的表。

环境:rails (4.1.8), activerecord (4.1.8), arel (5.0.1.20140414130214)

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 arel


    【解决方案1】:

    这是一个棘手的问题。首先,您的 NamedFunction 定义有几个完全不明显的问题:首先,第一个参数必须是字符串,否则您会得到:

    irb(main):040:0> User.group( Arel::Nodes::NamedFunction.new(:date, [User.arel_table[:created_at]], 'date') )
    TypeError: no implicit conversion of Symbol into String
            from /var/lib/gems/2.1.0/gems/arel-6.0.2/lib/arel/collectors/plain_string.rb:13:in `<<'
            from /var/lib/gems/2.1.0/gems/arel-6.0.2/lib/arel/visitors/to_sql.rb:458:in `visit_Arel_Nodes_NamedFunction'
    

    其次,您需要删除第三个参数(别名),否则 Arel 会尝试将其塞入 GROUP BY 子句中并且您会收到错误(至少在 Postgres 中):

    irb(main):045:0> User.group( Arel::Nodes::NamedFunction.new('date', [User.arel_table[:created_at]], 'date') )
      User Load (4.9ms)  SELECT "users".* FROM "users" GROUP BY date("users"."created_at") AS date
    PG::SyntaxError: ERROR:  syntax error at or near "AS"
    LINE 1: ...".* FROM "users" GROUP BY date("users"."created_at") AS date
                                                                    ^
    : SELECT "users".* FROM "users" GROUP BY date("users"."created_at") AS date
    ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR:  syntax error at or near "AS"
    LINE 1: ...".* FROM "users" GROUP BY date("users"."created_at") AS date
                                                                    ^
    : SELECT "users".* FROM "users" GROUP BY date("users"."created_at") AS date
            from /var/lib/gems/2.1.0/gems/activerecord-4.2.3/lib/active_record/connection_adapters/postgresql_adapter.rb:596:in `async_exec'
    

    您没有注意到这些,因为您收到的错误实际上与.count 调用有关;没有它,这实际上现在有效:

    irb(main):066:0> nf = Arel::Nodes::NamedFunction.new('date', [User.arel_table[:created_at]])
    => #<Arel::Nodes::NamedFunction:0x9dc9ca0 @expressions=[#<struct Arel::Attributes::Attribute relation=#<Arel::Table:0xbdb689c @name="users", @engine=User(id: integer, name: string, age: integer, created_at: datetime, updated_at: datetime), @columns=nil, @aliases=[], @table_alias=nil, @primary_key=nil>, name=:created_at>], @alias=nil, @distinct=false, @name="date">
    irb(main):067:0> User.group(nf).select(nf)
      User Load (2.5ms)  SELECT date("users"."created_at") FROM "users" GROUP BY date("users"."created_at")
    => #<ActiveRecord::Relation [#<User id: nil>]>
    

    那么,为什么.count 不起作用?不幸的是,在堆栈跟踪之后,这看起来像是一个错误。 count 方法进入 ActiveRelation::Calculations#execute_grouped_calculation 并且只是走上了一条坏路。那里不支持处理来自 Arel 的 NamedFunction。

    但是,您可以解决它:

    irb(main):017:0> User.group(nf).pluck('count(*)').first
       (2.5ms)  SELECT count(*) FROM "users" GROUP BY date("users"."created_at")
    => 1
    

    所以...是的。您可能想在 Rails 中为此打开一个问题。我个人只是建议您按字符串表达式分组,以免自己头疼!

    【讨论】:

    • 感谢@Robert 的确认。我怀疑这是一个错误/不受支持。我还怀疑这是由于 Rails 3 和 Rails 4 之间的某个地方发生了变化或在 Rails 4 中引入的,因为网上有迹象表明这在过去是有效的(我还没有尝试过自己确认)。谢谢你的解释!
    猜你喜欢
    • 1970-01-01
    • 2015-02-17
    • 1970-01-01
    • 2013-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多