active_record_extended 1.3.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -10
  3. data/lib/active_record_extended/active_record/relation_patch.rb +16 -1
  4. data/lib/active_record_extended/active_record.rb +2 -11
  5. data/lib/active_record_extended/arel/nodes.rb +24 -21
  6. data/lib/active_record_extended/arel/predications.rb +3 -2
  7. data/lib/active_record_extended/arel/sql_literal.rb +16 -0
  8. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +1 -1
  9. data/lib/active_record_extended/arel.rb +1 -0
  10. data/lib/active_record_extended/query_methods/any_of.rb +5 -4
  11. data/lib/active_record_extended/query_methods/either.rb +2 -1
  12. data/lib/active_record_extended/query_methods/inet.rb +6 -2
  13. data/lib/active_record_extended/query_methods/json.rb +14 -17
  14. data/lib/active_record_extended/query_methods/select.rb +13 -12
  15. data/lib/active_record_extended/query_methods/unionize.rb +13 -7
  16. data/lib/active_record_extended/query_methods/where_chain.rb +19 -8
  17. data/lib/active_record_extended/query_methods/window.rb +4 -3
  18. data/lib/active_record_extended/query_methods/with_cte.rb +104 -37
  19. data/lib/active_record_extended/utilities/order_by.rb +11 -30
  20. data/lib/active_record_extended/utilities/support.rb +9 -16
  21. data/lib/active_record_extended/version.rb +1 -1
  22. data/spec/query_methods/any_of_spec.rb +2 -2
  23. data/spec/query_methods/either_spec.rb +11 -0
  24. data/spec/query_methods/json_spec.rb +5 -5
  25. data/spec/query_methods/select_spec.rb +13 -13
  26. data/spec/query_methods/unionize_spec.rb +6 -6
  27. data/spec/query_methods/with_cte_spec.rb +14 -4
  28. data/spec/spec_helper.rb +1 -1
  29. data/spec/sql_inspections/any_of_sql_spec.rb +2 -2
  30. data/spec/sql_inspections/contains_sql_queries_spec.rb +8 -8
  31. data/spec/sql_inspections/either_sql_spec.rb +19 -3
  32. data/spec/sql_inspections/json_sql_spec.rb +0 -1
  33. data/spec/sql_inspections/unionize_sql_spec.rb +2 -2
  34. data/spec/sql_inspections/window_sql_spec.rb +12 -0
  35. data/spec/sql_inspections/with_cte_sql_spec.rb +30 -1
  36. data/spec/support/database_cleaner.rb +1 -1
  37. data/spec/support/models.rb +12 -0
  38. metadata +18 -20
  39. data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +0 -87
  40. data/lib/active_record_extended/patch/5_0/regex_match.rb +0 -10
@@ -74,7 +74,7 @@ RSpec.describe "Union SQL Queries" do
74
74
 
75
75
  it "should alias the union from clause to 'happy_users'" do
76
76
  expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+ happy_users$/)
77
- expect(described_method).to match_regex(/^SELECT happy_users\.id FROM.+happy_users$/)
77
+ expect(described_method).to match_regex(/^SELECT (happy_users\.id|"happy_users"\."id") FROM.+happy_users$/)
78
78
  end
79
79
  end
80
80
 
@@ -83,7 +83,7 @@ RSpec.describe "Union SQL Queries" do
83
83
 
84
84
  it "should retain the actual class calling table name as the union alias" do
85
85
  expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+ users$/)
86
- expect(described_method).to match_regex(/^SELECT \"users\"\.\"id\" FROM.+users$/)
86
+ expect(described_method).to match_regex(/^SELECT "users"\."id" FROM.+users$/)
87
87
  end
88
88
  end
89
89
  end
@@ -82,5 +82,17 @@ RSpec.describe "Active Record WINDOW Query inspection" do
82
82
  end
83
83
  end
84
84
  end
85
+
86
+ context "when not providing a partition by value" do
87
+ it "should construct a window function" do
88
+ query =
89
+ Tag
90
+ .define_window(:no_args).partition_by(order_by: { tag_number: :desc })
91
+ .select_window(:row_number, over: :no_args, as: :my_row)
92
+ .to_sql
93
+
94
+ expect(query).to eq("SELECT (ROW_NUMBER() OVER no_args) AS \"my_row\" FROM \"tags\" WINDOW no_args AS (ORDER BY tag_number DESC)")
95
+ end
96
+ end
85
97
  end
86
98
  end
@@ -21,6 +21,20 @@ RSpec.describe "Active Record WITH CTE tables" do
21
21
  expect(query).to match_regex(with_personal_query)
22
22
  end
23
23
 
24
+ it "will pipe Children CTE's into the Parent relation" do
25
+ personal_id_one_query = User.where(personal_id: 1)
26
+ personal_id_two_query = User.where(personal_id: 2)
27
+
28
+ sub_query = personal_id_two_query.with(personal_id_one: personal_id_one_query)
29
+ query = User.all.with(personal_id_two: sub_query)
30
+ expected_order = User.with(
31
+ personal_id_one: personal_id_one_query,
32
+ personal_id_two: personal_id_two_query
33
+ )
34
+
35
+ expect(query.to_sql).to eq(expected_order.to_sql)
36
+ end
37
+
24
38
  context "when multiple CTE's" do
25
39
  let(:chained_with) do
26
40
  User.with(personal_id_one: User.where(personal_id: 1))
@@ -36,6 +50,7 @@ RSpec.describe "Active Record WITH CTE tables" do
36
50
  .joins("JOIN personal_id_two ON personal_id_two.id = users.id")
37
51
  .to_sql
38
52
  end
53
+
39
54
  it "Should only contain a single WITH statement" do
40
55
  expect(with_arguments.scan(/WITH/).count).to eq(1)
41
56
  expect(with_arguments.scan(/AS/).count).to eq(2)
@@ -60,7 +75,21 @@ RSpec.describe "Active Record WITH CTE tables" do
60
75
  end
61
76
 
62
77
  it "generates an expression with recursive" do
63
- expect(with_recursive).to match_regex(with_recursive_personal_query)
78
+ query = User.with
79
+ .recursive(personal_id_one: User.where(personal_id: 1))
80
+ .joins("JOIN personal_id_one ON personal_id_one.id = users.id")
81
+ .to_sql
82
+
83
+ expect(query).to match_regex(with_recursive_personal_query)
84
+ end
85
+
86
+ it "will maintain the CTE table when merging" do
87
+ sub_query = User.with.recursive(personal_id_one: User.where(personal_id: 1))
88
+ query = User.merge(sub_query)
89
+ .joins("JOIN personal_id_one ON personal_id_one.id = users.id")
90
+ .to_sql
91
+
92
+ expect(query).to match_regex(with_recursive_personal_query)
64
93
  end
65
94
  end
66
95
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "database_cleaner"
4
4
 
5
- DatabaseCleaner.strategy = :truncation
5
+ DatabaseCleaner.strategy = :transaction
6
6
 
7
7
  RSpec.configure do |config|
8
8
  config.before do
@@ -5,6 +5,8 @@ class ApplicationRecord < ActiveRecord::Base
5
5
  end
6
6
 
7
7
  class User < ApplicationRecord
8
+ has_many :groups_users, class_name: "GroupsUser"
9
+ has_many :groups, through: :groups_users, dependent: :destroy
8
10
  has_many :hm_tags, class_name: "Tag"
9
11
  has_one :profile_l, class_name: "ProfileL"
10
12
  has_one :profile_r, class_name: "ProfileR"
@@ -66,3 +68,13 @@ class VersionControl < ApplicationRecord
66
68
  # t.jsonb :source, default: {}, null: false
67
69
  #
68
70
  end
71
+
72
+ class Group < ApplicationRecord
73
+ has_many :groups_users, class_name: "GroupsUser"
74
+ has_many :users, through: :groups_users, dependent: :destroy
75
+ end
76
+
77
+ class GroupsUser < ApplicationRecord
78
+ belongs_to :user
79
+ belongs_to :group
80
+ end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_extended
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - George Protacio-Karaszi
8
8
  - Dan McClain
9
9
  - Olivier El Mekki
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-10-02 00:00:00.000000000 Z
13
+ date: 2022-01-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -18,20 +18,20 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '5.0'
21
+ version: '5.1'
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '6.1'
24
+ version: 7.1.0
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
28
28
  requirements:
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: '5.0'
31
+ version: '5.1'
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '6.1'
34
+ version: 7.1.0
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: ar_outer_joins
37
37
  requirement: !ruby/object:Gem::Requirement
@@ -52,14 +52,14 @@ dependencies:
52
52
  requirements:
53
53
  - - "<"
54
54
  - !ruby/object:Gem::Version
55
- version: '2.0'
55
+ version: '3.0'
56
56
  type: :runtime
57
57
  prerelease: false
58
58
  version_requirements: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - "<"
61
61
  - !ruby/object:Gem::Version
62
- version: '2.0'
62
+ version: '3.0'
63
63
  - !ruby/object:Gem::Dependency
64
64
  name: bundler
65
65
  requirement: !ruby/object:Gem::Requirement
@@ -69,7 +69,7 @@ dependencies:
69
69
  version: '1.16'
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
- version: '2.1'
72
+ version: '3.0'
73
73
  type: :development
74
74
  prerelease: false
75
75
  version_requirements: !ruby/object:Gem::Requirement
@@ -79,7 +79,7 @@ dependencies:
79
79
  version: '1.16'
80
80
  - - "<"
81
81
  - !ruby/object:Gem::Version
82
- version: '2.1'
82
+ version: '3.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: database_cleaner
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -98,14 +98,14 @@ dependencies:
98
98
  name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
103
  version: '10.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '10.0'
111
111
  - !ruby/object:Gem::Dependency
@@ -153,9 +153,8 @@ files:
153
153
  - lib/active_record_extended/arel/aggregate_function_name.rb
154
154
  - lib/active_record_extended/arel/nodes.rb
155
155
  - lib/active_record_extended/arel/predications.rb
156
+ - lib/active_record_extended/arel/sql_literal.rb
156
157
  - lib/active_record_extended/arel/visitors/postgresql_decorator.rb
157
- - lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb
158
- - lib/active_record_extended/patch/5_0/regex_match.rb
159
158
  - lib/active_record_extended/patch/5_1/where_clause.rb
160
159
  - lib/active_record_extended/patch/5_2/where_clause.rb
161
160
  - lib/active_record_extended/predicate_builder/array_handler_decorator.rb
@@ -199,7 +198,7 @@ homepage: https://github.com/georgekaraszi/ActiveRecordExtended
199
198
  licenses:
200
199
  - MIT
201
200
  metadata: {}
202
- post_install_message:
201
+ post_install_message:
203
202
  rdoc_options: []
204
203
  require_paths:
205
204
  - lib
@@ -207,16 +206,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
207
206
  requirements:
208
207
  - - ">="
209
208
  - !ruby/object:Gem::Version
210
- version: '0'
209
+ version: '2.4'
211
210
  required_rubygems_version: !ruby/object:Gem::Requirement
212
211
  requirements:
213
212
  - - ">="
214
213
  - !ruby/object:Gem::Version
215
214
  version: '0'
216
215
  requirements: []
217
- rubyforge_project:
218
- rubygems_version: 2.7.7
219
- signing_key:
216
+ rubygems_version: 3.1.4
217
+ signing_key:
220
218
  specification_version: 4
221
219
  summary: Adds extended functionality to Activerecord Postgres implementation
222
220
  test_files:
@@ -1,87 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Stripped from Rails 5.1.x
4
- # This patch is used so that when querying with hash elements that do not belong to an association,
5
- # but instead a data attribute. It returns the corrected attribute binds back to the query builder.
6
- #
7
- # Without joins
8
- # Before:
9
- # User.where.contains(data: { nickname: "george" })
10
- # #=> "SELECT \"people\".* FROM \"people\" WHERE (\"data\".\"nickname\" @> 'george')"
11
- #
12
- # After:
13
- # User.where.contains(data: { nickname: "george" })
14
- # #=> "SELECT \"people\".* FROM \"people\" WHERE (\"people\".\"data\" @> '\"nickname\"=>\"george\"')"
15
- #
16
- # With Joins
17
- # Before:
18
- # Tag.joins(:user).where.contains(people: { data: { nickname: "george" } })
19
- # #=> NoMethodError: undefined method `type' for nil:NilClass
20
- #
21
- # After:
22
- # Tag.joins(:user).where.contains(people: { data: { nickname: "george" } })
23
- # #=> "SELECT \"tags\".* FROM \"tags\" INNER JOIN \"people\" ON \"people\".\"id\" = \"tags\".\"person_id\"
24
- # WHERE (\"people\".\"data\" @> '\"nickname\"=>\"george\"')"
25
- #
26
- module ActiveRecord
27
- class TableMetadata
28
- def has_column?(column_name) # rubocop:disable Naming/PredicateName
29
- klass&.columns_hash&.key?(column_name.to_s)
30
- end
31
- end
32
-
33
- class PredicateBuilder
34
- def create_binds_for_hash(attributes) # rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize
35
- result = attributes.dup
36
- binds = []
37
-
38
- attributes.each do |column_name, value| # rubocop:disable Metrics/BlockLength
39
- if value.is_a?(Hash) && !table.has_column?(column_name)
40
- attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
41
- result[column_name] = attrs
42
- binds += bvs
43
- next
44
- elsif value.is_a?(Relation)
45
- binds += value.bound_attributes
46
- elsif value.is_a?(Range) && !table.type(column_name).respond_to?(:subtype)
47
- first = value.begin
48
- last = value.end
49
- unless first.respond_to?(:infinite?) && first.infinite?
50
- binds << build_bind_param(column_name, first)
51
- first = Arel::Nodes::BindParam.new
52
- end
53
- unless last.respond_to?(:infinite?) && last.infinite?
54
- binds << build_bind_param(column_name, last)
55
- last = Arel::Nodes::BindParam.new
56
- end
57
-
58
- result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?)
59
- elsif can_be_bound?(column_name, value)
60
- result[column_name] = Arel::Nodes::BindParam.new
61
- binds << build_bind_param(column_name, value)
62
- end
63
-
64
- # Find the foreign key when using queries such as:
65
- # Post.where(author: author)
66
- #
67
- # For polymorphic relationships, find the foreign key and type:
68
- # PriceEstimate.where(estimate_of: treasure)
69
- if table.associated_with?(column_name)
70
- result[column_name] = AssociationQueryHandler.value_for(table, column_name, value)
71
- end
72
- end
73
-
74
- [result, binds]
75
- end
76
-
77
- def can_be_bound?(column_name, value)
78
- return if table.associated_with?(column_name)
79
- case value
80
- when Array, Range
81
- table.type(column_name).respond_to?(:subtype)
82
- else
83
- !value.nil? && handler_for(value).is_a?(BasicObjectHandler)
84
- end
85
- end
86
- end
87
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Regexp
4
- # Stripped from ActiveSupport v5.1
5
- unless //.respond_to?(:match?)
6
- def match?(string, pos = 0)
7
- !(!match(string, pos))
8
- end
9
- end
10
- end