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 +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
|
[![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
|
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
|