active_record_extended 1.4.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/README.md +8 -7
- data/lib/active_record_extended/active_record.rb +1 -10
- data/lib/active_record_extended/active_record/relation_patch.rb +16 -1
- data/lib/active_record_extended/arel.rb +1 -0
- data/lib/active_record_extended/arel/nodes.rb +22 -21
- data/lib/active_record_extended/arel/sql_literal.rb +16 -0
- data/lib/active_record_extended/query_methods/any_of.rb +5 -4
- data/lib/active_record_extended/query_methods/either.rb +1 -1
- data/lib/active_record_extended/query_methods/inet.rb +6 -2
- data/lib/active_record_extended/query_methods/json.rb +13 -16
- data/lib/active_record_extended/query_methods/select.rb +11 -10
- data/lib/active_record_extended/query_methods/unionize.rb +10 -4
- data/lib/active_record_extended/query_methods/where_chain.rb +14 -6
- data/lib/active_record_extended/query_methods/window.rb +4 -3
- data/lib/active_record_extended/query_methods/with_cte.rb +102 -35
- data/lib/active_record_extended/utilities/order_by.rb +9 -28
- data/lib/active_record_extended/utilities/support.rb +8 -15
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/any_of_spec.rb +2 -2
- data/spec/query_methods/json_spec.rb +5 -5
- data/spec/query_methods/select_spec.rb +13 -13
- data/spec/query_methods/unionize_spec.rb +5 -5
- data/spec/query_methods/with_cte_spec.rb +12 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/sql_inspections/any_of_sql_spec.rb +2 -2
- data/spec/sql_inspections/contains_sql_queries_spec.rb +8 -8
- data/spec/sql_inspections/either_sql_spec.rb +3 -3
- data/spec/sql_inspections/json_sql_spec.rb +0 -1
- data/spec/sql_inspections/unionize_sql_spec.rb +2 -2
- data/spec/sql_inspections/window_sql_spec.rb +12 -0
- data/spec/sql_inspections/with_cte_sql_spec.rb +30 -1
- metadata +18 -20
- data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +0 -87
- 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:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9934d5a90324b46dc3faa7b496724ac219a2582d264db89e64e2b9be574adab6
         | 
| 4 | 
            +
              data.tar.gz: c1359988f9a6b83148f36548e842a25cab68a191e7a839caef7e783b1415b346
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: e7385b414ef4c8d8b0c5b29eaeb70e77ba79f07828255deef5db79e99cf33f18de7bab696501711dee1f8e056ca3bdf628cdd498b341a91b5ad5cd2c766bd61c
         | 
| 7 | 
            +
              data.tar.gz: 10e8d4f65c570dba757b2cf47aaaff7f8a5538974e9c8aadb3c267e3e3fccb9508b05a8722eb5d6680937cb15827f6fa4f88d6dd219d8240c58323385fe44eb2
         | 
    
        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. | 
| 56 | 
            -
             - Minimum Rails Version: 5. | 
| 57 | 
            -
             -  | 
| 58 | 
            -
             - Latest  | 
| 59 | 
            -
             -  | 
| 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)
         | 
| 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,  | 
| 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,  | 
| 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" => {
         | 
| @@ -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,11 +18,7 @@ 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  | 
| 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 ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR == 1
         | 
| 31 22 | 
             
              require "active_record_extended/patch/5_1/where_clause"
         | 
| 32 23 | 
             
            elsif ActiveRecord::VERSION::MAJOR >= 5
         | 
| 33 24 | 
             
              require "active_record_extended/patch/5_2/where_clause"
         | 
| @@ -14,7 +14,22 @@ module ActiveRecordExtended | |
| 14 14 |  | 
| 15 15 | 
             
                module Merger
         | 
| 16 16 | 
             
                  def normal_values
         | 
| 17 | 
            -
                    super + [: | 
| 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,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,29 @@ require "arel/nodes/function" | |
| 5 5 |  | 
| 6 6 | 
             
            module Arel
         | 
| 7 7 | 
             
              module Nodes
         | 
| 8 | 
            -
                 | 
| 9 | 
            -
                  Overlap
         | 
| 10 | 
            -
                  Contains
         | 
| 11 | 
            -
                  ContainsHStore
         | 
| 12 | 
            -
                  ContainsArray
         | 
| 13 | 
            -
                  ContainedInArray
         | 
| 8 | 
            +
                [
         | 
| 9 | 
            +
                  "Overlap",
         | 
| 10 | 
            +
                  "Contains",
         | 
| 11 | 
            +
                  "ContainsHStore",
         | 
| 12 | 
            +
                  "ContainsArray",
         | 
| 13 | 
            +
                  "ContainedInArray"
         | 
| 14 14 | 
             
                ].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
         | 
| 15 15 |  | 
| 16 | 
            -
                 | 
| 17 | 
            -
                  RowToJson
         | 
| 18 | 
            -
                  JsonBuildObject
         | 
| 19 | 
            -
                  JsonbBuildObject
         | 
| 20 | 
            -
                  ToJson
         | 
| 21 | 
            -
                  ToJsonb
         | 
| 22 | 
            -
                  Array
         | 
| 23 | 
            -
                  ArrayAgg
         | 
| 16 | 
            +
                [
         | 
| 17 | 
            +
                  "RowToJson",
         | 
| 18 | 
            +
                  "JsonBuildObject",
         | 
| 19 | 
            +
                  "JsonbBuildObject",
         | 
| 20 | 
            +
                  "ToJson",
         | 
| 21 | 
            +
                  "ToJsonb",
         | 
| 22 | 
            +
                  "Array",
         | 
| 23 | 
            +
                  "ArrayAgg"
         | 
| 24 24 | 
             
                ].each do |function_node_name|
         | 
| 25 25 | 
             
                  func_klass = Class.new(::Arel::Nodes::Function) do
         | 
| 26 26 | 
             
                    def initialize(*args)
         | 
| 27 27 | 
             
                      super
         | 
| 28 28 | 
             
                      return if @expressions.is_a?(::Array)
         | 
| 29 | 
            -
             | 
| 29 | 
            +
             | 
| 30 | 
            +
                      @expressions = @expressions.is_a?(::Arel::Nodes::Node) ? [@expressions] : [::Arel.sql(@expressions)]
         | 
| 30 31 | 
             
                    end
         | 
| 31 32 | 
             
                  end
         | 
| 32 33 |  | 
| @@ -34,12 +35,12 @@ module Arel | |
| 34 35 | 
             
                end
         | 
| 35 36 |  | 
| 36 37 | 
             
                module Inet
         | 
| 37 | 
            -
                   | 
| 38 | 
            -
                    Contains
         | 
| 39 | 
            -
                    ContainsEquals
         | 
| 40 | 
            -
                    ContainedWithin
         | 
| 41 | 
            -
                    ContainedWithinEquals
         | 
| 42 | 
            -
                    ContainsOrContainedWithin
         | 
| 38 | 
            +
                  [
         | 
| 39 | 
            +
                    "Contains",
         | 
| 40 | 
            +
                    "ContainsEquals",
         | 
| 41 | 
            +
                    "ContainedWithin",
         | 
| 42 | 
            +
                    "ContainedWithinEquals",
         | 
| 43 | 
            +
                    "ContainsOrContainedWithin"
         | 
| 43 44 | 
             
                  ].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
         | 
| 44 45 | 
             
                end
         | 
| 45 46 | 
             
              end
         | 
| @@ -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)
         | 
| @@ -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(/((?<!\\)'.*?(?<!\\)'|(?<!\\)".*?(?<!\\)")|( | 
| 70 | 
            -
                      Regexp.last_match(2)&.gsub( | 
| 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
         | 
| @@ -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( | 
| 78 | 
            -
             | 
| 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
         | 
| @@ -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  | 
| 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. | 
| 117 | 
            -
                          lean_opts.call(:value)     { arg | 
| 118 | 
            -
                          lean_opts.call(:col_alias) { arg | 
| 119 | 
            -
                          lean_opts.call(:order_by)  { order_by_expression(arg | 
| 120 | 
            -
                          lean_opts.call(:from)      { arg | 
| 121 | 
            -
                          lean_opts.call(:cast_with) { casting_options(arg | 
| 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,  | 
| 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,  | 
| 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 | 
| 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,  | 
| 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 | 
| 56 | 
            -
                        order_by:  hash_of_options | 
| 57 | 
            -
                        distinct:  !(!hash_of_options | 
| 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 | 
| 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:
         | 
| @@ -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,9 +84,9 @@ 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 | 
| 88 | 
            -
                      order_expr = order_by_expression(options | 
| 89 | 
            -
                      distinct   = cast_with.chomp!("_distinct") || options | 
| 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"
         | 
| @@ -102,7 +102,8 @@ module ActiveRecordExtended | |
| 102 102 | 
             
                  end
         | 
| 103 103 |  | 
| 104 104 | 
             
                  def foster_select(*args)
         | 
| 105 | 
            -
                    raise ArgumentError | 
| 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 |  | 
| @@ -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 |  | 
| @@ -107,11 +108,13 @@ module ActiveRecordExtended | |
| 107 108 |  | 
| 108 109 | 
             
                  def union(opts = :chain, *args)
         | 
| 109 110 | 
             
                    return UnionChain.new(spawn) if opts == :chain
         | 
| 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 |  | 
| @@ -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. | 
| 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 | 
| 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
         |