active_record_extended 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd022475e7df8677d1211cfefe42f7090eca4a29fe54b96254f9490baaf101f0
4
- data.tar.gz: ae8827a88842bcba66d440ed1ffa540349007e124516ad2d2d9e8f64d553f840
3
+ metadata.gz: 6d852a8ee92e11e7a68b77e9763553d43e965778fdebd063a7d9bce6f94e9094
4
+ data.tar.gz: 72b31962b0f85f57436e2f364022a1f43edae3d471eff1efcb1b8b7d066b05d1
5
5
  SHA512:
6
- metadata.gz: 5ff916a3e069accb63c3d98c1f46705ac2191de4c40a7b8f67415e0f752c420f6efd5c6601041b11bcc6cf7ae0bd776a4a2c22d2b89c2239bb9fb68c04ba40ed
7
- data.tar.gz: 41b2f1e01aab68f69bc6753066c242475a3ecdd6f8fed68214065c5a55ecef5b9c8d261c6bf3f6d9264f9031775ef5d8571f43ca48561a36432c273672d11c9a
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
@@ -29,7 +29,7 @@ GEM
29
29
  concurrent-ruby (1.0.5)
30
30
  database_cleaner (1.7.0)
31
31
  diff-lcs (1.3)
32
- dotenv (2.2.2)
32
+ dotenv (2.4.0)
33
33
  i18n (1.0.1)
34
34
  concurrent-ruby (~> 1.0)
35
35
  method_source (0.9.0)
@@ -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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordExtended
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -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.1.1
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-05 00:00:00.000000000 Z
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