where_exists 1.2.0 → 2.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 +4 -4
- data/MIT-LICENSE +0 -0
- data/README.markdown +172 -0
- data/Rakefile +0 -0
- data/lib/where_exists/version.rb +1 -1
- data/lib/where_exists.rb +5 -1
- data/test/belongs_to_polymorphic_test.rb +12 -0
- data/test/belongs_to_test.rb +0 -0
- data/test/db/test.db +0 -0
- data/test/documentation_test.rb +0 -0
- data/test/has_and_belongs_to_many.rb +0 -0
- data/test/has_many_polymorphic_test.rb +0 -0
- data/test/has_many_test.rb +0 -0
- data/test/has_many_through_test.rb +0 -0
- data/test/test_helper.rb +0 -0
- metadata +24 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f3ba0936a289c595a6efec3b73bcc9f46f445981150a43fa72af419eb13dec8
|
4
|
+
data.tar.gz: 022c543b783c601be1076b4de866e9052d1c6b352b727b52a793585a571aed61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 265df32741d7633eaaac17b5dfa27a33e813ba4680a98ca4c8fd05d58d839adf4439944dd5e0942b1b44af850a14fb2bf5b48848d73fb8c4beb6fb2b94766749
|
7
|
+
data.tar.gz: 7dd2b69d952f63f4bb96999b0e4c86672fe08cdd77f37755a48a7d6d2aab0072bf0a65b5f6aed8183a6688dcbdff453fd00d41a035e29009762cfc69fcc8a261
|
data/MIT-LICENSE
CHANGED
File without changes
|
data/README.markdown
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
# Where Exists
|
2
|
+
**Rails way to harness the power of SQL EXISTS condition**<br>
|
3
|
+
[](http://badge.fury.io/rb/where_exists)
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
<img src="http://i.imgur.com/psLfPoW.gif" alt="Exists" align="right" width="100" height="200">
|
8
|
+
|
9
|
+
This gem does exactly two things:
|
10
|
+
|
11
|
+
* Selects each model object for which there is a certain associated object
|
12
|
+
* Selects each model object for which there aren't any certain associated objects
|
13
|
+
|
14
|
+
It uses SQL [EXISTS condition](http://www.techonthenet.com/sql/exists.php) to do it fast, and extends ActiveRecord with `where_exists` and `where_not_exists` methods to make its usage simple and straightforward.
|
15
|
+
|
16
|
+
## Quick start
|
17
|
+
|
18
|
+
Add gem to Gemfile:
|
19
|
+
|
20
|
+
gem 'where_exists'
|
21
|
+
|
22
|
+
and run `bundle install` as usual.
|
23
|
+
|
24
|
+
And now you have `where_exists` and `where_not_exists` methods available for your ActiveRecord models and relations.
|
25
|
+
|
26
|
+
Syntax:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
Model.where_exists(association, additional_finder_parameters)
|
30
|
+
```
|
31
|
+
|
32
|
+
Supported Rails versions: >= 5.2.
|
33
|
+
|
34
|
+
## Example of usage
|
35
|
+
|
36
|
+
Given there is User model:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class User < ActiveRecord::Base
|
40
|
+
has_many :connections
|
41
|
+
has_many :groups, through: :connections
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
And Group:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class Group < ActiveRecord::Base
|
49
|
+
has_many :connections
|
50
|
+
has_many :users, through: :connections
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
And standard many-to-many Connection:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class Connection
|
58
|
+
belongs_to :user
|
59
|
+
belongs_to :group
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
What I want to do is to:
|
64
|
+
|
65
|
+
* Select users who don't belong to given set of Groups (groups with ids `[4,5,6]`)
|
66
|
+
* Select users who belong to one set of Groups (`[1,2,3]`) and don't belong to another (`[4,5,6]`)
|
67
|
+
* Select users who don't belong to a Group
|
68
|
+
|
69
|
+
Also, I don't want to:
|
70
|
+
|
71
|
+
* Fetch a lot of data from database to manipulate it with Ruby code. I know that will be inefficient in terms of CPU and memory (Ruby is much slower than any commonly used DB engine, and typically I want to rely on DB engine to do the heavy lifting)
|
72
|
+
* I tried queries like `User.joins(:group).where(group_id: [1,2,3]).where.not(group_id: [4,5,6])` and they return wrong results (some users from the result set belong to groups 4,5,6 *as well as* 1,2,3)
|
73
|
+
* I don't want to do `join` merely for the sake of only checking for existence, because I know that that is a pretty complex (i.e. CPU/memory-intensive) operation for DB
|
74
|
+
|
75
|
+
<sub><sup>If you wonder how to do that without the gem (i.e. essentially by writing SQL EXISTS statement manually) see that [StackOverflow answer](http://stackoverflow.com/a/32016347/5029266) (disclosure: it's self-answered question of a contributor of this gem).</sup></sub>
|
76
|
+
|
77
|
+
And now you are able to do all these things (and more) as simple as:
|
78
|
+
|
79
|
+
> Select only users who don't belong to given set of Groups (groups with ids `[4,5,6]`)
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# It's really neat, isn't it?
|
83
|
+
User.where_exists(:groups, id: [4,5,6])
|
84
|
+
```
|
85
|
+
|
86
|
+
<sub><sup>Notice that the second argument is `where` parameters for Group model</sup></sub>
|
87
|
+
|
88
|
+
> Select only users who belong to one set of Groups (`[1,2,3]`) and don't belong to another (`[4,5,6]`)
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
# Chain-able like you expect them to be.
|
92
|
+
#
|
93
|
+
# Additional finder parameters is anything that
|
94
|
+
# could be fed to 'where' method.
|
95
|
+
#
|
96
|
+
# Let's use 'name' instead of 'id' here, for example.
|
97
|
+
|
98
|
+
User.where_exists(:groups, name: ['first','second','third']).
|
99
|
+
where_not_exists(:groups, name: ['fourth','fifth','sixth'])
|
100
|
+
```
|
101
|
+
|
102
|
+
<sub><sup>It is possible to add as much attributes to the criteria as it is necessary, just as with regular `where(...)`</sub></sup>
|
103
|
+
|
104
|
+
> Select only users who don't belong to a Group
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
# And that's just its basic capabilities
|
108
|
+
User.where_not_exists(:groups)
|
109
|
+
```
|
110
|
+
|
111
|
+
<sub><sup>Adding parameters (the second argument) to `where_not_exists` method is feasible as well, if you have such requirements.</sup></sub>
|
112
|
+
|
113
|
+
|
114
|
+
> Re-use existing scopes
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
User.where_exists(:groups) do |groups_scope|
|
118
|
+
groups_scope.activated_since(Time.now)
|
119
|
+
end
|
120
|
+
|
121
|
+
User.where_exists(:groups, &:approved)
|
122
|
+
```
|
123
|
+
<sub><sup>If you pass a block to `where_exists`, the scope of the relation will be yielded to your block so you can re-use existing scopes.</sup></sub>
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
## Additional capabilities
|
128
|
+
|
129
|
+
**Q**: Does it support both `has_many` and `belongs_to` association type?<br>
|
130
|
+
**A**: Yes.
|
131
|
+
|
132
|
+
|
133
|
+
**Q**: Does it support polymorphic associations?<br>
|
134
|
+
**A**: Yes, both ways.
|
135
|
+
|
136
|
+
|
137
|
+
**Q**: Does it support multi-level (recursive) `:through` associations?<br>
|
138
|
+
**A**: You bet. (Now you can forget complex EXISTS or JOIN statetements in a pretty wide variety of similar cases.)
|
139
|
+
|
140
|
+
|
141
|
+
**Q**: Does it support `where` parameters with interpolation, e.g. `parent.where_exists(:child, 'fieldA > ?', 1)`?<br>
|
142
|
+
**A**: Yes.
|
143
|
+
|
144
|
+
|
145
|
+
**Q**: Does it take into account default association condition, e.g. `has_many :drafts, -> { where published: nil }`?<br>
|
146
|
+
**A**: Yes.
|
147
|
+
|
148
|
+
## Contributing
|
149
|
+
|
150
|
+
If you find that this gem lacks certain possibilities that you would have found useful, don't hesitate to create a [feature request](https://github.com/EugZol/where_exists/issues).
|
151
|
+
|
152
|
+
Also,
|
153
|
+
|
154
|
+
* Report bugs
|
155
|
+
* Submit pull request with new features or bug fixes
|
156
|
+
* Enhance or clarify the documentation that you are reading
|
157
|
+
|
158
|
+
To run tests:
|
159
|
+
```
|
160
|
+
> bundle exec appraisal install
|
161
|
+
> bundle exec appraisal rake test
|
162
|
+
```
|
163
|
+
|
164
|
+
## License
|
165
|
+
|
166
|
+
This project uses MIT license. See [`MIT-LICENSE`](https://github.com/EugZol/where_exists/blob/master/MIT-LICENSE) file for full text.
|
167
|
+
|
168
|
+
## Alternatives
|
169
|
+
|
170
|
+
One known alternative is https://github.com/MaxLap/activerecord_where_assoc
|
171
|
+
|
172
|
+
A comprehensive comparison is made by MaxLap here: https://github.com/MaxLap/activerecord_where_assoc/blob/master/ALTERNATIVES_PROBLEMS.md
|
data/Rakefile
CHANGED
File without changes
|
data/lib/where_exists/version.rb
CHANGED
data/lib/where_exists.rb
CHANGED
@@ -20,7 +20,11 @@ module WhereExists
|
|
20
20
|
not_string = "NOT "
|
21
21
|
end
|
22
22
|
|
23
|
-
|
23
|
+
if queries_sql.empty?
|
24
|
+
does_exist ? self.none : self.all
|
25
|
+
else
|
26
|
+
self.where("#{not_string}(#{queries_sql})")
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
def build_exists_string(association_name, *where_parameters, &block)
|
@@ -61,6 +61,18 @@ class BelongsToPolymorphicTest < Minitest::Test
|
|
61
61
|
assert_equal orphaned_child.id, result.first.id
|
62
62
|
end
|
63
63
|
|
64
|
+
def test_no_entities_or_empty_child_relation
|
65
|
+
result = BelongsToPolymorphicChild.where_not_exists(:polymorphic_entity)
|
66
|
+
assert_equal 0, result.length
|
67
|
+
|
68
|
+
_first_child = BelongsToPolymorphicChild.create!
|
69
|
+
result = BelongsToPolymorphicChild.where_not_exists(:polymorphic_entity)
|
70
|
+
assert_equal 1, result.length
|
71
|
+
|
72
|
+
result = BelongsToPolymorphicChild.where_exists(:polymorphic_entity)
|
73
|
+
assert_equal 0, result.length
|
74
|
+
end
|
75
|
+
|
64
76
|
def test_table_name_based_lookup
|
65
77
|
first_entity = FirstPolymorphicEntity.create!
|
66
78
|
second_entity = SecondPolymorphicEntity.create! id: first_entity.id + 1
|
data/test/belongs_to_test.rb
CHANGED
File without changes
|
data/test/db/test.db
CHANGED
Binary file
|
data/test/documentation_test.rb
CHANGED
File without changes
|
File without changes
|
File without changes
|
data/test/has_many_test.rb
CHANGED
File without changes
|
File without changes
|
data/test/test_helper.rb
CHANGED
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: where_exists
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eugene Zolotarev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,34 +16,34 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5.2'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
22
|
+
version: '7.1'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
29
|
+
version: '5.2'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
32
|
+
version: '7.1'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: sqlite3
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '1.
|
39
|
+
version: '1.4'
|
40
40
|
type: :development
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '1.
|
46
|
+
version: '1.4'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: minitest
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,6 +86,20 @@ dependencies:
|
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: '6.0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: appraisal
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
89
103
|
description: Rails way to harness the power of SQL "EXISTS" statement
|
90
104
|
email:
|
91
105
|
- eugzol@gmail.com
|
@@ -94,6 +108,7 @@ extensions: []
|
|
94
108
|
extra_rdoc_files: []
|
95
109
|
files:
|
96
110
|
- MIT-LICENSE
|
111
|
+
- README.markdown
|
97
112
|
- Rakefile
|
98
113
|
- lib/where_exists.rb
|
99
114
|
- lib/where_exists/version.rb
|
@@ -125,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
140
|
- !ruby/object:Gem::Version
|
126
141
|
version: '0'
|
127
142
|
requirements: []
|
128
|
-
rubygems_version: 3.
|
143
|
+
rubygems_version: 3.1.6
|
129
144
|
signing_key:
|
130
145
|
specification_version: 4
|
131
146
|
summary: "#where_exists extension of ActiveRecord"
|