How to use ActiveRecord to perform a chain of nested INNER JOIN
operations.
TL;DR
“I want to know all the distinct option values of the option type “gender”, but only for the products associated with a given category”
translates to
1 2 3 4 5 6 |
|
assuming that the given category has the taxon ID 2 and that the gender option type has ID 1.
The Spree 1.3 models:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
The schema:
1 2 3 4 5 6 7 8 9 10 |
|
Explain
Lately, I’ve working on this project for an online store in Rails, and for that I’m using Spree. In this store, every product belongs to a category, and every product has the option type gender (man or woman). In every view’s header there is a main navigation bar with a button for each category. Each button also has a dropdown menu for the available genders, so the user can easily see all the products for man in category A, for example.
The dropdown menus are not the same for all the categories. In other words, CategoryA
has both man
and woman
in its dropdown menu, while CategoryB
has only woman
. As such, the dropdown is hard-coded for each button, which means that the navigation menu is completely hard-coded. This is ugly, hard to maintain and unnecessary, so I decided to change it to show the genders for which there are in fact products in each category.
Models and Schema
As you can see in the TL;DR section:
- An
OptionValue
has and belongs to manyVariants
(with the join tablespree_option_values_variants
) - An
OptionValue
belongs to anOptionType
- A
Variant
belongs to aProduct
- A
Product
has manyClassifications
(which use the tablespree_products_taxons
) - A
Classification
also belongs to aTaxon
- A
Taxon
belongs to aTaxonomy
This store only has one taxonomy named Category. All the categories are taxons that belong to this taxonomy.
In order to make the menu dynamic I have to be able to obtain the gender values used among the products of a given category. In other words, I want to know all the distinct option values of the option type “gender”, but only for the products associated with a given category.
SQL
If I was using SQL directly, my life would be easier. Let’s assume that my gender option type has de ID 1 and that my taxon (category) has the ID 4. I can easily translate what I want into a single query:
1 2 3 4 5 6 7 |
|
Yet, using ActiveRecord I access my records usually using some kind of <class>.where(<conditions>)
. How do I get to that result?
Join
Formally, what happens in that previous SQL query, as intuitive as it might be, is a set of consecutive INNER JOIN
operations. Using that operation explicitly, the same query can be rewritten as:
1 2 3 4 5 6 7 8 9 10 |
|
This is the way ActiveRecord allows to do this kind of searches, through the join
method. Let’s skip to the answer:
1 2 3 4 5 6 |
|
The join
method uses the associations names to perform the joins, instead of using the table names. This is why instead of using :spree_products_taxons
we use :classifications
. In this specific case, the join creates a chain: we want to chain the variants of an option value, using the product of each of those variants to get to the taxons.
Lastly, notice the where
clause. When referring to the fields of the initial table (option values), using the field normally is enough. For the remaining tables, the table name and a hash of conditions is used.