activerecord_any_of 0.0.1 → 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.
- data/README.md +11 -1
- data/lib/activerecord_any_of/alternative_builder.rb +99 -0
- data/lib/activerecord_any_of/version.rb +1 -1
- data/lib/activerecord_any_of.rb +18 -14
- data/test/activerecord_any_of_test.rb +36 -0
- data/test/dummy/app/models/sti_post.rb +2 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +1 -0
- data/test/dummy/log/test.log +1354 -0
- metadata +7 -4
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# ActiverecordAnyOf
|
2
2
|
|
3
|
-
This
|
3
|
+
This gem provides `#any_of` and `#none_of` on ActiveRecord.
|
4
|
+
|
5
|
+
`#any_of` is inspired by [any_of from mongoid](http://two.mongoid.org/docs/querying/criteria.html#any_of).
|
4
6
|
|
5
7
|
It allows to compute an `OR` like query that leverages AR's `#where` syntax:
|
6
8
|
|
@@ -24,6 +26,14 @@ Its main purpose is to both :
|
|
24
26
|
* remove the need to write a sql string when we want an `OR`
|
25
27
|
* allows to write dynamic `OR` queries, which would be a pain with a string
|
26
28
|
|
29
|
+
`#none_of` is the negative version of `#any_of`. This will return all active users :
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
banned_users = User.where(banned: true)
|
33
|
+
unconfirmed_users = User.where("confirmed_at IS NULL")
|
34
|
+
active_users = User.none_of(banned_users, unconfirmed_users)
|
35
|
+
```
|
36
|
+
|
27
37
|
|
28
38
|
## Installation
|
29
39
|
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module ActiverecordAnyOf
|
2
|
+
class AlternativeBuilder
|
3
|
+
def initialize(match_type, context, *queries)
|
4
|
+
@builder = match_type == :negative ? NegativeBuilder.new(context, *queries) : PositiveBuilder.new(context, *queries)
|
5
|
+
end
|
6
|
+
|
7
|
+
def build
|
8
|
+
@builder.build
|
9
|
+
end
|
10
|
+
|
11
|
+
class Builder
|
12
|
+
attr_accessor :queries_bind_values, :queries_joins_values
|
13
|
+
|
14
|
+
def initialize(context, *source_queries)
|
15
|
+
@context, @source_queries = context, source_queries
|
16
|
+
@queries_bind_values, @queries_joins_values = [], { includes: [], joins: [], references: [] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
ActiveRecord::Base.connection.supports_statement_cache? ? with_statement_cache : without_statement_cache
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def queries
|
26
|
+
@queries ||= @source_queries.map do |query|
|
27
|
+
query = where(query) if [String, Hash].any? { |type| query.kind_of?(type) }
|
28
|
+
query = where(*query) if query.kind_of?(Array)
|
29
|
+
self.queries_bind_values += query.bind_values if query.bind_values.any?
|
30
|
+
queries_joins_values[:includes] += query.includes_values if query.includes_values.any?
|
31
|
+
queries_joins_values[:joins] += query.joins_values if query.joins_values.any?
|
32
|
+
queries_joins_values[:references] += query.references_values if Rails.version >= '4' && query.references_values.any?
|
33
|
+
query.arel.constraints.reduce(:and)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def uniq_queries_joins_values
|
38
|
+
@uniq_queries_joins_values ||= queries_joins_values.each { |tables| tables.uniq }
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_missing(method_name, *args, &block)
|
42
|
+
@context.send(method_name, *args, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_joins_to( relation )
|
46
|
+
relation = relation.references(uniq_queries_joins_values[:references]) if Rails.version >= '4'
|
47
|
+
relation = relation.includes(uniq_queries_joins_values[:includes])
|
48
|
+
relation.joins(uniq_queries_joins_values[:joins])
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_related_values_to( relation )
|
52
|
+
relation.bind_values += queries_bind_values
|
53
|
+
relation.includes_values += uniq_queries_joins_values[:includes]
|
54
|
+
relation.joins_values += uniq_queries_joins_values[:joins]
|
55
|
+
relation.references_values += uniq_queries_joins_values[:references] if Rails.version >= '4'
|
56
|
+
|
57
|
+
relation
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class PositiveBuilder < Builder
|
62
|
+
private
|
63
|
+
|
64
|
+
def with_statement_cache
|
65
|
+
relation = where([queries.reduce(:or).to_sql, *queries_bind_values.map { |v| v[1] }])
|
66
|
+
add_joins_to relation
|
67
|
+
end
|
68
|
+
|
69
|
+
def without_statement_cache
|
70
|
+
relation = where(queries.reduce(:or))
|
71
|
+
add_related_values_to relation
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class NegativeBuilder < Builder
|
76
|
+
private
|
77
|
+
|
78
|
+
def with_statement_cache
|
79
|
+
if Rails.version >= '4'
|
80
|
+
relation = where.not([queries.reduce(:or).to_sql, *queries_bind_values.map { |v| v[1] }])
|
81
|
+
else
|
82
|
+
relation = where([Arel::Nodes::Not.new(queries.reduce(:or)).to_sql, *queries_bind_values.map { |v| v[1] }])
|
83
|
+
end
|
84
|
+
|
85
|
+
add_joins_to relation
|
86
|
+
end
|
87
|
+
|
88
|
+
def without_statement_cache
|
89
|
+
if Rails.version >= '4'
|
90
|
+
relation = where.not(queries.reduce(:or))
|
91
|
+
else
|
92
|
+
relation = where(Arel::Nodes::Not.new(queries.reduce(:or)))
|
93
|
+
end
|
94
|
+
|
95
|
+
add_related_values_to relation
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/activerecord_any_of.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'activerecord_any_of/alternative_builder'
|
2
|
+
|
1
3
|
module ActiverecordAnyOf
|
2
4
|
# Returns a new relation, which includes results matching any of conditions
|
3
5
|
# passed as parameters. You can think of it as a sql <tt>OR</tt> implementation.
|
@@ -21,31 +23,33 @@ module ActiverecordAnyOf
|
|
21
23
|
# unconfirmed_users = User.where("confirmed_at IS NULL")
|
22
24
|
# unactive_users = User.any_of(banned_users, unconfirmed_users)
|
23
25
|
def any_of(*queries)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
query = where(*query) if query.kind_of?(Array)
|
28
|
-
queries_bind_values += query.bind_values if query.bind_values.any?
|
29
|
-
query.arel.constraints.reduce(:and)
|
30
|
-
end
|
26
|
+
raise ArgumentError, 'Called any_of() with no arguments.' if queries.none?
|
27
|
+
AlternativeBuilder.new(:positive, self, *queries).build
|
28
|
+
end
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
30
|
+
# Returns a new relation, which includes results not matching any of conditions
|
31
|
+
# passed as parameters. It's the negative version of <tt>#any_of</tt>.
|
32
|
+
#
|
33
|
+
# This will return all active users :
|
34
|
+
#
|
35
|
+
# banned_users = User.where(banned: true)
|
36
|
+
# unconfirmed_users = User.where("confirmed_at IS NULL")
|
37
|
+
# active_users = User.none_of(banned_users, unconfirmed_users)
|
38
|
+
def none_of(*queries)
|
39
|
+
raise ArgumentError, 'Called none_of() with no arguments.' if queries.none?
|
40
|
+
AlternativeBuilder.new(:negative, self, *queries).build
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
44
|
if Rails.version >= '4'
|
43
45
|
module ActiverecordAnyOfDelegation
|
44
46
|
delegate :any_of, to: :all
|
47
|
+
delegate :none_of, to: :all
|
45
48
|
end
|
46
49
|
else
|
47
50
|
module ActiverecordAnyOfDelegation
|
48
51
|
delegate :any_of, to: :scoped
|
52
|
+
delegate :none_of, to: :scoped
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
@@ -24,4 +24,40 @@ class ActiverecordAnyOfTest < ActiveSupport::TestCase
|
|
24
24
|
expected = ['Welcome to the weblog', 'So I was thinking']
|
25
25
|
assert_equal expected, david.posts.any_of(welcome, {type: 'SpecialPost'}).map(&:title)
|
26
26
|
end
|
27
|
+
|
28
|
+
test 'finding alternate dynamically with joined queries' do
|
29
|
+
david = Author.where(posts: { title: 'Welcome to the weblog' }).joins(:posts)
|
30
|
+
mary = Author.where(posts: { title: "eager loading with OR'd conditions" }).joins(:posts)
|
31
|
+
|
32
|
+
assert_equal ['David', 'Mary'], Author.any_of(david, mary).map(&:name)
|
33
|
+
|
34
|
+
if Rails.version >= '4'
|
35
|
+
david = Author.where(posts: { title: 'Welcome to the weblog' }).includes(:posts).references(:posts)
|
36
|
+
mary = Author.where(posts: { title: "eager loading with OR'd conditions" }).includes(:posts).references(:posts)
|
37
|
+
else
|
38
|
+
david = Author.where(posts: { title: 'Welcome to the weblog' }).includes(:posts)
|
39
|
+
mary = Author.where(posts: { title: "eager loading with OR'd conditions" }).includes(:posts)
|
40
|
+
end
|
41
|
+
|
42
|
+
assert_equal ['David', 'Mary'], Author.any_of(david, mary).map(&:name)
|
43
|
+
end
|
44
|
+
|
45
|
+
test 'finding with alternate negative conditions' do
|
46
|
+
assert_equal ['Bob'], Author.none_of({name: 'David'}, {name: 'Mary'}).map(&:name)
|
47
|
+
end
|
48
|
+
|
49
|
+
test 'finding with alternate negative conditions on association' do
|
50
|
+
david = Author.where(name: 'David').first
|
51
|
+
welcome = david.posts.where(body: 'Such a lovely day')
|
52
|
+
expected = ['sti comments', 'sti me', 'habtm sti test']
|
53
|
+
assert_equal expected, david.posts.none_of(welcome, {type: 'SpecialPost'}).map(&:title)
|
54
|
+
end
|
55
|
+
|
56
|
+
test 'calling #any_of with no argument raise exception' do
|
57
|
+
assert_raise(ArgumentError) { Author.any_of }
|
58
|
+
end
|
59
|
+
|
60
|
+
test 'calling #none_of with no argument raise exception' do
|
61
|
+
assert_raise(ArgumentError) { Author.none_of }
|
62
|
+
end
|
27
63
|
end
|
data/test/dummy/db/test.sqlite3
CHANGED
Binary file
|
@@ -88,3 +88,4 @@ Connecting to database specified by database.yml
|
|
88
88
|
[1m[36m (0.1ms)[0m [1mSELECT version FROM "schema_migrations"[0m
|
89
89
|
[1m[35m (288.7ms)[0m INSERT INTO "schema_migrations" (version) VALUES ('20130617173313')
|
90
90
|
[1m[36m (289.4ms)[0m [1mINSERT INTO "schema_migrations" (version) VALUES ('20130617172335')[0m
|
91
|
+
Connecting to database specified by database.yml
|