query_methods.rb 5.7 KB
Newer Older
1 2
require 'active_support/core_ext/object/blank'

3 4
module ActiveRecord
  module QueryMethods
5 6 7 8 9 10
    extend ActiveSupport::Concern

    included do
      (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method|
        attr_accessor :"#{query_method}_values"

11
        next if [:where, :having].include?(query_method)
12 13
        class_eval <<-CEVAL
          def #{query_method}(*args)
14
            new_relation = clone
P
Pratik Naik 已提交
15 16 17
            value = Array.wrap(args.flatten).reject {|x| x.blank? }
            new_relation.#{query_method}_values += value if value.present?
            new_relation
18 19 20 21 22 23 24
          end
        CEVAL
      end

      [:where, :having].each do |query_method|
        class_eval <<-CEVAL
          def #{query_method}(*args)
25
            new_relation = clone
P
Pratik Naik 已提交
26 27 28
            value = build_where(*args)
            new_relation.#{query_method}_values += [*value] if value.present?
            new_relation
29 30
          end
        CEVAL
31 32
      end

33 34
      ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
        attr_accessor :"#{query_method}_value"
35

36 37
        class_eval <<-CEVAL
          def #{query_method}(value = true)
38
            new_relation = clone
P
Pratik Naik 已提交
39 40
            new_relation.#{query_method}_value = value
            new_relation
41 42
          end
        CEVAL
43 44 45 46
      end
    end

    def lock(locks = true)
47
      relation = clone
48
      case locks
49
      when String, TrueClass, NilClass
50
        clone.tap {|new_relation| new_relation.lock_value = locks || true }
51
      else
52
        clone.tap {|new_relation| new_relation.lock_value = false }
53 54 55 56
      end
    end

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

60 61 62 63 64 65 66
      if order_clause.present?
        relation.order(reverse_sql_order(order_clause))
      else
        relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC")
      end
    end

67 68
    def arel
      @arel ||= build_arel
69 70
    end

71 72
    def build_arel
      arel = table
73

74 75 76 77 78 79 80
      joined_associations = []
      association_joins = []

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

      # Build association joins first
      joins.each do |join|
P
Pratik Naik 已提交
81
        association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
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
      end

      if association_joins.any?
        join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins.uniq, nil)
        to_join = []

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

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

      joins.each do |join|
        next if join.blank?
107

108
        @implicit_readonly = true
109

110
        case join
111
        when Relation::JoinOperation
112
          arel = arel.join(join.relation, join.join_class).on(*join.on)
113
        when Hash, Array, Symbol
P
Pratik Naik 已提交
114
          if array_of_strings?(join)
115 116
            join_string = join.join(' ')
            arel = arel.join(join_string)
117
          end
118
        else
119
          arel = arel.join(join)
120 121 122
        end
      end

123 124 125 126 127 128 129 130 131 132
      @where_values.uniq.each do |where|
        next if where.blank?

        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
133 134
      end

135 136
      @having_values.uniq.each do |h|
        arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
137 138
      end

139 140
      arel = arel.take(@limit_value) if @limit_value.present?
      arel = arel.skip(@offset_value) if @offset_value.present?
141

142
      @group_values.uniq.each do |g|
143 144 145
        arel = arel.group(g) if g.present?
      end

146
      @order_values.uniq.each do |o|
147
        arel = arel.order(Arel::SqlLiteral.new(o.to_s)) if o.present?
148 149
      end

150 151
      selects = @select_values.uniq

152 153
      quoted_table_name = @klass.quoted_table_name

154 155 156 157 158
      if selects.present?
        selects.each do |s|
          @implicit_readonly = false
          arel = arel.project(s) if s.present?
        end
159
      else
160
        arel = arel.project(quoted_table_name + '.*')
161 162
      end

163
      arel = @from_value.present? ? arel.from(@from_value) : arel.from(quoted_table_name)
164 165 166 167 168 169

      case @lock_value
      when TrueClass
        arel = arel.lock
      when String
        arel = arel.lock(@lock_value)
170
      end if @lock_value.present?
171 172

      arel
173 174
    end

175 176
    def build_where(*args)
      return if args.blank?
177

178
      builder = PredicateBuilder.new(table.engine)
179

A
Aaron Patterson 已提交
180 181 182 183 184 185
      opts = args.first
      case opts
      when String, Array
        @klass.send(:sanitize_sql, args.size > 1 ? args : opts)
      when Hash
        attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
186
        builder.build_from_hash(attributes, table)
187
      else
A
Aaron Patterson 已提交
188
        opts
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
      end
    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

P
Pratik Naik 已提交
206 207 208 209
    def array_of_strings?(o)
      o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
    end

210 211
  end
end