active_record_extended 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +87 -15
  3. data/lib/active_record_extended.rb +2 -1
  4. data/lib/active_record_extended/active_record.rb +2 -9
  5. data/lib/active_record_extended/active_record/relation_patch.rb +21 -4
  6. data/lib/active_record_extended/arel.rb +2 -0
  7. data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
  8. data/lib/active_record_extended/arel/nodes.rb +32 -41
  9. data/lib/active_record_extended/arel/predications.rb +4 -1
  10. data/lib/active_record_extended/arel/sql_literal.rb +16 -0
  11. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +40 -1
  12. data/lib/active_record_extended/query_methods/any_of.rb +10 -8
  13. data/lib/active_record_extended/query_methods/either.rb +1 -1
  14. data/lib/active_record_extended/query_methods/inet.rb +7 -3
  15. data/lib/active_record_extended/query_methods/json.rb +156 -50
  16. data/lib/active_record_extended/query_methods/select.rb +118 -0
  17. data/lib/active_record_extended/query_methods/unionize.rb +14 -43
  18. data/lib/active_record_extended/query_methods/where_chain.rb +14 -6
  19. data/lib/active_record_extended/query_methods/window.rb +93 -0
  20. data/lib/active_record_extended/query_methods/with_cte.rb +102 -35
  21. data/lib/active_record_extended/utilities/order_by.rb +77 -0
  22. data/lib/active_record_extended/utilities/support.rb +178 -0
  23. data/lib/active_record_extended/version.rb +1 -1
  24. data/spec/query_methods/any_of_spec.rb +40 -40
  25. data/spec/query_methods/array_query_spec.rb +14 -14
  26. data/spec/query_methods/either_spec.rb +14 -14
  27. data/spec/query_methods/hash_query_spec.rb +11 -11
  28. data/spec/query_methods/inet_query_spec.rb +33 -31
  29. data/spec/query_methods/json_spec.rb +42 -27
  30. data/spec/query_methods/select_spec.rb +115 -0
  31. data/spec/query_methods/unionize_spec.rb +56 -56
  32. data/spec/query_methods/window_spec.rb +51 -0
  33. data/spec/query_methods/with_cte_spec.rb +22 -12
  34. data/spec/spec_helper.rb +1 -1
  35. data/spec/sql_inspections/any_of_sql_spec.rb +12 -12
  36. data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
  37. data/spec/sql_inspections/arel/array_spec.rb +7 -7
  38. data/spec/sql_inspections/arel/inet_spec.rb +7 -7
  39. data/spec/sql_inspections/contains_sql_queries_spec.rb +14 -14
  40. data/spec/sql_inspections/either_sql_spec.rb +11 -11
  41. data/spec/sql_inspections/json_sql_spec.rb +44 -8
  42. data/spec/sql_inspections/unionize_sql_spec.rb +27 -27
  43. data/spec/sql_inspections/window_sql_spec.rb +98 -0
  44. data/spec/sql_inspections/with_cte_sql_spec.rb +52 -23
  45. data/spec/support/models.rb +24 -4
  46. metadata +31 -20
  47. data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +0 -87
  48. 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.+people.+FROM.+WHERE.+people.+personal_id.+ = 1\)/ }
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 = Person.with(personal_id_one: Person.where(personal_id: 1))
10
- .joins("JOIN personal_id_one ON personal_id_one.id = people.id")
11
- .to_sql
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 = Person.all
17
- .merge(Person.with(personal_id_one: Person.where(personal_id: 1)))
18
- .joins("JOIN personal_id_one ON personal_id_one.id = people.id")
19
- .to_sql
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
- Person.with(personal_id_one: Person.where(personal_id: 1))
27
- .with(personal_id_two: Person.where(personal_id: 2))
28
- .joins("JOIN personal_id_one ON personal_id_one.id = people.id")
29
- .joins("JOIN personal_id_two ON personal_id_two.id = people.id")
30
- .to_sql
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
- Person.with(personal_id_one: Person.where(personal_id: 1), personal_id_two: Person.where(personal_id: 2))
35
- .joins("JOIN personal_id_one ON personal_id_one.id = people.id")
36
- .joins("JOIN personal_id_two ON personal_id_two.id = people.id")
37
- .to_sql
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.+people.+FROM.+WHERE.+people.+personal_id.+ = 1\)/
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
- Person.with
57
- .recursive(personal_id_one: Person.where(personal_id: 1))
58
- .joins("JOIN personal_id_one ON personal_id_one.id = people.id")
59
- .to_sql
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
- expect(with_recursive).to match_regex(with_recursive_personal_query)
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
@@ -4,13 +4,14 @@ class ApplicationRecord < ActiveRecord::Base
4
4
  self.abstract_class = true
5
5
  end
6
6
 
7
- class Person < ApplicationRecord
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 :person
43
+ belongs_to :user
24
44
  # attributes: tag_number
25
45
  end
26
46
 
27
47
  class ProfileL < ApplicationRecord
28
- belongs_to :person
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 :person
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: 1.1.0
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: 2019-05-04 00:00:00.000000000 Z
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.0'
21
+ version: '5.1'
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '6.1'
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.0'
31
+ version: '5.1'
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '6.1'
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: '2.0'
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: '2.0'
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: '2.1'
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: '2.1'
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: '0'
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
- rubyforge_project:
209
- rubygems_version: 2.7.7
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