activerecord_where_assoc 0.1.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/ALTERNATIVES_PROBLEMS.md +15 -5
- data/CHANGELOG.md +6 -0
- data/EXAMPLES.md +33 -2
- data/README.md +62 -122
- data/lib/active_record_where_assoc.rb +10 -1
- data/lib/active_record_where_assoc/active_record_compat.rb +31 -9
- data/lib/active_record_where_assoc/core_logic.rb +221 -109
- data/lib/active_record_where_assoc/exceptions.rb +3 -0
- data/lib/active_record_where_assoc/query_methods.rb +315 -116
- data/lib/active_record_where_assoc/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c5feb6230bf9dd2b1e1b8d74443da251e7ae7fbf
|
4
|
+
data.tar.gz: 1917b5b495c2808adf89737ab31676ca99fa5923
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 858c201216236ced5dbc8580874153319f11526849edb21b900302f59aabc7d99d4bd56e35a74c49ef5cd4fe07e8d0b0c4ee092d8fc8cefdcd40f0024dc371ef
|
7
|
+
data.tar.gz: c2ae301a6ad1f8920d33608527db33241c6e6f2bb1f14151cfa8db100af0ad5b24a01debdc46efbbde1268fd4fa7f687b89dce09a147bc8c4f7a174743e8d75a
|
data/ALTERNATIVES_PROBLEMS.md
CHANGED
@@ -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
|
-
*
|
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.
|
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
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 (
|
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
|
[](https://codeclimate.com/github/MaxLap/activerecord_where_assoc)
|
6
6
|
[](https://codeclimate.com/github/MaxLap/activerecord_where_assoc)
|
7
7
|
|
8
|
-
This gem
|
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.
|
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
|
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
|
-
##
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
127
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
*
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
*
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
*
|
160
|
-
*
|
161
|
-
*
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
*
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|