20

レールの関連付け方法はどのように機能しますか?この例を考えてみましょう

class User < ActiveRecord::Base
   has_many :articles
end

class Article < ActiveRecord::Base
   belongs_to :user
end

今、私は次のようなことができます

@user = User.find(:first)
@user.articles

これにより、そのユーザーに属する記事が取得されます。ここまでは順調ですね。

これでさらに先に進んで、いくつかの条件でこれらの記事を見つけることができます。

@user.articles.find(:all, :conditions => {:sector_id => 3})

または単に次のようなメソッドを宣言して関連付けます

class User < ActiveRecord::Base
   has_many :articles do
     def of_sector(sector_id)
       find(:all, :conditions => {:sector_id => sector_id})
     end
   end
end

そして、やります

@user.articles.of_sector(3)

今私の質問は、これが関連付けメソッドを使用してフェッチされたオブジェクトfindの配列でどのように機能するかということです。ActiveRecordと呼ばれる独自のUserインスタンスメソッドを実装articlesし、関連付けメソッドとまったく同じ結果が得られる独自の実装を作成した場合、ActiveRecordオブジェクトのフェッチ配列での検索は機能しません。

私の推測では、関連付けメソッドは、フェッチされたオブジェクトの配列に特定のプロパティをアタッチしfind、他のActiveRecordメソッドを使用してさらにクエリを実行できるようにします。この場合のコード実行のシーケンスは何ですか?どうすればこれを検証できますか?

4

4 に答える 4

22

実際にどのように機能するかは、関連付けオブジェクトが「プロキシ オブジェクト」であることです。特定のクラスはAssociationProxyです。そのファイルの 52 行目を見ると、次のように表示されます。

instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }

これにより、このオブジェクトには のようなメソッドがclass存在しなくなります。したがって、classこのオブジェクトを呼び出すと、メソッドが失われます。そのmethod_missingため、メソッド呼び出しを「ターゲット」に転送するプロキシ オブジェクトの実装があります。

def method_missing(method, *args)
  if load_target
    unless @target.respond_to?(method)
      message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}"
      raise NoMethodError, message
    end

    if block_given?
      @target.send(method, *args)  { |*block_args| yield(*block_args) }
    else
      @target.send(method, *args)
    end
  end
end

ターゲットは配列であるため、このオブジェクトを呼び出すと配列であるとclass表示されますが、それは単にターゲットが配列であり、実際のクラスが AssociationProxy であるためです。

したがって、追加するすべてのメソッド ( など)of_sectorはアソシエーション プロキシに追加されるため、直接呼び出されます。[]やのようなメソッドclassはアソシエーション プロキシで定義されていないため、配列であるターゲットに送信されます。

これがどのように行われているかを確認するには、association_proxy.rb のローカル コピーにあるファイルの 217 行目に次のコードを追加します。

Rails.logger.info "AssociationProxy forwarding call to `#{method.to_s}' method to \"#{@target}\":#{@target.class.to_s}" 

そのファイルの場所がわからない場合は、コマンドgem which 'active_record/associations/association_proxy'が教えてくれます。これで、AssociationProxyを呼び出すclassと、それがターゲットに送信されていることを示すログ メッセージが表示されます。これにより、何が起こっているのかが明確になります。これはすべて Rails 2.3.2 用であり、他のバージョンでは変更される可能性があります。

于 2009-10-12T14:33:28.093 に答える
10

すでに述べたように、アクティブなレコードの関連付けは、便利なメソッドのメトリクス バットロードを作成します。もちろん、独自のメソッドを記述してすべてをフェッチすることもできます。しかし、それは Rails Way ではありません。

Rails Way は 2 つのモットーの集大成です。DRY (Don't Repeat Yourself) と「Convention over Configuration」。基本的に、意味のある名前を付けることにより、フレームワークによって提供されるいくつかの堅牢なメソッドは、すべての共通コードを抽象化できます。質問に配置したコードは、単一のメソッド呼び出しで置き換えることができるものの完璧な例です。

これらの便利なメソッドが本当に役立つのは、より複雑な状況です。結合モデル、条件、検証などに関連するもの。

のようなことをするときの質問に答えるために@user.articles.find(:all, :conditions => ["created_at > ? ", tuesday])、Rails は 2 つの SQL クエリを準備し、それらを 1 つにマージします。あなたのバージョンはオブジェクトのリストを返すだけです。名前付きスコープも同じことを行いますが、通常はモデルの境界を越えません。

コンソールでこれらのことを呼び出すときに、development.log で SQL クエリをチェックすることで検証できます。

RailsがSQLを処理する方法の素晴らしい例を提供するので、名前付きスコープについて少し話しましょう.見せびらかす。

名前付きスコープを使用して、モデルのカスタム検索を実行できます。これらは連鎖させることも、関連付けを介して呼び出すこともできます。同一のリストを返すカスタム ファインダーを簡単に作成できますが、質問で述べたのと同じ問題に遭遇します。

class Article < ActiveRecord::Base
  belongs_to :user
  has_many :comments
  has_many :commentators, :through :comments, :class_name => "user"
  named_scope :edited_scope, :conditions => {:edited => true}
  named_scope :recent_scope, lambda do
    { :conditions => ["updated_at > ? ", DateTime.now - 7.days]}

  def self.edited_method
    self.find(:all, :conditions => {:edited => true})
  end

  def self.recent_method
    self.find(:all, :conditions => ["updated_at > ?", DateTime.now - 7 days])
  end
end

Article.edited_scope
=>     # Array of articles that have been flagged as edited. 1 SQL query.
Article.edited_method
=>     # Array of Articles that have been flagged as edited. 1 SQL query.
Array.edited_scope == Array.edited_method
=> true     # return identical lists.

Article.recent_scope
=>     # Array of articles that have been updated in the past 7 days.
   1 SQL query.
Article.recent_method
=>     # Array of Articles that have been updated in the past 7 days.
   1 SQL query.
Array.recent_scope == Array.recent_method
=> true     # return identical lists.

変更点は次のとおりです。

Article.edited_scope.recent_scope
=>     # Array of articles that have both been edited and updated 
    in the past 7 days. 1 SQL query.
Article.edited_method.recent_method 
=> # no method error recent_scope on Array

# Can't even mix and match.
Article.edited_scope.recent_method
=>     # no method error
Article.recent_method.edited_scope
=>     # no method error

# works even across associations.
@user.articles.edited.comments
=>     # Array of comments belonging to Articles that are flagged as 
  edited and belong to @user. 1 SQL query. 

基本的に、名前付きスコープごとに SQL フラグメントが作成されます。Rails は、チェーン内の他のすべての SQL フラグメントと巧みにマージして、必要なものを正確に返す単一のクエリを生成します。関連メソッドによって追加されたメソッドは、同じように機能します。これが、named_scopes とシームレスに統合される理由です。

mix & match が機能しなかった理由は、質問で定義された of_sector メソッドが機能しないことと同じです。edit_methods は配列を返します。ここで、edited_scope (チェーンの一部として呼び出される find および他のすべての AR 便利なメソッドと同様) は、SQL フラグメントをチェーン内の次のものに渡します。チェーンの最後の場合は、クエリを実行します。同様に、これも機能しません。

@edited = Article.edited_scope
@edited.recent_scope

このコードを使用しようとしました。これを行う適切な方法は次のとおりです。

class User < ActiveRecord::Base
   has_many :articles do
     def of_sector(sector_id)
       find(:all, :conditions => {:sector_id => sector_id})
     end
   end
end

この機能を実現するには、次のようにします。

class Articles < ActiveRecord::Base
  belongs_to :user
  named_scope :of_sector, lambda do |*sectors|
    { :conditions => {:sector_id => sectors} }
  end
end

class User < ActiveRecord::Base
  has_many :articles
end

次に、次のようなことができます。

@user.articles.of_sector(4) 
=>    # articles belonging to @user and sector of 4
@user.articles.of_sector(5,6) 
=>    # articles belonging to @user and either sector 4 or 5
@user.articles.of_sector([1,2,3,]) 
=>    # articles belonging to @user and either sector 1,2, or 3
于 2009-10-12T09:23:44.713 に答える
1

前に述べたように、

@user.articles.class
=> Array

実際に得られるのは配列です。これは、前述の #class メソッドが定義されていなかったためです。

しかし、@user.articles (プロキシである必要があります) の実際のクラスを取得するにはどうすればよいでしょうか?

Object.instance_method(:class).bind(@user.articles).call
=> ActiveRecord::Associations::CollectionProxy

そもそも、なぜ Array を取得したのですか? #class メソッドは、実際には配列であるメソッド missin を介して CollectionProxy @target インスタンスに委任されたためです。次のようにして、舞台裏をのぞくことができます。

@user.articles.proxy_association
于 2012-08-16T14:26:00.020 に答える
0

関連付け ( has_onehas_manyなど) を行うと、ActiveRecord によっていくつかのメソッドが自動的に含まれるようにモデルに指示されます。ただし、アソシエーションを返すインスタンス メソッドを自分で作成することにした場合、それらのメソッドを利用することはできません。

序列はこんな感じ

  1. モデルでセットアップarticlesしますUser。つまり、has_many :articles
  2. ActiveRecord は便利なメソッドをモデルに自動的に組み込みます (例: sizeempty?findallなどfirst)。
  3. でセットアップuserしますArticle。つまり、belongs_to :user
  4. ActiveRecord は便利なメソッドをモデルに自動的に組み込みます (例:user=など)。

したがって、アソシエーションを宣言すると、ActiveRecord によってメソッドが自動的に追加されることは明らかです。これは、膨大な量の作業を処理していたため、非常に優れています。それ以外の場合は手動で行う必要があります =)

ここで詳細を読むことができます: http://guides.rubyonrails.org/association_basics.html#detailed-association-reference

これが役立つことを願っています=)

于 2009-10-07T06:22:59.547 に答える