activerecord_where_assoc 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: '0805c22b1a2c499a1d642f4bf4c624e31318192e2243a38ef3dca92cadce2a58'
4
- data.tar.gz: bbebf2d6e4402c07a54af54fa87c25c5f94c7813e4b0cc6cc63a761bd291cb7c
2
+ SHA1:
3
+ metadata.gz: c5feb6230bf9dd2b1e1b8d74443da251e7ae7fbf
4
+ data.tar.gz: 1917b5b495c2808adf89737ab31676ca99fa5923
5
5
  SHA512:
6
- metadata.gz: b17588d07e22a79132e62f6341fad4b4e39106ec2b957bed866d85ae3e20c59de95efae7ea1a91cdd812111c40afda385277800a90fc33b95ec14ad5af9f9bdb
7
- data.tar.gz: b34651a306ac5503fa96bf30058c00e1b071f9bcb5bc429b249c7455a2d8ec6d6fe6518c95c6060aa507f3fb59bfe44434aba4c5fc0e1ffec6b25c673209e13f
6
+ metadata.gz: 858c201216236ced5dbc8580874153319f11526849edb21b900302f59aabc7d99d4bd56e35a74c49ef5cd4fe07e8d0b0c4ee092d8fc8cefdcd40f0024dc371ef
7
+ data.tar.gz: c2ae301a6ad1f8920d33608527db33241c6e6f2bb1f14151cfa8db100af0ad5b24a01debdc46efbbde1268fd4fa7f687b89dce09a147bc8c4f7a174743e8d75a
@@ -10,9 +10,11 @@ This is a list of some of those alternatives, explaining what issues they have o
10
10
  * No more having to choose, case by case, which way has the less problems.
11
11
  Just use `#where_assoc_*` each time and avoid every problems.
12
12
  * Need less raw SQL, which means less code, more clarity and less maintenance.
13
- * Allows powerful scopes without traps.
13
+ * Generates a single `#where`. No weird side-effects things like `#eager_load` or `#join`
14
+ This makes well-behaved scopes, you can even have multiple conditions on the same association
14
15
  * Handles recursive associations correctly.
15
- * Handles has_one correctly.
16
+ * Handles has_one correctly (Except [MySQL has a limitation](README.md#mysql-doesnt-support-sub-limit)).
17
+ * Handles polymorphic belongs_to
16
18
 
17
19
  ## Short version
18
20
 
@@ -26,6 +28,7 @@ Summary of the problems of the alternatives that `activerecord_where_assoc` solv
26
28
  * doing `not exists` with conditions requires a `LEFT JOIN` with the conditions as part of the `ON`, which requires raw SQL.
27
29
  * checking for 2 sets of conditions on different records of the same association won't work. (so your scopes can be incompatible)
28
30
  * can't be used with Rails 5's `or` unless both sides do the same `joins` / `includes` / `eager_load`.
31
+ * Doesn't work for polymorphic belongs_to.
29
32
  * `joins`:
30
33
  * `has_many` may return duplicate records.
31
34
  * using `uniq` / `distinct` to solve duplicate rows is an unexpected side-effect when this is in a scope.
@@ -65,6 +68,8 @@ Person.where_assoc_exists(:addresses, city: 'Montreal')
65
68
 
66
69
  The general version of this problem is the handling of `limit` and `offset` on associations and in default_scopes. where_assoc_exists handle those correctly and only checks the records that match the limit and the offset.
67
70
 
71
+ Note: [MySQL has a limitation](README.md#mysql-doesnt-support-sub-limit), this makes handling has_one correctly not possible with MySQL.
72
+
68
73
  ### Raw SQL joins or sub-selects
69
74
 
70
75
  Having to write the joins and conditions in raw SQL is more painful and more error prone than having a method do it for you. It hides the important details of what you are doing in a lot of verbosity.
@@ -95,6 +100,10 @@ This brings us back to the [raw SQL joins](#raw-sql-joins-or-sub-selects) proble
95
100
 
96
101
  `where_assoc_*` methods handle this seemlessly.
97
102
 
103
+ ### Unable to handle polymorphic belongs_to
104
+
105
+ When you have a polymorphic belongs_to, you can't use `joins` or `includes` in order to do queries on it. You have to use manual SQL ([raw SQL joins](#raw-sql-joins-or-sub-selects)) or a gem that provides the feature, such as `activerecord_where_assoc`.
106
+
98
107
  ## ActiveRecord only
99
108
 
100
109
  Those are the common ways given in stack overflow answers.
@@ -136,6 +145,7 @@ Post.joins(:comments).where(comments: {is_spam: true})
136
145
  * Cannot be used with Rails 5's `or` unless both side do the same `joins`.
137
146
  * [Treats has_one like a has_many](#treating-has_one-like-has_many)
138
147
  * [Can't handle recursive associations](#unable-to-handle-recursive-associations)
148
+ * [Can't handle polymorphic belongs_to](#unable-to-handle-polymorphic-belongs_to)
139
149
 
140
150
  ### Using `includes` (or `eager_load`) and `where`
141
151
 
@@ -155,6 +165,7 @@ Post.eager_load(:comments).where(comments: {is_spam: true})
155
165
 
156
166
  * [Treats has_one like a has_many](#treating-has_one-like-has_many)
157
167
  * [Can't handle recursive associations](#unable-to-handle-recursive-associations)
168
+ * [Can't handle polymorphic belongs_to](#unable-to-handle-polymorphic-belongs_to)
158
169
 
159
170
  * Simply cannot be used for complex cases.
160
171
 
@@ -180,10 +191,9 @@ This is what is gem does behind the scene, but doing it manually can lead to tro
180
191
 
181
192
  https://github.com/EugZol/where_exists
182
193
 
183
- An interesting gem that also does `EXISTS (SELECT ... )`behind the scene. Solves most issues from ActiveRecord only alternatives, but appears less powerful than where_assoc_exists.
194
+ An interesting gem that also does `EXISTS (SELECT ... )` behind the scene. Solves most issues from ActiveRecord only alternatives, but appears less powerful than where_assoc_exists.
184
195
 
185
- * where_exists supports polymorphic belongs_to. This is something that where_assoc doesn't do at the moment.
186
- However, the way it does this is by doing a pluck on the type column, which in some situation could be a slow query if there is a lots of rows to scan.
196
+ * where_exists supports polymorphic belongs_to only by always doing a `pluck` everytime. In some situation could be a slow query if there is a lots of rows to scan. where_assoc also allows directly specifying the classes manually, avoiding the pluck and possibly filtering the choices.
187
197
 
188
198
  * Unable to use scopes of the association's model.
189
199
  ```ruby
data/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
1
 
2
+ # 1.0.0
3
+
4
+ * Now supports polymorphic belongs_to
5
+
6
+ # 0.1.3
7
+
2
8
  * Use `SELECT 1` instead of `SELECT 0`...
3
9
  ... it just seems more natural that way.
4
10
  * Bugfixes
data/EXAMPLES.md CHANGED
@@ -2,7 +2,7 @@ Here are some example usages of the gem, along with the generated SQL. Each of t
2
2
 
3
3
  The models can be found in [examples/models.md](examples/models.md). The comments in that file explain how to get a console to try the queries. There are also example uses of the gem for scopes.
4
4
 
5
- The content of this file is generated from running `ruby examples/examples.rb`
5
+ The content of this file is generated from running `ruby examples/examples.rb > EXAMPLES.md`
6
6
 
7
7
  -------
8
8
 
@@ -50,6 +50,37 @@ SELECT "posts".* FROM "posts"
50
50
 
51
51
  ---
52
52
 
53
+ ```ruby
54
+ # Users that have made posts
55
+ User.where_assoc_exists(:posts)
56
+ ```
57
+ ```sql
58
+ SELECT "users".* FROM "users"
59
+ WHERE (EXISTS (
60
+ SELECT 1 FROM "posts"
61
+ WHERE "posts"."author_id" = "users"."id"
62
+ ))
63
+ ```
64
+
65
+ ---
66
+
67
+ ```ruby
68
+ # Users that have made posts that have comments
69
+ User.where_assoc_exists([:posts, :comments])
70
+ ```
71
+ ```sql
72
+ SELECT "users".* FROM "users"
73
+ WHERE (EXISTS (
74
+ SELECT 1 FROM "posts"
75
+ WHERE "posts"."author_id" = "users"."id" AND (EXISTS (
76
+ SELECT 1 FROM "comments"
77
+ WHERE "comments"."post_id" = "posts"."id"
78
+ ))
79
+ ))
80
+ ```
81
+
82
+ ---
83
+
53
84
  ## Examples with condition / scope
54
85
 
55
86
  ```ruby
@@ -170,7 +201,7 @@ SELECT "posts".* FROM "posts"
170
201
  ---
171
202
 
172
203
  ```ruby
173
- # posts where the author also commented on the post (use conditions between posts)
204
+ # posts where the author also commented on the post (uses a conditions between tables)
174
205
  Post.where_assoc_exists(:comments, "posts.author_id = comments.author_id")
175
206
  ```
176
207
  ```sql
data/README.md CHANGED
@@ -5,46 +5,45 @@
5
5
  [![Code Climate](https://codeclimate.com/github/MaxLap/activerecord_where_assoc/badges/gpa.svg)](https://codeclimate.com/github/MaxLap/activerecord_where_assoc)
6
6
  [![Issue Count](https://codeclimate.com/github/MaxLap/activerecord_where_assoc/badges/issue_count.svg)](https://codeclimate.com/github/MaxLap/activerecord_where_assoc)
7
7
 
8
- This gem provides powerful methods to add conditions based on the associations of your records. (Using SQL's `EXISTS` operator)
8
+ This gem makes it easy to do conditions based on the associations of your records in ActiveRecord (Rails). (Using SQL's `EXISTS` operator)
9
9
 
10
10
  ```ruby
11
11
  # Find my_post's comments that were not made by an admin
12
12
  my_post.comments.where_assoc_not_exists(:author, is_admin: true).where(...)
13
13
 
14
- # Find posts that have comments by an admin
14
+ # Find every posts that have comments by an admin
15
15
  Post.where_assoc_exists([:comments, :author], &:admins).where(...)
16
16
 
17
- # Find my_user's posts that have at least 5 non-spam comments
18
- my_user.posts.where_assoc_count(5, :>=, :comments) { |comments| comments.where(is_spam: false) }.where(...)
17
+ # Find my_user's posts that have at least 5 non-spam comments (not_spam is a scope on comments)
18
+ my_user.posts.where_assoc_count(5, :>=, :comments) { |comments| comments.not_spam }.where(...)
19
19
  ```
20
20
 
21
21
  These allow for powerful, chainable, clear and easy to reuse queries. (Great for scopes)
22
22
 
23
- You also avoid many [problems with the alternative options](ALTERNATIVES_PROBLEMS.md).
24
-
25
- Works with SQLite3, PostgreSQL and MySQL. [MySQL has one limitation](#mysql-doesnt-support-sub-limit). Untested with other RDBMS.
23
+ You avoid many [problems with the alternative options](ALTERNATIVES_PROBLEMS.md).
26
24
 
27
25
  Here are [many examples](EXAMPLES.md), including the generated SQL queries.
28
26
 
29
- ## Feedback
30
-
31
- This gem is very new. If you have any feedback, good or bad, do not hesitate to write it here: [General feedback](https://github.com/MaxLap/activerecord_where_assoc/issues/3). If you find any bug, please create a new issue.
32
-
33
- * Failure stories, if you had difficulties that kept you from using the gem.
34
- * Success stories, if you are using it and things are going great, I wanna hear this too.
35
- * Suggestions to make the documentation easier to follow / more complete.
36
-
37
-
38
- ## 0.1
27
+ ## Advantages
39
28
 
40
- Since the gem is brand new, I'm releasing as 0.1 before bumping to 1.0 once I have some feedback. There is an extensive test suite testing for lots of cases, making me quite confident that it can handle most of the cases you can throw at it.
29
+ These methods have many advantages over the alternative ways of achieving the similar results:
30
+ * Avoids the [problems with the alternative ways](ALTERNATIVES_PROBLEMS.md)
31
+ * Can be chained and nested with regular ActiveRecord methods (`where`, `merge`, `scope`, etc).
32
+ * Adds a single condition in the `WHERE` of the query instead of complex things like joins.
33
+ So it's easy to have multiple conditions on the same association
34
+ * Handles `has_one` correctly: only testing the "first" record of the association that matches the default_scope and the scope on the association itself.
35
+ * Handles recursive associations (such as parent/children) seemlessly.
36
+ * Can be used to quickly generate a SQL query that you can edit/use manually.
41
37
 
42
38
  ## Installation
43
39
 
40
+ Rails 4.1 to 6.0 are supported with Ruby 2.1 to 2.6.
41
+ Works with SQLite3, PostgreSQL and MySQL. Untested with other RDBMS.
42
+
44
43
  Add this line to your application's Gemfile:
45
44
 
46
45
  ```ruby
47
- gem 'activerecord_where_assoc'
46
+ gem 'activerecord_where_assoc', '~> 1.0'
48
47
  ```
49
48
 
50
49
  And then execute:
@@ -57,112 +56,53 @@ Or install it yourself as:
57
56
 
58
57
  ## Usage
59
58
 
60
- ### `#where_assoc_exists` & `#where_assoc_not_exists`
61
-
62
- Returns a new relation, which is the result of filtering the current relation based on if a record for the specified association of the model exists (or not). Conditions that the associated model must match to count as existing can also be specified.
63
-
64
- ```ruby
65
- Post.where_assoc_exists(:comments, is_spam: true)
66
- Post.where_assoc_not_exists(:comments, is_spam: true)
67
- ```
68
-
69
- * 1st parameter: the association we are doing the condition on.
70
- * 2nd parameter: (optional) the condition to apply on the association. It can be anything that `#where` can receive, so: Hash, String and Array (string with binds).
71
- * 3rd parameter: [options (listed below)](#options) to alter some behaviors. (rarely necessary)
72
- * block: adds more complex conditions by receiving a relation on the association. Can apply `#where`, `#where_assoc_*`, scopes, and other scoping methods.
73
- The block either:
74
-
75
- * receives no argument, in which case `self` is set to the relation, so you can do `{ where(id: 123) }`
76
- * receives arguments, in which case the block is called with the relation as first parameter
77
-
78
- The block should return the new relation to use or `nil` to do as if there were no blocks
79
- It's common to use `where_assoc_*(..., &:scope_name)` to apply a single scope quickly
80
-
81
- ### `#where_assoc_count`
82
-
83
- This is a generalization of `#where_assoc_exists` and `#where_assoc_not_exists`. It behaves the same way as them, but is more flexible as it allows you to be specific about how many matches there should be. To clarify, here are equivalent examples:
84
-
85
- ```ruby
86
- Post.where_assoc_exists(:comments, is_spam: true)
87
- Post.where_assoc_count(1, :<=, :comments, is_spam: true)
88
-
89
- Post.where_assoc_not_exists(:comments, is_spam: true)
90
- Post.where_assoc_count(0, :==, :comments, is_spam: true)
91
- ```
92
-
93
- * 1st parameter: the left side of the comparison. One of:
94
- * a number
95
- * a string of SQL to embed in the query
96
- * a range (operator must be `:==` or `:!=`)
97
- will use SQL's `BETWEEN` or `NOT BETWEEN`
98
- supports infinite ranges and exclusive end
99
- * 2nd parameter: the operator to use: `:<`, `:<=`, `:==`, `:!=`, `:>=`, `:>`
100
- * 3rd, 4th, 5th parameters are the same as the 1st, 2nd and 3rd parameters of `#where_assoc_exists`.
101
- * block: same as `#where_assoc_exists`' block
59
+ The [documentation is nicely structured](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/QueryMethods.html)
60
+ You can view [many examples](EXAMPLES.md).
102
61
 
103
- The order of the parameters may seem confusing, but you will get used to it. To help remember the order of the parameters, remember that the goal is to do:
62
+ Otherwise, here is a short explanation:
104
63
 
105
- 5 < (SELECT COUNT(*) FROM ...)
106
-
107
- The parameters are in the same order as in that query: number, operator, association.
108
-
109
- ### More examples
110
-
111
- You can view [more usage examples](EXAMPLES.md).
112
-
113
- ### Options
114
-
115
- Each of the methods above can take an options argument. It is also possible to change the default value for the options.
116
-
117
- * On a per-call basis:
118
- ```ruby
119
- # Options are passed after the conditions argument
120
- Posts.where_assoc_exists(:last_status, nil, ignore_limit: true)
121
- Posts.where_assoc_count(1, :<, :last_status, nil, ignore_limit: true)
122
- ```
123
-
124
- * As default for everywhere
125
64
  ```ruby
126
- # Somewhere in your setup code, such as an initializer in Rails
127
- ActiveRecordWhereAssoc.default_options[:ignore_limit] = true
65
+ where_assoc_exists(association_name, conditions, options, &block)
66
+ where_assoc_not_exists(association_name, conditions, options, &block)
67
+ where_assoc_count(left_operand, operator, association_name, conditions, options, &block)
128
68
  ```
129
69
 
130
- Here is a list of the available options:
131
-
132
- #### :ignore_limit
133
-
134
- When this option is true, then `#limit` and `#offset` that are set either from default_scope or on associations are ignored. `#has_one` means `limit(1)`, so `#has_one` will behave like `#has_many` with this option.
135
-
136
- Main reasons to use this:
137
- * This is needed for MySQL to be able to do anything with `#has_one` associations because [MySQL doesn't support sub-limit](#mysql-doesnt-support-sub-limit).
138
- * You have a `#has_one` association which you know can never have more than one record. Using `:ignore_limit`, you will use the simpler query of `#has_many`, which can be more efficient.
139
-
140
- Why this isn't the default:
141
- * From very few tests, the aliasing way seems to produce better plans.
142
- * Using aliasing produces a shorter query.
143
-
144
- #### :never_alias_limit
145
-
146
- When this option is true, `#where_assoc_*` will not use `#from` to build relations that have `#limit` or `#offset` set on default_scope or on associations. Note, `#has_one` means `limit(1)`, so it will also use `#from` unless this option is activated.
147
-
148
- Main reasons to use this:
149
- * You have to use `#from` as condition for `#where_assoc_*` method (possibly because a scope needs it).
150
- * This might result in a difference execution plan for the query since the query ends up being quite different.
151
-
152
- ## Supported Rails versions
153
-
154
- Rails 4.1 to 5.2 are supported with Ruby 2.1 to 2.5.
155
-
156
- ## Advantages
157
-
158
- These methods have many advantages over the alternative ways of achieving the similar results:
159
- * Avoids the [problems with the alternative ways](ALTERNATIVES_PROBLEMS.md)
160
- * Can be chained and nested with regular ActiveRecord methods (`where`, `merge`, `scope`, etc).
161
- * Adds a single condition in the `WHERE` of the query instead of complex things like joins.
162
- * So it's easy to have multiple conditions on the same association
163
- * Handles `has_one` correctly: only testing the "first" record of the association that matches the default_scope and the scope on the association itself.
164
- * Handles recursive associations (such as parent/children) seemlessly.
165
- * Can be used to quickly generate a SQL query that you can edit/use manually.
70
+ * These methods add a condition (a `#where`) to the relation that checks if the association exists (or not)
71
+ * You can specify condition on the association, so you could check only comments that are made by an admin.
72
+ * Each method returns a new relation, meaning you can chain `#where`, `#order`, `limit`, etc.
73
+ * common arguments:
74
+ * association_name: the association we are doing the condition on.
75
+ * conditions: (optional) the condition to apply on the association. It can be anything that `#where` can receive, so: Hash, String and Array (string with binds).
76
+ * options: [available options](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/QueryMethods#module-ActiveRecordWhereAssoc::QueryMethods-label-Options) to alter some behaviors. (rarely necessary)
77
+ * block: adds more complex conditions by receiving a relation on the association. Can use `#where`, `#where_assoc_*`, scopes, and other scoping methods.
78
+ Must return a relation.
79
+ The block either:
80
+ * receives no argument, in which case `self` is set to the relation, so you can do `{ where(id: 123) }`
81
+ * receives arguments, in which case the block is called with the relation as first parameter.
82
+
83
+ The block should return the new relation to use or `nil` to do as if there were no blocks.
84
+ It's common to use `where_assoc_*(..., &:scope_name)` to use a single scope.
85
+ * `#where_assoc_count` is a generalization of `#where_assoc_exists` and `#where_assoc_not_exists`. It behaves the same way, but is more powerful, as it allows you to specify how many matches there should be.
86
+ ```ruby
87
+ # These are equivalent:
88
+ Post.where_assoc_exists(:comments, is_spam: true)
89
+ Post.where_assoc_count(1, :<=, :comments, is_spam: true)
90
+
91
+ Post.where_assoc_not_exists(:comments, is_spam: true)
92
+ Post.where_assoc_count(0, :==, :comments, is_spam: true)
93
+
94
+ # This has no equivalent (Posts with at least 5 spam comments)
95
+ Post.where_assoc_count(5, :<=, :comments, is_spam: true)
96
+ ```
97
+ * `where_assoc_count`'s additional arguments
98
+ The order of the parameters of `#where_assoc_count` can be confusingof may seem confusing, but you will get used to it. To help remember: the goal is to do: `5 < (SELECT COUNT(*) FROM ...)`, the number is first, then operator, then the association and its conditions.
99
+ * left_operand:
100
+ * a number
101
+ * a string of SQL to embed in the query
102
+ * a range (operator must be `:==` or `:!=`)
103
+ will use SQL's `BETWEEN` or `NOT BETWEEN`
104
+ supports infinite ranges and exclusive end
105
+ * operator: one of `:<`, `:<=`, `:==`, `:!=`, `:>=`, `:>`
166
106
 
167
107
  ## Usage tips
168
108
 
@@ -225,7 +165,7 @@ Doing the same thing but with less associations between `address` and `posts` wo
225
165
 
226
166
  ### The opposite of multiple nested EXISTS...
227
167
 
228
- ... is a single `NOT EXISTS` with then nested ones still using `EXISTS`.
168
+ ... is a single `NOT EXISTS` with the nested ones still using `EXISTS`.
229
169
 
230
170
  All the methods always chain nested associations using an `EXISTS` when they have to go through multiple hoops. Only the outer-most, or first, association will have a `NOT EXISTS` when using `#where_assoc_not_exists` or a `COUNT` when using `#where_assoc_count`. This is the logical way of doing it.
231
171
 
@@ -255,7 +195,7 @@ Note that the support of `#limit` and `#offset` for the `:source` and `:through`
255
195
 
256
196
  After checking out the repo, run `bundle install` to install dependencies.
257
197
 
258
- Run `rake test` to run the tests for the latest version of rails
198
+ Run `rake test` to run the tests for the latest version of rails. If you want SQL queries printed when you have failures, use `SQL_WITH_FAILURES=1 rake test`.
259
199
 
260
200
  Run `bin/console` for an interactive prompt that will allow you to experiment in the same environment as the tests.
261
201
 
@@ -4,11 +4,20 @@ require_relative "active_record_where_assoc/version"
4
4
  require "active_record"
5
5
 
6
6
  module ActiveRecordWhereAssoc
7
- # Default options for the gem. Meant to be modified in place by external code
7
+ # Default options for the gem. Meant to be modified in place by external code, such as in
8
+ # an initializer.
9
+ # Ex:
10
+ # ActiveRecordWhereAssoc[:ignore_limit] = true
11
+ #
12
+ # A description for each can be found in ActiveRecordWhereAssoc::QueryMethods#where_assoc_exists.
13
+ #
14
+ # The only one that truly makes sense to change is :ignore_limit, when you are using MySQL, since
15
+ # limit are never supported on it.
8
16
  def self.default_options
9
17
  @default_options ||= {
10
18
  ignore_limit: false,
11
19
  never_alias_limit: false,
20
+ poly_belongs_to: :raise,
12
21
  }
13
22
  end
14
23
  end
@@ -3,22 +3,24 @@
3
3
  module ActiveRecordWhereAssoc
4
4
  module ActiveRecordCompat
5
5
  if ActiveRecord.gem_version >= Gem::Version.new("5.1")
6
- def self.join_keys(reflection)
7
- reflection.join_keys
6
+ def self.join_keys(reflection, poly_belongs_to_klass)
7
+ if poly_belongs_to_klass
8
+ reflection.get_join_keys(poly_belongs_to_klass)
9
+ else
10
+ reflection.join_keys
11
+ end
8
12
  end
9
13
  elsif ActiveRecord.gem_version >= Gem::Version.new("4.2")
10
- def self.join_keys(reflection)
11
- reflection.join_keys(reflection.klass)
14
+ def self.join_keys(reflection, poly_belongs_to_klass)
15
+ reflection.join_keys(poly_belongs_to_klass || reflection.klass)
12
16
  end
13
17
  else
14
18
  # 4.1 change that introduced JoinKeys:
15
19
  # https://github.com/rails/rails/commit/5823e429981dc74f8f53187d2ab573823381bf28#diff-523caff658498027f61cae9d91c8503dL108
16
20
  JoinKeys = Struct.new(:key, :foreign_key)
17
- def self.join_keys(reflection)
21
+ def self.join_keys(reflection, poly_belongs_to_klass)
18
22
  if reflection.source_macro == :belongs_to
19
- # The original code had to handle polymorphic here. But we don't support polymorphic belongs_to
20
- # So the code would never reach here in the polymorphic case.
21
- key = reflection.association_primary_key
23
+ key = reflection.association_primary_key(poly_belongs_to_klass)
22
24
  foreign_key = reflection.foreign_key
23
25
  else
24
26
  key = reflection.foreign_key
@@ -31,7 +33,17 @@ module ActiveRecordWhereAssoc
31
33
 
32
34
  if ActiveRecord.gem_version >= Gem::Version.new("5.0")
33
35
  def self.chained_reflection_and_chained_constraints(reflection)
34
- reflection.chain.map { |ref| [ref, ref.constraints] }.transpose
36
+ pairs = reflection.chain.map do |ref|
37
+ # PolymorphicReflection is a super weird thing. Like a partial reflection, I don't get it.
38
+ # Seems like just bypassing it works for our needs.
39
+ # When doing a has_many through that has a polymorphic source and a source_type, this ends up
40
+ # part of the chain instead of the regular HasManyReflection that one would expect.
41
+ ref = ref.instance_variable_get(:@reflection) if ref.is_a?(ActiveRecord::Reflection::PolymorphicReflection)
42
+
43
+ [ref, ref.constraints]
44
+ end
45
+
46
+ pairs.transpose
35
47
  end
36
48
  else
37
49
  def self.chained_reflection_and_chained_constraints(reflection)
@@ -59,5 +71,15 @@ module ActiveRecordWhereAssoc
59
71
  association_name.to_sym
60
72
  end
61
73
  end
74
+
75
+ if ActiveRecord.gem_version >= Gem::Version.new("5.0")
76
+ def self.through_reflection?(reflection)
77
+ reflection.through_reflection?
78
+ end
79
+ else
80
+ def self.through_reflection?(reflection)
81
+ reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
82
+ end
83
+ end
62
84
  end
63
85
  end