active_record_extended 1.1.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/README.md +87 -15
- data/lib/active_record_extended.rb +2 -1
- data/lib/active_record_extended/active_record.rb +2 -9
- data/lib/active_record_extended/active_record/relation_patch.rb +21 -4
- data/lib/active_record_extended/arel.rb +2 -0
- data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
- data/lib/active_record_extended/arel/nodes.rb +32 -41
- data/lib/active_record_extended/arel/predications.rb +4 -1
- data/lib/active_record_extended/arel/sql_literal.rb +16 -0
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +40 -1
- data/lib/active_record_extended/query_methods/any_of.rb +10 -8
- data/lib/active_record_extended/query_methods/either.rb +1 -1
- data/lib/active_record_extended/query_methods/inet.rb +7 -3
- data/lib/active_record_extended/query_methods/json.rb +156 -50
- data/lib/active_record_extended/query_methods/select.rb +118 -0
- data/lib/active_record_extended/query_methods/unionize.rb +14 -43
- data/lib/active_record_extended/query_methods/where_chain.rb +14 -6
- data/lib/active_record_extended/query_methods/window.rb +93 -0
- data/lib/active_record_extended/query_methods/with_cte.rb +102 -35
- data/lib/active_record_extended/utilities/order_by.rb +77 -0
- data/lib/active_record_extended/utilities/support.rb +178 -0
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/any_of_spec.rb +40 -40
- data/spec/query_methods/array_query_spec.rb +14 -14
- data/spec/query_methods/either_spec.rb +14 -14
- data/spec/query_methods/hash_query_spec.rb +11 -11
- data/spec/query_methods/inet_query_spec.rb +33 -31
- data/spec/query_methods/json_spec.rb +42 -27
- data/spec/query_methods/select_spec.rb +115 -0
- data/spec/query_methods/unionize_spec.rb +56 -56
- data/spec/query_methods/window_spec.rb +51 -0
- data/spec/query_methods/with_cte_spec.rb +22 -12
- data/spec/spec_helper.rb +1 -1
- data/spec/sql_inspections/any_of_sql_spec.rb +12 -12
- data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
- data/spec/sql_inspections/arel/array_spec.rb +7 -7
- data/spec/sql_inspections/arel/inet_spec.rb +7 -7
- data/spec/sql_inspections/contains_sql_queries_spec.rb +14 -14
- data/spec/sql_inspections/either_sql_spec.rb +11 -11
- data/spec/sql_inspections/json_sql_spec.rb +44 -8
- data/spec/sql_inspections/unionize_sql_spec.rb +27 -27
- data/spec/sql_inspections/window_sql_spec.rb +98 -0
- data/spec/sql_inspections/with_cte_sql_spec.rb +52 -23
- data/spec/support/models.rb +24 -4
- metadata +31 -20
- data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +0 -87
- data/lib/active_record_extended/utilities.rb +0 -141
@@ -3,39 +3,54 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
RSpec.describe "Active Record WITH CTE tables" do
|
6
|
-
let(:with_personal_query) { /WITH.+personal_id_one.+AS \(SELECT.+
|
6
|
+
let(:with_personal_query) { /WITH.+personal_id_one.+AS \(SELECT.+users.+FROM.+WHERE.+users.+personal_id.+ = 1\)/ }
|
7
7
|
|
8
8
|
it "should contain WITH statement that creates the CTE table" do
|
9
|
-
query =
|
10
|
-
|
11
|
-
|
9
|
+
query = User.with(personal_id_one: User.where(personal_id: 1))
|
10
|
+
.joins("JOIN personal_id_one ON personal_id_one.id = users.id")
|
11
|
+
.to_sql
|
12
12
|
expect(query).to match_regex(with_personal_query)
|
13
13
|
end
|
14
14
|
|
15
15
|
it "will maintain the CTE table when merging" do
|
16
|
-
query =
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
query = User.all
|
17
|
+
.merge(User.with(personal_id_one: User.where(personal_id: 1)))
|
18
|
+
.joins("JOIN personal_id_one ON personal_id_one.id = users.id")
|
19
|
+
.to_sql
|
20
20
|
|
21
21
|
expect(query).to match_regex(with_personal_query)
|
22
22
|
end
|
23
23
|
|
24
|
+
it "will pipe Children CTE's into the Parent relation" do
|
25
|
+
personal_id_one_query = User.where(personal_id: 1)
|
26
|
+
personal_id_two_query = User.where(personal_id: 2)
|
27
|
+
|
28
|
+
sub_query = personal_id_two_query.with(personal_id_one: personal_id_one_query)
|
29
|
+
query = User.all.with(personal_id_two: sub_query)
|
30
|
+
expected_order = User.with(
|
31
|
+
personal_id_one: personal_id_one_query,
|
32
|
+
personal_id_two: personal_id_two_query
|
33
|
+
)
|
34
|
+
|
35
|
+
expect(query.to_sql).to eq(expected_order.to_sql)
|
36
|
+
end
|
37
|
+
|
24
38
|
context "when multiple CTE's" do
|
25
39
|
let(:chained_with) do
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
40
|
+
User.with(personal_id_one: User.where(personal_id: 1))
|
41
|
+
.with(personal_id_two: User.where(personal_id: 2))
|
42
|
+
.joins("JOIN personal_id_one ON personal_id_one.id = users.id")
|
43
|
+
.joins("JOIN personal_id_two ON personal_id_two.id = users.id")
|
44
|
+
.to_sql
|
31
45
|
end
|
32
46
|
|
33
47
|
let(:with_arguments) do
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
48
|
+
User.with(personal_id_one: User.where(personal_id: 1), personal_id_two: User.where(personal_id: 2))
|
49
|
+
.joins("JOIN personal_id_one ON personal_id_one.id = users.id")
|
50
|
+
.joins("JOIN personal_id_two ON personal_id_two.id = users.id")
|
51
|
+
.to_sql
|
38
52
|
end
|
53
|
+
|
39
54
|
it "Should only contain a single WITH statement" do
|
40
55
|
expect(with_arguments.scan(/WITH/).count).to eq(1)
|
41
56
|
expect(with_arguments.scan(/AS/).count).to eq(2)
|
@@ -49,18 +64,32 @@ RSpec.describe "Active Record WITH CTE tables" do
|
|
49
64
|
|
50
65
|
context "when chaining the recursive method" do
|
51
66
|
let(:with_recursive_personal_query) do
|
52
|
-
/WITH.+RECURSIVE.+personal_id_one.+AS \(SELECT.+
|
67
|
+
/WITH.+RECURSIVE.+personal_id_one.+AS \(SELECT.+users.+FROM.+WHERE.+users.+personal_id.+ = 1\)/
|
53
68
|
end
|
54
69
|
|
55
70
|
let(:with_recursive) do
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
71
|
+
User.with
|
72
|
+
.recursive(personal_id_one: User.where(personal_id: 1))
|
73
|
+
.joins("JOIN personal_id_one ON personal_id_one.id = users.id")
|
74
|
+
.to_sql
|
60
75
|
end
|
61
76
|
|
62
77
|
it "generates an expression with recursive" do
|
63
|
-
|
78
|
+
query = User.with
|
79
|
+
.recursive(personal_id_one: User.where(personal_id: 1))
|
80
|
+
.joins("JOIN personal_id_one ON personal_id_one.id = users.id")
|
81
|
+
.to_sql
|
82
|
+
|
83
|
+
expect(query).to match_regex(with_recursive_personal_query)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "will maintain the CTE table when merging" do
|
87
|
+
sub_query = User.with.recursive(personal_id_one: User.where(personal_id: 1))
|
88
|
+
query = User.merge(sub_query)
|
89
|
+
.joins("JOIN personal_id_one ON personal_id_one.id = users.id")
|
90
|
+
.to_sql
|
91
|
+
|
92
|
+
expect(query).to match_regex(with_recursive_personal_query)
|
64
93
|
end
|
65
94
|
end
|
66
95
|
end
|
data/spec/support/models.rb
CHANGED
@@ -4,13 +4,14 @@ class ApplicationRecord < ActiveRecord::Base
|
|
4
4
|
self.abstract_class = true
|
5
5
|
end
|
6
6
|
|
7
|
-
class
|
7
|
+
class User < ApplicationRecord
|
8
8
|
has_many :hm_tags, class_name: "Tag"
|
9
9
|
has_one :profile_l, class_name: "ProfileL"
|
10
10
|
has_one :profile_r, class_name: "ProfileR"
|
11
11
|
# attributes
|
12
12
|
# t.string "tags", array: true
|
13
13
|
# t.integer "number", default: 0
|
14
|
+
# t.string "name"
|
14
15
|
# t.integer "personal_id"
|
15
16
|
# t.hstore "data"
|
16
17
|
# t.jsonb "jsonb_data"
|
@@ -19,13 +20,32 @@ class Person < ApplicationRecord
|
|
19
20
|
#
|
20
21
|
end
|
21
22
|
|
23
|
+
class StiRecord < ApplicationRecord
|
24
|
+
# t.string "type"
|
25
|
+
end
|
26
|
+
|
27
|
+
class AdminSti < StiRecord; end
|
28
|
+
|
29
|
+
module Namespaced
|
30
|
+
def self.table_name_prefix
|
31
|
+
"namespaced_"
|
32
|
+
end
|
33
|
+
|
34
|
+
class Record < ApplicationRecord
|
35
|
+
# attributes
|
36
|
+
# t.inet :ip
|
37
|
+
# t.cidr :subnet
|
38
|
+
#
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
22
42
|
class Tag < ApplicationRecord
|
23
|
-
belongs_to :
|
43
|
+
belongs_to :user
|
24
44
|
# attributes: tag_number
|
25
45
|
end
|
26
46
|
|
27
47
|
class ProfileL < ApplicationRecord
|
28
|
-
belongs_to :
|
48
|
+
belongs_to :user
|
29
49
|
has_one :version, as: :versionable, class_name: "VersionControl"
|
30
50
|
# attributes
|
31
51
|
# t.integer :likes
|
@@ -33,7 +53,7 @@ class ProfileL < ApplicationRecord
|
|
33
53
|
end
|
34
54
|
|
35
55
|
class ProfileR < ApplicationRecord
|
36
|
-
belongs_to :
|
56
|
+
belongs_to :user
|
37
57
|
has_one :version, as: :versionable, class_name: "VersionControl"
|
38
58
|
# attributes
|
39
59
|
# t.integer :dislikes
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_extended
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- George Protacio-Karaszi
|
8
8
|
- Dan McClain
|
9
9
|
- Olivier El Mekki
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2020-12-22 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -18,20 +18,20 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ">="
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '5.
|
21
|
+
version: '5.1'
|
22
22
|
- - "<"
|
23
23
|
- !ruby/object:Gem::Version
|
24
|
-
version: '6.
|
24
|
+
version: '6.2'
|
25
25
|
type: :runtime
|
26
26
|
prerelease: false
|
27
27
|
version_requirements: !ruby/object:Gem::Requirement
|
28
28
|
requirements:
|
29
29
|
- - ">="
|
30
30
|
- !ruby/object:Gem::Version
|
31
|
-
version: '5.
|
31
|
+
version: '5.1'
|
32
32
|
- - "<"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '6.
|
34
|
+
version: '6.2'
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: ar_outer_joins
|
37
37
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,14 +52,14 @@ dependencies:
|
|
52
52
|
requirements:
|
53
53
|
- - "<"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
55
|
+
version: '3.0'
|
56
56
|
type: :runtime
|
57
57
|
prerelease: false
|
58
58
|
version_requirements: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - "<"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '
|
62
|
+
version: '3.0'
|
63
63
|
- !ruby/object:Gem::Dependency
|
64
64
|
name: bundler
|
65
65
|
requirement: !ruby/object:Gem::Requirement
|
@@ -69,7 +69,7 @@ dependencies:
|
|
69
69
|
version: '1.16'
|
70
70
|
- - "<"
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version: '
|
72
|
+
version: '3.0'
|
73
73
|
type: :development
|
74
74
|
prerelease: false
|
75
75
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -79,7 +79,7 @@ dependencies:
|
|
79
79
|
version: '1.16'
|
80
80
|
- - "<"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '3.0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: database_cleaner
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,14 +98,14 @@ dependencies:
|
|
98
98
|
name: rake
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- - "
|
101
|
+
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '10.0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- - "
|
108
|
+
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '10.0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
@@ -150,10 +150,11 @@ files:
|
|
150
150
|
- lib/active_record_extended/active_record.rb
|
151
151
|
- lib/active_record_extended/active_record/relation_patch.rb
|
152
152
|
- lib/active_record_extended/arel.rb
|
153
|
+
- lib/active_record_extended/arel/aggregate_function_name.rb
|
153
154
|
- lib/active_record_extended/arel/nodes.rb
|
154
155
|
- lib/active_record_extended/arel/predications.rb
|
156
|
+
- lib/active_record_extended/arel/sql_literal.rb
|
155
157
|
- lib/active_record_extended/arel/visitors/postgresql_decorator.rb
|
156
|
-
- lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb
|
157
158
|
- lib/active_record_extended/patch/5_1/where_clause.rb
|
158
159
|
- lib/active_record_extended/patch/5_2/where_clause.rb
|
159
160
|
- lib/active_record_extended/predicate_builder/array_handler_decorator.rb
|
@@ -161,10 +162,13 @@ files:
|
|
161
162
|
- lib/active_record_extended/query_methods/either.rb
|
162
163
|
- lib/active_record_extended/query_methods/inet.rb
|
163
164
|
- lib/active_record_extended/query_methods/json.rb
|
165
|
+
- lib/active_record_extended/query_methods/select.rb
|
164
166
|
- lib/active_record_extended/query_methods/unionize.rb
|
165
167
|
- lib/active_record_extended/query_methods/where_chain.rb
|
168
|
+
- lib/active_record_extended/query_methods/window.rb
|
166
169
|
- lib/active_record_extended/query_methods/with_cte.rb
|
167
|
-
- lib/active_record_extended/utilities.rb
|
170
|
+
- lib/active_record_extended/utilities/order_by.rb
|
171
|
+
- lib/active_record_extended/utilities/support.rb
|
168
172
|
- lib/active_record_extended/version.rb
|
169
173
|
- spec/active_record_extended_spec.rb
|
170
174
|
- spec/query_methods/any_of_spec.rb
|
@@ -173,16 +177,20 @@ files:
|
|
173
177
|
- spec/query_methods/hash_query_spec.rb
|
174
178
|
- spec/query_methods/inet_query_spec.rb
|
175
179
|
- spec/query_methods/json_spec.rb
|
180
|
+
- spec/query_methods/select_spec.rb
|
176
181
|
- spec/query_methods/unionize_spec.rb
|
182
|
+
- spec/query_methods/window_spec.rb
|
177
183
|
- spec/query_methods/with_cte_spec.rb
|
178
184
|
- spec/spec_helper.rb
|
179
185
|
- spec/sql_inspections/any_of_sql_spec.rb
|
186
|
+
- spec/sql_inspections/arel/aggregate_function_name_spec.rb
|
180
187
|
- spec/sql_inspections/arel/array_spec.rb
|
181
188
|
- spec/sql_inspections/arel/inet_spec.rb
|
182
189
|
- spec/sql_inspections/contains_sql_queries_spec.rb
|
183
190
|
- spec/sql_inspections/either_sql_spec.rb
|
184
191
|
- spec/sql_inspections/json_sql_spec.rb
|
185
192
|
- spec/sql_inspections/unionize_sql_spec.rb
|
193
|
+
- spec/sql_inspections/window_sql_spec.rb
|
186
194
|
- spec/sql_inspections/with_cte_sql_spec.rb
|
187
195
|
- spec/support/database_cleaner.rb
|
188
196
|
- spec/support/models.rb
|
@@ -190,7 +198,7 @@ homepage: https://github.com/georgekaraszi/ActiveRecordExtended
|
|
190
198
|
licenses:
|
191
199
|
- MIT
|
192
200
|
metadata: {}
|
193
|
-
post_install_message:
|
201
|
+
post_install_message:
|
194
202
|
rdoc_options: []
|
195
203
|
require_paths:
|
196
204
|
- lib
|
@@ -198,16 +206,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
198
206
|
requirements:
|
199
207
|
- - ">="
|
200
208
|
- !ruby/object:Gem::Version
|
201
|
-
version: '
|
209
|
+
version: '2.4'
|
202
210
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
203
211
|
requirements:
|
204
212
|
- - ">="
|
205
213
|
- !ruby/object:Gem::Version
|
206
214
|
version: '0'
|
207
215
|
requirements: []
|
208
|
-
|
209
|
-
|
210
|
-
signing_key:
|
216
|
+
rubygems_version: 3.0.6
|
217
|
+
signing_key:
|
211
218
|
specification_version: 4
|
212
219
|
summary: Adds extended functionality to Activerecord Postgres implementation
|
213
220
|
test_files:
|
@@ -218,16 +225,20 @@ test_files:
|
|
218
225
|
- spec/query_methods/hash_query_spec.rb
|
219
226
|
- spec/query_methods/inet_query_spec.rb
|
220
227
|
- spec/query_methods/json_spec.rb
|
228
|
+
- spec/query_methods/select_spec.rb
|
221
229
|
- spec/query_methods/unionize_spec.rb
|
230
|
+
- spec/query_methods/window_spec.rb
|
222
231
|
- spec/query_methods/with_cte_spec.rb
|
223
232
|
- spec/spec_helper.rb
|
224
233
|
- spec/sql_inspections/any_of_sql_spec.rb
|
234
|
+
- spec/sql_inspections/arel/aggregate_function_name_spec.rb
|
225
235
|
- spec/sql_inspections/arel/array_spec.rb
|
226
236
|
- spec/sql_inspections/arel/inet_spec.rb
|
227
237
|
- spec/sql_inspections/contains_sql_queries_spec.rb
|
228
238
|
- spec/sql_inspections/either_sql_spec.rb
|
229
239
|
- spec/sql_inspections/json_sql_spec.rb
|
230
240
|
- spec/sql_inspections/unionize_sql_spec.rb
|
241
|
+
- spec/sql_inspections/window_sql_spec.rb
|
231
242
|
- spec/sql_inspections/with_cte_sql_spec.rb
|
232
243
|
- spec/support/database_cleaner.rb
|
233
244
|
- spec/support/models.rb
|
@@ -1,87 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Stripped from Rails 5.1.x
|
4
|
-
# This patch is used so that when querying with hash elements that do not belong to an association,
|
5
|
-
# but instead a data attribute. It returns the corrected attribute binds back to the query builder.
|
6
|
-
#
|
7
|
-
# Without joins
|
8
|
-
# Before:
|
9
|
-
# Person.where.contains(data: { nickname: "george" })
|
10
|
-
# #=> "SELECT \"people\".* FROM \"people\" WHERE (\"data\".\"nickname\" @> 'george')"
|
11
|
-
#
|
12
|
-
# After:
|
13
|
-
# Person.where.contains(data: { nickname: "george" })
|
14
|
-
# #=> "SELECT \"people\".* FROM \"people\" WHERE (\"people\".\"data\" @> '\"nickname\"=>\"george\"')"
|
15
|
-
#
|
16
|
-
# With Joins
|
17
|
-
# Before:
|
18
|
-
# Tag.joins(:person).where.contains(people: { data: { nickname: "george" } })
|
19
|
-
# #=> NoMethodError: undefined method `type' for nil:NilClass
|
20
|
-
#
|
21
|
-
# After:
|
22
|
-
# Tag.joins(:person).where.contains(people: { data: { nickname: "george" } })
|
23
|
-
# #=> "SELECT \"tags\".* FROM \"tags\" INNER JOIN \"people\" ON \"people\".\"id\" = \"tags\".\"person_id\"
|
24
|
-
# WHERE (\"people\".\"data\" @> '\"nickname\"=>\"george\"')"
|
25
|
-
#
|
26
|
-
module ActiveRecord
|
27
|
-
class TableMetadata
|
28
|
-
def has_column?(column_name) # rubocop:disable Naming/PredicateName
|
29
|
-
klass&.columns_hash&.key?(column_name.to_s)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class PredicateBuilder
|
34
|
-
def create_binds_for_hash(attributes) # rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize
|
35
|
-
result = attributes.dup
|
36
|
-
binds = []
|
37
|
-
|
38
|
-
attributes.each do |column_name, value| # rubocop:disable Metrics/BlockLength
|
39
|
-
if value.is_a?(Hash) && !table.has_column?(column_name)
|
40
|
-
attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
|
41
|
-
result[column_name] = attrs
|
42
|
-
binds += bvs
|
43
|
-
next
|
44
|
-
elsif value.is_a?(Relation)
|
45
|
-
binds += value.bound_attributes
|
46
|
-
elsif value.is_a?(Range) && !table.type(column_name).respond_to?(:subtype)
|
47
|
-
first = value.begin
|
48
|
-
last = value.end
|
49
|
-
unless first.respond_to?(:infinite?) && first.infinite?
|
50
|
-
binds << build_bind_param(column_name, first)
|
51
|
-
first = Arel::Nodes::BindParam.new
|
52
|
-
end
|
53
|
-
unless last.respond_to?(:infinite?) && last.infinite?
|
54
|
-
binds << build_bind_param(column_name, last)
|
55
|
-
last = Arel::Nodes::BindParam.new
|
56
|
-
end
|
57
|
-
|
58
|
-
result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?)
|
59
|
-
elsif can_be_bound?(column_name, value)
|
60
|
-
result[column_name] = Arel::Nodes::BindParam.new
|
61
|
-
binds << build_bind_param(column_name, value)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Find the foreign key when using queries such as:
|
65
|
-
# Post.where(author: author)
|
66
|
-
#
|
67
|
-
# For polymorphic relationships, find the foreign key and type:
|
68
|
-
# PriceEstimate.where(estimate_of: treasure)
|
69
|
-
if table.associated_with?(column_name)
|
70
|
-
result[column_name] = AssociationQueryHandler.value_for(table, column_name, value)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
[result, binds]
|
75
|
-
end
|
76
|
-
|
77
|
-
def can_be_bound?(column_name, value)
|
78
|
-
return if table.associated_with?(column_name)
|
79
|
-
case value
|
80
|
-
when Array, Range
|
81
|
-
table.type(column_name).respond_to?(:subtype)
|
82
|
-
else
|
83
|
-
!value.nil? && handler_for(value).is_a?(BasicObjectHandler)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
@@ -1,141 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActiveRecordExtended
|
4
|
-
module Utilities
|
5
|
-
A_TO_Z_KEYS = ("a".."z").to_a.freeze
|
6
|
-
|
7
|
-
# We need to ensure we can flatten nested ActiveRecord::Relations
|
8
|
-
# that might have been nested due to the (splat)*args parameters
|
9
|
-
#
|
10
|
-
# Note: calling `Array.flatten[!]/1` will actually remove all AR relations from the array.
|
11
|
-
#
|
12
|
-
def flatten_to_sql(*values)
|
13
|
-
flatten_safely(values) do |value|
|
14
|
-
value = yield value if block_given?
|
15
|
-
to_arel_sql(value)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
alias to_sql_array flatten_to_sql
|
19
|
-
|
20
|
-
def flatten_safely(values, &block)
|
21
|
-
unless values.is_a?(Array)
|
22
|
-
values = yield values if block_given?
|
23
|
-
return [values]
|
24
|
-
end
|
25
|
-
|
26
|
-
values.map { |value| flatten_safely(value, &block) }.reduce(:+)
|
27
|
-
end
|
28
|
-
|
29
|
-
# Applies aliases to the given query
|
30
|
-
# Ex: `SELECT * FROM users` => `(SELECT * FROM users) AS "members"`
|
31
|
-
def nested_alias_escape(query, alias_name)
|
32
|
-
sql_query = Arel::Nodes::Grouping.new(to_arel_sql(query))
|
33
|
-
Arel::Nodes::As.new(sql_query, to_arel_sql(double_quote(alias_name)))
|
34
|
-
end
|
35
|
-
|
36
|
-
# Wraps subquery into an Aliased ARRAY
|
37
|
-
# Ex: `SELECT * FROM users` => (ARRAY(SELECT * FROM users)) AS "members"
|
38
|
-
def wrap_with_array(arel_or_rel_query, alias_name)
|
39
|
-
query = Arel::Nodes::NamedFunction.new("ARRAY", to_sql_array(arel_or_rel_query))
|
40
|
-
nested_alias_escape(query, alias_name)
|
41
|
-
end
|
42
|
-
|
43
|
-
# Will attempt to digest and resolve the from clause
|
44
|
-
#
|
45
|
-
# If the from clause is a String, it will check to see if a table reference key has been assigned.
|
46
|
-
# - If one cannot be detected, one will be appended.
|
47
|
-
# - Rails does not allow assigning table references using the `.from/2` method, when its a string / sym type.
|
48
|
-
#
|
49
|
-
# If the from clause is an AR relation; it will duplicate the object.
|
50
|
-
# - Ensures any memorizers are reset (ex: `.to_sql` sets a memorizer on the instance)
|
51
|
-
# - Key's can be assigned using the `.from/2` method.
|
52
|
-
#
|
53
|
-
def from_clause_constructor(from, reference_key)
|
54
|
-
case from
|
55
|
-
when /\s.?#{reference_key}.?$/ # The from clause is a string and has the tbl reference key
|
56
|
-
@scope.unscoped.from(from)
|
57
|
-
when String, Symbol
|
58
|
-
@scope.unscoped.from("#{from} #{reference_key}")
|
59
|
-
else
|
60
|
-
replicate_klass = from.respond_to?(:unscoped) ? from.unscoped : @scope.unscoped
|
61
|
-
replicate_klass.from(from.dup, reference_key)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# Will carry defined CTE tables from the nested sub-query and gradually pushes it up to the parents query stack
|
66
|
-
# I.E: It pushes `WITH [:cte_name:] AS(...), ..` to the top of the query structure tree
|
67
|
-
#
|
68
|
-
# SPECIAL GOTCHA NOTE: (if duplicate keys are found) This will favor the parents query `with's` over nested ones!
|
69
|
-
def pipe_cte_with!(subquery)
|
70
|
-
return self unless subquery.try(:with_values?)
|
71
|
-
|
72
|
-
cte_ary = flatten_safely(subquery.with_values)
|
73
|
-
subquery.with_values = nil # Remove nested queries with values
|
74
|
-
|
75
|
-
# Add subquery's CTE's to the parents query stack. (READ THE SPECIAL NOTE ABOVE!)
|
76
|
-
if @scope.with_values?
|
77
|
-
# combine top-level and lower level queries `.with` values into 1 structure
|
78
|
-
with_hash = cte_ary.each_with_object(@scope.with_values.first) do |from_cte, hash|
|
79
|
-
hash.reverse_merge!(from_cte)
|
80
|
-
end
|
81
|
-
|
82
|
-
@scope.with_values = [with_hash]
|
83
|
-
else
|
84
|
-
# Top level has no with values
|
85
|
-
@scope.with!(*cte_ary)
|
86
|
-
end
|
87
|
-
|
88
|
-
self
|
89
|
-
end
|
90
|
-
|
91
|
-
# Ensures the given value is properly double quoted.
|
92
|
-
# This also ensures we don't have conflicts with reversed keywords.
|
93
|
-
#
|
94
|
-
# IE: `user` is a reserved keyword in PG. But `"user"` is allowed and works the same
|
95
|
-
# when used as an column/tbl alias.
|
96
|
-
def double_quote(value)
|
97
|
-
return if value.nil?
|
98
|
-
|
99
|
-
case value.to_s
|
100
|
-
when "*", /^".+"$/ # Ignore keys that contain double quotes or a Arel.star (*)[all columns]
|
101
|
-
value
|
102
|
-
else
|
103
|
-
PG::Connection.quote_ident(value.to_s)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# Ensures the key is properly single quoted and treated as a actual PG key reference.
|
108
|
-
def literal_key(key)
|
109
|
-
case key
|
110
|
-
when TrueClass then "'t'"
|
111
|
-
when FalseClass then "'f'"
|
112
|
-
when Numeric then key
|
113
|
-
else
|
114
|
-
key = key.to_s
|
115
|
-
key.start_with?("'") && key.end_with?("'") ? key : "'#{key}'"
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# Converts a potential subquery into a compatible Arel SQL node.
|
120
|
-
#
|
121
|
-
# Note:
|
122
|
-
# We convert relations to SQL to maintain compatibility with Rails 5.[0/1].
|
123
|
-
# Only Rails 5.2+ maintains bound attributes in Arel, so its better to be safe then sorry.
|
124
|
-
# When we drop support for Rails 5.[0/1], we then can then drop the '.to_sql' conversation
|
125
|
-
|
126
|
-
def to_arel_sql(value)
|
127
|
-
case value
|
128
|
-
when Arel::Node, Arel::Nodes::SqlLiteral, nil
|
129
|
-
value
|
130
|
-
when ActiveRecord::Relation
|
131
|
-
Arel.sql(value.spawn.to_sql)
|
132
|
-
else
|
133
|
-
Arel.sql(value.respond_to?(:to_sql) ? value.to_sql : value.to_s)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def key_generator
|
138
|
-
A_TO_Z_KEYS.sample
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|