我有以下模型层次结构,其中每个模型都具有下面的模型:
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包含一堆帐户,一个帐户包含一堆职位。
目标:我想要一个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不能做的事情吗?
答案 0 :(得分:3)
Rails(4.0.0)可以执行此操作 - 我们目前有两种方法可以执行此操作:
<强> 1。 SQL“别名”列
Rails Scoping For has_many :through To Access Extra Data
#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 Association Extensions 强>
这是Rails的一个鲜为人知的功能,它允许您使用collection
对象。它的做法是扩展你创建的has_many
关系:
class AccountGroup < ActiveRecord::Base
has_many :accounts do
def X
#your code here
end
end
end
我们只有这个方法适用于集合,但你可以用它做各种各样的事情。您应该查看this tutorial以了解有关它的更多信息
<强>更新强>
我们通过使用扩展模块来实现这一点:
#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
为变量函数
道具this gist这基本上覆盖了load
类中的CollectionProxy
ActiveRecord函数,并使用它来创建我们自己的proxy_association.target
数组:)
如果您需要有关如何实施的任何信息,请在评论中提问
答案 1 :(得分: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")
答案 2 :(得分: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
答案 3 :(得分: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
答案 4 :(得分: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,而不是更漂亮。
答案 5 :(得分:0)
这是Rails不能做的事情吗?
由于这个问题已经开放了大约一个月,我将继续并假设这个问题的答案是......
是强>
编辑:是, for Rails 3 。 但是Rails 4 可以做到这一点!请参阅接受的答案。
除了使用find_by_sql
或ActiveRecord::Base.connection.execute(query)
之外,Rails无法做到这一点,这些非常糟糕而且不是rails-y。