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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e7c1bd1aaee6cd16497cd2f64bed3f06907a31ac48059da909dc33c0f4caa26
4
- data.tar.gz: 5d1e21f8547272af210b11c7590e7094e69eeaad6714a50f76566b04eef3003e
3
+ metadata.gz: d3332acf1ce7aa530fcbaddc871d5bdfc4560f71794fc4efbdd33653c6ea1eb5
4
+ data.tar.gz: 73df494665607c73c5ddb2999aa0e021e166fc436a1cd75784cd1ffa30e1fe9e
5
5
  SHA512:
6
- metadata.gz: 320aa807eded4abeb5f519b7e58b66a4cdaba77f62eaa0549bffeddc000bf6426726733337dd3dfc18e1c651594c7f2fb4f09c32f4d9bbe640a71abe16ce5fda
7
- data.tar.gz: 3e8ffdc9e76ea8eb5ec47dba4d8d795bbcda5f780879213fc385074dea024ab01851b4b552631cb1011b2b83a949416348efbd749c442255e76b00e4ebcd9f38
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
- Dir["#{File.dirname(__FILE__)}/query_methods/**/*.rb"].each { |f| require File.expand_path(f) }
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?
@@ -1,4 +1,3 @@
1
-
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require "active_record_extended/arel/nodes"
@@ -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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordExtended
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "spec_helper"
4
+
3
5
  RSpec.describe "Active Record Any / None of Methods" do
4
6
  let!(:one) { Person.create!(personal_id: 1) }
5
7
  let!(:two) { Person.create!(personal_id: 2) }
@@ -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
@@ -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.6.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-07-25 00:00:00.000000000 Z
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