where_exists 1.1.4 → 1.2.3
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 +168 -0
- data/Rakefile +0 -0
- data/lib/where_exists.rb +25 -17
- data/lib/where_exists/version.rb +1 -1
- data/test/belongs_to_polymorphic_test.rb +29 -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 +9 -2
- data/test/has_many_test.rb +0 -0
- data/test/has_many_through_test.rb +72 -7
- data/test/test_helper.rb +0 -0
- metadata +13 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 397b2a3fc406fb516d6f060ae46b25580a764c668476859737ee07fbd979c15b
|
|
4
|
+
data.tar.gz: 1c23ab279b89ede28edb71e3b951f7587ab2a7b27605bba607888f0b89e03c58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 454c6302e62cbc669c8d800bab1305b2e83402f46ed2499a4caf373cfaa23547cbaf261163fc28cd2fe224a31c7f5bb927a5baf09deb04c856de49c481b5fc75
|
|
7
|
+
data.tar.gz: b32e53db604284721d96afd9b1fa7ac204277dda4cc27f4851ac1918760980fa64c17392917ca770a712541f5ab132bedaa2f0f102ac6c20fa50354d16e18ad1
|
data/MIT-LICENSE
CHANGED
|
File without changes
|
data/README.markdown
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
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: >= 4.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: `bundle exec rake test`
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
This project uses MIT license. See [`MIT-LICENSE`](https://github.com/EugZol/where_exists/blob/master/MIT-LICENSE) file for full text.
|
|
163
|
+
|
|
164
|
+
## Alternatives
|
|
165
|
+
|
|
166
|
+
One known alternative is https://github.com/MaxLap/activerecord_where_assoc
|
|
167
|
+
|
|
168
|
+
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.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,7 +65,9 @@ module WhereExists
|
|
|
61
65
|
association_scope = association.scope
|
|
62
66
|
|
|
63
67
|
if polymorphic
|
|
64
|
-
associated_models = self.select("DISTINCT #{connection.quote_column_name(association.foreign_type)}").
|
|
68
|
+
associated_models = self.select("DISTINCT #{connection.quote_column_name(association.foreign_type)}").
|
|
69
|
+
where("#{connection.quote_column_name(association.foreign_type)} IS NOT NULL").pluck(association.foreign_type).
|
|
70
|
+
uniq.map(&:classify).map(&:constantize)
|
|
65
71
|
else
|
|
66
72
|
associated_models = [association.klass]
|
|
67
73
|
end
|
|
@@ -82,8 +88,10 @@ module WhereExists
|
|
|
82
88
|
query = query.instance_exec(&association_scope)
|
|
83
89
|
end
|
|
84
90
|
if polymorphic
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
other_types = [associated_model.name, associated_model.table_name]
|
|
92
|
+
other_types << associated_model.polymorphic_name if associated_model.respond_to?(:polymorphic_name)
|
|
93
|
+
|
|
94
|
+
query = query.where("#{self_type} IN (?)", other_types.uniq)
|
|
87
95
|
end
|
|
88
96
|
queries.push query
|
|
89
97
|
end
|
|
@@ -129,8 +137,14 @@ module WhereExists
|
|
|
129
137
|
|
|
130
138
|
if association.options[:as]
|
|
131
139
|
other_types = quote_table_and_column_name(associated_model.table_name, association.type)
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
class_values = [self.name, self.table_name]
|
|
141
|
+
class_values << self.polymorphic_name if associated_model.respond_to?(:polymorphic_name)
|
|
142
|
+
|
|
143
|
+
result = result.where("#{other_types} IN (?)", class_values.uniq)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if association_scope
|
|
147
|
+
result = result.instance_exec(&association_scope)
|
|
134
148
|
end
|
|
135
149
|
|
|
136
150
|
if next_association[:association]
|
|
@@ -141,10 +155,6 @@ module WhereExists
|
|
|
141
155
|
result = result.where(*where_parameters)
|
|
142
156
|
end
|
|
143
157
|
|
|
144
|
-
if association_scope
|
|
145
|
-
result = result.instance_exec(&association_scope)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
158
|
[result]
|
|
149
159
|
end
|
|
150
160
|
|
|
@@ -155,11 +165,9 @@ module WhereExists
|
|
|
155
165
|
|
|
156
166
|
primary_key = association.options[:primary_key] || self.primary_key
|
|
157
167
|
|
|
158
|
-
join_table = [self.table_name, associated_model.table_name].sort.join("_")
|
|
159
|
-
|
|
160
168
|
self_ids = quote_table_and_column_name(self.table_name, primary_key)
|
|
161
|
-
join_ids = quote_table_and_column_name(join_table, association.foreign_key)
|
|
162
|
-
associated_join_ids = quote_table_and_column_name(join_table,
|
|
169
|
+
join_ids = quote_table_and_column_name(association.join_table, association.foreign_key)
|
|
170
|
+
associated_join_ids = quote_table_and_column_name(association.join_table, association.association_foreign_key)
|
|
163
171
|
associated_ids = quote_table_and_column_name(associated_model.table_name, associated_model.primary_key)
|
|
164
172
|
|
|
165
173
|
result =
|
|
@@ -167,7 +175,7 @@ module WhereExists
|
|
|
167
175
|
select("1").
|
|
168
176
|
joins(
|
|
169
177
|
<<-SQL
|
|
170
|
-
INNER JOIN #{connection.quote_table_name(join_table)}
|
|
178
|
+
INNER JOIN #{connection.quote_table_name(association.join_table)}
|
|
171
179
|
ON #{associated_ids} = #{associated_join_ids}
|
|
172
180
|
SQL
|
|
173
181
|
).
|
|
@@ -198,13 +206,13 @@ module WhereExists
|
|
|
198
206
|
|
|
199
207
|
if next_association[:next_association] && next_association[:next_association][:association]
|
|
200
208
|
subq = str.match(/\([^\(\)]+\)/mi)[0]
|
|
201
|
-
str.sub!(subq
|
|
209
|
+
str.sub!(subq) do
|
|
202
210
|
"(#{subq} AND (#{loop_nested_association(
|
|
203
211
|
next_association[:association],
|
|
204
212
|
next_association[:next_association],
|
|
205
213
|
true
|
|
206
214
|
)}))"
|
|
207
|
-
|
|
215
|
+
end
|
|
208
216
|
end
|
|
209
217
|
|
|
210
218
|
nested ? str : [query.where(str)]
|
data/lib/where_exists/version.rb
CHANGED
|
@@ -60,4 +60,33 @@ class BelongsToPolymorphicTest < Minitest::Test
|
|
|
60
60
|
assert_equal 1, result.length
|
|
61
61
|
assert_equal orphaned_child.id, result.first.id
|
|
62
62
|
end
|
|
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
|
+
|
|
76
|
+
def test_table_name_based_lookup
|
|
77
|
+
first_entity = FirstPolymorphicEntity.create!
|
|
78
|
+
second_entity = SecondPolymorphicEntity.create! id: first_entity.id + 1
|
|
79
|
+
|
|
80
|
+
first_child = BelongsToPolymorphicChild.create!(polymorphic_entity_id: first_entity.id, polymorphic_entity_type: first_entity.class.table_name)
|
|
81
|
+
second_child = BelongsToPolymorphicChild.create!(polymorphic_entity_id: second_entity.id, polymorphic_entity_type: second_entity.class.table_name)
|
|
82
|
+
orphaned_child = BelongsToPolymorphicChild.create!(polymorphic_entity_id: second_entity.id, polymorphic_entity_type: first_entity.class.table_name)
|
|
83
|
+
|
|
84
|
+
result = BelongsToPolymorphicChild.where_exists(:polymorphic_entity)
|
|
85
|
+
assert_equal 2, result.length
|
|
86
|
+
assert_equal [first_child, second_child].map(&:id).sort, result.map(&:id).sort
|
|
87
|
+
|
|
88
|
+
result = BelongsToPolymorphicChild.where_not_exists(:polymorphic_entity)
|
|
89
|
+
assert_equal 1, result.length
|
|
90
|
+
assert_equal [orphaned_child].map(&:id).sort, result.map(&:id).sort
|
|
91
|
+
end
|
|
63
92
|
end
|
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
|
|
@@ -35,10 +35,17 @@ class HasManyPolymorphicTest < Minitest::Test
|
|
|
35
35
|
child = HasManyPolymorphicChild.create!
|
|
36
36
|
|
|
37
37
|
irrelevant_entity = IrrelevantPolymorphicEntity.create!(children: [child])
|
|
38
|
-
|
|
38
|
+
relevant_entity = RelevantPolymorphicEntity.create!(id: irrelevant_entity.id)
|
|
39
|
+
|
|
40
|
+
assert_equal 0, RelevantPolymorphicEntity.where_exists(:children).length
|
|
41
|
+
assert_equal 1, IrrelevantPolymorphicEntity.where_exists(:children).length
|
|
42
|
+
|
|
43
|
+
child.update!(polymorphic_thing_type: RelevantPolymorphicEntity.table_name)
|
|
39
44
|
|
|
40
45
|
result = RelevantPolymorphicEntity.where_exists(:children)
|
|
41
46
|
|
|
42
|
-
assert_equal 0,
|
|
47
|
+
assert_equal 0, IrrelevantPolymorphicEntity.where_exists(:children).length
|
|
48
|
+
assert_equal 1, result.length
|
|
49
|
+
assert_equal relevant_entity.id, result.first&.id
|
|
43
50
|
end
|
|
44
51
|
end
|
data/test/has_many_test.rb
CHANGED
|
File without changes
|
|
@@ -1,34 +1,71 @@
|
|
|
1
1
|
require_relative 'test_helper'
|
|
2
2
|
|
|
3
|
-
ActiveRecord::Migration.create_table :projects, :
|
|
3
|
+
ActiveRecord::Migration.create_table :projects, force: true do |t|
|
|
4
4
|
t.string :name
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
ActiveRecord::Migration.create_table :tasks, :
|
|
7
|
+
ActiveRecord::Migration.create_table :tasks, force: true do |t|
|
|
8
8
|
t.string :name
|
|
9
9
|
t.integer :project_id
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
ActiveRecord::Migration.create_table :line_items, :
|
|
12
|
+
ActiveRecord::Migration.create_table :line_items, force: true do |t|
|
|
13
13
|
t.string :name
|
|
14
14
|
t.integer :invoice_id
|
|
15
15
|
t.integer :task_id
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
ActiveRecord::Migration.create_table :work_details, :
|
|
18
|
+
ActiveRecord::Migration.create_table :work_details, force: true do |t|
|
|
19
19
|
t.string :name
|
|
20
20
|
t.integer :line_item_id
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
ActiveRecord::Migration.create_table :invoices, :
|
|
23
|
+
ActiveRecord::Migration.create_table :invoices, force: true do |t|
|
|
24
24
|
t.string :name
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
ActiveRecord::Migration.create_table :blobs, force: true do |t|
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
ActiveRecord::Migration.create_table :attachments, force: true do |t|
|
|
31
|
+
t.string :name, null: false
|
|
32
|
+
t.references :record, null: false, polymorphic: true, index: false
|
|
33
|
+
t.references :blob, null: false
|
|
34
|
+
|
|
35
|
+
t.datetime :created_at, null: false
|
|
36
|
+
|
|
37
|
+
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_attachments_uniqueness", unique: true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Attachment < ActiveRecord::Base
|
|
41
|
+
belongs_to :record, polymorphic: true, touch: true
|
|
42
|
+
belongs_to :blob
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class Blob < ActiveRecord::Base
|
|
46
|
+
has_many :attachments
|
|
47
|
+
|
|
48
|
+
scope :unattached, -> { left_joins(:attachments).where(Attachment.table_name => { blob_id: nil }) }
|
|
49
|
+
|
|
50
|
+
before_destroy(prepend: true) do
|
|
51
|
+
raise ActiveRecord::InvalidForeignKey if attachments.exists?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
27
57
|
class Project < ActiveRecord::Base
|
|
28
58
|
has_many :tasks
|
|
29
59
|
has_many :invoices, :through => :tasks
|
|
30
60
|
has_many :project_line_items, :through => :tasks, :source => :line_items
|
|
31
61
|
has_many :work_details, :through => :project_line_items
|
|
62
|
+
|
|
63
|
+
has_many :attachments, as: :record
|
|
64
|
+
has_many :blobs, through: :attachments, source: :blob
|
|
65
|
+
has_many :relevant_attachments, -> { where(name: "relevant") }, as: :record, class_name: "Attachment", inverse_of: :record, dependent: false
|
|
66
|
+
has_many :relevant_blobs, through: :relevant_attachments, class_name: "Blob", source: :blob
|
|
67
|
+
has_many :irrelevant_attachments, -> { where(name: "irrelevant") }, as: :record, class_name: "Attachment", inverse_of: :record, dependent: false
|
|
68
|
+
has_many :irrelevant_blobs, through: :irrelevant_attachments, class_name: "Blob", source: :blob
|
|
32
69
|
end
|
|
33
70
|
|
|
34
71
|
class Task < ActiveRecord::Base
|
|
@@ -36,6 +73,7 @@ class Task < ActiveRecord::Base
|
|
|
36
73
|
|
|
37
74
|
has_many :invoices, :through => :line_items
|
|
38
75
|
has_many :line_items
|
|
76
|
+
has_many :scoped_line_items, -> { where(name: 'relevant') }, class_name: 'LineItem'
|
|
39
77
|
end
|
|
40
78
|
|
|
41
79
|
class LineItem < ActiveRecord::Base
|
|
@@ -94,12 +132,16 @@ class HasManyThroughTest < Minitest::Test
|
|
|
94
132
|
invoice = Invoice.create!(name: 'relevant')
|
|
95
133
|
irrelevant_invoice = Invoice.create!(name: 'irrelevant')
|
|
96
134
|
|
|
97
|
-
line_item = LineItem.create!(task: task, invoice: invoice)
|
|
98
|
-
irrelevant_line_item = LineItem.create!(task: irrelevant_task, invoice: irrelevant_invoice)
|
|
135
|
+
line_item = LineItem.create!(name: 'relevant', task: task, invoice: invoice)
|
|
136
|
+
irrelevant_line_item = LineItem.create!(name: 'relevant', task: irrelevant_task, invoice: irrelevant_invoice)
|
|
99
137
|
|
|
100
138
|
_work_detail = WorkDetail.create!(line_item: line_item, name: 'relevant')
|
|
101
139
|
_irrelevant_work_detail = WorkDetail.create!(line_item: irrelevant_line_item, name: 'irrelevant')
|
|
102
140
|
|
|
141
|
+
blob = Blob.create!()
|
|
142
|
+
_relevant_attachment = Attachment.create!(name: 'relevant', blob: blob, record: project)
|
|
143
|
+
_irrelevant_attachment = Attachment.create!(name: 'irrelevant', blob: blob, record: irrelevant_project)
|
|
144
|
+
|
|
103
145
|
result = Project.where_exists(:invoices, name: 'relevant')
|
|
104
146
|
|
|
105
147
|
assert_equal 1, result.length
|
|
@@ -124,5 +166,28 @@ class HasManyThroughTest < Minitest::Test
|
|
|
124
166
|
|
|
125
167
|
assert_equal 1, result.length
|
|
126
168
|
assert_equal irrelevant_project.id, result.first.id
|
|
169
|
+
|
|
170
|
+
result = Task.where_exists(:scoped_line_items)
|
|
171
|
+
|
|
172
|
+
assert_equal 2, result.length
|
|
173
|
+
|
|
174
|
+
result = Project.where_exists(:relevant_blobs)
|
|
175
|
+
|
|
176
|
+
assert_equal 1, result.length
|
|
177
|
+
assert_equal project.id, result.first.id
|
|
178
|
+
|
|
179
|
+
result = Project.where_not_exists(:relevant_blobs)
|
|
180
|
+
|
|
181
|
+
assert_equal 1, result.length
|
|
182
|
+
assert_equal irrelevant_project.id, result.first.id
|
|
183
|
+
|
|
184
|
+
result = Project.where_exists(:blobs)
|
|
185
|
+
|
|
186
|
+
assert_equal 2, result.length
|
|
187
|
+
|
|
188
|
+
result = Project.where_not_exists(:blobs)
|
|
189
|
+
|
|
190
|
+
assert_equal 0, result.length
|
|
191
|
+
|
|
127
192
|
end
|
|
128
193
|
end
|
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: 1.
|
|
4
|
+
version: 1.2.3
|
|
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-02-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -19,7 +19,7 @@ dependencies:
|
|
|
19
19
|
version: '4.2'
|
|
20
20
|
- - "<"
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '6'
|
|
22
|
+
version: '6.2'
|
|
23
23
|
type: :runtime
|
|
24
24
|
prerelease: false
|
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -29,21 +29,21 @@ dependencies:
|
|
|
29
29
|
version: '4.2'
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '6'
|
|
32
|
+
version: '6.2'
|
|
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
|
|
@@ -94,6 +94,7 @@ extensions: []
|
|
|
94
94
|
extra_rdoc_files: []
|
|
95
95
|
files:
|
|
96
96
|
- MIT-LICENSE
|
|
97
|
+
- README.markdown
|
|
97
98
|
- Rakefile
|
|
98
99
|
- lib/where_exists.rb
|
|
99
100
|
- lib/where_exists/version.rb
|
|
@@ -125,18 +126,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
125
126
|
- !ruby/object:Gem::Version
|
|
126
127
|
version: '0'
|
|
127
128
|
requirements: []
|
|
128
|
-
|
|
129
|
-
rubygems_version: 2.7.7
|
|
129
|
+
rubygems_version: 3.1.2
|
|
130
130
|
signing_key:
|
|
131
131
|
specification_version: 4
|
|
132
132
|
summary: "#where_exists extension of ActiveRecord"
|
|
133
133
|
test_files:
|
|
134
|
+
- test/has_and_belongs_to_many.rb
|
|
134
135
|
- test/belongs_to_polymorphic_test.rb
|
|
135
|
-
- test/
|
|
136
|
+
- test/has_many_through_test.rb
|
|
136
137
|
- test/db/test.db
|
|
137
|
-
- test/
|
|
138
|
-
- test/has_and_belongs_to_many.rb
|
|
138
|
+
- test/test_helper.rb
|
|
139
139
|
- test/has_many_polymorphic_test.rb
|
|
140
140
|
- test/has_many_test.rb
|
|
141
|
-
- test/
|
|
142
|
-
- test/
|
|
141
|
+
- test/documentation_test.rb
|
|
142
|
+
- test/belongs_to_test.rb
|