active_record_extended 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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