aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/relation
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/relation')
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb21
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb20
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb16
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb96
5 files changed, 129 insertions, 26 deletions
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index ec1176e3dd..2fd89882ff 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -62,7 +62,7 @@ module ActiveRecord
start = options.delete(:start).to_i
batch_size = options.delete(:batch_size) || 1000
- relation = relation.except(:order).order(batch_order).limit(batch_size)
+ relation = relation.reorder(batch_order).limit(batch_size)
records = relation.where(table[primary_key].gteq(start)).all
while records.any?
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 9a7ff87e88..0f57e9831d 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -166,6 +166,23 @@ module ActiveRecord
0
end
+ # This method is designed to perform select by a single column as direct SQL query
+ # Returns <tt>Array</tt> with values of the specified column name
+ # The values has same data type as column.
+ #
+ # Examples:
+ #
+ # Person.pluck(:id) # SELECT people.id FROM people
+ # Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people
+ # Person.where(:confirmed => true).limit(5).pluck(:id)
+ #
+ def pluck(column_name)
+ scope = self.select(column_name)
+ self.connection.select_values(scope.to_sql).map! do |value|
+ type_cast_using_column(value, column_for(column_name))
+ end
+ end
+
private
def perform_calculation(operation, column_name, options = {})
@@ -223,7 +240,7 @@ module ActiveRecord
query_builder = relation.arel
end
- type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation)
+ type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
@@ -259,7 +276,7 @@ module ActiveRecord
relation = except(:group).group(group.join(','))
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation.to_sql)
+ calculated_data = @klass.connection.select_all(relation)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 8cef4e5554..3c8e0f2052 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -114,7 +114,7 @@ module ActiveRecord
def first(*args)
if args.any?
if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- to_a.first(*args)
+ limit(*args).to_a
else
apply_finder_options(args.first).first
end
@@ -134,7 +134,11 @@ module ActiveRecord
def last(*args)
if args.any?
if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- to_a.last(*args)
+ if order_values.empty? && reorder_value.nil?
+ order("#{primary_key} DESC").limit(*args).reverse
+ else
+ to_a.last(*args)
+ end
else
apply_finder_options(args.first).last
end
@@ -180,12 +184,14 @@ module ActiveRecord
# Person.exists?(:name => "David")
# Person.exists?(['name LIKE ?', "%#{query}%"])
# Person.exists?
- def exists?(id = nil)
+ def exists?(id = false)
+ return false if id.nil?
+
id = id.id if ActiveRecord::Base === id
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
- relation = relation.except(:select).select("1").limit(1)
+ relation = relation.except(:select, :order).select("1").limit(1)
case id
when Array, Hash
@@ -193,8 +199,8 @@ module ActiveRecord
else
relation = relation.where(table[primary_key].eq(id)) if id
end
-
- connection.select_value(relation.to_sql, "#{name} Exists") ? true : false
+
+ connection.select_value(relation, "#{name} Exists") ? true : false
end
protected
@@ -202,7 +208,7 @@ module ActiveRecord
def find_with_associations
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
- rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
+ rows = connection.select_all(relation, 'SQL', relation.bind_values)
join_dependency.instantiate(rows)
rescue ThrowResult
[]
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 2814771002..a789f48725 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -19,24 +19,26 @@ module ActiveRecord
case value
when ActiveRecord::Relation
- value.select_values = [value.klass.arel_table['id']] if value.select_values.empty?
+ value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
attribute.in(value.arel.ast)
when Array, ActiveRecord::Associations::CollectionProxy
- values = value.to_a.map { |x|
- x.is_a?(ActiveRecord::Base) ? x.id : x
- }
+ values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x}
+ ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
+
+ array_predicates = ranges.map {|range| attribute.in(range)}
if values.include?(nil)
values = values.compact
if values.empty?
- attribute.eq nil
+ array_predicates << attribute.eq(nil)
else
- attribute.in(values.compact).or attribute.eq(nil)
+ array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
end
else
- attribute.in(values)
+ array_predicates << attribute.in(values)
end
+ array_predicates.inject {|composite, predicate| composite.or(predicate)}
when Range, Arel::Relation
attribute.in(value)
when ActiveRecord::Base
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 3a7b245c51..c281bead0d 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -9,7 +9,8 @@ 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, :reverse_order_value
+ :from_value, :reorder_value, :reverse_order_value,
+ :uniq_value
def includes(*args)
args.reject! {|a| a.blank? }
@@ -37,6 +38,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) }
@@ -147,6 +177,58 @@ module ActiveRecord
relation
end
+ # Specifies whether the records should be unique or not. For example:
+ #
+ # User.select(:name)
+ # # => Might return two records with the same name
+ #
+ # User.select(:name).uniq
+ # # => Returns 1 record per unique name
+ #
+ # User.select(:name).uniq.uniq(false)
+ # # => You can also remove the uniqueness
+ def uniq(value = true)
+ relation = clone
+ relation.uniq_value = value
+ 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?
@@ -187,6 +269,7 @@ module ActiveRecord
build_select(arel, @select_values.uniq)
+ arel.distinct(@uniq_value)
arel.from(@from_value) if @from_value
arel.lock(@lock_value) if @lock_value
@@ -254,12 +337,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,
@@ -267,10 +350,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?
@@ -280,7 +359,6 @@ module ActiveRecord
association.join_to(manager)
end
- manager.join_sources.concat join_nodes.uniq
manager.join_sources.concat join_list
manager
@@ -312,7 +390,7 @@ module ActiveRecord
when String, Symbol
o.to_s.split(',').collect do |s|
s.strip!
- (s if s =~ /\(/) || s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
+ s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
end
else
o