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? 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) else args.first end spawn(@relation.where(*conditions)) 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 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 end end