active_record_extended 0.1.1 → 0.2.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/CHANGELOG.md +20 -0
- data/Gemfile.lock +1 -1
- data/active_record_extended.gemspec +2 -2
- data/lib/active_record_extended/active_record.rb +1 -0
- data/lib/active_record_extended/query_methods/any_of.rb +91 -0
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/any_of_spec.rb +129 -0
- data/spec/sql_inspections/any_of_sql_spec.rb +41 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d852a8ee92e11e7a68b77e9763553d43e965778fdebd063a7d9bce6f94e9094
|
4
|
+
data.tar.gz: 72b31962b0f85f57436e2f364022a1f43edae3d471eff1efcb1b8b7d066b05d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1170988a8f118aaa08a7257177427748c584aebe340310aba9afe93ec4878dc237efd8098c071029ef5df200d30f1349b02f6f0e1a3862b8bc5d14f20d0c00c9
|
7
|
+
data.tar.gz: cc6b9102753928f300986767cf4c9e7466ae8758e55cabc54e0ef967a40c95c981199f4319bde271155868f02bc6fec4b77f3889585354ba56023ca3bf7c271d
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# 0.2.0 - May 6th 2018
|
2
|
+
|
3
|
+
Added ActiveRecord Where Chain Functionality
|
4
|
+
- .where.any_of
|
5
|
+
- .where.none_of
|
6
|
+
|
7
|
+
# 0.1.1 - May 2nd 2018
|
8
|
+
|
9
|
+
Added ActiveRecord Where Chain Functionality:
|
10
|
+
- .where.overlap
|
11
|
+
- .where.contained_within
|
12
|
+
- .where.contained_within_or_equals
|
13
|
+
- .where.contains_or_equals
|
14
|
+
- .where.any/1
|
15
|
+
- .where.all/1
|
16
|
+
|
17
|
+
Added ActiveRecord Base Extentions
|
18
|
+
- .either_order/2
|
19
|
+
- .either_join/2
|
20
|
+
|
data/Gemfile.lock
CHANGED
@@ -8,8 +8,8 @@ require "active_record_extended/version"
|
|
8
8
|
Gem::Specification.new do |spec|
|
9
9
|
spec.name = "active_record_extended"
|
10
10
|
spec.version = ActiveRecordExtended::VERSION
|
11
|
-
spec.authors = ["George Protacio-Karaszi", "Dan McClain"]
|
12
|
-
spec.email = ["georgekaraszi@gmail.com", "git@danmcclain.net"]
|
11
|
+
spec.authors = ["George Protacio-Karaszi", "Dan McClain", "Olivier El Mekki"]
|
12
|
+
spec.email = ["georgekaraszi@gmail.com", "git@danmcclain.net", "olivier@el-mekki.com"]
|
13
13
|
|
14
14
|
spec.summary = "Adds extended functionality to Activerecord Postgres implementation"
|
15
15
|
spec.description = "Adds extended functionality to Activerecord Postgres implementation"
|
@@ -5,6 +5,7 @@ require "active_record"
|
|
5
5
|
require "active_record_extended/predicate_builder/array_handler_decorator"
|
6
6
|
require "active_record_extended/query_methods/where_chain"
|
7
7
|
require "active_record_extended/query_methods/either"
|
8
|
+
require "active_record_extended/query_methods/any_of"
|
8
9
|
|
9
10
|
if ActiveRecord::VERSION::MAJOR >= 5
|
10
11
|
if ActiveRecord::VERSION::MINOR >= 2
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordExtended
|
4
|
+
module QueryMethods
|
5
|
+
module AnyOf
|
6
|
+
def any_of(*queries)
|
7
|
+
queries = hash_map_queries(queries)
|
8
|
+
build_query(queries) do |arel_query, binds|
|
9
|
+
if binds.any?
|
10
|
+
@scope.where(unprepared_query(arel_query.to_sql), *binds)
|
11
|
+
else
|
12
|
+
@scope.where(arel_query)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def none_of(*queries)
|
18
|
+
queries = hash_map_queries(queries)
|
19
|
+
build_query(queries) do |arel_query, binds|
|
20
|
+
if binds.any?
|
21
|
+
@scope.where.not(unprepared_query(arel_query.to_sql), *binds)
|
22
|
+
else
|
23
|
+
@scope.where.not(arel_query)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def hash_map_queries(queries)
|
31
|
+
if queries.count == 1 && queries.first.is_a?(Hash)
|
32
|
+
queries.first.each_pair.map { |attr, predicate| Hash[attr, predicate] }
|
33
|
+
else
|
34
|
+
queries
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_query(queries)
|
39
|
+
query_map = construct_query_mappings(queries)
|
40
|
+
query = yield(query_map[:arel_query], query_map[:binds])
|
41
|
+
query.joins(query_map[:joins].to_a)
|
42
|
+
.includes(query_map[:includes].to_a)
|
43
|
+
.references(query_map[:references].to_a)
|
44
|
+
end
|
45
|
+
|
46
|
+
def construct_query_mappings(queries) # rubocop:disable Metrics/AbcSize
|
47
|
+
{ joins: Set.new, references: Set.new, includes: Set.new, arel_query: nil, binds: [] }.tap do |query_map|
|
48
|
+
query_map[:arel_query] = queries.map do |raw_query|
|
49
|
+
query = generate_where_clause(raw_query)
|
50
|
+
query_map[:joins] << translate_reference(query.joins_values) if query.joins_values.any?
|
51
|
+
query_map[:includes] << translate_reference(query.includes_values) if query.includes_values.any?
|
52
|
+
query_map[:references] << translate_reference(query.references_values) if query.references_values.any?
|
53
|
+
query_map[:binds] += bind_attributes(query)
|
54
|
+
query.arel.constraints.reduce(:and)
|
55
|
+
end.reduce(:group_or)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Rails 5.1 fix
|
60
|
+
# In Rails 5.2 the arel table maintains attribute binds
|
61
|
+
def bind_attributes(query)
|
62
|
+
return [] unless query.respond_to?(:bound_attributes)
|
63
|
+
query.bound_attributes.map(&:value)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Rails 5.1 fix
|
67
|
+
def unprepared_query(query)
|
68
|
+
query.gsub(/((?<!\\)'.*?(?<!\\)'|(?<!\\)".*?(?<!\\)")|(\=\ \$\d+)/) do |match|
|
69
|
+
Regexp.last_match(2)&.gsub(/\=\ \$\d+/, "= ?") || match
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def translate_reference(reference)
|
74
|
+
reference.map { |ref| ref.try(:to_sql) || ref }.compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def generate_where_clause(query)
|
78
|
+
case query
|
79
|
+
when String, Hash
|
80
|
+
@scope.where(query)
|
81
|
+
when Array
|
82
|
+
@scope.where(*query)
|
83
|
+
else
|
84
|
+
query
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
ActiveRecord::QueryMethods::WhereChain.prepend(ActiveRecordExtended::QueryMethods::AnyOf)
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe "Active Record Any / None of Methods" do
|
4
|
+
let!(:one) { Person.create!(personal_id: 1) }
|
5
|
+
let!(:two) { Person.create!(personal_id: 2) }
|
6
|
+
let!(:three) { Person.create!(personal_id: 3) }
|
7
|
+
|
8
|
+
let!(:tag_one) { Tag.create!(person_id: one.id) }
|
9
|
+
let!(:tag_two) { Tag.create!(person_id: two.id) }
|
10
|
+
let!(:tag_three) { Tag.create!(person_id: three.id) }
|
11
|
+
|
12
|
+
describe "where.any_of/1" do
|
13
|
+
it "Should return queries that match any of the outlined queries" do
|
14
|
+
query = Person.where.any_of({ personal_id: 1 }, { personal_id: 2 })
|
15
|
+
expect(query).to include(one, two)
|
16
|
+
expect(query).to_not include(three)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "Should accept where query predicates" do
|
20
|
+
personal_one = Person.where(personal_id: 1)
|
21
|
+
personal_two = Person.where(personal_id: 2)
|
22
|
+
query = Person.where.any_of(personal_one, personal_two)
|
23
|
+
|
24
|
+
expect(query).to include(one, two)
|
25
|
+
expect(query).to_not include(three)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "Should accept query strings" do
|
29
|
+
personal_one = Person.where(personal_id: 1)
|
30
|
+
|
31
|
+
query = Person.where.any_of(personal_one, "personal_id > 2")
|
32
|
+
expect(query).to include(one, three)
|
33
|
+
expect(query).to_not include(two)
|
34
|
+
|
35
|
+
query = Person.where.any_of(["personal_id >= ?", 2])
|
36
|
+
expect(query).to include(two, three)
|
37
|
+
expect(query).to_not include(one)
|
38
|
+
end
|
39
|
+
|
40
|
+
context "Relationship queries" do
|
41
|
+
it "Finds records that are queried from two or more has_many associations" do
|
42
|
+
person_one_tag = Tag.create!(person_id: one.id)
|
43
|
+
person_two_tag = Tag.create!(person_id: two.id)
|
44
|
+
query = Tag.where.any_of(one.hm_tags, two.hm_tags)
|
45
|
+
|
46
|
+
expect(query).to include(tag_one, tag_two, person_one_tag, person_two_tag)
|
47
|
+
expect(query).to_not include(tag_three)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "Finds records that are dynamically joined" do
|
51
|
+
person_one_tag = Tag.where(people: { id: one.id }).includes(:person).references(:person)
|
52
|
+
person_two_tag = Tag.where(people: { id: two.id }).joins(:person)
|
53
|
+
query = Tag.where.any_of(person_one_tag, person_two_tag)
|
54
|
+
|
55
|
+
expect(query).to include(tag_one, tag_two)
|
56
|
+
expect(query).to_not include(tag_three)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "Return matched records of a joined table on the parent level" do
|
60
|
+
query = Tag.joins(:person).where.any_of(
|
61
|
+
{ people: { personal_id: 1 } },
|
62
|
+
{ people: { personal_id: 3 } },
|
63
|
+
)
|
64
|
+
|
65
|
+
expect(query).to include(tag_one, tag_three)
|
66
|
+
expect(query).to_not include(tag_two)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "where.none_of/1" do
|
72
|
+
it "Should return queries that match none of the outlined queries" do
|
73
|
+
query = Person.where.none_of({ personal_id: 1 }, { personal_id: 2 })
|
74
|
+
expect(query).to include(three)
|
75
|
+
expect(query).to_not include(one, two)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "Should accept where query predicates" do
|
79
|
+
personal_one = Person.where(personal_id: 1)
|
80
|
+
personal_two = Person.where(personal_id: 2)
|
81
|
+
query = Person.where.none_of(personal_one, personal_two)
|
82
|
+
|
83
|
+
expect(query).to include(three)
|
84
|
+
expect(query).to_not include(one, two)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "Should accept query strings" do
|
88
|
+
personal_one = Person.where(personal_id: 1)
|
89
|
+
|
90
|
+
query = Person.where.none_of(personal_one, "personal_id > 2")
|
91
|
+
expect(query).to include(two)
|
92
|
+
expect(query).to_not include(one, three)
|
93
|
+
|
94
|
+
query = Person.where.none_of(["personal_id >= ?", 2])
|
95
|
+
expect(query).to include(one)
|
96
|
+
expect(query).to_not include(two, three)
|
97
|
+
end
|
98
|
+
|
99
|
+
context "Relationship queries" do
|
100
|
+
it "Finds records that are queried from two or more has_many associations" do
|
101
|
+
person_one_tag = Tag.create!(person_id: one.id)
|
102
|
+
person_two_tag = Tag.create!(person_id: two.id)
|
103
|
+
query = Tag.where.none_of(one.hm_tags, two.hm_tags)
|
104
|
+
|
105
|
+
expect(query).to include(tag_three)
|
106
|
+
expect(query).to_not include(tag_one, tag_two, person_one_tag, person_two_tag)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "Finds records that are dynamically joined" do
|
110
|
+
person_one_tag = Tag.where(people: { id: one.id }).includes(:person).references(:person)
|
111
|
+
person_two_tag = Tag.where(people: { id: two.id }).joins(:person)
|
112
|
+
query = Tag.where.none_of(person_one_tag, person_two_tag)
|
113
|
+
|
114
|
+
expect(query).to include(tag_three)
|
115
|
+
expect(query).to_not include(tag_one, tag_two)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "Return matched records of a joined table on the parent level" do
|
119
|
+
query = Tag.joins(:person).where.none_of(
|
120
|
+
{ people: { personal_id: 1 } },
|
121
|
+
{ people: { personal_id: 3 } },
|
122
|
+
)
|
123
|
+
|
124
|
+
expect(query).to include(tag_two)
|
125
|
+
expect(query).to_not include(tag_one, tag_three)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe "Any / None of SQL Queries" do
|
6
|
+
let(:equal_query) { "people\".\"personal_id\" = 1" }
|
7
|
+
let(:or_query) { "OR (\"people\".\"personal_id\" = 2)" }
|
8
|
+
let(:equal_or) { equal_query + " " + or_query }
|
9
|
+
let(:join_query) { /INNER JOIN \"tags\" ON \"tags\".\"person_id\" = \"people\".\"id/ }
|
10
|
+
|
11
|
+
describe "where.any_of/1" do
|
12
|
+
it "should group different column arguments into nested or conditions" do
|
13
|
+
query = Person.where.any_of({ personal_id: 1 }, { id: 2 }, { personal_id: 2 }).to_sql
|
14
|
+
expect(query).to match_regex(/WHERE .+ = 1 OR \(.+\) OR \(.+\)/)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "Should assign where clause predicates for standard queries" do
|
18
|
+
query = Person.where.any_of({ personal_id: 1 }, { personal_id: 2 }).to_sql
|
19
|
+
expect(query).to include(equal_or)
|
20
|
+
|
21
|
+
personal_one = Person.where(personal_id: 1)
|
22
|
+
personal_two = Person.where(personal_id: 2)
|
23
|
+
query = Person.where.any_of(personal_one, personal_two).to_sql
|
24
|
+
expect(query).to include(equal_or)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "Joining queries should be added to the select statement" do
|
28
|
+
person_two_tag = Person.where(personal_id: 1).joins(:hm_tags)
|
29
|
+
query = Person.where.any_of(person_two_tag).to_sql
|
30
|
+
expect(query).to match_regex(join_query)
|
31
|
+
expect(query).to include(equal_query)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "where.none_of/1" do
|
36
|
+
it "Should surround the query in a WHERE NOT clause" do
|
37
|
+
query = Person.where.none_of({ personal_id: 1 }, { id: 2 }, { personal_id: 2 }).to_sql
|
38
|
+
expect(query).to match_regex(/WHERE.+NOT \(.+ = 1 OR \(.+\) OR \(.+\)\)/)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_extended
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- George Protacio-Karaszi
|
8
8
|
- Dan McClain
|
9
|
+
- Olivier El Mekki
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2018-05-
|
13
|
+
date: 2018-05-06 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: activerecord
|
@@ -119,6 +120,7 @@ description: Adds extended functionality to Activerecord Postgres implementation
|
|
119
120
|
email:
|
120
121
|
- georgekaraszi@gmail.com
|
121
122
|
- git@danmcclain.net
|
123
|
+
- olivier@el-mekki.com
|
122
124
|
executables:
|
123
125
|
- console
|
124
126
|
- setup
|
@@ -132,6 +134,7 @@ files:
|
|
132
134
|
- ".ruby-gemset"
|
133
135
|
- ".ruby-version"
|
134
136
|
- ".travis.yml"
|
137
|
+
- CHANGELOG.md
|
135
138
|
- CODE_OF_CONDUCT.md
|
136
139
|
- Gemfile
|
137
140
|
- Gemfile.lock
|
@@ -153,14 +156,17 @@ files:
|
|
153
156
|
- lib/active_record_extended/patch/5_1/where_clause.rb
|
154
157
|
- lib/active_record_extended/patch/5_2/where_clause.rb
|
155
158
|
- lib/active_record_extended/predicate_builder/array_handler_decorator.rb
|
159
|
+
- lib/active_record_extended/query_methods/any_of.rb
|
156
160
|
- lib/active_record_extended/query_methods/either.rb
|
157
161
|
- lib/active_record_extended/query_methods/where_chain.rb
|
158
162
|
- lib/active_record_extended/version.rb
|
159
163
|
- spec/active_record_extended_spec.rb
|
164
|
+
- spec/query_methods/any_of_spec.rb
|
160
165
|
- spec/query_methods/array_query_spec.rb
|
161
166
|
- spec/query_methods/either_spec.rb
|
162
167
|
- spec/query_methods/hash_query_spec.rb
|
163
168
|
- spec/spec_helper.rb
|
169
|
+
- spec/sql_inspections/any_of_sql_spec.rb
|
164
170
|
- spec/sql_inspections/arel/array_spec.rb
|
165
171
|
- spec/sql_inspections/contains_sql_queries_spec.rb
|
166
172
|
- spec/sql_inspections/either_sql_spec.rb
|
@@ -192,10 +198,12 @@ specification_version: 4
|
|
192
198
|
summary: Adds extended functionality to Activerecord Postgres implementation
|
193
199
|
test_files:
|
194
200
|
- spec/active_record_extended_spec.rb
|
201
|
+
- spec/query_methods/any_of_spec.rb
|
195
202
|
- spec/query_methods/array_query_spec.rb
|
196
203
|
- spec/query_methods/either_spec.rb
|
197
204
|
- spec/query_methods/hash_query_spec.rb
|
198
205
|
- spec/spec_helper.rb
|
206
|
+
- spec/sql_inspections/any_of_sql_spec.rb
|
199
207
|
- spec/sql_inspections/arel/array_spec.rb
|
200
208
|
- spec/sql_inspections/contains_sql_queries_spec.rb
|
201
209
|
- spec/sql_inspections/either_sql_spec.rb
|