query_methods.rb 5.5 KB
Newer Older
1 2
module ActiveRecord
  module QueryMethods
3 4 5 6 7 8 9 10 11 12
    extend ActiveSupport::Concern

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

        class_eval <<-CEVAL
          def #{query_method}(*args)
            spawn.tap do |new_relation|
              new_relation.#{query_method}_values ||= []
13 14 15 16 17 18 19 20 21 22 23 24 25 26
              value = Array.wrap(args.flatten).reject {|x| x.blank? }
              new_relation.#{query_method}_values += value if value.present?
            end
          end
        CEVAL
      end

      [:where, :having].each do |query_method|
        class_eval <<-CEVAL
          def #{query_method}(*args)
            spawn.tap do |new_relation|
              new_relation.#{query_method}_values ||= []
              value = build_where(*args)
              new_relation.#{query_method}_values += [*value] if value.present?
27 28 29
            end
          end
        CEVAL
30 31
      end

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

35 36 37 38 39 40 41
        class_eval <<-CEVAL
          def #{query_method}(value = true)
            spawn.tap do |new_relation|
              new_relation.#{query_method}_value = value
            end
          end
        CEVAL
42 43 44 45
      end
    end

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

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

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

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

70 71
    def build_arel
      arel = table
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
      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|
        association_joins << join if [Hash, Array, Symbol].include?(join.class) && !@klass.send(:array_of_strings?, join)
      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?
106

107
        @implicit_readonly = true
108

109
        case join
110
        when Relation::JoinOperation
111
          arel = arel.join(join.relation, join.join_class).on(*join.on)
112
        when Hash, Array, Symbol
113 114 115
          if @klass.send(:array_of_strings?, join)
            join_string = join.join(' ')
            arel = arel.join(join_string)
116
          end
117
        else
118
          arel = arel.join(join)
119 120 121
        end
      end

122 123
      @where_values.uniq.each do |w|
        arel = w.is_a?(String) ? arel.where(w) : arel.where(*w)
124 125
      end

126 127
      @having_values.uniq.each do |h|
        arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
128 129 130 131 132
      end

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

133
      @group_values.uniq.each do |g|
134 135 136
        arel = arel.group(g) if g.present?
      end

137
      @order_values.uniq.each do |o|
138 139 140
        arel = arel.order(o) if o.present?
      end

141 142 143 144 145 146 147 148 149
      selects = @select_values.uniq

      if selects.present?
        selects.each do |s|
          @implicit_readonly = false
          arel = arel.project(s) if s.present?
        end
      elsif joins.present?
        arel = arel.project(@klass.quoted_table_name + '.*')
150 151 152 153 154 155 156 157 158 159 160 161
      end

      arel = arel.from(@from_value) if @from_value.present?

      case @lock_value
      when TrueClass
        arel = arel.lock
      when String
        arel = arel.lock(@lock_value)
      end

      arel
162 163
    end

164 165
    def build_where(*args)
      return if args.blank?
166

167
      builder = PredicateBuilder.new(table.engine)
168

169 170 171 172
      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)
173 174
        attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first)
        builder.build_from_hash(attributes, table)
175
      else
176
        args.first
177 178
      end

179
      conditions
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    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

  end
end