has_many:through =>結合オブジェクトにすばやくアクセスする方法は?

2つのモデル間の多対多のリレーションシップを整理する場合、人類の進歩的な部分は、オプション:through => :join_model_name使用して結合テーブルとhas_manyメソッドを使用することをhas_manyています。 2つのActiveRecordオブジェクト間の各関係は、ActiveRecordオブジェクトです。

結合テーブルでは、オブジェクト間の関係に関する追加情報を含む便利な(いわゆる「追加」)フィールドを作成できるため、これは素晴らしいことです。

問題は、これらの追加の属性にどれだけうまくアクセスできるかです。

すべてのスクリーンキャストと本は、運がよければ簡単な例で動作します。 たとえば、 ArticleモデルとCategoryモデルはフレンドです。 もちろん、結合クラスの場合、 CategorizationまたはArticleCategorizationという名前が直感的にわかります。

has_many through

したがって、2つのオブジェクト( articlecategory )があり、それらの関係を具体化するARオブジェクト(またはオブジェクト)を見つけたい場合、純粋な心を持つ本の著者はこれを行うことを提案します:

 relations = article.article_categorizations.find_by_category_id(category) 


人生では、すべてがより複雑です。 多くの場合、モデルには長い複合名があります。または、モデル間にこのようなつながりがあり、各結合モデルの名前を発明すると小さな拷問になります。 モデルがArticleCategoryではなく、 UserGroupCommunity 、またはPreorderCustomerNotificationであると想像してください。 バインディングモデルとは何ですか? 可能なオプション。


そのため、プログラマーは、プロジェクトのフレームワークで名前を標準化するように描かれています。 好みに応じてテンプレートが選択されます。例:

1) FirstmodelSecondmodelRelationArticleCategoryRelation 、UserGroupCommunityRelationまたは
2) FirstmodelVsSecondmodel :ArticleVsCategory、UserGroupVsCommunity
3)...

最初のオプションを選択したとします。 次に、接続モデルのオブジェクトに到達するために何をしなければならないかを見てみましょう。

 preorder, message = Preorder.first, CustomerNotification.first relations = preorder.preorder_customer_notification_relations.find_by_customer_notification_id(message) 

つまり、「本のバリアント」は非常に冗長に見えます。 そして、私は次のようなものを見たいです:

 preorder.relation_to(message) #      =>   PreorderCustomerNotificationRelation preorder.relations_to(message) #  ,  join-    =>   ActiveRecord::Relation 


同様に、対称的に反対方向に機能するはずです:

 message.relations_to(preorder).where(:extra_field => "value") 


そのような機能がRailsに実装されないのはなぜですか? 答えは簡単です。その名前とパラメーターにはリンクテーブルのヒントさえ含まれていません。また、2つのモデルの場合、プログラマーは好きなだけ多くの結合テーブルとリンクを作成できます。 どちらを検索する必要がありますか? -明確ではありません。

ただし、上記の2つの機能には、生存権と合理的な使用権があります。

経験が示唆するので:
1)ほとんどの場合、2つのモデルの間には、結合モデルが1つしかありません。 そして、それは計算することができます。
2)特にオブジェクトに追加の属性がある場合は、そのオブジェクトに頻繁にアクセスする必要があります。
3)結合モデルの長い名前を持つのは怖くない-それらがコードの可読性に影響しないなら。

Railsプロジェクトに2つのファイルを追加します。

/lib/ext/active_record/base.rbActiveRecord :: Base拡張自体です
 module MyExtensions module ActiveRecord module Base #   ,  has_many->through #    ActiveRecord::Relation  nil def relations_to(target) return nil unless target.kind_of? ::ActiveRecord::Base reflection = self.class.reflections.find do |r| r[1].instance_of? ::ActiveRecord::Reflection::ThroughReflection and r[1].klass == target.class end.at 1 rescue nil #    Array return nil unless reflection self.send(reflection.through_reflection.name).where(reflection.foreign_key.to_sym => target.id) end def relation_to(target) rels = relations_to(target) if rels.instance_of? ::ActiveRecord::Relation return (rels.count > 0) ? rels.first : nil end rels end end end end class ActiveRecord::Base include MyExtensions::ActiveRecord::Base end 


/config/initializers/ext.rb
 # Load extensions to existing classes. Dir["lib/ext/**/*.rb"].each do |fn| require File.expand_path( fn ) end 


コードを簡素化する追加や修正があれば嬉しいです。

私はまた、「親愛なる、もっと簡単な方法があります、これをしてください:...」とコメントすることを嬉しく思います。

Source: https://habr.com/ru/post/J130301/


All Articles