active_record_extended 1.1.0 → 1.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -18
  3. data/lib/active_record_extended.rb +2 -1
  4. data/lib/active_record_extended/active_record.rb +2 -0
  5. data/lib/active_record_extended/active_record/relation_patch.rb +1 -1
  6. data/lib/active_record_extended/arel.rb +1 -0
  7. data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
  8. data/lib/active_record_extended/arel/nodes.rb +31 -41
  9. data/lib/active_record_extended/arel/predications.rb +4 -1
  10. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +40 -1
  11. data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +4 -4
  12. data/lib/active_record_extended/patch/5_0/regex_match.rb +10 -0
  13. data/lib/active_record_extended/query_methods/any_of.rb +5 -4
  14. data/lib/active_record_extended/query_methods/inet.rb +1 -1
  15. data/lib/active_record_extended/query_methods/json.rb +152 -43
  16. data/lib/active_record_extended/query_methods/select.rb +117 -0
  17. data/lib/active_record_extended/query_methods/unionize.rb +4 -39
  18. data/lib/active_record_extended/query_methods/with_cte.rb +3 -3
  19. data/lib/active_record_extended/utilities/order_by.rb +96 -0
  20. data/lib/active_record_extended/utilities/support.rb +175 -0
  21. data/lib/active_record_extended/version.rb +1 -1
  22. data/spec/query_methods/any_of_spec.rb +40 -40
  23. data/spec/query_methods/array_query_spec.rb +14 -14
  24. data/spec/query_methods/either_spec.rb +14 -14
  25. data/spec/query_methods/hash_query_spec.rb +11 -11
  26. data/spec/query_methods/inet_query_spec.rb +33 -31
  27. data/spec/query_methods/json_spec.rb +40 -25
  28. data/spec/query_methods/select_spec.rb +115 -0
  29. data/spec/query_methods/unionize_spec.rb +54 -54
  30. data/spec/query_methods/with_cte_spec.rb +12 -12
  31. data/spec/sql_inspections/any_of_sql_spec.rb +11 -11
  32. data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
  33. data/spec/sql_inspections/arel/array_spec.rb +7 -7
  34. data/spec/sql_inspections/arel/inet_spec.rb +7 -7
  35. data/spec/sql_inspections/contains_sql_queries_spec.rb +14 -14
  36. data/spec/sql_inspections/either_sql_spec.rb +11 -11
  37. data/spec/sql_inspections/json_sql_spec.rb +38 -8
  38. data/spec/sql_inspections/unionize_sql_spec.rb +27 -27
  39. data/spec/sql_inspections/with_cte_sql_spec.rb +22 -22
  40. data/spec/support/models.rb +18 -4
  41. metadata +11 -3
  42. data/lib/active_record_extended/utilities.rb +0 -141
@@ -3,38 +3,38 @@
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
24
  context "when multiple CTE's" do
25
25
  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
26
+ User.with(personal_id_one: User.where(personal_id: 1))
27
+ .with(personal_id_two: User.where(personal_id: 2))
28
+ .joins("JOIN personal_id_one ON personal_id_one.id = users.id")
29
+ .joins("JOIN personal_id_two ON personal_id_two.id = users.id")
30
+ .to_sql
31
31
  end
32
32
 
33
33
  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
34
+ User.with(personal_id_one: User.where(personal_id: 1), personal_id_two: User.where(personal_id: 2))
35
+ .joins("JOIN personal_id_one ON personal_id_one.id = users.id")
36
+ .joins("JOIN personal_id_two ON personal_id_two.id = users.id")
37
+ .to_sql
38
38
  end
39
39
  it "Should only contain a single WITH statement" do
40
40
  expect(with_arguments.scan(/WITH/).count).to eq(1)
@@ -49,14 +49,14 @@ RSpec.describe "Active Record WITH CTE tables" do
49
49
 
50
50
  context "when chaining the recursive method" do
51
51
  let(:with_recursive_personal_query) do
52
- /WITH.+RECURSIVE.+personal_id_one.+AS \(SELECT.+people.+FROM.+WHERE.+people.+personal_id.+ = 1\)/
52
+ /WITH.+RECURSIVE.+personal_id_one.+AS \(SELECT.+users.+FROM.+WHERE.+users.+personal_id.+ = 1\)/
53
53
  end
54
54
 
55
55
  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
56
+ User.with
57
+ .recursive(personal_id_one: User.where(personal_id: 1))
58
+ .joins("JOIN personal_id_one ON personal_id_one.id = users.id")
59
+ .to_sql
60
60
  end
61
61
 
62
62
  it "generates an expression with recursive" do
@@ -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,26 @@ class Person < ApplicationRecord
19
20
  #
20
21
  end
21
22
 
23
+ module Namespaced
24
+ def self.table_name_prefix
25
+ "namespaced_"
26
+ end
27
+
28
+ class Record < ApplicationRecord
29
+ # attributes
30
+ # t.inet :ip
31
+ # t.cidr :subnet
32
+ #
33
+ end
34
+ end
35
+
22
36
  class Tag < ApplicationRecord
23
- belongs_to :person
37
+ belongs_to :user
24
38
  # attributes: tag_number
25
39
  end
26
40
 
27
41
  class ProfileL < ApplicationRecord
28
- belongs_to :person
42
+ belongs_to :user
29
43
  has_one :version, as: :versionable, class_name: "VersionControl"
30
44
  # attributes
31
45
  # t.integer :likes
@@ -33,7 +47,7 @@ class ProfileL < ApplicationRecord
33
47
  end
34
48
 
35
49
  class ProfileR < ApplicationRecord
36
- belongs_to :person
50
+ belongs_to :user
37
51
  has_one :version, as: :versionable, class_name: "VersionControl"
38
52
  # attributes
39
53
  # t.integer :dislikes
metadata CHANGED
@@ -1,7 +1,7 @@
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: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - George Protacio-Karaszi
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-05-04 00:00:00.000000000 Z
13
+ date: 2019-08-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -150,10 +150,12 @@ 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
155
156
  - lib/active_record_extended/arel/visitors/postgresql_decorator.rb
156
157
  - lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb
158
+ - lib/active_record_extended/patch/5_0/regex_match.rb
157
159
  - lib/active_record_extended/patch/5_1/where_clause.rb
158
160
  - lib/active_record_extended/patch/5_2/where_clause.rb
159
161
  - lib/active_record_extended/predicate_builder/array_handler_decorator.rb
@@ -161,10 +163,12 @@ files:
161
163
  - lib/active_record_extended/query_methods/either.rb
162
164
  - lib/active_record_extended/query_methods/inet.rb
163
165
  - lib/active_record_extended/query_methods/json.rb
166
+ - lib/active_record_extended/query_methods/select.rb
164
167
  - lib/active_record_extended/query_methods/unionize.rb
165
168
  - lib/active_record_extended/query_methods/where_chain.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,10 +177,12 @@ 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
177
182
  - spec/query_methods/with_cte_spec.rb
178
183
  - spec/spec_helper.rb
179
184
  - spec/sql_inspections/any_of_sql_spec.rb
185
+ - spec/sql_inspections/arel/aggregate_function_name_spec.rb
180
186
  - spec/sql_inspections/arel/array_spec.rb
181
187
  - spec/sql_inspections/arel/inet_spec.rb
182
188
  - spec/sql_inspections/contains_sql_queries_spec.rb
@@ -218,10 +224,12 @@ test_files:
218
224
  - spec/query_methods/hash_query_spec.rb
219
225
  - spec/query_methods/inet_query_spec.rb
220
226
  - spec/query_methods/json_spec.rb
227
+ - spec/query_methods/select_spec.rb
221
228
  - spec/query_methods/unionize_spec.rb
222
229
  - spec/query_methods/with_cte_spec.rb
223
230
  - spec/spec_helper.rb
224
231
  - spec/sql_inspections/any_of_sql_spec.rb
232
+ - spec/sql_inspections/arel/aggregate_function_name_spec.rb
225
233
  - spec/sql_inspections/arel/array_spec.rb
226
234
  - spec/sql_inspections/arel/inet_spec.rb
227
235
  - spec/sql_inspections/contains_sql_queries_spec.rb
@@ -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