1

次のモデルの階層があり、それぞれがその下のモデルの has_many を持っています。

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 には多数の Account が含まれ、Account には多数の Position が含まれます。

目標:のハッシュが必要です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)ではこれを行うことができます。現在、2 つの方法があります。


1. SQL「エイリアス」列

Rails の has_many :through のスコーピングによる追加データへのアクセス

#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 関数をオーバーライドし、それを使用して独自の配列を作成します:)CollectionProxyproxy_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 に答える
0

例では include 関数を使用します

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

これに (SQL なしで) ActiveRecord を本当に使用したい場合は、次のようになります。

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

これはRailsではできないことですか?

この質問は約 1 か月間開かれているので、先に進んで、この質問に対する答えは...

はい。

編集:はい、Rails 3 の場合しかし、Rails 4ならそれが可能です。 受け入れられた回答を参照してください。

Rails はfind_by_sqlorを使用しない限り、それを行うことができませんActiveRecord::Base.connection.execute(query)

于 2013-07-09T18:15:38.190 に答える