active_record_extended 1.4.0 → 2.1.1

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.
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 +5 -2
  12. data/lib/active_record_extended/query_methods/inet.rb +6 -2
  13. data/lib/active_record_extended/query_methods/json.rb +13 -16
  14. data/lib/active_record_extended/query_methods/select.rb +11 -10
  15. data/lib/active_record_extended/query_methods/unionize.rb +12 -6
  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 +9 -28
  20. data/lib/active_record_extended/utilities/support.rb +8 -15
  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
@@ -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.4.0
4
+ version: 2.1.1
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-11-06 00:00:00.000000000 Z
13
+ date: 2022-02-14 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.2.15
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