query_methods.rb 4.1 KB
Newer Older
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
module ActiveRecord
  module QueryMethods

    def preload(*associations)
      spawn.tap {|r| r.preload_associations += Array.wrap(associations) }
    end

    def eager_load(*associations)
      spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) }
    end

    def readonly(status = true)
      spawn.tap {|r| r.readonly = status }
    end

    def select(selects)
      if selects.present?
        relation = spawn(@relation.project(selects))
        relation.readonly = @relation.joins(relation).present? ? false : @readonly
        relation
      else
        spawn
      end
    end

    def from(from)
      from.present? ? spawn(@relation.from(from)) : spawn
    end

    def having(*args)
      return spawn if args.blank?

      if [String, Hash, Array].include?(args.first.class)
        havings = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
      else
        havings = args.first
      end

      spawn(@relation.having(havings))
    end

    def group(groups)
      groups.present? ? spawn(@relation.group(groups)) : spawn
    end

    def order(orders)
      orders.present? ? spawn(@relation.order(orders)) : spawn
    end

    def lock(locks = true)
      case locks
      when String
        spawn(@relation.lock(locks))
      when TrueClass, NilClass
        spawn(@relation.lock)
      else
        spawn
      end
    end

    def reverse_order
      relation = spawn
      relation.instance_variable_set(:@orders, nil)

      order_clause = @relation.send(:order_clauses).join(', ')
      if order_clause.present?
        relation.order(reverse_sql_order(order_clause))
      else
        relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC")
      end
    end

    def limit(limits)
      limits.present? ? spawn(@relation.take(limits)) : spawn
    end

    def offset(offsets)
      offsets.present? ? spawn(@relation.skip(offsets)) : spawn
    end

    def on(join)
      spawn(@relation.on(join))
    end

    def joins(join, join_type = nil)
      return spawn if join.blank?

      join_relation = case join
      when String
        @relation.join(join)
      when Hash, Array, Symbol
        if @klass.send(:array_of_strings?, join)
          @relation.join(join.join(' '))
        else
          @relation.join(@klass.send(:build_association_joins, join))
        end
      else
        @relation.join(join, join_type)
      end

      spawn(join_relation).tap { |r| r.readonly = true }
    end

    def where(*args)
      return spawn if args.blank?

107 108 109 110 111
      conditions = if [String, Array].include?(args.first.class)
        merged = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first)
        Arel::SqlLiteral.new(merged) if merged
      elsif args.first.is_a?(Hash)
        build_predicate_from_hash(args.first)
112
      else
113
        args.first
114 115
      end

116
      spawn(@relation.where(*conditions))
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
    end

    private

    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

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
    def build_predicate_from_hash(attributes, default_table = self.table)
      attributes = @klass.send(:expand_hash_conditions_for_aggregates, attributes)

      predicates = attributes.map do |column, value|
        arel_table = default_table

        if value.is_a?(Hash)
          arel_table = Arel::Table.new(column, Arel::Sql::Engine.new(@klass))
          build_predicate_from_hash(value, arel_table)
        else
          column = column.to_s

          if column.include?('.')
            table_name, column = column.split('.', 2)
            arel_table = Arel::Table.new(table_name, Arel::Sql::Engine.new(@klass))
          end

          case value
          when Array, Range, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope
            arel_table[column].in(value)
          else
            arel_table[column].eq(value)
          end
        end
      end

      predicates.flatten
    end

162 163
  end
end