active_record_extended 0.6.0 → 0.7.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/lib/active_record_extended/active_record.rb +7 -1
- data/lib/active_record_extended/arel.rb +0 -1
- data/lib/active_record_extended/query_methods/with_cte.rb +101 -0
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/any_of_spec.rb +2 -0
- data/spec/query_methods/with_cte_spec.rb +40 -0
- data/spec/sql_inspections/with_cte_sql_spec.rb +66 -0
- data/spec/support/models.rb +6 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3332acf1ce7aa530fcbaddc871d5bdfc4560f71794fc4efbdd33653c6ea1eb5
|
4
|
+
data.tar.gz: 73df494665607c73c5ddb2999aa0e021e166fc436a1cd75784cd1ffa30e1fe9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94c04d0fb830fd9e4b7f0b21bea2fb7a3b753903f4fb41ffb71fc72ed147ac3247507438411a37bf74235aaea943c2f98b5b5141467cf09b663dd9930269d2f0
|
7
|
+
data.tar.gz: 3c5915ec558d623704dc4b824f052639dc25df2a0c0385f143eb597f4984f4beab1ef1d4083c7fbbf696ac0314b3ddd860640f8ecdc601681a4b042fe0224256
|
@@ -2,10 +2,16 @@
|
|
2
2
|
|
3
3
|
require "active_record"
|
4
4
|
require "active_record/relation"
|
5
|
+
require "active_record/relation/merger"
|
5
6
|
require "active_record/relation/query_methods"
|
6
7
|
|
7
8
|
require "active_record_extended/predicate_builder/array_handler_decorator"
|
8
|
-
|
9
|
+
|
10
|
+
require "active_record_extended/query_methods/where_chain"
|
11
|
+
require "active_record_extended/query_methods/with_cte"
|
12
|
+
require "active_record_extended/query_methods/any_of"
|
13
|
+
require "active_record_extended/query_methods/either"
|
14
|
+
require "active_record_extended/query_methods/inet"
|
9
15
|
|
10
16
|
if ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR <= 1
|
11
17
|
if ActiveRecord::VERSION::MINOR.zero?
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordExtended
|
4
|
+
module QueryMethods
|
5
|
+
module MergerCTE
|
6
|
+
def normal_values
|
7
|
+
super + [:with]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module QueryDelegationCTE
|
12
|
+
delegate :with, to: :all
|
13
|
+
end
|
14
|
+
|
15
|
+
module WithCTE
|
16
|
+
class WithChain
|
17
|
+
def initialize(scope)
|
18
|
+
@scope = scope
|
19
|
+
end
|
20
|
+
|
21
|
+
def recursive(*args)
|
22
|
+
@scope.tap do |scope|
|
23
|
+
scope.with_values += args
|
24
|
+
scope.recursive_value = true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_values
|
30
|
+
@values[:with] || []
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_values?
|
34
|
+
!(@values[:with].nil? || @values[:with].empty?)
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_values=(values)
|
38
|
+
@values[:with] = values
|
39
|
+
end
|
40
|
+
|
41
|
+
def recursive_value=(value)
|
42
|
+
raise ImmutableRelation if @loaded
|
43
|
+
@values[:recursive] = value
|
44
|
+
end
|
45
|
+
|
46
|
+
def recursive_value
|
47
|
+
@values[:recursive]
|
48
|
+
end
|
49
|
+
alias recursive_value? recursive_value
|
50
|
+
|
51
|
+
def with(opts = :chain, *rest)
|
52
|
+
return WithChain.new(spawn) if opts == :chain
|
53
|
+
opts.blank? ? self : spawn.with!(opts, *rest)
|
54
|
+
end
|
55
|
+
|
56
|
+
def with!(opts = :chain, *rest)
|
57
|
+
return WithChain.new(self) if opts == :chain
|
58
|
+
self.with_values += [opts] + rest
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_arel(*aliases)
|
63
|
+
super.tap do |arel|
|
64
|
+
build_with(arel) if with_values?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_with_hashed_value(with_value)
|
69
|
+
with_value.map do |name, expression|
|
70
|
+
select =
|
71
|
+
case expression
|
72
|
+
when String
|
73
|
+
Arel::Nodes::SqlLiteral.new("(#{expression})")
|
74
|
+
when ActiveRecord::Relation, Arel::SelectManager
|
75
|
+
Arel::Nodes::SqlLiteral.new("(#{expression.to_sql})")
|
76
|
+
end
|
77
|
+
next if select.nil?
|
78
|
+
Arel::Nodes::As.new(Arel::Nodes::SqlLiteral.new(PG::Connection.quote_ident(name.to_s)), select)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_with(arel)
|
83
|
+
with_statements = with_values.flat_map do |with_value|
|
84
|
+
case with_value
|
85
|
+
when String, Arel::Nodes::As
|
86
|
+
with_value
|
87
|
+
when Hash
|
88
|
+
build_with_hashed_value(with_value)
|
89
|
+
end
|
90
|
+
end.compact
|
91
|
+
|
92
|
+
return if with_statements.empty?
|
93
|
+
recursive_value? ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::WithCTE)
|
100
|
+
ActiveRecord::Relation::Merger.prepend(ActiveRecordExtended::QueryMethods::MergerCTE)
|
101
|
+
ActiveRecord::Querying.prepend(ActiveRecordExtended::QueryMethods::QueryDelegationCTE)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe "Active Record With CTE Query Methods" do
|
6
|
+
let!(:person_one) { Person.create! }
|
7
|
+
let!(:person_two) { Person.create! }
|
8
|
+
let!(:profile_one) { ProfileL.create!(person_id: person_one.id, likes: 200) }
|
9
|
+
let!(:profile_two) { ProfileL.create!(person_id: person_two.id, likes: 500) }
|
10
|
+
|
11
|
+
describe ".with/1" do
|
12
|
+
context "when using as a standalone query" do
|
13
|
+
it "should only return a person with less than 300 likes" do
|
14
|
+
query = Person.with(profile: ProfileL.where("likes < 300"))
|
15
|
+
.joins("JOIN profile ON profile.id = people.id")
|
16
|
+
|
17
|
+
expect(query).to match_array([person_one])
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return anyone with likes greater than or equal to 200" do
|
21
|
+
query = Person.with(profile: ProfileL.where("likes >= 200"))
|
22
|
+
.joins("JOIN profile ON profile.id = people.id")
|
23
|
+
|
24
|
+
expect(query).to match_array([person_one, person_two])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when merging in query" do
|
29
|
+
let!(:version_one) { VersionControl.create!(versionable: profile_one, source: { help: "me" }) }
|
30
|
+
let!(:version_two) { VersionControl.create!(versionable: profile_two, source: { help: "no one" }) }
|
31
|
+
|
32
|
+
it "will maintain the CTE table when merging into existing AR queries" do
|
33
|
+
sub_query = ProfileL.with(version_controls: VersionControl.where.contains(source: { help: "me" }))
|
34
|
+
query = Person.joins(profile_l: :version).merge(sub_query)
|
35
|
+
|
36
|
+
expect(query).to match_array([person_one])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
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\)/ }
|
7
|
+
|
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
|
12
|
+
expect(query).to match_regex(with_personal_query)
|
13
|
+
end
|
14
|
+
|
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
|
20
|
+
|
21
|
+
expect(query).to match_regex(with_personal_query)
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when multiple CTE's" do
|
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
|
31
|
+
end
|
32
|
+
|
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
|
38
|
+
end
|
39
|
+
it "Should only contain a single WITH statement" do
|
40
|
+
expect(with_arguments.scan(/WITH/).count).to eq(1)
|
41
|
+
expect(with_arguments.scan(/AS/).count).to eq(2)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "Should only contain a single WITH statement when chaining" do
|
45
|
+
expect(chained_with.scan(/WITH/).count).to eq(1)
|
46
|
+
expect(chained_with.scan(/AS/).count).to eq(2)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when chaining the recursive method" do
|
51
|
+
let(:with_recursive_personal_query) do
|
52
|
+
/WITH.+RECURSIVE.+personal_id_one.+AS \(SELECT.+people.+FROM.+WHERE.+people.+personal_id.+ = 1\)/
|
53
|
+
end
|
54
|
+
|
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
|
60
|
+
end
|
61
|
+
|
62
|
+
it "generates an expression with recursive" do
|
63
|
+
expect(with_recursive).to match_regex(with_recursive_personal_query)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/spec/support/models.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
class Person < ActiveRecord::Base
|
@@ -13,8 +12,14 @@ end
|
|
13
12
|
|
14
13
|
class ProfileL < ActiveRecord::Base
|
15
14
|
belongs_to :person
|
15
|
+
has_one :version, as: :versionable, class_name: "VersionControl"
|
16
16
|
end
|
17
17
|
|
18
18
|
class ProfileR < ActiveRecord::Base
|
19
19
|
belongs_to :person
|
20
|
+
has_one :version, as: :versionable, class_name: "VersionControl"
|
21
|
+
end
|
22
|
+
|
23
|
+
class VersionControl < ActiveRecord::Base
|
24
|
+
belongs_to :versionable, polymorphic: true, optional: false
|
20
25
|
end
|
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: 0.
|
4
|
+
version: 0.7.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: 2018-
|
13
|
+
date: 2018-09-22 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -160,6 +160,7 @@ files:
|
|
160
160
|
- lib/active_record_extended/query_methods/either.rb
|
161
161
|
- lib/active_record_extended/query_methods/inet.rb
|
162
162
|
- lib/active_record_extended/query_methods/where_chain.rb
|
163
|
+
- lib/active_record_extended/query_methods/with_cte.rb
|
163
164
|
- lib/active_record_extended/version.rb
|
164
165
|
- spec/active_record_extended_spec.rb
|
165
166
|
- spec/query_methods/any_of_spec.rb
|
@@ -167,12 +168,14 @@ files:
|
|
167
168
|
- spec/query_methods/either_spec.rb
|
168
169
|
- spec/query_methods/hash_query_spec.rb
|
169
170
|
- spec/query_methods/inet_query_spec.rb
|
171
|
+
- spec/query_methods/with_cte_spec.rb
|
170
172
|
- spec/spec_helper.rb
|
171
173
|
- spec/sql_inspections/any_of_sql_spec.rb
|
172
174
|
- spec/sql_inspections/arel/array_spec.rb
|
173
175
|
- spec/sql_inspections/arel/inet_spec.rb
|
174
176
|
- spec/sql_inspections/contains_sql_queries_spec.rb
|
175
177
|
- spec/sql_inspections/either_sql_spec.rb
|
178
|
+
- spec/sql_inspections/with_cte_sql_spec.rb
|
176
179
|
- spec/support/database_cleaner.rb
|
177
180
|
- spec/support/models.rb
|
178
181
|
homepage: https://github.com/georgekaraszi/ActiveRecordExtended
|
@@ -206,11 +209,13 @@ test_files:
|
|
206
209
|
- spec/query_methods/either_spec.rb
|
207
210
|
- spec/query_methods/hash_query_spec.rb
|
208
211
|
- spec/query_methods/inet_query_spec.rb
|
212
|
+
- spec/query_methods/with_cte_spec.rb
|
209
213
|
- spec/spec_helper.rb
|
210
214
|
- spec/sql_inspections/any_of_sql_spec.rb
|
211
215
|
- spec/sql_inspections/arel/array_spec.rb
|
212
216
|
- spec/sql_inspections/arel/inet_spec.rb
|
213
217
|
- spec/sql_inspections/contains_sql_queries_spec.rb
|
214
218
|
- spec/sql_inspections/either_sql_spec.rb
|
219
|
+
- spec/sql_inspections/with_cte_sql_spec.rb
|
215
220
|
- spec/support/database_cleaner.rb
|
216
221
|
- spec/support/models.rb
|