aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record.rb3
-rwxr-xr-xactiverecord/lib/active_record/associations.rb28
-rwxr-xr-xactiverecord/lib/active_record/base.rb156
-rw-r--r--activerecord/lib/active_record/calculations.rb214
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb20
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb23
-rw-r--r--activerecord/lib/active_record/migration.rb63
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/method_scoping_test.rb2
12 files changed, 296 insertions, 223 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 2d98239052..05afd20f23 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -25,6 +25,9 @@ activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
$:.unshift(activesupport_path) if File.directory?(activesupport_path)
require 'active_support'
+$:.unshift(File.dirname(__FILE__) + '/../../arel/lib')
+require 'arel'
+
module ActiveRecord
# TODO: Review explicit loads to see if they will automatically be handled by the initilizer.
def self.load_all!
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 76726b7845..2c998faf37 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -516,14 +516,14 @@ module ActiveRecord
#
# Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case
# Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
- #
+ #
# Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
#
# will result in a single SQL query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
# <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions like this can have unintended consequences.
# In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole
# and not just to the association. You must disambiguate column references for this fallback to happen, for example
- # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
+ # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
#
# If you do want eagerload only some members of an association it is usually more natural to <tt>:include</tt> an association
# which has conditions defined on it:
@@ -557,10 +557,10 @@ module ActiveRecord
#
# Address.find(:all, :include => :addressable)
#
- # will execute one query to load the addresses and load the addressables with one query per addressable type.
+ # will execute one query to load the addresses and load the addressables with one query per addressable type.
# For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of
# addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback
- # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
# model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query.
#
# == Table Aliasing
@@ -875,7 +875,7 @@ module ActiveRecord
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
- # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
+ # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
# <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
@@ -1129,8 +1129,8 @@ module ActiveRecord
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
# [:conditions]
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
- # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
- # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
+ # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
+ # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
# or <tt>@blog.posts.build</tt>.
# [:order]
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
@@ -1341,12 +1341,12 @@ module ActiveRecord
"#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
)
end
-
+
def add_touch_callbacks(reflection, touch_attribute)
method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
association = send(reflection.name)
-
+
if touch_attribute == true
association.touch unless association.nil?
else
@@ -1558,7 +1558,7 @@ module ActiveRecord
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
-
+
if reflection.association_foreign_key == reflection.primary_key_name
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
end
@@ -1613,6 +1613,14 @@ module ActiveRecord
end
end
+ def construct_limited_ids_condition(where, options, join_dependency)
+ unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
+ "#{where.blank? ? 'WHERE ' : ' AND '} #{connection.quote_table_name table_name}.#{primary_key} IN (#{id_list}) "
+ else
+ throw :invalid_query
+ end
+ end
+
def select_limited_ids_list(options, join_dependency)
pk = columns_hash[primary_key]
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index ec49d40a12..56f3bd9faa 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -400,7 +400,7 @@ module ActiveRecord #:nodoc:
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
# instances in the current object space.
class Base
- ##
+ ##
# :singleton-method:
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
@@ -434,11 +434,11 @@ module ActiveRecord #:nodoc:
# as a Hash.
#
# For example, the following database.yml...
- #
+ #
# development:
# adapter: sqlite3
# database: db/development.sqlite3
- #
+ #
# production:
# adapter: sqlite3
# database: db/production.sqlite3
@@ -909,9 +909,7 @@ module ActiveRecord #:nodoc:
# Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
# associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
- sql = "DELETE FROM #{quoted_table_name} "
- add_conditions!(sql, conditions, scope(:find))
- connection.delete(sql, "#{name} Delete all")
+ arel_table.where(construct_conditions(conditions, scope(:find))).delete
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
@@ -1084,7 +1082,7 @@ module ActiveRecord #:nodoc:
# Returns an array of all the attributes that have been specified as readonly.
def readonly_attributes
- read_inheritable_attribute(:attr_readonly)
+ read_inheritable_attribute(:attr_readonly) || []
end
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
@@ -1356,7 +1354,7 @@ module ActiveRecord #:nodoc:
def self_and_descendants_from_active_record#nodoc:
klass = self
classes = [klass]
- while klass != klass.base_class
+ while klass != klass.base_class
classes << klass = klass.superclass
end
classes
@@ -1390,7 +1388,7 @@ module ActiveRecord #:nodoc:
def human_name(options = {})
defaults = self_and_descendants_from_active_record.map do |klass|
:"#{klass.name.underscore}"
- end
+ end
defaults << self.name.humanize
I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
end
@@ -1692,22 +1690,92 @@ module ActiveRecord #:nodoc:
end
end
- def construct_finder_sql(options)
+ def arel_table(table = table_name)
+ @arel_table = Arel(table)
+ end
+
+ def construct_finder_arel(options)
scope = scope(:find)
- sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
- sql << "FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} "
- add_joins!(sql, options[:joins], scope)
- add_conditions!(sql, options[:conditions], scope)
+ # TODO add lock to Arel
+ arel_table(table_name).
+ join(construct_join(options[:joins], scope)).
+ where(construct_conditions(options[:conditions], scope)).
+ project(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))).
+ group(construct_group(options[:group], options[:having], scope)).
+ order(construct_order(options[:order], scope)).
+ take(construct_limit(options, scope)).
+ skip(construct_offset(options, scope)
+ )
+ end
- add_group!(sql, options[:group], options[:having], scope)
- add_order!(sql, options[:order], scope)
- add_limit!(sql, options, scope)
- add_lock!(sql, options, scope)
+ def construct_finder_sql(options)
+ construct_finder_arel(options).to_sql
+ end
+
+ def construct_join(joins, scope = :auto)
+ merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
+ case merged_joins
+ when Symbol, Hash, Array
+ if array_of_strings?(merged_joins)
+ merged_joins.join(' ') + " "
+ else
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
+ " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
+ end
+ when String
+ " #{merged_joins} "
+ else
+ ""
+ end
+ end
+ def construct_group(group, having, scope = :auto)
+ sql = ''
+ if group
+ sql << group.to_s
+ sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having
+ else
+ scope = scope(:find) if :auto == scope
+ if scope && (scoped_group = scope[:group])
+ sql << scoped_group.to_s
+ sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having]
+ end
+ end
sql
end
+ def construct_order(order, scope = :auto)
+ sql = ''
+ scoped_order = scope[:order] if scope
+ if order
+ sql << order.to_s
+ if scoped_order && scoped_order != order
+ sql << ", #{scoped_order}"
+ end
+ else
+ sql << scoped_order.to_s if scoped_order
+ end
+ sql
+ end
+
+ def construct_limit(options, scope = :auto)
+ options[:limit] ||= scope[:limit] if scope
+ options[:limit]
+ end
+
+ def construct_offset(options, scope = :auto)
+ options[:offset] ||= scope[:offset] if scope
+ options[:offset]
+ end
+
+ def construct_conditions(conditions, scope = :auto)
+ conditions = [conditions]
+ conditions << scope[:conditions] if scope
+ conditions << type_condition if finder_needs_type_condition?
+ merge_conditions(*conditions)
+ end
+
# Merges includes so that the result is a valid +include+
def merge_includes(first, second)
(safe_to_array(first) + safe_to_array(second)).uniq
@@ -1963,7 +2031,7 @@ module ActiveRecord #:nodoc:
attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
[:#{attribute_names.join(',:')}], args # [:user_name, :password], args
) # )
- #
+ #
scoped(:conditions => attributes) # scoped(:conditions => attributes)
end # end
}, __FILE__, __LINE__
@@ -2483,7 +2551,7 @@ module ActiveRecord #:nodoc:
# name
# end
# end
- #
+ #
# user = User.find_by_name('Phusion')
# user_path(user) # => "/users/Phusion"
def to_param
@@ -2538,12 +2606,12 @@ module ActiveRecord #:nodoc:
# If +perform_validation+ is true validations run. If any of them fail
# the action is cancelled and +save+ returns +false+. If the flag is
# false validations are bypassed altogether. See
- # ActiveRecord::Validations for more information.
+ # ActiveRecord::Validations for more information.
#
# There's a series of callbacks associated with +save+. If any of the
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
# +save+ returns +false+. See ActiveRecord::Callbacks for further
- # details.
+ # details.
def save
create_or_update
end
@@ -2555,12 +2623,12 @@ module ActiveRecord #:nodoc:
#
# With <tt>save!</tt> validations always run. If any of them fail
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
- # for more information.
+ # for more information.
#
# There's a series of callbacks associated with <tt>save!</tt>. If any of
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
- # ActiveRecord::Callbacks for further details.
+ # ActiveRecord::Callbacks for further details.
def save!
create_or_update || raise(RecordNotSaved)
end
@@ -2584,11 +2652,7 @@ module ActiveRecord #:nodoc:
# be made (since they can't be persisted).
def destroy
unless new_record?
- connection.delete(
- "DELETE FROM #{self.class.quoted_table_name} " +
- "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}",
- "#{self.class.name} Destroy"
- )
+ arel_table.where(arel_table[self.class.primary_key].eq(id)).delete
end
freeze
@@ -2731,12 +2795,12 @@ module ActiveRecord #:nodoc:
# class User < ActiveRecord::Base
# attr_protected :is_admin
# end
- #
+ #
# user = User.new
# user.attributes = { :username => 'Phusion', :is_admin => true }
# user.username # => "Phusion"
# user.is_admin? # => false
- #
+ #
# user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
# user.is_admin? # => true
def attributes=(new_attributes, guard_protected_attributes = true)
@@ -2881,14 +2945,9 @@ module ActiveRecord #:nodoc:
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def update(attribute_names = @attributes.keys)
- quoted_attributes = attributes_with_quotes(false, false, attribute_names)
- return 0 if quoted_attributes.empty?
- connection.update(
- "UPDATE #{self.class.quoted_table_name} " +
- "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " +
- "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
- "#{self.class.name} Update"
- )
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
+ return 0 if attributes_with_values.empty?
+ arel_table.where(arel_table[self.class.primary_key].eq(id)).update(attributes_with_values)
end
# Creates a record with values matching those of the instance attributes
@@ -2998,6 +3057,25 @@ module ActiveRecord #:nodoc:
include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
end
+ def arel_table
+ @arel_table ||= Arel(self.class.table_name)
+ end
+
+ def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
+ attrs = {}
+ connection = self.class.connection
+ attribute_names.each do |name|
+ if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
+ value = read_attribute(name)
+
+ if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
+ attrs[arel_table[name]] = value
+ end
+ end
+ end
+ attrs
+ end
+
# Quote strings appropriately for SQL statements.
def quote_value(value, column = nil)
self.class.connection.quote(value, column)
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 7afa7c49bd..98f9450662 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -53,7 +53,7 @@ module ActiveRecord
#
# Person.average('age') # => 35.8
def average(column_name, options = {})
- calculate(:avg, column_name, options)
+ calculate(:average, column_name, options)
end
# Calculates the minimum value on a given column. The value is returned
@@ -62,7 +62,7 @@ module ActiveRecord
#
# Person.minimum('age') # => 7
def minimum(column_name, options = {})
- calculate(:min, column_name, options)
+ calculate(:minimum, column_name, options)
end
# Calculates the maximum value on a given column. The value is returned
@@ -71,7 +71,7 @@ module ActiveRecord
#
# Person.maximum('age') # => 93
def maximum(column_name, options = {})
- calculate(:max, column_name, options)
+ calculate(:maximum, column_name, options)
end
# Calculates the sum of values on a given column. The value is returned
@@ -123,20 +123,111 @@ module ActiveRecord
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
validate_calculation_options(operation, options)
- column_name = options[:select] if options[:select]
- column_name = '*' if column_name == :all
- column = column_for column_name
+ operation = operation.to_s.downcase
+
+ scope = scope(:find)
+
+ merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
+ joins = construct_join(options[:joins], scope)
+
+ if merged_includes.any?
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
+ joins << join_dependency.join_associations.collect{|join| join.association_join }.join
+ end
+
+ if operation == "count"
+ if merged_includes.any?
+ distinct = true
+ column_name = options[:select] || primary_key
+ end
+
+ distinct = nil if column_name.to_s =~ /\s*DISTINCT\s+/i
+ distinct ||= options[:distinct]
+ else
+ distinct = nil
+ end
+
catch :invalid_query do
+ conditions = construct_conditions(options[:conditions], scope)
+ conditions << construct_limited_ids_condition(conditions, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
+
if options[:group]
- return execute_grouped_calculation(operation, column_name, column, options)
+ return execute_grouped_calculation(operation, column_name, options.merge(:conditions => conditions, :joins => joins, :distinct => distinct))
else
- return execute_simple_calculation(operation, column_name, column, options)
+ return execute_simple_calculation(operation, column_name, options.merge(:conditions => conditions, :joins => joins, :distinct => distinct))
end
end
0
end
- protected
+ def execute_simple_calculation(operation, column_name, options) #:nodoc:
+ table = options[:from] || table_name
+
+ value = if operation == 'count'
+ if column_name == :all && options[:select].blank?
+ column_name = "*"
+ elsif !options[:select].blank?
+ column_name = options[:select]
+ end
+ construct_calculation_arel(options.merge(:select => Arel::Attribute.new(Arel(table), column_name).count(options[:distinct])))
+ else
+ construct_calculation_arel(options.merge(:select => Arel::Attribute.new(Arel(table), column_name).send(operation)))
+ end
+
+ type_cast_calculated_value(connection.select_value(value.to_sql), column_for(column_name), operation)
+ end
+
+ def execute_grouped_calculation(operation, column_name, options) #:nodoc:
+ group_attr = options[:group].to_s
+ association = reflect_on_association(group_attr.to_sym)
+ associated = association && association.macro == :belongs_to # only count belongs_to associations
+ group_field = associated ? association.primary_key_name : group_attr
+ group_alias = column_alias_for(group_field)
+ group_column = column_for group_field
+
+ options[:group] = connection.adapter_name == 'FrontBase' ? group_alias : group_field
+
+ aggregate_alias = column_alias_for(operation, column_name)
+
+ options[:select] = (operation == 'count' && column_name == :all) ?
+ "COUNT(*) AS count_all" :
+ Arel::Attribute.new(arel_table, column_name).send(operation).as(aggregate_alias).to_sql
+
+ options[:select] << ", #{group_field} AS #{group_alias}"
+
+ calculated_data = connection.select_all(construct_calculation_arel(options).to_sql)
+
+ if association
+ key_ids = calculated_data.collect { |row| row[group_alias] }
+ key_records = association.klass.base_class.find(key_ids)
+ key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
+ end
+
+ calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
+ key = type_cast_calculated_value(row[group_alias], group_column)
+ key = key_records[key] if associated
+ value = row[aggregate_alias]
+ all[key] = type_cast_calculated_value(value, column_for(column_name), operation)
+ all
+ end
+ end
+
+ protected
+
+ def construct_calculation_arel(options)
+ scope = scope(:find)
+
+ arel_table(options[:from] || table_name).
+ join(options[:joins]).
+ where(options[:conditions]).
+ project(options[:select]).
+ group(construct_group(options[:group], options[:having], scope)).
+ order(options[:order].to_s).
+ take(construct_limit(options, scope)).
+ skip(construct_offset(options, scope)
+ )
+ end
+
def construct_count_options_from_args(*args)
options = {}
column_name = :all
@@ -166,108 +257,6 @@ module ActiveRecord
[column_name || :all, options]
end
- def construct_calculation_sql(operation, column_name, options) #:nodoc:
- operation = operation.to_s.downcase
- options = options.symbolize_keys
-
- scope = scope(:find)
- merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
- aggregate_alias = column_alias_for(operation, column_name)
- column_name = "#{connection.quote_table_name(table_name)}.#{column_name}" if column_names.include?(column_name.to_s)
-
- if operation == 'count'
- if merged_includes.any?
- options[:distinct] = true
- column_name = options[:select] || [connection.quote_table_name(table_name), primary_key] * '.'
- end
-
- if options[:distinct]
- use_workaround = !connection.supports_count_distinct?
- end
- end
-
- if options[:distinct] && column_name.to_s !~ /\s*DISTINCT\s+/i
- distinct = 'DISTINCT '
- end
- sql = "SELECT #{operation}(#{distinct}#{column_name}) AS #{aggregate_alias}"
-
- # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
- sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
-
- sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
- if options[:from]
- sql << " FROM #{options[:from]} "
- else
- sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
- sql << " FROM #{connection.quote_table_name(table_name)} "
- end
-
- joins = ""
- add_joins!(joins, options[:joins], scope)
-
- if merged_includes.any?
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
- sql << join_dependency.join_associations.collect{|join| join.association_join }.join
- end
-
- sql << joins unless joins.blank?
-
- add_conditions!(sql, options[:conditions], scope)
- add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
-
- if options[:group]
- group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
- sql << " GROUP BY #{options[group_key]} "
- end
-
- if options[:group] && options[:having]
- having = sanitize_sql_for_conditions(options[:having])
-
- # FrontBase requires identifiers in the HAVING clause and chokes on function calls
- if connection.adapter_name == 'FrontBase'
- having.downcase!
- having.gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
- end
-
- sql << " HAVING #{having} "
- end
-
- sql << " ORDER BY #{options[:order]} " if options[:order]
- add_limit!(sql, options, scope)
- sql << ") #{aggregate_alias}_subquery" if use_workaround
- sql
- end
-
- def execute_simple_calculation(operation, column_name, column, options) #:nodoc:
- value = connection.select_value(construct_calculation_sql(operation, column_name, options))
- type_cast_calculated_value(value, column, operation)
- end
-
- def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
- group_attr = options[:group].to_s
- association = reflect_on_association(group_attr.to_sym)
- associated = association && association.macro == :belongs_to # only count belongs_to associations
- group_field = associated ? association.primary_key_name : group_attr
- group_alias = column_alias_for(group_field)
- group_column = column_for group_field
- sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias))
- calculated_data = connection.select_all(sql)
- aggregate_alias = column_alias_for(operation, column_name)
-
- if association
- key_ids = calculated_data.collect { |row| row[group_alias] }
- key_records = association.klass.base_class.find(key_ids)
- key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
- end
-
- calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
- key = type_cast_calculated_value(row[group_alias], group_column)
- key = key_records[key] if associated
- value = row[aggregate_alias]
- all[key] = type_cast_calculated_value(value, column, operation)
- all
- end
- end
private
def validate_calculation_options(operation, options = {})
@@ -299,11 +288,10 @@ module ActiveRecord
end
def type_cast_calculated_value(value, column, operation = nil)
- operation = operation.to_s.downcase
case operation
when 'count' then value.to_i
when 'sum' then type_cast_using_column(value || '0', column)
- when 'avg' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d
+ when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d
else type_cast_using_column(value, column)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index d5536e4d67..0aae97a6a9 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -212,7 +212,7 @@ module ActiveRecord
def supports_migrations? #:nodoc:
true
end
-
+
def supports_savepoints? #:nodoc:
true
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 002696d2c4..4b2ddac634 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -68,7 +68,7 @@ module ActiveRecord
# depending on the server specifics
super
end
-
+
# Maps PostgreSQL-specific data types to logical Rails types.
def simplified_type(field_type)
case field_type
@@ -103,7 +103,7 @@ module ActiveRecord
:string
# Arrays
when /^\D+\[\]$/
- :string
+ :string
# Object identifier types
when /^oid$/
:integer
@@ -112,7 +112,7 @@ module ActiveRecord
super
end
end
-
+
# Extracts the value from a PostgreSQL column default definition.
def self.extract_value_from_default(default)
case default
@@ -273,7 +273,7 @@ module ActiveRecord
def supports_ddl_transactions?
true
end
-
+
def supports_savepoints?
true
end
@@ -552,7 +552,7 @@ module ActiveRecord
def rollback_db_transaction
execute "ROLLBACK"
end
-
+
if defined?(PGconn::PQTRANS_IDLE)
# The ruby-pg driver supports inspecting the transaction status,
# while the ruby-postgres driver does not.
@@ -897,18 +897,18 @@ module ActiveRecord
sql = "DISTINCT ON (#{columns}) #{columns}, "
sql << order_columns * ', '
end
-
+
# Returns an ORDER BY clause for the passed order option.
- #
+ #
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
# by wrapping the +sql+ string as a sub-select and ordering in that query.
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
return sql if options[:order].blank?
-
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
-
+
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
end
@@ -1021,7 +1021,7 @@ module ActiveRecord
if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
# Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators):
- # (1) $12,345,678.12
+ # (1) $12,345,678.12
# (2) $12.345.678,12
case column = row[cell_index]
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index cf4f8864c6..e51748b7d2 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -89,12 +89,12 @@ module ActiveRecord
attribute_names.uniq!
begin
- affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
- UPDATE #{self.class.quoted_table_name}
- SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false, attribute_names))}
- WHERE #{self.class.primary_key} = #{quote_value(id)}
- AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}
- end_sql
+ affected_rows = arel_table.where(
+ arel_table[self.class.primary_key].eq(quoted_id).and(
+ arel_table[self.class.locking_column].eq(quote_value(previous_value))
+ )
+ ).update(arel_attributes_values(false, false, attribute_names))
+
unless affected_rows == 1
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
@@ -116,12 +116,11 @@ module ActiveRecord
lock_col = self.class.locking_column
previous_value = send(lock_col).to_i
- affected_rows = connection.delete(
- "DELETE FROM #{self.class.quoted_table_name} " +
- "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " +
- "AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}",
- "#{self.class.name} Destroy"
- )
+ affected_rows = arel_table.where(
+ arel_table[self.class.primary_key].eq(quoted_id).and(
+ arel_table[self.class.locking_column].eq(quote_value(previous_value))
+ )
+ ).delete
unless affected_rows == 1
raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object"
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index a7be3539d5..557ab07abf 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -103,7 +103,7 @@ module ActiveRecord
#
# The Rails package has several tools to help create and apply migrations.
#
- # To generate a new migration, you can use
+ # To generate a new migration, you can use
# script/generate migration MyNewMigration
#
# where MyNewMigration is the name of your migration. The generator will
@@ -121,16 +121,16 @@ module ActiveRecord
# def self.up
# add_column :tablenames, :fieldname, :string
# end
- #
+ #
# def self.down
# remove_column :tablenames, :fieldname
# end
# end
- #
+ #
# To run migrations against the currently configured database, use
# <tt>rake db:migrate</tt>. This will update the database by running all of the
# pending migrations, creating the <tt>schema_migrations</tt> table
- # (see "About the schema_migrations table" section below) if missing. It will also
+ # (see "About the schema_migrations table" section below) if missing. It will also
# invoke the db:schema:dump task, which will update your db/schema.rb file
# to match the structure of your database.
#
@@ -240,7 +240,7 @@ module ActiveRecord
# lower than the current schema version: when migrating up, those
# never-applied "interleaved" migrations will be automatically applied, and
# when migrating down, never-applied "interleaved" migrations will be skipped.
- #
+ #
# == Timestamped Migrations
#
# By default, Rails generates migrations that look like:
@@ -253,7 +253,7 @@ module ActiveRecord
# off by setting:
#
# config.active_record.timestamped_migrations = false
- #
+ #
# In environment.rb.
#
class Migration
@@ -338,10 +338,6 @@ module ActiveRecord
self.verbose = save
end
- def connection
- ActiveRecord::Base.connection
- end
-
def method_missing(method, *arguments, &block)
arg_list = arguments.map(&:inspect) * ', '
@@ -349,7 +345,7 @@ module ActiveRecord
unless arguments.empty? || method == :execute
arguments[0] = Migrator.proper_table_name(arguments.first)
end
- connection.send(method, *arguments, &block)
+ Base.connection.send(method, *arguments, &block)
end
end
end
@@ -389,9 +385,9 @@ module ActiveRecord
def rollback(migrations_path, steps=1)
migrator = self.new(:down, migrations_path)
start_index = migrator.migrations.index(migrator.current_migration)
-
+
return unless start_index
-
+
finish = migrator.migrations[start_index + steps]
down(migrations_path, finish ? finish.version : 0)
end
@@ -403,7 +399,7 @@ module ActiveRecord
def down(migrations_path, target_version = nil)
self.new(:down, migrations_path, target_version).migrate
end
-
+
def run(direction, migrations_path, target_version)
self.new(direction, migrations_path, target_version).run
end
@@ -413,7 +409,8 @@ module ActiveRecord
end
def get_all_versions
- Base.connection.select_values("SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).sort
+ table = Arel(schema_migrations_table_name)
+ Base.connection.select_values(table.project(table['version']).to_sql).map(&:to_i).sort
end
def current_version
@@ -434,17 +431,17 @@ module ActiveRecord
def initialize(direction, migrations_path, target_version = nil)
raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
Base.connection.initialize_schema_migrations_table
- @direction, @migrations_path, @target_version = direction, migrations_path, target_version
+ @direction, @migrations_path, @target_version = direction, migrations_path, target_version
end
def current_version
migrated.last || 0
end
-
+
def current_migration
migrations.detect { |m| m.version == current_version }
end
-
+
def run
target = migrations.detect { |m| m.version == @target_version }
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
@@ -461,14 +458,14 @@ module ActiveRecord
if target.nil? && !@target_version.nil? && @target_version > 0
raise UnknownMigrationVersionError.new(@target_version)
end
-
+
start = up? ? 0 : (migrations.index(current) || 0)
finish = migrations.index(target) || migrations.size - 1
runnable = migrations[start..finish]
-
+
# skip the last migration if we're headed down, but not ALL the way down
runnable.pop if down? && !target.nil?
-
+
runnable.each do |migration|
Base.logger.info "Migrating to #{migration.name} (#{migration.version})"
@@ -496,28 +493,28 @@ module ActiveRecord
def migrations
@migrations ||= begin
files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
-
+
migrations = files.inject([]) do |klasses, file|
version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
-
+
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
-
+
if klasses.detect { |m| m.version == version }
- raise DuplicateMigrationVersionError.new(version)
+ raise DuplicateMigrationVersionError.new(version)
end
if klasses.detect { |m| m.name == name.camelize }
- raise DuplicateMigrationNameError.new(name.camelize)
+ raise DuplicateMigrationNameError.new(name.camelize)
end
-
+
migration = MigrationProxy.new
migration.name = name.camelize
migration.version = version
migration.filename = file
klasses << migration
end
-
+
migrations = migrations.sort_by(&:version)
down? ? migrations.reverse : migrations
end
@@ -534,15 +531,15 @@ module ActiveRecord
private
def record_version_state_after_migrating(version)
- sm_table = self.class.schema_migrations_table_name
+ table = Arel(self.class.schema_migrations_table_name)
@migrated_versions ||= []
if down?
- @migrated_versions.delete(version.to_i)
- Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'")
+ @migrated_versions.delete(version)
+ table.where(table["version"].eq(version.to_s)).delete
else
- @migrated_versions.push(version.to_i).sort!
- Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')")
+ @migrated_versions.push(version).sort!
+ table.insert table["version"] => version.to_s
end
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 7141531740..5f08c40005 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -26,7 +26,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_construct_finder_sql_applies_association_conditions
sql = Author.send(:construct_finder_sql, :joins => :categories_like_general, :conditions => "TERMINATING_MARKER")
- assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?.*TERMINATING_MARKER/, sql
+ assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?(.|\n)*TERMINATING_MARKER/, sql
end
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index fd455e0864..c29d79f7a0 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -25,7 +25,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_return_nil_as_average
assert_nil NumericData.average(:bank_balance)
end
-
+
def test_type_cast_calculated_value_should_convert_db_averages_of_fixnum_class_to_decimal
assert_equal 0, NumericData.send(:type_cast_calculated_value, 0, nil, 'avg')
assert_equal 53.0, NumericData.send(:type_cast_calculated_value, 53, nil, 'avg')
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index eae5a60829..1943af2b9c 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -30,7 +30,7 @@ class InheritanceTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.store_full_sti_class = old
end
-
+
def test_should_store_full_class_name_with_store_full_sti_class_option_enabled
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
@@ -39,7 +39,7 @@ class InheritanceTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.store_full_sti_class = old
end
-
+
def test_different_namespace_subclass_should_load_correctly_with_store_full_sti_class_option
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index d8246f49b8..bea5c5fb76 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -379,7 +379,7 @@ class NestedScopingTest < ActiveRecord::TestCase
poor_jamis = developers(:poor_jamis)
Developer.with_scope(:find => { :conditions => "salary < 100000" }) do
Developer.with_scope(:find => { :offset => 1, :order => 'id asc' }) do
- assert_sql /ORDER BY id asc / do
+ assert_sql /ORDER BY id asc/ do
assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
end
end