query_methods.rb 7.4 KB
Newer Older
1
require 'active_support/core_ext/array/wrap'
2 3
require 'active_support/core_ext/object/blank'

4 5
module ActiveRecord
  module QueryMethods
6 7
    extend ActiveSupport::Concern

8 9 10
    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
11

12 13
    def includes(*args)
      args.reject! { |a| a.blank? }
P
Pratik Naik 已提交
14
      clone.tap {|r| r.includes_values += args if args.present? }
15
    end
16

17
    def eager_load(*args)
P
Pratik Naik 已提交
18
      clone.tap {|r| r.eager_load_values += args if args.present? }
19 20 21
    end

    def preload(*args)
P
Pratik Naik 已提交
22
      clone.tap {|r| r.preload_values += args if args.present? }
23
    end
24

25 26
    def select(*args)
      if block_given?
P
Pratik Naik 已提交
27
        to_a.select {|*block_args| yield(*block_args) }
28
      else
P
Pratik Naik 已提交
29
        clone.tap {|r| r.select_values += args if args.present? }
S
Santiago Pastorino 已提交
30
      end
31
    end
S
Santiago Pastorino 已提交
32

33
    def group(*args)
P
Pratik Naik 已提交
34
      clone.tap {|r| r.group_values += args if args.present? }
35
    end
36

37
    def order(*args)
P
Pratik Naik 已提交
38
      clone.tap {|r| r.order_values += args if args.present? }
39
    end
40

41
    def reorder(*args)
P
Pratik Naik 已提交
42
      clone.tap {|r| r.order_values = args if args.present? }
43 44
    end

45 46
    def joins(*args)
      args.flatten!
P
Pratik Naik 已提交
47
      clone.tap {|r| r.joins_values += args if args.present? }
P
Pratik Naik 已提交
48 49
    end

50 51
    def where(opts, other = nil)
      value = build_where(opts, other)
52
      value ? clone.tap {|r| r.where_values += Array.wrap(value) } : clone
53
    end
P
Pratik Naik 已提交
54

55 56
    def having(*args)
      value = build_where(*args)
P
Pratik Naik 已提交
57
      clone.tap {|r| r.having_values += Array.wrap(value) if value.present? }
58 59 60
    end

    def limit(value = true)
P
Pratik Naik 已提交
61
      clone.tap {|r| r.limit_value = value }
62 63 64
    end

    def offset(value = true)
P
Pratik Naik 已提交
65
      clone.tap {|r| r.offset_value = value }
66 67 68
    end

    def lock(locks = true)
69
      case locks
70
      when String, TrueClass, NilClass
P
Pratik Naik 已提交
71
        clone.tap {|r| r.lock_value = locks || true }
72
      else
P
Pratik Naik 已提交
73
        clone.tap {|r| r.lock_value = false }
74 75 76
      end
    end

77
    def readonly(value = true)
P
Pratik Naik 已提交
78
      clone.tap {|r| r.readonly_value = value }
79 80 81
    end

    def create_with(value = true)
P
Pratik Naik 已提交
82
      clone.tap {|r| r.create_with_value = value }
83 84 85
    end

    def from(value = true)
P
Pratik Naik 已提交
86
      clone.tap {|r| r.from_value = value }
87 88
    end

89 90
    def extending(*modules, &block)
      modules << Module.new(&block) if block_given?
P
Pratik Naik 已提交
91
      clone.tap {|r| r.send(:apply_modules, *modules) }
92 93
    end

94
    def reverse_order
95
      order_clause = arel.send(:order_clauses).join(', ')
96 97
      relation = except(:order)

98 99 100 101 102 103 104
      if order_clause.present?
        relation.order(reverse_sql_order(order_clause))
      else
        relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC")
      end
    end

105 106
    def arel
      @arel ||= build_arel
107 108
    end

109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
    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(join_string)
          end
        else
          arel = arel.join(join)
        end
      end
      arel.joins(arel)
    end

129 130
    def build_arel
      arel = table
131

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

134 135 136 137
      @where_values.uniq.each do |where|
        next if where.blank?

        case where
138 139
        when Arel::SqlLiteral
          arel = arel.where(where)
140
        else
141 142
          sql = where.is_a?(String) ? where : where.to_sql
          arel = arel.where(Arel::SqlLiteral.new("(#{sql})"))
143
        end
144 145
      end

146
      arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
147

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

151
      arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
152

153
      arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
154

155
      arel = build_select(arel, @select_values.uniq)
156

157
      arel = arel.from(@from_value) if @from_value.present?
158
      arel = arel.lock(@lock_value) if @lock_value
159 160

      arel
161 162
    end

163
    def build_where(opts, other = nil)
A
Aaron Patterson 已提交
164 165
      case opts
      when String, Array
166
        @klass.send(:sanitize_sql, other ? [opts, other] : opts)
A
Aaron Patterson 已提交
167 168
      when Hash
        attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
169
        PredicateBuilder.new(table.engine).build_from_hash(attributes, table)
170
      else
A
Aaron Patterson 已提交
171
        opts
172 173 174 175 176
      end
    end

    private

177 178 179 180 181 182 183 184 185 186 187 188
    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.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}

S
Santiago Pastorino 已提交
189
      non_association_joins = (joins - association_joins - stashed_association_joins)
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
      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

219 220 221
    def build_select(arel, selects)
      if selects.present?
        @implicit_readonly = false
222 223
        # 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
P
Pratik Naik 已提交
224
        if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
225 226 227
          arel.project(*selects)
        else
          arel.project(selects.last)
228 229
        end
      else
230
        arel.project(@klass.quoted_table_name + '.*')
231 232 233
      end
    end

P
Pratik Naik 已提交
234 235 236 237 238 239
    def apply_modules(modules)
      values = Array.wrap(modules)
      @extensions += values if values.present?
      values.each {|extension| extend(extension) }
    end

240 241 242 243 244 245 246 247 248 249 250 251
    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 已提交
252 253 254 255
    def array_of_strings?(o)
      o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
    end

256 257
  end
end