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.
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