active_record_extended 1.2.0 → 2.0.3

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 +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: