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