active_record_extended 1.2.0 → 2.0.3

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 +85 -7
  3. data/lib/active_record_extended/active_record.rb +2 -11
  4. data/lib/active_record_extended/active_record/relation_patch.rb +21 -4
  5. data/lib/active_record_extended/arel.rb +1 -0
  6. data/lib/active_record_extended/arel/nodes.rb +24 -21
  7. data/lib/active_record_extended/arel/predications.rb +3 -2
  8. data/lib/active_record_extended/arel/sql_literal.rb +16 -0
  9. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +1 -1
  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 +17 -8
  17. data/lib/active_record_extended/query_methods/window.rb +93 -0
  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 +21 -18
  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 +5 -5
  27. data/spec/query_methods/window_spec.rb +51 -0
  28. data/spec/query_methods/with_cte_spec.rb +12 -2
  29. data/spec/spec_helper.rb +1 -1
  30. data/spec/sql_inspections/any_of_sql_spec.rb +2 -2
  31. data/spec/sql_inspections/contains_sql_queries_spec.rb +8 -8
  32. data/spec/sql_inspections/either_sql_spec.rb +19 -3
  33. data/spec/sql_inspections/json_sql_spec.rb +7 -1
  34. data/spec/sql_inspections/unionize_sql_spec.rb +2 -2
  35. data/spec/sql_inspections/window_sql_spec.rb +98 -0
  36. data/spec/sql_inspections/with_cte_sql_spec.rb +30 -1
  37. data/spec/support/models.rb +18 -0
  38. metadata +23 -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: b524c24c37e0b6f7449d70e5030b893119b98dec6646b905c3a307c9a671c7f1
4
- data.tar.gz: 18cb235fba334ebfaa752b92ad024117f686176b0e9107af6333e317a333c70f
3
+ metadata.gz: bbcdc3e208ac89fd2d55b8944ad7c0483e57f21a89941a4848beb548b56ce293
4
+ data.tar.gz: 10465bfe73686aae4fad33a7da0e533ed22401c68da5655654e1538a8bbb34c0
5
5
  SHA512:
6
- metadata.gz: 78539255c169c26a1d6bab3968a2eed038d8391d532f97d55dabf7a7c071cddf4eee1424586021cd7b790db4abf042cf0617ac69ede670caeb9cde55a23125a4
7
- data.tar.gz: 798ea355e5ebe213735db1552e7328d05c855c45800c941d4c5400a812b6d71f169ff52a48c0b9e6bf678d2dcf83ac1c7308a172b120caf9f83a240b0025e599
6
+ metadata.gz: 1ed8f4d6fe19c384bbb8add11443cc78b6f23cf4adc1808bdd0a1b6328a2dbeac22d4e0fa8fc6257dde505646d1c3613ed90711bbd36321cbb74231cf621958d
7
+ data.tar.gz: e139454fc996ad112d70332e39b002ba164d2172100f6b61f2494f090a0a3afe16c0e5e51998ab8f8cb8e839c6b0cb1104d152f821ae65d55bec4c83ec9c990a
data/README.md CHANGED
@@ -36,6 +36,9 @@
36
36
  - [Union As](#union-as)
37
37
  - [Union Order](#union-order)
38
38
  - [Union Reorder](#union-reorder)
39
+ - [Window Functions](#window-functions)
40
+ - [Define Window](#define-window)
41
+ - [Select Window](#select-window)
39
42
 
40
43
  ## Description and History
41
44
 
@@ -49,11 +52,12 @@ Active Record Extended is essentially providing users with the other half of Pos
49
52
  ## Compatibility
50
53
 
51
54
  This package is designed align and work with any officially supported Ruby and Rails versions.
52
- - Minimum Ruby Version: 2.3.x **(EOL warning!)**
53
- - Minimum Rails Version: 5.0.x **(EOL warning!)**
54
- - Latest Ruby supported: 2.6.x
55
- - Latest Rails supported: 6.0.x
56
- - 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: 2.7.x
59
+ - Latest Rails supported: 6.1.x
60
+ - Postgres: 9.6-current(13) (probably works with most older versions to a certain point)
57
61
 
58
62
  ## Installation
59
63
 
@@ -427,7 +431,7 @@ While quite the mouthful of an explanation. The implementation of combining unre
427
431
  product_query =
428
432
  Product.select(:id)
429
433
  .joins(:items)
430
- .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|
431
435
  item_scope.where("outer_items.product_id = products.id")
432
436
  # Results to:
433
437
  # SELECT ..., ARRAY(SELECT ROW_TO_JSON("outer_items")
@@ -437,7 +441,7 @@ While quite the mouthful of an explanation. The implementation of combining unre
437
441
  end
438
442
 
439
443
  # Not defining a key will automatically generate a random key between a-z
440
- 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)
441
445
  Category.json_build_object(:physical_category, category_query.where(id: physical_cat.id)).results
442
446
  #=> {
443
447
  # "physical_category" => {
@@ -787,6 +791,80 @@ SELECT "people".*
787
791
  ) ) ORDER BY personal_id DESC, id DESC) people
788
792
  ```
789
793
 
794
+ #### Window Functions
795
+ [Postgres Window Functions](https://www.postgresql.org/docs/current/tutorial-window.html)
796
+
797
+ Let's address the elephant in the room. Arel has had, for a long time now, window function capabilities;
798
+ However they've never seen the lime light in ActiveRecord's query logic.
799
+ The following brings the dormant Arel methods up to the ActiveRecord Querying level.
800
+
801
+ #### Define Window
802
+
803
+ To set up a window function, we first must establish the window and we do this by using the `.define_window/1` method.
804
+ This method also requires you to call chain `.partition_by/2`
805
+
806
+ `.define_window/1` - Establishes the name of the window you'll reference later on in [.select_window](#select-window)
807
+ - Aliased name of window
808
+
809
+ `.partition_by/2` - Establishes the windows operations a [pre-defined window function](https://www.postgresql.org/docs/current/functions-window.html) will leverage.
810
+ - column name being partitioned against
811
+ - (**optional**) `order_by`: Processes how the window should be ordered
812
+
813
+ ```ruby
814
+ User
815
+ .define_window(:number_window).partition_by(:number, order_by: { id: :desc })
816
+ .define_window(:name_window).partition_by(:name, order_by: :id)
817
+ .define_window(:no_order_name).partition_by(:name)
818
+ ```
819
+
820
+ Query Output
821
+ ```sql
822
+ SELECT *
823
+ FROM users
824
+ WINDOW number_window AS (PARTITION BY number ORDER BY id DESC),
825
+ name_window AS (PARTITION BY name ORDER BY id),
826
+ no_order_name AS (PARTITION BY name)
827
+ ```
828
+
829
+ #### Select Window
830
+
831
+ Once you've define a window, the next step to to utilize it on one of the many provided postgres window functions.
832
+
833
+ `.select_window/3`
834
+ - [window function name](https://www.postgresql.org/docs/current/functions-window.html)
835
+ - (**optional**) Window function arguments (treated as a splatted array)
836
+ - (**optional**) `as:` : Alias name of the final result
837
+ - `over:` : name of [defined window](#define-window)
838
+
839
+ ```ruby
840
+ User.create!(name: "Alice", number: 100) #=> id: 1
841
+ User.create!(name: "Randy", number: 100) #=> id: 2
842
+ User.create!(name: "Bob", number: 300) #=> id: 3
843
+
844
+ User
845
+ .define_window(:number_window).partition_by(:number, order_by: { id: :desc })
846
+ .select(:id, :name)
847
+ .select_window(:row_number, over: :number_window, as: :row_id)
848
+ .select_window(:first_value, :name, over: :number_window, as: :first_value_name)
849
+ #=> [
850
+ # { id: 1, name: "Alice", row_id: 2, first_value_name: "Randy" }
851
+ # { id: 2, name: "Randy", row_id: 1, first_value_name: "Randy" }
852
+ # { id: 3, name: "Bob", row_id: 1, first_value_name: "Bob" }
853
+ # ]
854
+ #
855
+
856
+ ```
857
+
858
+ Query Output
859
+ ```sql
860
+ SELECT "users"."id",
861
+ "users"."name",
862
+ (ROW_NUMBER() OVER number_window) AS "row_id",
863
+ (FIRST_VALUE(name) OVER number_window) AS "first_value_name"
864
+ FROM "users"
865
+ WINDOW number_window AS (PARTITION BY number ORDER BY id DESC)
866
+ ```
867
+
790
868
  ## License
791
869
 
792
870
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -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
@@ -1,27 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record_extended/query_methods/window"
3
4
  require "active_record_extended/query_methods/unionize"
4
5
  require "active_record_extended/query_methods/json"
5
6
 
6
7
  module ActiveRecordExtended
7
8
  module RelationPatch
8
9
  module QueryDelegation
9
- delegate :with, :foster_select, to: :all
10
+ delegate :with, :define_window, :select_window, :foster_select, to: :all
10
11
  delegate(*::ActiveRecordExtended::QueryMethods::Unionize::UNIONIZE_METHODS, to: :all)
11
12
  delegate(*::ActiveRecordExtended::QueryMethods::Json::JSON_QUERY_METHODS, to: :all)
12
13
  end
13
14
 
14
15
  module Merger
15
16
  def normal_values
16
- super + [:with, :union]
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
17
33
  end
18
34
  end
19
35
 
20
36
  module ArelBuildPatch
21
37
  def build_arel(*aliases)
22
38
  super.tap do |arel|
23
- build_unions(arel) if union_values?
24
- build_with(arel) if with_values?
39
+ build_windows(arel) if window_values?
40
+ build_unions(arel) if union_values?
41
+ build_with(arel) if with_values?
25
42
  end
26
43
  end
27
44
  end
@@ -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"
@@ -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
 
@@ -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: