Skip to content
This repository
tag: v3.0.0
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 262 lines (206 sloc) 7.682 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'

module ActiveRecord
  module QueryMethods
    extend ActiveSupport::Concern

    attr_accessor :includes_values, :eager_load_values, :preload_values,
                  :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values,
                  :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value

    def includes(*args)
      args.reject! { |a| a.blank? }
      clone.tap {|r| r.includes_values = (r.includes_values + args).flatten.uniq if args.present? }
    end

    def eager_load(*args)
      clone.tap {|r| r.eager_load_values += args if args.present? }
    end

    def preload(*args)
      clone.tap {|r| r.preload_values += args if args.present? }
    end

    def select(*args)
      if block_given?
        to_a.select {|*block_args| yield(*block_args) }
      else
        clone.tap {|r| r.select_values += args if args.present? }
      end
    end

    def group(*args)
      clone.tap {|r| r.group_values += args.flatten if args.present? }
    end

    def order(*args)
      clone.tap {|r| r.order_values += args if args.present? }
    end

    def reorder(*args)
      clone.tap {|r| r.order_values = args if args.present? }
    end

    def joins(*args)
      args.flatten!
      clone.tap {|r| r.joins_values += args if args.present? }
    end

    def where(opts, *rest)
      value = build_where(opts, rest)
      copy = clone
      copy.where_values += Array.wrap(value) if value
      copy
    end

    def having(*args)
      value = build_where(*args)
      clone.tap {|r| r.having_values += Array.wrap(value) if value.present? }
    end

    def limit(value = true)
      copy = clone
      copy.limit_value = value
      copy
    end

    def offset(value = true)
      clone.tap {|r| r.offset_value = value }
    end

    def lock(locks = true)
      case locks
      when String, TrueClass, NilClass
        clone.tap {|r| r.lock_value = locks || true }
      else
        clone.tap {|r| r.lock_value = false }
      end
    end

    def readonly(value = true)
      clone.tap {|r| r.readonly_value = value }
    end

    def create_with(value = true)
      clone.tap {|r| r.create_with_value = value }
    end

    def from(value = true)
      clone.tap {|r| r.from_value = value }
    end

    def extending(*modules, &block)
      modules << Module.new(&block) if block_given?
      clone.tap {|r| r.send(:apply_modules, *modules) }
    end

    def reverse_order
      order_clause = arel.order_clauses.join(', ')
      relation = except(:order)

      order = order_clause.blank? ?
        "#{@klass.table_name}.#{@klass.primary_key} DESC" :
        reverse_sql_order(order_clause)

      relation.order Arel::SqlLiteral.new order
    end

    def arel
      @arel ||= build_arel
    end

    def custom_join_sql(*joins)
      arel = table
      joins.each do |join|
        next if join.blank?

        @implicit_readonly = true

        case join
        when Hash, Array, Symbol
          if array_of_strings?(join)
            join_string = join.join(' ')
            arel = arel.join(Arel::SqlLiteral.new(join_string))
          end
        when String
          arel = arel.join(Arel::SqlLiteral.new(join))
        else
          arel = arel.join(join)
        end
      end
      arel.joins(arel)
    end

    def build_arel
      arel = table

      arel = build_joins(arel, @joins_values) unless @joins_values.empty?

      (@where_values - ['']).uniq.each do |where|
        case where
        when Arel::SqlLiteral
          arel = arel.where(where)
        else
          sql = where.is_a?(String) ? where : where.to_sql
          arel = arel.where(Arel::SqlLiteral.new("(#{sql})"))
        end
      end

      arel = arel.having(*@having_values.uniq.select{|h| h.present?}) unless @having_values.empty?

      arel = arel.take(@limit_value) if @limit_value
      arel = arel.skip(@offset_value) if @offset_value

      arel = arel.group(*@group_values.uniq.select{|g| g.present?}) unless @group_values.empty?

      arel = arel.order(*@order_values.uniq.select{|o| o.present?}) unless @order_values.empty?

      arel = build_select(arel, @select_values.uniq)

      arel = arel.from(@from_value) if @from_value
      arel = arel.lock(@lock_value) if @lock_value

      arel
    end

    def build_where(opts, other = [])
      case opts
      when String, Array
        @klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))
      when Hash
        attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
        PredicateBuilder.new(table.engine).build_from_hash(attributes, table)
      else
        opts
      end
    end

    private

    def build_joins(relation, joins)
      joined_associations = []
      association_joins = []

      joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq

      joins.each do |join|
        association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
      end

      stashed_association_joins = joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)

      non_association_joins = (joins - association_joins - stashed_association_joins)
      custom_joins = custom_join_sql(*non_association_joins)

      join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)

      join_dependency.graft(*stashed_association_joins)

      @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?

      to_join = []

      join_dependency.join_associations.each do |association|
        if (association_relation = association.relation).is_a?(Array)
          to_join << [association_relation.first, association.join_class, association.association_join.first]
          to_join << [association_relation.last, association.join_class, association.association_join.last]
        else
          to_join << [association_relation, association.join_class, association.association_join]
        end
      end

      to_join.each do |tj|
        unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
          joined_associations << tj
          relation = relation.join(tj[0], tj[1]).on(*tj[2])
        end
      end

      relation.join(custom_joins)
    end

    def build_select(arel, selects)
      unless selects.empty?
        @implicit_readonly = false
        # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array.
        # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array
        if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
          arel.project(*selects)
        else
          arel.project(selects.last)
        end
      else
        arel.project(Arel::SqlLiteral.new(@klass.quoted_table_name + '.*'))
      end
    end

    def apply_modules(modules)
      values = Array.wrap(modules)
      @extensions += values if values.present?
      values.each {|extension| extend(extension) }
    end

    def reverse_sql_order(order_query)
      order_query.to_s.split(/,/).each { |s|
        if s.match(/\s(asc|ASC)$/)
          s.gsub!(/\s(asc|ASC)$/, ' DESC')
        elsif s.match(/\s(desc|DESC)$/)
          s.gsub!(/\s(desc|DESC)$/, ' ASC')
        else
          s.concat(' DESC')
        end
      }.join(',')
    end

    def array_of_strings?(o)
      o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
    end

  end
end
Something went wrong with that request. Please try again.