has_many: through => How to quickly access join objects?
You know that when you want to organize many-to-many relationships between two models, the progressive part of humanity uses join tables and a method
.

Accordingly, if we have two objects - article and category , and we want to find an AR-object (or objects) that personifies the relationship between them, then the authors of books with a pure heart suggest doing this:
In life, everything is more complicated. Models often have long compound names, or there is such a connection between models that inventing a name for each join model turns into a little torture. Imagine that our models are not Article and Category , but UserGroup and Community , or Preorder and CustomerNotification . What should be called a binding model? Possible options.
Therefore, programmers are drawn to somehow standardize their names in the framework of the project, so as not to keep in mind. Templates are chosen to taste, for example:
1) FirstmodelSecondmodelRelation : ArticleCategoryRelation, UserGroupCommunityRelation or
2) FirstmodelVsSecondmodel : ArticleVsCategory, UserGroupVsCommunity
3) ...
Suppose we chose the first option. Now look at what you’ll have to do to just get to the objects of the connecting model:
That is, the “variant from the book” looks very verbose. And I would like to see something like:
similarly, it should work in the opposite direction, symmetrically:
Why aren't such features implemented in Rails? The answer is simple: their names and parameters do not even contain a hint of a link table, and for any two models the programmer can create as many join tables and links as you like. Which of them should be searched? - unclear.
However, the two functions mentioned above have the right to life and reasonable use.
Because experience suggests:
1) most often between any two models there is only one join-model. And it can be calculated.
2) it is necessary to access its objects often, especially if they have extra attributes.
3) it is not scary to have long names of join models - if they do not affect the readability of the code.
Add two files to our Rails project:
/lib/ext/active_record/base.rbIs the actual ActiveRecord :: Base extension
/config/initializers/ext.rb
I will be glad to any additions and fixes that simplify the code.
I will also be glad to comment “dear, there is a simpler way, do this: ...”, because I plan to live a century and study as much :)
has_manywith an option . Each relationship between two ActiveRecord objects is an ActiveRecord object.
And this is wonderful, because in the join-table you can create useful (so-called “extra”) fields with additional information about the relationships between objects.
The question is how nicely to get to these extra attributes.
All screencasts and books, as luck would have it, operate with simple examples. For example, Article and Category models are friends . Of course, for a join class, the name Categorization or ArticleCategorization intuitively begs:through => :join_model_name.

Accordingly, if we have two objects - article and category , and we want to find an AR-object (or objects) that personifies the relationship between them, then the authors of books with a pure heart suggest doing this:
relations = article.article_categorizations.find_by_category_id(category)In life, everything is more complicated. Models often have long compound names, or there is such a connection between models that inventing a name for each join model turns into a little torture. Imagine that our models are not Article and Category , but UserGroup and Community , or Preorder and CustomerNotification . What should be called a binding model? Possible options.
Therefore, programmers are drawn to somehow standardize their names in the framework of the project, so as not to keep in mind. Templates are chosen to taste, for example:
1) FirstmodelSecondmodelRelation : ArticleCategoryRelation, UserGroupCommunityRelation or
2) FirstmodelVsSecondmodel : ArticleVsCategory, UserGroupVsCommunity
3) ...
Suppose we chose the first option. Now look at what you’ll have to do to just get to the objects of the connecting model:
preorder, message = Preorder.first, CustomerNotification.first
relations = preorder.preorder_customer_notification_relations.find_by_customer_notification_id(message)
That is, the “variant from the book” looks very verbose. And I would like to see something like:
preorder.relation_to(message) # это если запись заведомо одна
=> объект класса PreorderCustomerNotificationRelation
preorder.relations_to(message) # если допускается, что join-записей может быть несколько
=> объект класса ActiveRecord::Relation
similarly, it should work in the opposite direction, symmetrically:
message.relations_to(preorder).where(:extra_field => "value")
Why aren't such features implemented in Rails? The answer is simple: their names and parameters do not even contain a hint of a link table, and for any two models the programmer can create as many join tables and links as you like. Which of them should be searched? - unclear.
However, the two functions mentioned above have the right to life and reasonable use.
Because experience suggests:
1) most often between any two models there is only one join-model. And it can be calculated.
2) it is necessary to access its objects often, especially if they have extra attributes.
3) it is not scary to have long names of join models - if they do not affect the readability of the code.
Add two files to our Rails project:
/lib/ext/active_record/base.rbIs the actual ActiveRecord :: Base extension
moduleMyExtensionsmoduleActiveRecordmoduleBase# применяется в моделях, использующих has_many->through# возвращает объект класса ActiveRecord::Relation либо nildefrelations_to(target)returnnilunless target.kind_of? ::ActiveRecord::Base
reflection = self.class.reflections.find do|r|
r[1].instance_of? ::ActiveRecord::Reflection::ThroughReflectionand r[1].klass == target.classend.at 1rescuenil# потому что вернётся Arrayreturnnilunless reflection
self.send(reflection.through_reflection.name).where(reflection.foreign_key.to_sym => target.id)
enddefrelation_to(target)
rels = relations_to(target)
if rels.instance_of? ::ActiveRecord::Relationreturn (rels.count > 0) ? rels.first : nilend
rels
endendendendclassActiveRecord::Baseinclude 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 )
endI will be glad to any additions and fixes that simplify the code.
I will also be glad to comment “dear, there is a simpler way, do this: ...”, because I plan to live a century and study as much :)