1

我有以下模型的层次结构,其中每个模型都具有下面的模型:

class AccountGroup < ActiveRecord::Base
  has_many :accounts, :inverse_of=>:account_group
  # name: string

class Account < ActiveRecord::Base
  belongs_to :accountGroup, :inverse_of=>:account
  has_many :positions, :inverse_of=>:account

class Position < ActiveRecord::Base
  belongs_to :account, :inverse_of=>:positions
  # net_position: integer

换句话说,一个 AccountGroup 包含一堆 Accounts,一个 Account 包含一堆 Positions。

目标:我想要一个AccountGroup => (sum of its net_positions). 这意味着涉及到 GROUP BY。

我可以用原始 SQL 做到这一点,但我还没有用 Rails 函数破解它。原始 SQL 是:

SELECT account_groups.id,SUM(net_position),account_groups.name
FROM account_groups
LEFT JOIN accounts ON accounts.account_group_id = account_groups.id
LEFT JOIN positions ON positions.account_id = accounts.id
GROUP BY account_groups.id,account_groups.name;

这是 Rails 做不到的吗?

4

6 回答 6

3

Rails (4.0.0)可以做到这一点——我们目前有两种方法:


1. SQL“别名”列

Rails 为 has_many 确定范围:通过访问额外数据

#Images
has_many :image_messages, :class_name => 'ImageMessage'
has_many :images, -> { select("#{Image.table_name}.*, #{ImageMessage.table_name}.caption AS caption") }, :class_name => 'Image', :through => :image_messages, dependent: :destroy

2. ActiveRecord 关联扩展

这是 Rails 的一个鲜为人知的特性,它可以让你玩弄collection对象。它的方式是扩展has_many您创建的关系:

class AccountGroup < ActiveRecord::Base
  has_many :accounts do
      def X
          #your code here
      end
  end
end

我们只有这种方法适用于集合,但你可以用它做各种事情。您应该查看本教程以了解更多信息


更新

我们刚刚通过使用扩展模块来完成这项工作:

#app/models/message.rb
Class Message < ActiveRecord::Base
    has_many :image_messages #-> join model
    has_many :images, through: :image_messages, extend: ImageCaption
end

#app/models/concerns/image_caption.rb
module ImageCaption

    #Load
    def load
      captions.each do |caption|
        proxy_association.target << caption
      end
    end

    #Private
    private

    #Captions
    def captions
        return_array = []
        through_collection.each_with_index do |through,i|
            associate = through.send(reflection_name)
            associate.assign_attributes({caption: items[i]})
            return_array.concat Array.new(1).fill( associate )
        end
        return return_array
    end

    #######################
    #      Variables      #
    #######################

    #Association
    def reflection_name
        proxy_association.source_reflection.name
    end

    #Foreign Key
    def through_source_key
        proxy_association.reflection.source_reflection.foreign_key
    end

    #Primary Key
    def through_primary_key
        proxy_association.reflection.through_reflection.active_record_primary_key
    end

    #Through Name
    def through_name
        proxy_association.reflection.through_reflection.name
    end

    #Through
    def through_collection
        proxy_association.owner.send through_name
    end

    #Captions
    def items
        through_collection.map(&:caption)
    end

    #Target
    def target_collection
        #load_target
        proxy_association.target
    end

end

对变量函数的这个要点的支持

这基本上覆盖了类中的loadActiveRecord 函数CollectionProxy,并使用它来创建我们自己的proxy_association.target数组:)

如果您需要有关如何实施的任何信息,请在评论中询问

于 2014-01-01T10:28:12.167 回答
1

您可以通过使用 rails AR 查询方法使这比原始 sql 更漂亮:

AccountGroup.
  select("account_groups.id, SUM(net_position), account_groups.name").
  joins("LEFT JOIN accounts ON accounts.account_group_id = account_groups.id").
  joins("LEFT JOIN positions ON positions.account_id = accounts.id").
  group("account_groups.id,account_groups.name")
于 2013-06-11T03:01:27.257 回答
1

这也可以用纯 Arel 来完成。

AccountGroup.select(
  AccountGroup.arel_table[:id], Arel::Nodes::NamedFunction.new('SUM', [:net_position]), AccountGroup.arel_table[:name]
).joins(
  AccountGroup.arel_table.join(Account.arel_table).on(
    Account.arel_table[:account_group_id].eq(AccountGroup.arel_table[:id])
  ).join_sources
).joins(
  AccountGroup.arel_table.join(Position.arel_table).on(
    Position.arel_table[:account_id].eq(Account.arel_table[:id])
  ).join_sources
).group(
  AccountGroup.arel_table[:id], AccountGroup.arel_table[:name]
)

我不是 100% 确定这会起作用,我只是从上面复制了你的 SQL 并将其放入scuttle.io

于 2014-05-21T20:37:58.077 回答
0

如果你真的想为此使用 ActiveRecord(没有 SQL),它将类似于:

ags = AccountGroup.all(:include => {:accounts => :positions})
hash = Hash[ags.map { |ag| [ag, ag.map(&:accounts).flatten.map(&:positions).flatten.map(&:net_position).reduce(0,&:+)]}]

但它会比你的 SQL 慢,而且不会更漂亮。

于 2013-06-11T15:11:53.170 回答
0

使用包含函数,例如

ac = AccountGroup.all(:include => :account)
$ AccountGroup Load (0.6ms)  SELECT `account_groups`.* FROM `groups` 
$ Account Load (16.4ms)  SELECT `accounts`.* FROM `accounts` WHERE `accounts`.`id` IN (1010, 3, 4, 202, 203, 204, 9999)

然后你可以调用 ac.account.name 或类似的东西

有一个很棒的 Railscast http://railscasts.com/episodes/22-eager-loading?view=asciicast

于 2013-06-11T15:06:21.347 回答
0

这是 Rails 做不到的吗?

由于这个问题已经开放了大约一个月,我将继续假设这个问题的答案是......

是的。

编辑:是的,对于 Rails 3但是 Rails 4可以做到! 请参阅已接受的答案。

Rails 无法做到这一点,除了使用find_by_sqlor之外ActiveRecord::Base.connection.execute(query),它们非常笨拙而不是 rails-y。

于 2013-07-09T18:15:38.190 回答