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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c136dceff4eb13a4c368aef03c70ae74bf3c856d3dadaa38e468e3f3369455d
4
- data.tar.gz: 1d7f194a4760a812e2a6aa22f20e1a62914e68d1fcb52cb223d724104597cc31
3
+ metadata.gz: 7f3f7041cc4869183bc20b38f8387bd8f3f216225990a62ec0c4e0fba5f847dd
4
+ data.tar.gz: c61e5ad1c2fae32a5dbfe8660bb888d85f255f285a2df8b1f02206f51cbd71b7
5
5
  SHA512:
6
- metadata.gz: a0d6e454d6c2dc91d28472cc1911043bc62a94e554d85bc77b0cdbbdedcc4158f7f6f43164a7cd5b27d8609ececf4b532f9a4ebe26c725267c77de4b1c201d38
7
- data.tar.gz: 28980520ea5779c79a9fd131157a5b2cdecba1d1fa8464dbba34ef299501094e97db64db303c5d0ad51cf3cb2a52b130562a71d64f866e67f6446ce79082de74
6
+ metadata.gz: c27cf6541f3e11a3beb00263be7c1478e8f4ddb64b0121ed141347ca995252453864bd3cf0992038b7ea2aa1c409f50792800fdf909d8e544edd470af35231fa
7
+ data.tar.gz: d0426b00cca79e3d0d145fb87fdd05c25611ff5bfde95c8c1845a281786bd4c7e6bfbd866df46cb8ce1ccd45ab5b4bd91aa83e6c21db879721436574f90087cf
data/README.md CHANGED
@@ -52,11 +52,12 @@ Active Record Extended is essentially providing users with the other half of Pos
52
52
  ## Compatibility
53
53
 
54
54
  This package is designed align and work with any officially supported Ruby and Rails versions.
55
- - Minimum Ruby Version: 2.3.x **(EOL warning!)**
56
- - Minimum Rails Version: 5.0.x **(EOL warning!)**
57
- - Latest Ruby supported: 2.6.x
58
- - Latest Rails supported: 6.0.x
59
- - Postgres: 9.6-current(11) (probably works with most older versions to a certain point)
55
+ - Minimum Ruby Version: 2.4.x **(EOL warning!)**
56
+ - Minimum Rails Version: 5.1.x **(EOL warning!)**
57
+ - Minimum Postgres Version: 9.6.x **(EOL warning!)**
58
+ - Latest Ruby supported: 3.0.x
59
+ - Latest Rails supported: 7.0.x
60
+ - Postgres: 9.6-current(13) (probably works with most older versions to a certain point)
60
61
 
61
62
  ## Installation
62
63
 
@@ -430,7 +431,7 @@ While quite the mouthful of an explanation. The implementation of combining unre
430
431
  product_query =
431
432
  Product.select(:id)
432
433
  .joins(:items)
433
- .select_row_to_json(item_query, key: :outer_items, as: :items, cast_as_array: true) do |item_scope|
434
+ .select_row_to_json(item_query, key: :outer_items, as: :items, cast_with: :array) do |item_scope|
434
435
  item_scope.where("outer_items.product_id = products.id")
435
436
  # Results to:
436
437
  # SELECT ..., ARRAY(SELECT ROW_TO_JSON("outer_items")
@@ -440,7 +441,7 @@ While quite the mouthful of an explanation. The implementation of combining unre
440
441
  end
441
442
 
442
443
  # Not defining a key will automatically generate a random key between a-z
443
- category_query = Category.select(:name, :id).select_row_to_json(product_query, as: :products, cast_as_array: true)
444
+ category_query = Category.select(:name, :id).select_row_to_json(product_query, as: :products, cast_with: :array)
444
445
  Category.json_build_object(:physical_category, category_query.where(id: physical_cat.id)).results
445
446
  #=> {
446
447
  # "physical_category" => {
@@ -648,12 +649,12 @@ SELECT "people".*
648
649
 
649
650
  ```ruby
650
651
  users = Person.where(id: 1..5)
651
- expect_these_users = Person.where(id: 2..4)
652
+ except_these_users = Person.where(id: 2..4)
652
653
 
653
- Person.union_except(users, expect_these_users) #=> [#<Person id: 1, ..>, #<Person id: 5,..>]
654
+ Person.union_except(users, except_these_users) #=> [#<Person id: 1, ..>, #<Person id: 5,..>]
654
655
 
655
656
  # You can also chain union's
656
- Person.union.except(users, expect_these_users).union(Person.where(id: 20))
657
+ Person.union.except(users, except_these_users).union(Person.where(id: 20))
657
658
  ```
658
659
 
659
660
  Query Output
@@ -14,7 +14,22 @@ module ActiveRecordExtended
14
14
 
15
15
  module Merger
16
16
  def normal_values
17
- super + [:with, :union, :define_window]
17
+ super + [:union, :define_window]
18
+ end
19
+
20
+ def merge
21
+ merge_ctes!
22
+ super
23
+ end
24
+
25
+ def merge_ctes!
26
+ return unless other.with_values?
27
+
28
+ if other.recursive_value? && !relation.recursive_value?
29
+ relation.with!(:chain).recursive(other.cte)
30
+ else
31
+ relation.with!(other.cte)
32
+ end
18
33
  end
19
34
  end
20
35
 
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # TODO: Remove this when ruby 2.3 support is dropped
4
- unless Hash.instance_methods(false).include?(:compact!)
5
- require "active_support/all"
6
- end
7
-
8
3
  require "active_record"
9
4
  require "active_record/relation"
10
5
  require "active_record/relation/merger"
@@ -23,12 +18,8 @@ require "active_record_extended/query_methods/inet"
23
18
  require "active_record_extended/query_methods/json"
24
19
  require "active_record_extended/query_methods/select"
25
20
 
26
- if ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR <= 1
27
- if ActiveRecord::VERSION::MINOR.zero?
28
- require "active_record_extended/patch/5_0/regex_match"
29
- require "active_record_extended/patch/5_0/predicate_builder_decorator"
30
- end
21
+ if Gem::Requirement.new("~> 5.1.0").satisfied_by?(ActiveRecord.gem_version)
31
22
  require "active_record_extended/patch/5_1/where_clause"
32
- elsif ActiveRecord::VERSION::MAJOR >= 5
23
+ else
33
24
  require "active_record_extended/patch/5_2/where_clause"
34
25
  end
@@ -5,28 +5,31 @@ require "arel/nodes/function"
5
5
 
6
6
  module Arel
7
7
  module Nodes
8
- %w[
9
- Overlap
10
- Contains
11
- ContainsHStore
12
- ContainsArray
13
- ContainedInArray
8
+ if Gem::Requirement.new("< 6.1").satisfied_by?(ActiveRecord.gem_version)
9
+ ["Contains", "Overlaps"].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
10
+ end
11
+
12
+ [
13
+ "ContainsHStore",
14
+ "ContainsArray",
15
+ "ContainedInArray"
14
16
  ].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
15
17
 
16
- %w[
17
- RowToJson
18
- JsonBuildObject
19
- JsonbBuildObject
20
- ToJson
21
- ToJsonb
22
- Array
23
- ArrayAgg
18
+ [
19
+ "RowToJson",
20
+ "JsonBuildObject",
21
+ "JsonbBuildObject",
22
+ "ToJson",
23
+ "ToJsonb",
24
+ "Array",
25
+ "ArrayAgg"
24
26
  ].each do |function_node_name|
25
27
  func_klass = Class.new(::Arel::Nodes::Function) do
26
28
  def initialize(*args)
27
29
  super
28
30
  return if @expressions.is_a?(::Array)
29
- @expressions = @expressions.is_a?(::Arel::Node) ? [@expressions] : [::Arel.sql(@expressions)]
31
+
32
+ @expressions = @expressions.is_a?(::Arel::Nodes::Node) ? [@expressions] : [::Arel.sql(@expressions)]
30
33
  end
31
34
  end
32
35
 
@@ -34,12 +37,12 @@ module Arel
34
37
  end
35
38
 
36
39
  module Inet
37
- %w[
38
- Contains
39
- ContainsEquals
40
- ContainedWithin
41
- ContainedWithinEquals
42
- ContainsOrContainedWithin
40
+ [
41
+ "Contains",
42
+ "ContainsEquals",
43
+ "ContainedWithin",
44
+ "ContainedWithinEquals",
45
+ "ContainsOrContainedWithin"
43
46
  ].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
44
47
  end
45
48
  end
@@ -14,9 +14,10 @@ module Arel
14
14
  Arel::Nodes::Equality.new(Nodes.build_quoted(other, self), all_tags_function)
15
15
  end
16
16
 
17
- def overlap(other)
18
- Nodes::Overlap.new(self, Nodes.build_quoted(other, self))
17
+ def overlaps(other)
18
+ Nodes::Overlaps.new(self, Nodes.build_quoted(other, self))
19
19
  end
20
+ alias overlap overlaps
20
21
 
21
22
  def contains(other)
22
23
  Nodes::Contains.new self, Nodes.build_quoted(other, self)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "arel/nodes/sql_literal"
4
+
5
+ # CTE alias fix for Rails 6.1
6
+ module Arel
7
+ module Nodes
8
+ module SqlLiteralDecorator
9
+ def name
10
+ self
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ Arel::Nodes::SqlLiteral.prepend(Arel::Nodes::SqlLiteralDecorator)
@@ -9,7 +9,7 @@ module ActiveRecordExtended
9
9
 
10
10
  # rubocop:disable Naming/MethodName
11
11
 
12
- def visit_Arel_Nodes_Overlap(object, collector)
12
+ def visit_Arel_Nodes_Overlaps(object, collector)
13
13
  infix_value object, collector, " && "
14
14
  end
15
15
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record_extended/arel/nodes"
4
+ require "active_record_extended/arel/sql_literal"
4
5
  require "active_record_extended/arel/aggregate_function_name"
5
6
  require "active_record_extended/arel/predications"
6
7
  require "active_record_extended/arel/visitors/postgresql_decorator"
@@ -61,13 +61,14 @@ module ActiveRecordExtended
61
61
  # In Rails 5.2 the arel table maintains attribute binds
62
62
  def bind_attributes(query)
63
63
  return [] unless query.respond_to?(:bound_attributes)
64
+
64
65
  query.bound_attributes.map(&:value)
65
66
  end
66
67
 
67
68
  # Rails 5.1 fix
68
69
  def unprepared_query(query)
69
- query.gsub(/((?<!\\)'.*?(?<!\\)'|(?<!\\)".*?(?<!\\)")|(\=\ \$\d+)/) do |match|
70
- Regexp.last_match(2)&.gsub(/\=\ \$\d+/, "= ?") || match
70
+ query.gsub(/((?<!\\)'.*?(?<!\\)'|(?<!\\)".*?(?<!\\)")|(=\ \$\d+)/) do |match|
71
+ Regexp.last_match(2)&.gsub(/=\ \$\d+/, "= ?") || match
71
72
  end
72
73
  end
73
74
 
@@ -78,9 +79,9 @@ module ActiveRecordExtended
78
79
  def generate_where_clause(query)
79
80
  case query
80
81
  when String, Hash
81
- @scope.where(query)
82
+ @scope.unscoped.where(query)
82
83
  when Array
83
- @scope.where(*query)
84
+ @scope.unscoped.where(*query)
84
85
  else
85
86
  query
86
87
  end
@@ -30,7 +30,7 @@ module ActiveRecordExtended
30
30
  end
31
31
 
32
32
  def sort_order_sql(dir)
33
- %w[asc desc].include?(dir.to_s) ? dir.to_s : "asc"
33
+ ["asc", "desc"].include?(dir.to_s) ? dir.to_s : "asc"
34
34
  end
35
35
 
36
36
  def xor_field_options(options)
@@ -52,6 +52,7 @@ module ActiveRecordExtended
52
52
  def xor_field_options_for_associations(associations)
53
53
  associations.each_with_object({}) do |association_name, options|
54
54
  reflection = reflect_on_association(association_name)
55
+ reflection = reflection.through_reflection if reflection.through_reflection?
55
56
  options[reflection.table_name] = reflection.foreign_key
56
57
  end
57
58
  end
@@ -74,8 +74,12 @@ module ActiveRecordExtended
74
74
  # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" && '127.0.0.255/32'"
75
75
  #
76
76
  def inet_contains_or_is_contained_within(opts, *rest)
77
- substitute_comparisons(opts, rest, Arel::Nodes::Inet::ContainsOrContainedWithin,
78
- "inet_contains_or_is_contained_within")
77
+ substitute_comparisons(
78
+ opts,
79
+ rest,
80
+ Arel::Nodes::Inet::ContainsOrContainedWithin,
81
+ "inet_contains_or_is_contained_within"
82
+ )
79
83
  end
80
84
  end
81
85
  end
@@ -8,7 +8,7 @@ module ActiveRecordExtended
8
8
  :json_build_object,
9
9
  :jsonb_build_object,
10
10
  :json_build_literal,
11
- :jsonb_build_literal,
11
+ :jsonb_build_literal
12
12
  ].freeze
13
13
 
14
14
  class JsonChain
@@ -51,7 +51,7 @@ module ActiveRecordExtended
51
51
  private
52
52
 
53
53
  def build_json_literal(arel_klass, values:, col_alias: DEFAULT_ALIAS)
54
- json_values = flatten_to_sql(values.to_a, &method(:literal_key))
54
+ json_values = flatten_to_sql(values.to_a) { |value| literal_key(value) }
55
55
  col_alias = double_quote(col_alias)
56
56
  json_build_obj = arel_klass.new(json_values)
57
57
  @scope.select(nested_alias_escape(json_build_obj, col_alias))
@@ -77,7 +77,7 @@ module ActiveRecordExtended
77
77
  row_to_json = ::Arel::Nodes::ToJsonb.new(row_to_json) if options.dig(:cast_with, :to_jsonb)
78
78
 
79
79
  dummy_table = from_clause_constructor(from, key).select(row_to_json)
80
- dummy_table = dummy_table.instance_eval(&block) if block_given?
80
+ dummy_table = dummy_table.instance_eval(&block) if block
81
81
  return dummy_table if options[:col_alias].blank?
82
82
 
83
83
  query = wrap_row_to_json(dummy_table, options)
@@ -98,7 +98,6 @@ module ActiveRecordExtended
98
98
  end
99
99
  end
100
100
 
101
- # TODO: [V2 release] Drop support for option :cast_as_array in favor of a more versatile :cast_with option
102
101
  def json_object_options(args, except: [], only: []) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
103
102
  options = {}
104
103
  lean_opts = lambda do |key, &block|
@@ -113,12 +112,12 @@ module ActiveRecordExtended
113
112
  next if arg.nil?
114
113
 
115
114
  if arg.is_a?(Hash)
116
- lean_opts.call(:key) { arg.delete(:key) || key_generator }
117
- lean_opts.call(:value) { arg.delete(:value).presence }
118
- lean_opts.call(:col_alias) { arg.delete(:as) }
119
- lean_opts.call(:order_by) { order_by_expression(arg.delete(:order_by)) }
120
- lean_opts.call(:from) { arg.delete(:from).tap(&method(:pipe_cte_with!)) }
121
- lean_opts.call(:cast_with) { casting_options(arg.delete(:cast_with) || arg.delete(:cast_as_array)) }
115
+ lean_opts.call(:key) { arg.fetch(:key, key_generator) }
116
+ lean_opts.call(:value) { arg[:value].presence }
117
+ lean_opts.call(:col_alias) { arg[:as] }
118
+ lean_opts.call(:order_by) { order_by_expression(arg[:order_by]) }
119
+ lean_opts.call(:from) { arg[:from].tap { |from_clause| pipe_cte_with!(from_clause) } }
120
+ lean_opts.call(:cast_with) { casting_options(arg[:cast_with]) }
122
121
  end
123
122
 
124
123
  unless except.include?(:values)
@@ -155,9 +154,6 @@ module ActiveRecordExtended
155
154
  # - key: [Symbol or String] (default=[random letter]) What the row clause will be set as.
156
155
  # - This is useful if you would like to add additional mid-level clauses (see mid-level scope example)
157
156
  #
158
- # - cast_as_array [boolean] (default=false): Determines if the query should be nested inside an Array() function
159
- # * Will be deprecated in V2.0 in favor of `cast_with` argument
160
- #
161
157
  # - cast_with [Symbol or Array of symbols]: Actions to transform your query
162
158
  # * :to_jsonb
163
159
  # * :array
@@ -172,13 +168,13 @@ module ActiveRecordExtended
172
168
  #
173
169
  # Examples:
174
170
  # subquery = Group.select(:name, :category_id).where("user_id = users.id")
175
- # User.select(:name, email).select_row_to_json(subquery, as: :users_groups, cast_as_array: true)
171
+ # User.select(:name, email).select_row_to_json(subquery, as: :users_groups, cast_with: :array)
176
172
  # #=> [<#User name:.., email:.., users_groups: [{ name: .., category_id: .. }, ..]]
177
173
  #
178
174
  # - Adding mid-level scopes:
179
175
  #
180
176
  # subquery = Group.select(:name, :category_id)
181
- # User.select_row_to_json(subquery, key: :group, cast_as_array: true) do |scope|
177
+ # User.select_row_to_json(subquery, key: :group, cast_with: :array) do |scope|
182
178
  # scope.where(group: { name: "Nerd Core" })
183
179
  # end
184
180
  # #=> ```sql
@@ -252,7 +248,8 @@ module ActiveRecordExtended
252
248
  def select_row_to_json(from = nil, **options, &block)
253
249
  from.is_a?(Hash) ? options.merge!(from) : options.reverse_merge!(from: from)
254
250
  options.compact!
255
- raise ArgumentError, "Required to provide a non-nilled from clause" unless options.key?(:from)
251
+ raise ArgumentError.new("Required to provide a non-nilled from clause") unless options.key?(:from)
252
+
256
253
  JsonChain.new(spawn).row_to_json!(**options, &block)
257
254
  end
258
255
 
@@ -273,7 +270,7 @@ module ActiveRecordExtended
273
270
  # - Generic example:
274
271
  #
275
272
  # subquery = Group.select(:name, :category_id).where("user_id = users.id")
276
- # User.select(:name, email).select_row_to_json(subquery, as: :users_groups, cast_as_array: true)
273
+ # User.select(:name, email).select_row_to_json(subquery, as: :users_groups, cast_with: :array)
277
274
  # #=> [<#User name:.., email:.., users_groups: [{ name: .., category_id: .. }, ..]]
278
275
  #
279
276
  # - Setting a custom value:
@@ -52,12 +52,12 @@ module ActiveRecordExtended
52
52
  # #=> SELECT (ARRAY_AGG(DISTINCT members.price)) AS past_purchases, ...
53
53
  def process_hash!(hash_of_options, alias_name)
54
54
  enforced_options = {
55
- cast_with: hash_of_options.delete(:cast_with),
56
- order_by: hash_of_options.delete(:order_by),
57
- distinct: !(!hash_of_options.delete(:distinct)),
55
+ cast_with: hash_of_options[:cast_with],
56
+ order_by: hash_of_options[:order_by],
57
+ distinct: !(!hash_of_options[:distinct])
58
58
  }
59
- query_statement = hash_to_dot_notation(hash_of_options.delete(:__select_statement) || hash_of_options.first)
60
- select!(query_statement, alias_name, enforced_options)
59
+ query_statement = hash_to_dot_notation(hash_of_options[:__select_statement] || hash_of_options.first)
60
+ select!(query_statement, alias_name, **enforced_options)
61
61
  end
62
62
 
63
63
  # Turn a hash chain into a query statement:
@@ -65,7 +65,7 @@ module ActiveRecordExtended
65
65
  def hash_to_dot_notation(column)
66
66
  case column
67
67
  when Hash, Array
68
- column.to_a.flat_map(&method(:hash_to_dot_notation)).join(".")
68
+ column.to_a.flat_map { |col| hash_to_dot_notation(col) }.join(".")
69
69
  when String, Symbol
70
70
  /^([[:alpha:]]+)$/.match?(column.to_s) ? double_quote(column) : column
71
71
  else
@@ -76,7 +76,7 @@ module ActiveRecordExtended
76
76
  # Add's select statement values to the current relation, select statement lists
77
77
  def select!(query, alias_name = nil, **options)
78
78
  pipe_cte_with!(query)
79
- @scope._select!(to_casted_query(query, alias_name, options))
79
+ @scope._select!(to_casted_query(query, alias_name, **options))
80
80
  end
81
81
 
82
82
  # Wraps the query with the requested query method
@@ -84,15 +84,15 @@ module ActiveRecordExtended
84
84
  # to_casted_query("memberships.cost", :total_revenue, :sum)
85
85
  # #=> SELECT (SUM(memberships.cost)) AS total_revenue
86
86
  def to_casted_query(query, alias_name, **options)
87
- cast_with = options.delete(:cast_with).to_s.downcase
88
- order_expr = order_by_expression(options.delete(:order_by))
89
- distinct = cast_with.chomp!("_distinct") || options.delete(:distinct) # account for [:agg_name:]_distinct
87
+ cast_with = options[:cast_with].to_s.downcase
88
+ order_expr = order_by_expression(options[:order_by])
89
+ distinct = cast_with.chomp!("_distinct") || options[:distinct] # account for [:agg_name:]_distinct
90
90
 
91
91
  case cast_with
92
92
  when "array", "true"
93
93
  wrap_with_array(query, alias_name)
94
94
  when AGGREGATE_ONE_LINERS
95
- expr = to_sql_array(query, &method(:group_when_needed))
95
+ expr = to_sql_array(query) { |value| group_when_needed(value) }
96
96
  casted_query = ::Arel::Nodes::AggregateFunctionName.new(cast_with, expr, distinct).order_by(order_expr)
97
97
  nested_alias_escape(casted_query, alias_name)
98
98
  else
@@ -102,7 +102,8 @@ module ActiveRecordExtended
102
102
  end
103
103
 
104
104
  def foster_select(*args)
105
- raise ArgumentError, "Call `.forster_select' with at least one field" if args.empty?
105
+ raise ArgumentError.new("Call `.forster_select' with at least one field") if args.empty?
106
+
106
107
  spawn._foster_select!(*args)
107
108
  end
108
109
 
@@ -59,7 +59,7 @@ module ActiveRecordExtended
59
59
  protected
60
60
 
61
61
  def append_union_order!(union_type, args)
62
- args.each(&method(:pipe_cte_with!))
62
+ args.each { |arg| pipe_cte_with!(arg) }
63
63
  flatten_scopes = flatten_to_sql(args)
64
64
  @scope.union_values += flatten_scopes
65
65
  calculate_union_operation!(union_type, flatten_scopes.size)
@@ -81,7 +81,7 @@ module ActiveRecordExtended
81
81
  union_values: [],
82
82
  union_operations: [],
83
83
  union_ordering_values: [],
84
- unionized_name: nil,
84
+ unionized_name: nil
85
85
  }
86
86
  end
87
87
 
@@ -89,10 +89,11 @@ module ActiveRecordExtended
89
89
  union_values: Array,
90
90
  union_operations: Array,
91
91
  union_ordering_values: Array,
92
- unionized_name: lambda { |klass| klass.arel_table.name },
92
+ unionized_name: lambda { |klass| klass.arel_table.name }
93
93
  }.each_pair do |method_name, default|
94
94
  define_method(method_name) do
95
95
  return unionize_storage[method_name] if send("#{method_name}?")
96
+
96
97
  (default.is_a?(Proc) ? default.call(@klass) : default.new)
97
98
  end
98
99
 
@@ -106,19 +107,21 @@ module ActiveRecordExtended
106
107
  end
107
108
 
108
109
  def union(opts = :chain, *args)
109
- return UnionChain.new(spawn) if opts == :chain
110
+ return UnionChain.new(spawn) if :chain == opts
111
+
110
112
  opts.nil? ? self : spawn.union!(opts, *args, chain_method: __callee__)
111
113
  end
112
114
 
113
115
  (UNIONIZE_METHODS + UNION_RELATION_METHODS).each do |union_method|
114
116
  next if union_method == :union
117
+
115
118
  alias_method union_method, :union
116
119
  end
117
120
 
118
121
  def union!(opts = :chain, *args, chain_method: :union)
119
122
  union_chain = UnionChain.new(self)
120
123
  chain_method ||= :union
121
- return union_chain if opts == :chain
124
+ return union_chain if :chain == opts
122
125
 
123
126
  union_chain.public_send(chain_method, *([opts] + args))
124
127
  end
@@ -126,11 +129,13 @@ module ActiveRecordExtended
126
129
  # Will construct *Just* the union SQL statement that was been built thus far
127
130
  def to_union_sql
128
131
  return unless union_values?
132
+
129
133
  apply_union_ordering(build_union_nodes!(false)).to_sql
130
134
  end
131
135
 
132
136
  def to_nice_union_sql(color = true)
133
137
  return to_union_sql unless defined?(::Niceql)
138
+
134
139
  ::Niceql::Prettifier.prettify_sql(to_union_sql, color)
135
140
  end
136
141
 
@@ -172,7 +177,7 @@ module ActiveRecordExtended
172
177
 
173
178
  def build_union_nodes!(raise_error = true)
174
179
  unionize_error_or_warn!(raise_error)
175
- union_values.each_with_index.inject(nil) do |union_node, (relation_node, index)|
180
+ union_values.each_with_index.reduce(nil) do |union_node, (relation_node, index)|
176
181
  next resolve_relation_node(relation_node) if union_node.nil?
177
182
 
178
183
  operation = union_operations.fetch(index - 1, :union)
@@ -215,6 +220,7 @@ module ActiveRecordExtended
215
220
  #
216
221
  def apply_union_ordering(union_nodes)
217
222
  return union_nodes unless union_ordering_values?
223
+
218
224
  UnionChain.new(self).inline_order_by(union_nodes, union_ordering_values)
219
225
  end
220
226
 
@@ -222,7 +228,7 @@ module ActiveRecordExtended
222
228
 
223
229
  def unionize_error_or_warn!(raise_error = true)
224
230
  if raise_error && union_values.size <= 1
225
- raise ArgumentError, "You are required to provide 2 or more unions to join!"
231
+ raise ArgumentError.new("You are required to provide 2 or more unions to join!")
226
232
  elsif !raise_error && union_values.size <= 1
227
233
  warn("Warning: You are required to provide 2 or more unions to join.")
228
234
  end
@@ -2,12 +2,15 @@
2
2
 
3
3
  module ActiveRecordExtended
4
4
  module WhereChain
5
+ AR_VERSION_AT_LEAST_6_1 = ActiveRecord.version >= Gem::Version.new('6.1')
6
+
5
7
  # Finds Records that have an array column that contain any a set of values
6
8
  # User.where.overlap(tags: [1,2])
7
9
  # # SELECT * FROM users WHERE tags && {1,2}
8
- def overlap(opts, *rest)
9
- substitute_comparisons(opts, rest, Arel::Nodes::Overlap, "overlap")
10
+ def overlaps(opts, *rest)
11
+ substitute_comparisons(opts, rest, Arel::Nodes::Overlaps, "overlap")
10
12
  end
13
+ alias overlap overlaps
11
14
 
12
15
  # Finds Records that contain an element in an array column
13
16
  # User.where.any(tags: 3)
@@ -54,10 +57,10 @@ module ActiveRecordExtended
54
57
  elsif column.try(:array)
55
58
  Arel::Nodes::ContainsArray.new(arel.left, arel.right)
56
59
  else
57
- raise ArgumentError, "Invalid argument for .where.contains(), got #{arel.class}"
60
+ raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
58
61
  end
59
62
  else
60
- raise ArgumentError, "Invalid argument for .where.contains(), got #{arel.class}"
63
+ raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
61
64
  end
62
65
  end
63
66
  end
@@ -88,7 +91,7 @@ module ActiveRecordExtended
88
91
  when Arel::Nodes::Equality
89
92
  Arel::Nodes::Equality.new(arel.right, Arel::Nodes::NamedFunction.new(function_name, [arel.left]))
90
93
  else
91
- raise ArgumentError, "Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}"
94
+ raise ArgumentError.new("Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}")
92
95
  end
93
96
  end
94
97
  end
@@ -99,10 +102,18 @@ module ActiveRecordExtended
99
102
  when Arel::Nodes::In, Arel::Nodes::Equality
100
103
  arel_node_class.new(arel.left, arel.right)
101
104
  else
102
- raise ArgumentError, "Invalid argument for .where.#{method}(), got #{arel.class}"
105
+ raise ArgumentError.new("Invalid argument for .where.#{method}(), got #{arel.class}")
103
106
  end
104
107
  end
105
108
  end
109
+
110
+ def build_where_clause_for(scope, opts, rest)
111
+ if AR_VERSION_AT_LEAST_6_1
112
+ scope.send(:build_where_clause, opts, rest)
113
+ else
114
+ scope.send(:where_clause_factory).build(opts, rest)
115
+ end
116
+ end
106
117
  end
107
118
  end
108
119
 
@@ -112,9 +123,9 @@ module ActiveRecord
112
123
  prepend ActiveRecordExtended::WhereChain
113
124
 
114
125
  def build_where_chain(opts, rest, &block)
115
- where_clause = @scope.send(:where_clause_factory).build(opts, rest)
126
+ where_clause = build_where_clause_for(@scope, opts, rest)
116
127
  @scope.tap do |scope|
117
- scope.references!(PredicateBuilder.references(opts)) if opts.is_a?(Hash)
128
+ scope.references!(PredicateBuilder.references(opts.stringify_keys)) if opts.is_a?(Hash)
118
129
  scope.where_clause += where_clause.modified_predicates(&block)
119
130
  end
120
131
  end
@@ -16,7 +16,7 @@ module ActiveRecordExtended
16
16
  @scope.window_values! << {
17
17
  window_name: to_arel_sql(@window_name),
18
18
  partition_by: flatten_to_sql(partitions),
19
- order_by: order_by_expression(order_by),
19
+ order_by: order_by_expression(order_by)
20
20
  }
21
21
 
22
22
  @scope
@@ -81,8 +81,9 @@ module ActiveRecordExtended
81
81
 
82
82
  def build_windows(arel)
83
83
  window_values.each do |window_value|
84
- window = arel.window(window_value[:window_name]).partition(window_value[:partition_by])
85
- window.order(window_value[:order_by]) if window_value[:order_by]
84
+ window = arel.window(window_value[:window_name])
85
+ window = window.partition(window_value[:partition_by]) if window_value[:partition_by].present?
86
+ window.order(window_value[:order_by]) if window_value[:order_by]
86
87
  end
87
88
  end
88
89
  end