aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/relation/query_methods.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/relation/query_methods.rb')
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb113
1 files changed, 91 insertions, 22 deletions
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 94aa999715..670ba0987d 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -9,7 +9,7 @@ module ActiveRecord
:select_values, :group_values, :order_values, :joins_values,
:where_values, :having_values, :bind_values,
:limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
- :from_value, :reorder_value
+ :from_value, :reorder_value, :reverse_order_value
def includes(*args)
args.reject! {|a| a.blank? }
@@ -37,6 +37,35 @@ module ActiveRecord
relation
end
+ # Works in two unique ways.
+ #
+ # First: takes a block so it can be used just like Array#select.
+ #
+ # Model.scoped.select { |m| m.field == value }
+ #
+ # This will build an array of objects from the database for the scope,
+ # converting them into an array and iterating through them using Array#select.
+ #
+ # Second: Modifies the SELECT statement for the query so that only certain
+ # fields are retrieved:
+ #
+ # >> Model.select(:field)
+ # => [#<Model field:value>]
+ #
+ # Although in the above example it looks as though this method returns an
+ # array, it actually returns a relation object and can have other query
+ # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
+ #
+ # This method will also take multiple parameters:
+ #
+ # >> Model.select(:field, :other_field, :and_one_more)
+ # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
+ #
+ # Any attributes that do not have fields retrieved by a select
+ # will return `nil` when the getter method for that attribute is used:
+ #
+ # >> Model.select(:field).first.other_field
+ # => nil
def select(value = Proc.new)
if block_given?
to_a.select {|*block_args| value.call(*block_args) }
@@ -96,11 +125,11 @@ module ActiveRecord
relation
end
- def having(*args)
- return self if args.blank?
+ def having(opts, *rest)
+ return self if opts.blank?
relation = clone
- relation.having_values += build_where(*args)
+ relation.having_values += build_where(opts, rest)
relation
end
@@ -137,7 +166,7 @@ module ActiveRecord
def create_with(value)
relation = clone
- relation.create_with_value = value && (@create_with_value || {}).merge(value)
+ relation.create_with_value = value ? create_with_value.merge(value) : {}
relation
end
@@ -147,6 +176,42 @@ module ActiveRecord
relation
end
+ # Used to extend a scope with additional methods, either through
+ # a module or through a block provided.
+ #
+ # The object returned is a relation, which can be further extended.
+ #
+ # === Using a module
+ #
+ # module Pagination
+ # def page(number)
+ # # pagination code goes here
+ # end
+ # end
+ #
+ # scope = Model.scoped.extending(Pagination)
+ # scope.page(params[:page])
+ #
+ # You can also pass a list of modules:
+ #
+ # scope = Model.scoped.extending(Pagination, SomethingElse)
+ #
+ # === Using a block
+ #
+ # scope = Model.scoped.extending do
+ # def page(number)
+ # # pagination code goes here
+ # end
+ # end
+ # scope.page(params[:page])
+ #
+ # You can also use a block and a module list:
+ #
+ # scope = Model.scoped.extending(Pagination) do
+ # def per_page(number)
+ # # pagination code goes here
+ # end
+ # end
def extending(*modules)
modules << Module.new(&Proc.new) if block_given?
@@ -158,13 +223,9 @@ module ActiveRecord
end
def reverse_order
- order_clause = arel.order_clauses
-
- order = order_clause.empty? ?
- "#{table_name}.#{primary_key} DESC" :
- reverse_sql_order(order_clause).join(', ')
-
- except(:order).order(Arel.sql(order))
+ relation = clone
+ relation.reverse_order_value = !relation.reverse_order_value
+ relation
end
def arel
@@ -186,6 +247,7 @@ module ActiveRecord
arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
order = @reorder_value ? @reorder_value : @order_values
+ order = reverse_sql_order(order) if @reverse_order_value
arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
build_select(arel, @select_values.uniq)
@@ -257,12 +319,12 @@ module ActiveRecord
association_joins = buckets['association_join'] || []
stashed_association_joins = buckets['stashed_join'] || []
- join_nodes = buckets['join_node'] || []
+ join_nodes = (buckets['join_node'] || []).uniq
string_joins = (buckets['string_join'] || []).map { |x|
x.strip
}.uniq
- join_list = custom_join_ast(manager, string_joins)
+ join_list = join_nodes + custom_join_ast(manager, string_joins)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
@klass,
@@ -270,10 +332,6 @@ module ActiveRecord
join_list
)
- join_nodes.each do |join|
- join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
- end
-
join_dependency.graft(*stashed_association_joins)
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
@@ -283,7 +341,6 @@ module ActiveRecord
association.join_to(manager)
end
- manager.join_sources.concat join_nodes.uniq
manager.join_sources.concat join_list
manager
@@ -306,9 +363,21 @@ module ActiveRecord
end
def reverse_sql_order(order_query)
- order_query.join(', ').split(',').collect do |s|
- s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
- end
+ order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
+
+ order_query.map do |o|
+ case o
+ when Arel::Nodes::Ordering
+ o.reverse
+ when String, Symbol
+ o.to_s.split(',').collect do |s|
+ s.strip!
+ s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
+ end
+ else
+ o
+ end
+ end.flatten
end
def array_of_strings?(o)