active_record_extended 2.0.3 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +140 -77
  3. data/lib/active_record_extended/arel/nodes.rb +1 -1
  4. data/lib/active_record_extended/arel/{sql_literal.rb → sql_literal_patch.rb} +2 -2
  5. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +16 -12
  6. data/lib/active_record_extended/arel.rb +1 -1
  7. data/lib/active_record_extended/patch/array_handler_patch.rb +22 -0
  8. data/lib/active_record_extended/patch/relation_patch.rb +82 -0
  9. data/lib/active_record_extended/patch/where_clause_patch.rb +13 -0
  10. data/lib/active_record_extended/query_methods/any_of.rb +1 -1
  11. data/lib/active_record_extended/query_methods/either.rb +5 -7
  12. data/lib/active_record_extended/query_methods/{select.rb → foster_select.rb} +4 -4
  13. data/lib/active_record_extended/query_methods/json.rb +2 -2
  14. data/lib/active_record_extended/query_methods/unionize.rb +5 -5
  15. data/lib/active_record_extended/query_methods/where_chain.rb +96 -90
  16. data/lib/active_record_extended/query_methods/window.rb +3 -3
  17. data/lib/active_record_extended/query_methods/with_cte.rb +61 -4
  18. data/lib/active_record_extended/utilities/order_by.rb +1 -1
  19. data/lib/active_record_extended/utilities/support.rb +1 -1
  20. data/lib/active_record_extended/version.rb +1 -1
  21. data/lib/active_record_extended.rb +55 -4
  22. metadata +20 -83
  23. data/lib/active_record_extended/active_record/relation_patch.rb +0 -50
  24. data/lib/active_record_extended/active_record.rb +0 -25
  25. data/lib/active_record_extended/patch/5_1/where_clause.rb +0 -11
  26. data/lib/active_record_extended/patch/5_2/where_clause.rb +0 -11
  27. data/lib/active_record_extended/predicate_builder/array_handler_decorator.rb +0 -20
  28. data/spec/active_record_extended_spec.rb +0 -7
  29. data/spec/query_methods/any_of_spec.rb +0 -131
  30. data/spec/query_methods/array_query_spec.rb +0 -64
  31. data/spec/query_methods/either_spec.rb +0 -70
  32. data/spec/query_methods/hash_query_spec.rb +0 -45
  33. data/spec/query_methods/inet_query_spec.rb +0 -112
  34. data/spec/query_methods/json_spec.rb +0 -157
  35. data/spec/query_methods/select_spec.rb +0 -115
  36. data/spec/query_methods/unionize_spec.rb +0 -165
  37. data/spec/query_methods/window_spec.rb +0 -51
  38. data/spec/query_methods/with_cte_spec.rb +0 -50
  39. data/spec/spec_helper.rb +0 -28
  40. data/spec/sql_inspections/any_of_sql_spec.rb +0 -41
  41. data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +0 -41
  42. data/spec/sql_inspections/arel/array_spec.rb +0 -63
  43. data/spec/sql_inspections/arel/inet_spec.rb +0 -66
  44. data/spec/sql_inspections/contains_sql_queries_spec.rb +0 -47
  45. data/spec/sql_inspections/either_sql_spec.rb +0 -71
  46. data/spec/sql_inspections/json_sql_spec.rb +0 -82
  47. data/spec/sql_inspections/unionize_sql_spec.rb +0 -124
  48. data/spec/sql_inspections/window_sql_spec.rb +0 -98
  49. data/spec/sql_inspections/with_cte_sql_spec.rb +0 -95
  50. data/spec/support/database_cleaner.rb +0 -15
  51. data/spec/support/models.rb +0 -80
@@ -29,7 +29,7 @@ module ActiveRecordExtended
29
29
 
30
30
  def hash_map_queries(queries)
31
31
  if queries.size == 1 && queries.first.is_a?(Hash)
32
- queries.first.each_pair.map { |attr, predicate| Hash[attr, predicate] }
32
+ queries.first.each_pair.map { |attr, predicate| { attr => predicate } }
33
33
  else
34
34
  queries
35
35
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ar_outer_joins"
4
-
5
3
  module ActiveRecordExtended
6
4
  module QueryMethods
7
5
  module Either
@@ -12,21 +10,21 @@ module ActiveRecordExtended
12
10
  associations = [initial_association, fallback_association]
13
11
  association_options = xor_field_options_for_associations(associations)
14
12
  condition__query = xor_field_sql(association_options) + "= #{table_name}.#{primary_key}"
15
- outer_joins(associations).where(Arel.sql(condition__query))
13
+ left_outer_joins(associations).where(Arel.sql(condition__query))
16
14
  end
17
15
  alias either_joins either_join
18
16
 
19
17
  def either_order(direction, **associations_and_columns)
20
18
  reflected_columns = map_columns_to_tables(associations_and_columns)
21
19
  conditional_query = xor_field_sql(reflected_columns) + sort_order_sql(direction)
22
- outer_joins(associations_and_columns.keys).order(Arel.sql(conditional_query))
20
+ left_outer_joins(associations_and_columns.keys).order(Arel.sql(conditional_query))
23
21
  end
24
22
  alias either_orders either_order
25
23
 
26
24
  private
27
25
 
28
26
  def xor_field_sql(options)
29
- XOR_FIELD_SQL % Hash[xor_field_options(options)]
27
+ XOR_FIELD_SQL % xor_field_options(options).to_h
30
28
  end
31
29
 
32
30
  def sort_order_sql(dir)
@@ -35,7 +33,7 @@ module ActiveRecordExtended
35
33
 
36
34
  def xor_field_options(options)
37
35
  str_args = options.flatten.take(XOR_FIELD_KEYS.size).map(&:to_s)
38
- Hash[XOR_FIELD_KEYS.zip(str_args)]
36
+ XOR_FIELD_KEYS.zip(str_args).to_h
39
37
  end
40
38
 
41
39
  def map_columns_to_tables(associations_and_columns)
@@ -60,4 +58,4 @@ module ActiveRecordExtended
60
58
  end
61
59
  end
62
60
 
63
- ActiveRecord::Base.extend(ActiveRecordExtended::QueryMethods::Either)
61
+ ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::Either)
@@ -2,10 +2,10 @@
2
2
 
3
3
  module ActiveRecordExtended
4
4
  module QueryMethods
5
- module Select
5
+ module FosterSelect
6
6
  class SelectHelper
7
- include ::ActiveRecordExtended::Utilities::Support
8
- include ::ActiveRecordExtended::Utilities::OrderBy
7
+ include ActiveRecordExtended::Utilities::Support
8
+ include ActiveRecordExtended::Utilities::OrderBy
9
9
 
10
10
  AGGREGATE_ONE_LINERS = /^(exists|sum|max|min|avg|count|jsonb?_agg|(bit|bool)_(and|or)|xmlagg|array_agg)$/.freeze
11
11
 
@@ -115,4 +115,4 @@ module ActiveRecordExtended
115
115
  end
116
116
  end
117
117
 
118
- ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::Select)
118
+ ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::FosterSelect)
@@ -12,8 +12,8 @@ module ActiveRecordExtended
12
12
  ].freeze
13
13
 
14
14
  class JsonChain
15
- include ::ActiveRecordExtended::Utilities::Support
16
- include ::ActiveRecordExtended::Utilities::OrderBy
15
+ include ActiveRecordExtended::Utilities::Support
16
+ include ActiveRecordExtended::Utilities::OrderBy
17
17
 
18
18
  DEFAULT_ALIAS = '"results"'
19
19
  TO_JSONB_OPTIONS = [:array_agg, :distinct, :to_jsonb].to_set.freeze
@@ -7,8 +7,8 @@ module ActiveRecordExtended
7
7
  UNIONIZE_METHODS = [:union, :union_all, :union_except, :union_intersect].freeze
8
8
 
9
9
  class UnionChain
10
- include ::ActiveRecordExtended::Utilities::Support
11
- include ::ActiveRecordExtended::Utilities::OrderBy
10
+ include ActiveRecordExtended::Utilities::Support
11
+ include ActiveRecordExtended::Utilities::OrderBy
12
12
 
13
13
  def initialize(scope)
14
14
  @scope = scope
@@ -107,7 +107,7 @@ module ActiveRecordExtended
107
107
  end
108
108
 
109
109
  def union(opts = :chain, *args)
110
- return UnionChain.new(spawn) if :chain == opts
110
+ return UnionChain.new(spawn) if opts == :chain
111
111
 
112
112
  opts.nil? ? self : spawn.union!(opts, *args, chain_method: __callee__)
113
113
  end
@@ -121,7 +121,7 @@ module ActiveRecordExtended
121
121
  def union!(opts = :chain, *args, chain_method: :union)
122
122
  union_chain = UnionChain.new(self)
123
123
  chain_method ||= :union
124
- return union_chain if :chain == opts
124
+ return union_chain if opts == :chain
125
125
 
126
126
  union_chain.public_send(chain_method, *([opts] + args))
127
127
  end
@@ -178,7 +178,7 @@ module ActiveRecordExtended
178
178
  def build_union_nodes!(raise_error = true)
179
179
  unionize_error_or_warn!(raise_error)
180
180
  union_values.each_with_index.reduce(nil) do |union_node, (relation_node, index)|
181
- next resolve_relation_node(relation_node) if union_node.nil?
181
+ next resolve_relation_node(relation_node) if union_node.nil? # rubocop:disable Lint/UnmodifiedReduceAccumulator
182
182
 
183
183
  operation = union_operations.fetch(index - 1, :union)
184
184
  left = union_node
@@ -1,115 +1,121 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordExtended
4
- module WhereChain
5
- # Finds Records that have an array column that contain any a set of values
6
- # User.where.overlap(tags: [1,2])
7
- # # SELECT * FROM users WHERE tags && {1,2}
8
- def overlaps(opts, *rest)
9
- substitute_comparisons(opts, rest, Arel::Nodes::Overlaps, "overlap")
10
- end
11
- alias overlap overlaps
4
+ module QueryMethods
5
+ module WhereChain
6
+ # Finds Records that have an array column that contain any a set of values
7
+ # User.where.overlap(tags: [1,2])
8
+ # # SELECT * FROM users WHERE tags && {1,2}
9
+ def overlaps(opts, *rest)
10
+ substitute_comparisons(opts, rest, Arel::Nodes::Overlaps, "overlap")
11
+ end
12
+ alias overlap overlaps
12
13
 
13
- # Finds Records that contain an element in an array column
14
- # User.where.any(tags: 3)
15
- # # SELECT user.* FROM user WHERE 3 = ANY(user.tags)
16
- def any(opts, *rest)
17
- equality_to_function("ANY", opts, rest)
18
- end
14
+ # Finds Records that contain an element in an array column
15
+ # User.where.any(tags: 3)
16
+ # # SELECT user.* FROM user WHERE 3 = ANY(user.tags)
17
+ def any(opts, *rest)
18
+ equality_to_function("ANY", opts, rest)
19
+ end
19
20
 
20
- # Finds Records that contain a single matchable array element
21
- # User.where.all(tags: 3)
22
- # # SELECT user.* FROM user WHERE 3 = ALL(user.tags)
23
- def all(opts, *rest)
24
- equality_to_function("ALL", opts, rest)
25
- end
21
+ # Finds Records that contain a single matchable array element
22
+ # User.where.all(tags: 3)
23
+ # # SELECT user.* FROM user WHERE 3 = ALL(user.tags)
24
+ def all(opts, *rest)
25
+ equality_to_function("ALL", opts, rest)
26
+ end
26
27
 
27
- # Finds Records that contains a nested set elements
28
- #
29
- # Array Column Type:
30
- # User.where.contains(tags: [1, 3])
31
- # # SELECT user.* FROM user WHERE user.tags @> {1,3}
32
- #
33
- # HStore Column Type:
34
- # User.where.contains(data: { nickname: 'chainer' })
35
- # # SELECT user.* FROM user WHERE user.data @> 'nickname' => 'chainer'
36
- #
37
- # JSONB Column Type:
38
- # User.where.contains(data: { nickname: 'chainer' })
39
- # # SELECT user.* FROM user WHERE user.data @> {'nickname': 'chainer'}
40
- #
41
- # This can also be used along side joined tables
42
- #
43
- # JSONB Column Type Example:
44
- # Tag.joins(:user).where.contains(user: { data: { nickname: 'chainer' } })
45
- # # SELECT tags.* FROM tags INNER JOIN user on user.id = tags.user_id WHERE user.data @> { nickname: 'chainer' }
46
- #
47
- def contains(opts, *rest)
48
- build_where_chain(opts, rest) do |arel|
49
- case arel
50
- when Arel::Nodes::In, Arel::Nodes::Equality
51
- column = left_column(arel) || column_from_association(arel)
52
-
53
- if [:hstore, :jsonb].include?(column.type)
54
- Arel::Nodes::ContainsHStore.new(arel.left, arel.right)
55
- elsif column.try(:array)
56
- Arel::Nodes::ContainsArray.new(arel.left, arel.right)
28
+ # Finds Records that contains a nested set elements
29
+ #
30
+ # Array Column Type:
31
+ # User.where.contains(tags: [1, 3])
32
+ # # SELECT user.* FROM user WHERE user.tags @> {1,3}
33
+ #
34
+ # HStore Column Type:
35
+ # User.where.contains(data: { nickname: 'chainer' })
36
+ # # SELECT user.* FROM user WHERE user.data @> 'nickname' => 'chainer'
37
+ #
38
+ # JSONB Column Type:
39
+ # User.where.contains(data: { nickname: 'chainer' })
40
+ # # SELECT user.* FROM user WHERE user.data @> {'nickname': 'chainer'}
41
+ #
42
+ # This can also be used along side joined tables
43
+ #
44
+ # JSONB Column Type Example:
45
+ # Tag.joins(:user).where.contains(user: { data: { nickname: 'chainer' } })
46
+ # # SELECT tags.* FROM tags INNER JOIN user on user.id = tags.user_id WHERE user.data @> { nickname: 'chainer' }
47
+ #
48
+ def contains(opts, *rest)
49
+ if ActiveRecordExtended::AR_VERSION_GTE_6_1
50
+ return substitute_comparisons(opts, rest, Arel::Nodes::Contains, "contains")
51
+ end
52
+
53
+ build_where_chain(opts, rest) do |arel|
54
+ case arel
55
+ when Arel::Nodes::In, Arel::Nodes::Equality
56
+ column = left_column(arel) || column_from_association(arel)
57
+
58
+ if [:hstore, :jsonb].include?(column.type)
59
+ Arel::Nodes::ContainsHStore.new(arel.left, arel.right)
60
+ elsif column.try(:array)
61
+ Arel::Nodes::ContainsArray.new(arel.left, arel.right)
62
+ else
63
+ raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
64
+ end
57
65
  else
58
66
  raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
59
67
  end
60
- else
61
- raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
62
68
  end
63
69
  end
64
- end
65
70
 
66
- private
71
+ private
67
72
 
68
- def matchable_column?(col, arel)
69
- col.name == arel.left.name.to_s || col.name == arel.left.relation.name.to_s
70
- end
73
+ def matchable_column?(col, arel)
74
+ col.name == arel.left.name.to_s || col.name == arel.left.relation.name.to_s
75
+ end
71
76
 
72
- def column_from_association(arel)
73
- assoc = assoc_from_related_table(arel)
74
- assoc.klass.columns.detect { |col| matchable_column?(col, arel) } if assoc
75
- end
77
+ def column_from_association(arel)
78
+ assoc = assoc_from_related_table(arel)
79
+ assoc.klass.columns.detect { |col| matchable_column?(col, arel) } if assoc
80
+ end
76
81
 
77
- def assoc_from_related_table(arel)
78
- @scope.klass.reflect_on_association(arel.left.relation.name.to_sym) ||
79
- @scope.klass.reflect_on_association(arel.left.relation.name.singularize.to_sym)
80
- end
82
+ def assoc_from_related_table(arel)
83
+ @scope.klass.reflect_on_association(arel.left.relation.name.to_sym) ||
84
+ @scope.klass.reflect_on_association(arel.left.relation.name.singularize.to_sym)
85
+ end
81
86
 
82
- def left_column(arel)
83
- @scope.klass.columns_hash[arel.left.name] || @scope.klass.columns_hash[arel.left.relation.name]
84
- end
87
+ def left_column(arel)
88
+ @scope.klass.columns_hash[arel.left.name] || @scope.klass.columns_hash[arel.left.relation.name]
89
+ end
85
90
 
86
- def equality_to_function(function_name, opts, rest)
87
- build_where_chain(opts, rest) do |arel|
88
- case arel
89
- when Arel::Nodes::Equality
90
- Arel::Nodes::Equality.new(arel.right, Arel::Nodes::NamedFunction.new(function_name, [arel.left]))
91
- else
92
- raise ArgumentError.new("Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}")
91
+ def equality_to_function(function_name, opts, rest)
92
+ build_where_chain(opts, rest) do |arel|
93
+ case arel
94
+ when Arel::Nodes::Equality
95
+ Arel::Nodes::Equality.new(arel.right, Arel::Nodes::NamedFunction.new(function_name, [arel.left]))
96
+ else
97
+ raise ArgumentError.new("Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}")
98
+ end
93
99
  end
94
100
  end
95
- end
96
101
 
97
- def substitute_comparisons(opts, rest, arel_node_class, method)
98
- build_where_chain(opts, rest) do |arel|
99
- case arel
100
- when Arel::Nodes::In, Arel::Nodes::Equality
101
- arel_node_class.new(arel.left, arel.right)
102
- else
103
- raise ArgumentError.new("Invalid argument for .where.#{method}(), got #{arel.class}")
102
+ def substitute_comparisons(opts, rest, arel_node_class, method)
103
+ build_where_chain(opts, rest) do |arel|
104
+ case arel
105
+ when Arel::Nodes::In, Arel::Nodes::Equality
106
+ arel_node_class.new(arel.left, arel.right)
107
+ else
108
+ raise ArgumentError.new("Invalid argument for .where.#{method}(), got #{arel.class}")
109
+ end
104
110
  end
105
111
  end
106
- end
107
112
 
108
- def build_where_clause_for(scope, opts, rest)
109
- if ActiveRecord::VERSION::MAJOR == 6 && ActiveRecord::VERSION::MINOR == 1
110
- scope.send(:build_where_clause, opts, rest)
111
- else
112
- scope.send(:where_clause_factory).build(opts, rest)
113
+ def build_where_clause_for(scope, opts, rest)
114
+ if ActiveRecordExtended::AR_VERSION_GTE_6_1
115
+ scope.send(:build_where_clause, opts, rest)
116
+ else
117
+ scope.send(:where_clause_factory).build(opts, rest)
118
+ end
113
119
  end
114
120
  end
115
121
  end
@@ -118,7 +124,7 @@ end
118
124
  module ActiveRecord
119
125
  module QueryMethods
120
126
  class WhereChain
121
- prepend ActiveRecordExtended::WhereChain
127
+ prepend ActiveRecordExtended::QueryMethods::WhereChain
122
128
 
123
129
  def build_where_chain(opts, rest, &block)
124
130
  where_clause = build_where_clause_for(@scope, opts, rest)
@@ -4,8 +4,8 @@ module ActiveRecordExtended
4
4
  module QueryMethods
5
5
  module Window
6
6
  class DefineWindowChain
7
- include ::ActiveRecordExtended::Utilities::Support
8
- include ::ActiveRecordExtended::Utilities::OrderBy
7
+ include ActiveRecordExtended::Utilities::Support
8
+ include ActiveRecordExtended::Utilities::OrderBy
9
9
 
10
10
  def initialize(scope, window_name)
11
11
  @scope = scope
@@ -24,7 +24,7 @@ module ActiveRecordExtended
24
24
  end
25
25
 
26
26
  class WindowSelectBuilder
27
- include ::ActiveRecordExtended::Utilities::Support
27
+ include ActiveRecordExtended::Utilities::Support
28
28
 
29
29
  def initialize(window_function, args, window_name)
30
30
  @window_function = window_function
@@ -4,12 +4,12 @@ module ActiveRecordExtended
4
4
  module QueryMethods
5
5
  module WithCTE
6
6
  class WithCTE
7
- include ::ActiveRecordExtended::Utilities::Support
7
+ include ActiveRecordExtended::Utilities::Support
8
8
  include Enumerable
9
9
  extend Forwardable
10
10
 
11
11
  def_delegators :@with_values, :empty?, :blank?, :present?
12
- attr_reader :with_values, :with_keys
12
+ attr_reader :with_values, :with_keys, :materialized_keys, :not_materialized_keys
13
13
 
14
14
  # @param [ActiveRecord::Relation] scope
15
15
  def initialize(scope)
@@ -33,6 +33,16 @@ module ActiveRecordExtended
33
33
  pipe_cte_with!(value)
34
34
  end
35
35
 
36
+ # @return [Boolean]
37
+ def materialized_key?(key)
38
+ materialized_keys.include?(key.to_sym)
39
+ end
40
+
41
+ # @return [Boolean]
42
+ def not_materialized_key?(key)
43
+ not_materialized_keys.include?(key.to_sym)
44
+ end
45
+
36
46
  # @param [Hash, WithCTE] value
37
47
  def pipe_cte_with!(value)
38
48
  return if value.nil? || value.empty?
@@ -44,6 +54,10 @@ module ActiveRecordExtended
44
54
  # Ensure we follow FIFO pattern.
45
55
  # If the parent has similar CTE alias keys, we want to favor the parent's expressions over its children's.
46
56
  if expression.is_a?(ActiveRecord::Relation) && expression.with_values?
57
+ # Add child's materialized keys to the parent
58
+ @materialized_keys += expression.cte.materialized_keys
59
+ @not_materialized_keys += expression.cte.not_materialized_keys
60
+
47
61
  pipe_cte_with!(expression.cte)
48
62
  expression.cte.reset!
49
63
  end
@@ -58,6 +72,8 @@ module ActiveRecordExtended
58
72
  def reset!
59
73
  @with_keys = []
60
74
  @with_values = {}
75
+ @materialized_keys = Set.new
76
+ @not_materialized_keys = Set.new
61
77
  end
62
78
  end
63
79
 
@@ -75,6 +91,32 @@ module ActiveRecordExtended
75
91
  scope.cte.pipe_cte_with!(args)
76
92
  end
77
93
  end
94
+
95
+ # @param [Hash, WithCTE] args
96
+ def materialized(args)
97
+ @scope.tap do |scope|
98
+ args.each_pair do |name, _expression|
99
+ sym_name = name.to_sym
100
+ raise ArgumentError.new("CTE already set as not_materialized") if scope.cte.not_materialized_key?(sym_name)
101
+
102
+ scope.cte.materialized_keys << sym_name
103
+ end
104
+ scope.cte.pipe_cte_with!(args)
105
+ end
106
+ end
107
+
108
+ # @param [Hash, WithCTE] args
109
+ def not_materialized(args)
110
+ @scope.tap do |scope|
111
+ args.each_pair do |name, _expression|
112
+ sym_name = name.to_sym
113
+ raise ArgumentError.new("CTE already set as materialized") if scope.cte.materialized_key?(sym_name)
114
+
115
+ scope.cte.not_materialized_keys << sym_name
116
+ end
117
+ scope.cte.pipe_cte_with!(args)
118
+ end
119
+ end
78
120
  end
79
121
 
80
122
  # @return [WithCTE]
@@ -113,14 +155,14 @@ module ActiveRecordExtended
113
155
 
114
156
  # @param [Hash, WithCTE] opts
115
157
  def with(opts = :chain, *rest)
116
- return WithChain.new(spawn) if :chain == opts
158
+ return WithChain.new(spawn) if opts == :chain
117
159
 
118
160
  opts.blank? ? self : spawn.with!(opts, *rest)
119
161
  end
120
162
 
121
163
  # @param [Hash, WithCTE] opts
122
164
  def with!(opts = :chain, *_rest)
123
- return WithChain.new(self) if :chain == opts
165
+ return WithChain.new(self) if opts == :chain
124
166
 
125
167
  tap do |scope|
126
168
  scope.cte ||= WithCTE.new(self)
@@ -134,6 +176,9 @@ module ActiveRecordExtended
134
176
  cte_statements = cte.map do |name, expression|
135
177
  grouped_expression = cte.generate_grouping(expression)
136
178
  cte_name = cte.to_arel_sql(cte.double_quote(name.to_s))
179
+
180
+ grouped_expression = add_materialized_modifier(grouped_expression, cte, name)
181
+
137
182
  Arel::Nodes::As.new(cte_name, grouped_expression)
138
183
  end
139
184
 
@@ -143,6 +188,18 @@ module ActiveRecordExtended
143
188
  arel.with(cte_statements)
144
189
  end
145
190
  end
191
+
192
+ private
193
+
194
+ def add_materialized_modifier(expression, cte, name)
195
+ if cte.materialized_key?(name)
196
+ Arel::Nodes::SqlLiteral.new("MATERIALIZED #{expression.to_sql}")
197
+ elsif cte.not_materialized_key?(name)
198
+ Arel::Nodes::SqlLiteral.new("NOT MATERIALIZED #{expression.to_sql}")
199
+ else
200
+ expression
201
+ end
202
+ end
146
203
  end
147
204
  end
148
205
  end
@@ -49,7 +49,7 @@ module ActiveRecordExtended
49
49
  obj.each_pair do |o_key, o_value|
50
50
  new_hash["#{tbl_or_col}.#{o_key}"] = o_value
51
51
  end
52
- elsif ::ActiveRecord::QueryMethods::VALID_DIRECTIONS.include?(obj)
52
+ elsif ActiveRecord::QueryMethods::VALID_DIRECTIONS.include?(obj)
53
53
  new_hash[tbl_or_col] = obj
54
54
  elsif obj.nil?
55
55
  new_hash[tbl_or_col.to_s] = :asc
@@ -113,7 +113,7 @@ module ActiveRecordExtended
113
113
  case value.to_s
114
114
  # Ignore keys that contain double quotes or a Arel.star (*)[all columns]
115
115
  # or if a table has already been explicitly declared (ex: users.id)
116
- when "*", /((^".+"$)|(^[[:alpha:]]+\.[[:alnum:]]+))/
116
+ when "*", /((^".+"$)|(^[[:alpha:]]+\.[[:alnum:]]+)|\(.+\))/
117
117
  value
118
118
  else
119
119
  PG::Connection.quote_ident(value.to_s)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordExtended
4
- VERSION = "2.0.3"
4
+ VERSION = "3.1.0"
5
5
  end
@@ -1,10 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record_extended/version"
4
- require "active_record_extended/utilities/support"
5
- require "active_record_extended/utilities/order_by"
6
- require "active_record_extended/active_record"
7
- require "active_record_extended/arel"
4
+
5
+ require "active_record"
6
+ require "active_record/relation"
7
+ require "active_record/relation/merger"
8
+ require "active_record/relation/query_methods"
8
9
 
9
10
  module ActiveRecordExtended
11
+ extend ActiveSupport::Autoload
12
+
13
+ AR_VERSION_GTE_6_1 = Gem::Requirement.new(">= 6.1").satisfied_by?(ActiveRecord.gem_version)
14
+
15
+ module Utilities
16
+ extend ActiveSupport::Autoload
17
+
18
+ eager_autoload do
19
+ autoload :OrderBy
20
+ autoload :Support
21
+ end
22
+ end
23
+
24
+ module Patch
25
+ extend ActiveSupport::Autoload
26
+
27
+ eager_autoload do
28
+ autoload :ArrayHandlerPatch
29
+ autoload :RelationPatch
30
+ autoload :WhereClausePatch
31
+ end
32
+ end
33
+
34
+ module QueryMethods
35
+ extend ActiveSupport::Autoload
36
+
37
+ eager_autoload do
38
+ autoload :AnyOf
39
+ autoload :Either
40
+ autoload :FosterSelect
41
+ autoload :Inet
42
+ autoload :Json
43
+ autoload :Unionize
44
+ autoload :WhereChain
45
+ autoload :Window
46
+ autoload :WithCTE
47
+ end
48
+ end
49
+
50
+ def self.eager_load!
51
+ super
52
+ ActiveRecordExtended::Utilities.eager_load!
53
+ ActiveRecordExtended::Patch.eager_load!
54
+ ActiveRecordExtended::QueryMethods.eager_load!
55
+ end
56
+ end
57
+
58
+ ActiveSupport.on_load(:active_record) do
59
+ require "active_record_extended/arel"
60
+ ActiveRecordExtended.eager_load!
10
61
  end