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.
- checksums.yaml +4 -4
- data/README.md +12 -18
- data/lib/active_record_extended.rb +2 -1
- data/lib/active_record_extended/active_record.rb +2 -0
- data/lib/active_record_extended/active_record/relation_patch.rb +1 -1
- data/lib/active_record_extended/arel.rb +1 -0
- data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
- data/lib/active_record_extended/arel/nodes.rb +31 -41
- data/lib/active_record_extended/arel/predications.rb +4 -1
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +40 -1
- data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +4 -4
- data/lib/active_record_extended/patch/5_0/regex_match.rb +10 -0
- data/lib/active_record_extended/query_methods/any_of.rb +5 -4
- data/lib/active_record_extended/query_methods/inet.rb +1 -1
- data/lib/active_record_extended/query_methods/json.rb +152 -43
- data/lib/active_record_extended/query_methods/select.rb +117 -0
- data/lib/active_record_extended/query_methods/unionize.rb +4 -39
- data/lib/active_record_extended/query_methods/with_cte.rb +3 -3
- data/lib/active_record_extended/utilities/order_by.rb +96 -0
- data/lib/active_record_extended/utilities/support.rb +175 -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 +40 -25
- data/spec/query_methods/select_spec.rb +115 -0
- data/spec/query_methods/unionize_spec.rb +54 -54
- data/spec/query_methods/with_cte_spec.rb +12 -12
- data/spec/sql_inspections/any_of_sql_spec.rb +11 -11
- 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 +38 -8
- data/spec/sql_inspections/unionize_sql_spec.rb +27 -27
- data/spec/sql_inspections/with_cte_sql_spec.rb +22 -22
- data/spec/support/models.rb +18 -4
- metadata +11 -3
- 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.+
|
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
24
|
context "when multiple CTE's" do
|
25
25
|
let(:chained_with) do
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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.+
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
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,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 :
|
37
|
+
belongs_to :user
|
24
38
|
# attributes: tag_number
|
25
39
|
end
|
26
40
|
|
27
41
|
class ProfileL < ApplicationRecord
|
28
|
-
belongs_to :
|
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 :
|
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.
|
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-
|
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
|