aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorGonçalo Silva <goncalossilva@gmail.com>2010-06-30 23:01:30 +0100
committerGonçalo Silva <goncalossilva@gmail.com>2010-06-30 23:01:30 +0100
commitd2c633ba0bfb7baacdee89a46d7d036d24c68817 (patch)
tree8f0974852b51597652e6ae73da26f3eb80fe878b /activerecord/lib
parent92c0f17d6d2a958d3a6285b0e5408e9e0e7122e1 (diff)
parentc63cf7bf0db708fe46a929cf57649ab5a92034af (diff)
downloadrails-d2c633ba0bfb7baacdee89a46d7d036d24c68817.tar.gz
rails-d2c633ba0bfb7baacdee89a46d7d036d24c68817.tar.bz2
rails-d2c633ba0bfb7baacdee89a46d7d036d24c68817.zip
Merge branch 'master' of http://github.com/rails/rails
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/aggregations.rb7
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/associations.rb28
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb33
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb1
-rw-r--r--activerecord/lib/active_record/autosave_association.rb8
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/base.rb119
-rw-r--r--activerecord/lib/active_record/callbacks.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb54
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb20
-rw-r--r--activerecord/lib/active_record/counter_cache.rb1
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb5
-rw-r--r--activerecord/lib/active_record/dynamic_scope_match.rb7
-rw-r--r--activerecord/lib/active_record/errors.rb3
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb32
-rw-r--r--activerecord/lib/active_record/migration.rb99
-rw-r--r--activerecord/lib/active_record/named_scope.rb46
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb30
-rw-r--r--activerecord/lib/active_record/observer.rb5
-rw-r--r--activerecord/lib/active_record/persistence.rb31
-rw-r--r--activerecord/lib/active_record/query_cache.rb1
-rw-r--r--activerecord/lib/active_record/railtie.rb5
-rw-r--r--activerecord/lib/active_record/railties/databases.rake44
-rw-r--r--activerecord/lib/active_record/railties/log_subscriber.rb32
-rw-r--r--activerecord/lib/active_record/reflection.rb53
-rw-r--r--activerecord/lib/active_record/relation.rb48
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb249
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb27
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/session_store.rb16
-rw-r--r--activerecord/lib/active_record/test_case.rb3
-rw-r--r--activerecord/lib/active_record/timestamp.rb18
-rw-r--r--activerecord/lib/active_record/transactions.rb7
-rw-r--r--activerecord/lib/active_record/validations.rb7
-rw-r--r--activerecord/lib/active_record/validations/associated.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb18
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb2
57 files changed, 730 insertions, 464 deletions
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 45aaea062d..c45400d3d9 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Aggregations
module Aggregations # :nodoc:
extend ActiveSupport::Concern
@@ -189,7 +190,7 @@ module ActiveRecord
# :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
# :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
#
- def composed_of(part_id, options = {}, &block)
+ def composed_of(part_id, options = {})
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
name = part_id.id2name
@@ -198,9 +199,7 @@ module ActiveRecord
mapping = [ mapping ] unless mapping.first.is_a?(Array)
allow_nil = options[:allow_nil] || false
constructor = options[:constructor] || :new
- converter = options[:converter] || block
-
- ActiveSupport::Deprecation.warn('The conversion block has been deprecated, use the :converter option instead.', caller) if block_given?
+ converter = options[:converter]
reader_method(name, class_name, mapping, allow_nil, constructor)
writer_method(name, class_name, mapping, allow_nil, converter)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 9b59266bbc..4caa434fc0 100755..100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -88,8 +88,8 @@ module ActiveRecord
end
end
- # This error is raised when trying to destroy a parent instance in a N:1, 1:1 assosications
- # (has_many, has_one) when there is at least 1 child assosociated instance.
+ # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
+ # (has_many, has_one) when there is at least 1 child associated instance.
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
class DeleteRestrictionError < ActiveRecordError #:nodoc:
def initialize(reflection)
@@ -886,11 +886,13 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. true by default.
# [:autosave]
- # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
+ # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
+ # If false, never save or destroy the associated objects.
+ # By default, only save associated objects that are new records.
# [:inverse_of]
# Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_many</tt>
# association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
- # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail.
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
# has_many :comments, :order => "posted_on"
@@ -1001,11 +1003,13 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated object when saving the parent object. +false+ by default.
# [:autosave]
- # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
+ # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
+ # If false, never save or destroy the associated object.
+ # By default, only save the associated object if it's a new record.
# [:inverse_of]
# Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_one</tt>
# association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
- # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail.
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -1103,14 +1107,16 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
# [:autosave]
- # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
+ # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
+ # If false, never save or destroy the associated object.
+ # By default, only save the associated object if it's a new record.
# [:touch]
# If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or
# destroyed. If you specify a symbol, that attribute will be updated with the current time instead of the updated_at/on attribute.
# [:inverse_of]
# Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated object that is the inverse of this <tt>belongs_to</tt>
# association. Does not work in combination with the <tt>:polymorphic</tt> options.
- # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail.
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
@@ -1290,7 +1296,9 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +true+ by default.
# [:autosave]
- # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
+ # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
+ # If false, never save or destroy the associated objects.
+ # By default, only save associated objects that are new records.
#
# Option examples:
# has_and_belongs_to_many :projects
@@ -2066,7 +2074,7 @@ module ActiveRecord
unless klass.descends_from_active_record?
sti_column = aliased_table[klass.inheritance_column]
sti_condition = sti_column.eq(klass.sti_name)
- klass.send(:subclasses).each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
+ klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
@join << sti_condition
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index d9903243ce..ddf4ce4058 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -3,6 +3,8 @@ require 'active_support/core_ext/array/wrap'
module ActiveRecord
module Associations
+ # = Active Record Association Collection
+ #
# AssociationCollection is an abstract class that provides common stuff to
# ease the implementation of association proxies that represent
# collections. See the class hierarchy in AssociationProxy.
@@ -24,10 +26,10 @@ module ActiveRecord
delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
- def select(select = nil, &block)
+ def select(select = nil)
if block_given?
load_target
- @target.select(&block)
+ @target.select.each { |e| yield e }
else
scoped.select(select)
end
@@ -121,7 +123,7 @@ module ActiveRecord
end
end
- # Add +records+ to this association. Returns +self+ so method calls may be chained.
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
def <<(*records)
result = true
@@ -166,7 +168,7 @@ module ActiveRecord
reset_target!
reset_named_scopes_cache!
end
-
+
# Calculate sum using SQL, not Enumerable
def sum(*args)
if block_given?
@@ -239,7 +241,7 @@ module ActiveRecord
if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
destroy_all
- else
+ else
delete_all
end
@@ -388,7 +390,11 @@ module ActiveRecord
begin
if !loaded?
if @target.is_a?(Array) && @target.any?
- @target = find_target + @target.find_all {|t| t.new_record? }
+ @target = find_target.map do |f|
+ i = @target.index(f)
+ t = @target.delete_at(i) if i
+ (t && t.changed?) ? t : f
+ end + @target
else
@target = find_target
end
@@ -403,6 +409,17 @@ module ActiveRecord
end
def method_missing(method, *args)
+ case method.to_s
+ when 'find_or_create'
+ return find(:first, :conditions => args.first) || create(args.first)
+ when /^find_or_create_by_(.*)$/
+ rest = $1
+ return send("find_by_#{rest}", *args) ||
+ method_missing("create_by_#{rest}", *args)
+ when /^create_by_(.*)$/
+ return create Hash[$1.split('_and_').zip(args)]
+ end
+
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
if block_given?
super { |*block_args| yield(*block_args) }
@@ -514,8 +531,8 @@ module ActiveRecord
def callbacks_for(callback_name)
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
- end
-
+ end
+
def ensure_owner_is_not_new
if @owner.new_record?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index e88618d278..f333f4d603 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -2,6 +2,8 @@ require 'active_support/core_ext/array/wrap'
module ActiveRecord
module Associations
+ # = Active Record Associations
+ #
# This is the root class of all association proxies:
#
# AssociationProxy
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index d2f2267e5c..c2a6495db5 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Belongs To Associations
module Associations
class BelongsToAssociation < AssociationProxy #:nodoc:
def create(attributes = {})
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index f6edd6383c..38454ec242 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Belongs To Polymorphic Association
module Associations
class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
def replace(record)
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 7f39a189e4..c989c3536d 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Has And Belongs To Many Association
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
def create(attributes = {})
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 0464e8ceea..d74fb7c702 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Has Many Association
module Associations
# This is the proxy that handles a has many association.
#
@@ -109,7 +110,11 @@ module ActiveRecord
create_scoping = {}
set_belongs_to_association_for(create_scoping)
{
- :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
+ :find => { :conditions => @finder_sql,
+ :readonly => false,
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit],
+ :include => @reflection.options[:include]},
:create => create_scoping
}
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 5338bb099d..17f850756f 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -2,6 +2,7 @@ require "active_record/associations/through_association_scope"
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Has Many Through Association
module Associations
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
include ThroughAssociationScope
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index ea769fd48b..68b8b792ad 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Belongs To Has One Association
module Associations
class HasOneAssociation < AssociationProxy #:nodoc:
def initialize(owner, reflection)
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index a79bf943d1..fba0a2bfcc 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -1,6 +1,7 @@
require "active_record/associations/through_association_scope"
module ActiveRecord
+ # = Active Record Has One Through Association
module Associations
class HasOneThroughAssociation < HasOneAssociation
include ThroughAssociationScope
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 1d2f323112..22e1033a9d 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Through Association Scope
module Associations
module ThroughAssociationScope
@@ -91,7 +92,7 @@ module ActiveRecord
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
- # TODO: revist this to allow it for deletion, supposing dependent option is supported
+ # TODO: revisit this to allow it for deletion, supposing dependent option is supported
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index c117271c71..56e18eced0 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/enumerable'
module ActiveRecord
+ # = Active Record Attribute Methods
module AttributeMethods #:nodoc:
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 0dcadfab5f..7517896235 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/array/wrap'
module ActiveRecord
+ # = Active Record Autosave Association
+ #
# AutosaveAssociation is a module that takes care of automatically saving
# your associations when the parent is saved. In addition to saving, it
# also destroys any associations that were marked for destruction.
@@ -145,12 +147,12 @@ module ActiveRecord
# add_autosave_association_callbacks(reflect_on_association(name))
# end
ASSOCIATION_TYPES.each do |type|
- module_eval %{
+ module_eval <<-CODE, __FILE__, __LINE__ + 1
def #{type}(name, options = {})
super
add_autosave_association_callbacks(reflect_on_association(name))
end
- }
+ CODE
end
# Adds a validate and save callback for the association as specified by
@@ -375,7 +377,7 @@ module ActiveRecord
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
self[reflection.primary_key_name] = association_id
- # TODO: Removing this code doesn't seem to matter…
+ # TODO: Removing this code doesn't seem to matter...
if reflection.options[:polymorphic]
self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 3b6ffa46f2..c8795e4496 100755..100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -2,6 +2,7 @@ require 'yaml'
require 'set'
require 'active_support/benchmarkable'
require 'active_support/dependencies'
+require 'active_support/descendants_tracker'
require 'active_support/time'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'
@@ -14,13 +15,17 @@ require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/blank'
require 'arel'
require 'active_record/errors'
+require 'active_record/log_subscriber'
module ActiveRecord #:nodoc:
+ # = Active Record
+ #
# Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
# which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
# is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
@@ -274,27 +279,17 @@ module ActiveRecord #:nodoc:
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
cattr_accessor :logger, :instance_writer => false
- def self.inherited(child) #:nodoc:
- @@subclasses[self] ||= []
- @@subclasses[self] << child
- super
- end
+ class << self
+ def reset_subclasses #:nodoc:
+ ActiveSupport::Deprecation.warn 'ActiveRecord::Base.reset_subclasses no longer does anything in Rails 3. It will be removed in the final release; please update your apps and plugins.', caller
+ end
- def self.reset_subclasses #:nodoc:
- nonreloadables = []
- subclasses.each do |klass|
- unless ActiveSupport::Dependencies.autoloaded? klass
- nonreloadables << klass
- next
- end
- klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
- klass.instance_methods(false).each { |m| klass.send :undef_method, m }
+ def subclasses
+ descendants
end
- @@subclasses = {}
- nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
- end
- @@subclasses = {}
+ deprecate :subclasses => :descendants
+ end
##
# :singleton-method:
@@ -403,7 +398,7 @@ module ActiveRecord #:nodoc:
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
+ delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -725,14 +720,6 @@ module ActiveRecord #:nodoc:
end
alias :sequence_name= :set_sequence_name
- # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
- def class_name(table_name = table_name) # :nodoc:
- # remove any prefix and/or suffix from the table name
- class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
- class_name = class_name.singularize if pluralize_table_names
- class_name
- end
-
# Indicates whether the table associated with this class exists
def table_exists?
connection.table_exists?(table_name)
@@ -806,11 +793,11 @@ module ActiveRecord #:nodoc:
def reset_column_information
undefine_attribute_methods
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
- @arel_engine = @unscoped = @arel_table = nil
+ @arel_engine = @relation = @arel_table = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
- subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
+ descendants.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
end
def attribute_method?(attribute)
@@ -909,9 +896,9 @@ module ActiveRecord #:nodoc:
store_full_sti_class ? name : name.demodulize
end
- def unscoped
- @unscoped ||= Relation.new(self, arel_table)
- finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
+ def relation
+ @relation ||= Relation.new(self, arel_table)
+ finder_needs_type_condition? ? @relation.where(type_condition) : @relation
end
def arel_table
@@ -928,6 +915,31 @@ module ActiveRecord #:nodoc:
end
end
+ # Returns a scope for this class without taking into account the default_scope.
+ #
+ # class Post < ActiveRecord::Base
+ # default_scope :published => true
+ # end
+ #
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
+ #
+ # This method also accepts a block meaning that all queries inside the block will
+ # not use the default_scope:
+ #
+ # Post.unscoped {
+ # limit(10) # Fires "SELECT * FROM posts LIMIT 10"
+ # }
+ #
+ def unscoped
+ block_given? ? relation.scoping { yield } : relation
+ end
+
+ def scoped_methods #:nodoc:
+ key = :"#{self}_scoped_methods"
+ Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
+ end
+
private
# Finder methods must instantiate through this method to work with the
# single-table inheritance model that makes it possible to create
@@ -935,8 +947,8 @@ module ActiveRecord #:nodoc:
def instantiate(record)
object = find_sti_class(record[inheritance_column]).allocate
- object.instance_variable_set(:'@attributes', record)
- object.instance_variable_set(:'@attributes_cache', {})
+ object.instance_variable_set(:@attributes, record)
+ object.instance_variable_set(:@attributes_cache, {})
object.instance_variable_set(:@new_record, false)
object.instance_variable_set(:@readonly, false)
object.instance_variable_set(:@destroyed, false)
@@ -975,7 +987,7 @@ module ActiveRecord #:nodoc:
def type_condition
sti_column = arel_table[inheritance_column]
condition = sti_column.eq(sti_name)
- subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
+ descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
condition
end
@@ -1162,16 +1174,22 @@ module ActiveRecord #:nodoc:
# Works like with_scope, but discards any nested properties.
def with_exclusive_scope(method_scoping = {}, &block)
- with_scope(method_scoping, :overwrite, &block)
- end
+ if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
+ raise ArgumentError, <<-MSG
+New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
- # Returns a list of all subclasses of this class, meaning all descendants.
- def subclasses
- @@subclasses[self] ||= []
- @@subclasses[self] + @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
- end
+ User.unscoped.where(:active => true)
- public :subclasses
+Or call unscoped with a block:
+
+ User.unscoped do
+ User.where(:active => true).all
+ end
+
+MSG
+ end
+ with_scope(method_scoping, :overwrite, &block)
+ end
# Sets the default options for the model. The format of the
# <tt>options</tt> argument is the same as in find.
@@ -1183,11 +1201,6 @@ module ActiveRecord #:nodoc:
self.default_scoping << construct_finder_arel(options, default_scoping.pop)
end
- def scoped_methods #:nodoc:
- key = :"#{self}_scoped_methods"
- Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
- end
-
def current_scoped_methods #:nodoc:
scoped_methods.last
end
@@ -1440,14 +1453,6 @@ module ActiveRecord #:nodoc:
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
# application specific and is therefore left to the application to implement according to its need.
def initialize_copy(other)
- # Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
- # deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
- # over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
- # For example in the test suite the topic model's after_initialize method sets the author_email_address to
- # test@test.com. I would have thought this would mean that all cloned models would have an author email address
- # of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
- # after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
- # for all tests to pass. This makes no sense to me.
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
cloned_attributes.delete(self.class.primary_key)
@@ -1460,6 +1465,7 @@ module ActiveRecord #:nodoc:
end
clear_aggregation_cache
+ clear_association_cache
@attributes_cache = {}
@new_record = true
ensure_proper_type
@@ -1900,6 +1906,7 @@ module ActiveRecord #:nodoc:
extend ActiveModel::Naming
extend QueryCache::ClassMethods
extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
include ActiveModel::Conversion
include Validations
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 44fee12001..637dac450b 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/array/wrap'
module ActiveRecord
+ # = Active Record Callbacks
+ #
# Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
# before or after an alteration of the object state. This can be used to make sure that associated and
# dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
@@ -234,8 +236,7 @@ module ActiveRecord
included do
extend ActiveModel::Callbacks
-
- define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ include ActiveModel::Validations::Callbacks
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
@@ -249,29 +250,6 @@ module ActiveRecord
send(meth.to_sym, meth.to_sym)
end
end
-
- def before_validation(*args, &block)
- options = args.last
- if options.is_a?(Hash) && options[:on]
- options[:if] = Array.wrap(options[:if])
- options[:if] << "@_on_validate == :#{options[:on]}"
- end
- set_callback(:validation, :before, *args, &block)
- end
-
- def after_validation(*args, &block)
- options = args.extract_options!
- options[:prepend] = true
- options[:if] = Array.wrap(options[:if])
- options[:if] << "!halted && value != false"
- options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
- set_callback(:validation, :after, *(args << options), &block)
- end
- end
-
- def valid?(*) #:nodoc:
- @_on_validate = new_record? ? :create : :update
- _run_validation_callbacks { super }
end
def destroy #:nodoc:
@@ -286,6 +264,7 @@ module ActiveRecord
end
private
+
def create_or_update #:nodoc:
_run_save_callbacks { super }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 979ed52f4a..c2d79a421d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -144,7 +144,9 @@ module ActiveRecord
@connections.each do |conn|
conn.disconnect! if conn.requires_reloading?
end
- @connections = []
+ @connections.delete_if do |conn|
+ conn.requires_reloading?
+ end
end
# Verify active connections and remove and disconnect connections
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index b9fb452eee..25432e9985 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -304,7 +304,7 @@ module ActiveRecord
begin
record.rolledback!(rollback)
rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
+ record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
end
@@ -319,7 +319,7 @@ module ActiveRecord
begin
record.committed!
rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
+ record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7d58bc2adf..7691b6a788 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -582,6 +582,11 @@ module ActiveRecord
@base.add_column(@table_name, column_name, type, options)
end
+ # Checks to see if a column exists. See SchemaStatements#column_exists?
+ def column_exists?(column_name, type = nil, options = nil)
+ @base.column_exists?(@table_name, column_name, type, options)
+ end
+
# Adds a new index to the table. +column_name+ can be a single Symbol, or
# an Array of Symbols. See SchemaStatements#add_index
#
@@ -596,6 +601,11 @@ module ActiveRecord
@base.add_index(@table_name, column_name, options)
end
+ # Checks to see if an index exists. See SchemaStatements#index_exists?
+ def index_exists?(column_name, options = {})
+ @base.index_exists?(@table_name, column_name, options)
+ end
+
# Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps
# ===== Example
# t.timestamps
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index d3499cea72..76b65bf219 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -24,10 +24,53 @@ module ActiveRecord
# Returns an array of indexes for the given table.
# def indexes(table_name, name = nil) end
+ # Checks to see if an index exists on a table for a given index definition
+ #
+ # === Examples
+ # # Check an index exists
+ # index_exists?(:suppliers, :company_id)
+ #
+ # # Check an index on multiple columns exists
+ # index_exists?(:suppliers, [:company_id, :company_type])
+ #
+ # # Check a unique index exists
+ # index_exists?(:suppliers, :company_id, :unique => true)
+ #
+ # # Check an index with a custom name exists
+ # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
+ def index_exists?(table_name, column_name, options = {})
+ column_names = Array.wrap(column_name)
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
+ if options[:unique]
+ indexes(table_name).any?{ |i| i.unique && i.name == index_name }
+ else
+ indexes(table_name).any?{ |i| i.name == index_name }
+ end
+ end
+
# Returns an array of Column objects for the table specified by +table_name+.
# See the concrete implementation for details on the expected parameter values.
def columns(table_name, name = nil) end
+ # Checks to see if a column exists in a given table.
+ #
+ # === Examples
+ # # Check a column exists
+ # column_exists?(:suppliers, :name)
+ #
+ # # Check a column exists of a particular type
+ # column_exists?(:suppliers, :name, :string)
+ #
+ # # Check a column exists with a specific definition
+ # column_exists?(:suppliers, :name, :string, :limit => 100)
+ def column_exists?(table_name, column_name, type = nil, options = {})
+ columns(table_name).any?{ |c| c.name == column_name.to_s &&
+ (!type || c.type == type) &&
+ (!options[:limit] || c.limit == options[:limit]) &&
+ (!options[:precision] || c.precision == options[:precision]) &&
+ (!options[:scale] || c.scale == options[:scale]) }
+ end
+
# Creates a new table with the name +table_name+. +table_name+ may either
# be a String or a Symbol.
#
@@ -205,6 +248,7 @@ module ActiveRecord
# remove_column(:suppliers, :qualification)
# remove_columns(:suppliers, :qualification, :experience)
def remove_column(table_name, *column_names)
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
column_names.flatten.each do |column_name|
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
end
@@ -292,7 +336,7 @@ module ActiveRecord
@logger.warn("Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters. Skipping.")
return
end
- if index_exists?(table_name, index_name, false)
+ if index_name_exists?(table_name, index_name, false)
@logger.warn("Index name '#{index_name}' on table '#{table_name}' already exists. Skipping.")
return
end
@@ -313,7 +357,7 @@ module ActiveRecord
# remove_index :accounts, :name => :by_branch_party
def remove_index(table_name, options = {})
index_name = index_name(table_name, options)
- unless index_exists?(table_name, index_name, true)
+ unless index_name_exists?(table_name, index_name, true)
@logger.warn("Index name '#{index_name}' on table '#{table_name}' does not exist. Skipping.")
return
end
@@ -321,7 +365,7 @@ module ActiveRecord
end
def remove_index!(table_name, index_name) #:nodoc:
- execute "DROP INDEX #{quote_column_name(index_name)} ON #{table_name}"
+ execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
end
# Rename an index.
@@ -350,11 +394,11 @@ module ActiveRecord
end
end
- # Verify the existence of an index.
+ # Verify the existence of an index with a given name.
#
# The default argument is returned if the underlying implementation does not define the indexes method,
# as there's no way to determine the correct answer in that case.
- def index_exists?(table_name, index_name, default)
+ def index_name_exists?(table_name, index_name, default)
return default unless respond_to?(:indexes)
indexes(table_name).detect { |i| i.name == index_name }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 4ee9fee4a9..be8d1bd76b 100755..100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -107,7 +107,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
# Override to turn off referential integrity while executing <tt>&block</tt>.
- def disable_referential_integrity(&block)
+ def disable_referential_integrity
yield
end
@@ -142,9 +142,10 @@ module ActiveRecord
# this should be overridden by concrete adapters
end
- # Returns true if its safe to reload the connection between requests for development mode.
+ # Returns true if its required to reload the connection between requests for development mode.
+ # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
def requires_reloading?
- true
+ false
end
# Checks whether the connection to the database is still active (i.e. not stale).
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 7c7bc5e292..aa3626a37e 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -219,7 +219,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
- def disable_referential_integrity(&block) #:nodoc:
+ def disable_referential_integrity #:nodoc:
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
begin
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e84242601b..2fe2ae7136 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -216,7 +216,10 @@ module ActiveRecord
super(connection, logger)
@connection_parameters, @config = connection_parameters, config
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
+ @local_tz = nil
connect
+ @local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
end
# Is this connection alive and ready for queries?
@@ -372,7 +375,7 @@ module ActiveRecord
return false
end
- def disable_referential_integrity(&block) #:nodoc:
+ def disable_referential_integrity #:nodoc:
if supports_disable_referential_integrity?() then
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
end
@@ -606,27 +609,22 @@ module ActiveRecord
SQL
- indexes = []
-
- indexes = result.map do |row|
+ result.map do |row|
index_name = row[0]
unique = row[1] == 't'
indkey = row[2].split(" ")
oid = row[3]
- columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = r[0]; attlist}
- SELECT a.attname, a.attnum
+ columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
+ SELECT a.attnum, a.attname
FROM pg_attribute a
WHERE a.attrelid = #{oid}
AND a.attnum IN (#{indkey.join(",")})
SQL
- column_names = indkey.map {|attnum| columns[attnum] }
- IndexDefinition.new(table_name, index_name, unique, column_names)
-
- end
-
- indexes
+ column_names = columns.values_at(*indkey).compact
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
+ end.compact
end
# Returns the list of all column definitions for a table.
@@ -929,9 +927,8 @@ module ActiveRecord
# TIMESTAMP WITH ZONE types in UTC.
if ActiveRecord::Base.default_timezone == :utc
execute("SET time zone 'UTC'")
- else
- offset = Time.local(2000).utc_offset / 3600
- execute("SET time zone '#{offset}'")
+ elsif @local_tz
+ execute("SET time zone '#{@local_tz}'")
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index f295af16f0..0d9a86a1ea 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -6,6 +6,10 @@ module ActiveRecord
def self.sqlite3_connection(config) # :nodoc:
parse_sqlite_config!(config)
+ unless 'sqlite3' == config[:adapter]
+ raise ArgumentError, 'adapter name should be "sqlite3"'
+ end
+
unless self.class.const_defined?(:SQLite3)
require_library_or_gem(config[:adapter])
end
@@ -24,13 +28,13 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
class SQLite3Adapter < SQLiteAdapter # :nodoc:
-
+
# Returns the current database encoding format as a string, eg: 'UTF-8'
def encoding
if @connection.respond_to?(:encoding)
- @connection.encoding[0]['encoding']
+ @connection.encoding.to_s
else
- encoding = @connection.send(:get_query_pragma, 'encoding')
+ encoding = @connection.execute('PRAGMA encoding')
encoding[0]['encoding']
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index deb62e3802..1927585c49 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -151,7 +151,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def execute(sql, name = nil) #:nodoc:
- catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
+ log(sql, name) { @connection.execute(sql) }
end
def update_sql(sql, name = nil) #:nodoc:
@@ -176,15 +176,15 @@ module ActiveRecord
end
def begin_db_transaction #:nodoc:
- catch_schema_changes { @connection.transaction }
+ @connection.transaction
end
def commit_db_transaction #:nodoc:
- catch_schema_changes { @connection.commit }
+ @connection.commit
end
def rollback_db_transaction #:nodoc:
- catch_schema_changes { @connection.rollback }
+ @connection.rollback
end
# SCHEMA STATEMENTS ========================================
@@ -246,6 +246,7 @@ module ActiveRecord
end
def remove_column(table_name, *column_names) #:nodoc:
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
column_names.flatten.each do |column_name|
alter_table(table_name) do |definition|
definition.columns.delete(definition[column_name])
@@ -390,17 +391,6 @@ module ActiveRecord
end
end
- def catch_schema_changes
- return yield
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message =~ /database schema has changed/
- reconnect!
- retry
- else
- raise
- end
- end
-
def sqlite_version
@sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 9601ed6afd..999322129a 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Counter Cache
module CounterCache
# Resets one or more counter caches to their correct value using an SQL
# count query. This is useful when adding new counter caches, or if the
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
index fa7a19487c..b39b291352 100644
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ b/activerecord/lib/active_record/dynamic_finder_match.rb
@@ -1,4 +1,9 @@
module ActiveRecord
+
+ # = Active Record Dynamic Finder Match
+ #
+ # Provides dynamic attribute-based finders such as <tt>find_by_country</tt>
+ # if, for example, the <tt>Person</tt> has an attribute with that name.
class DynamicFinderMatch
def self.match(method)
df_match = self.new(method)
diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb
index f796ba669a..15f65be6bc 100644
--- a/activerecord/lib/active_record/dynamic_scope_match.rb
+++ b/activerecord/lib/active_record/dynamic_scope_match.rb
@@ -1,4 +1,11 @@
module ActiveRecord
+
+ # = Active Record Dynamic Scope Match
+ #
+ # Provides dynamic attribute-based scopes such as <tt>scoped_by_price(4.99)</tt>
+ # if, for example, the <tt>Product</tt> has an attribute with that name. You can
+ # chain more <tt>scoped_by_* </tt> methods after the other. It acts like a named
+ # scope except that it's dynamic.
class DynamicScopeMatch
def self.match(method)
ds_match = self.new(method)
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index cf5ddca2ba..7aa725d095 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -1,4 +1,7 @@
module ActiveRecord
+
+ # = Active Record Errors
+ #
# Generic Active Record exception class.
class ActiveRecordError < StandardError
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
new file mode 100644
index 0000000000..71065f9908
--- /dev/null
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ class LogSubscriber < ActiveSupport::LogSubscriber
+ def initialize
+ super
+ @odd_or_even = false
+ end
+
+ def sql(event)
+ name = '%s (%.1fms)' % [event.payload[:name], event.duration]
+ sql = event.payload[:sql].squeeze(' ')
+
+ if odd?
+ name = color(name, :cyan, true)
+ sql = color(sql, nil, true)
+ else
+ name = color(name, :magenta, true)
+ end
+
+ debug " #{name} #{sql}"
+ end
+
+ def odd?
+ @odd_or_even = !@odd_or_even
+ end
+
+ def logger
+ ActiveRecord::Base.logger
+ end
+ end
+end
+
+ActiveRecord::LogSubscriber.attach_to :active_record \ No newline at end of file
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 940f825038..4c5e1ae218 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/module/aliasing'
module ActiveRecord
# Exception that can be raised to stop migrations from going backwards.
@@ -29,11 +30,15 @@ module ActiveRecord
end
end
- # Migrations can manage the evolution of a schema used by several physical databases. It's a solution
- # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
- # push that change to other developers and to the production server. With migrations, you can describe the transformations
- # in self-contained classes that can be checked into version control systems and executed against another database that
- # might be one, two, or five versions behind.
+ # = Active Record Migrations
+ #
+ # Migrations can manage the evolution of a schema used by several physical
+ # databases. It's a solution to the common problem of adding a field to make
+ # a new feature work in your local database, but being unsure of how to
+ # push that change to other developers and to the production server. With
+ # migrations, you can describe the transformations in self-contained classes
+ # that can be checked into version control systems and executed against
+ # another database that might be one, two, or five versions behind.
#
# Example of a simple migration:
#
@@ -47,10 +52,13 @@ module ActiveRecord
# end
# end
#
- # This migration will add a boolean flag to the accounts table and remove it if you're backing out of the migration.
- # It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement
- # or remove the migration. These methods can consist of both the migration specific methods like add_column and remove_column,
- # but may also contain regular Ruby code for generating data needed for the transformations.
+ # This migration will add a boolean flag to the accounts table and remove it
+ # if you're backing out of the migration. It shows how all migrations have
+ # two class methods +up+ and +down+ that describes the transformations
+ # required to implement or remove the migration. These methods can consist
+ # of both the migration specific methods like add_column and remove_column,
+ # but may also contain regular Ruby code for generating data needed for the
+ # transformations.
#
# Example of a more complex migration that also needs to initialize data:
#
@@ -64,7 +72,9 @@ module ActiveRecord
# t.integer :position
# end
#
- # SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
+ # SystemSetting.create :name => "notice",
+ # :label => "Use notice?",
+ # :value => 1
# end
#
# def self.down
@@ -72,35 +82,49 @@ module ActiveRecord
# end
# end
#
- # This migration first adds the system_settings table, then creates the very first row in it using the Active Record model
- # that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema
- # in one block call.
+ # This migration first adds the system_settings table, then creates the very
+ # first row in it using the Active Record model that relies on the table. It
+ # also uses the more advanced create_table syntax where you can specify a
+ # complete table schema in one block call.
#
# == Available transformations
#
- # * <tt>create_table(name, options)</tt> Creates a table called +name+ and makes the table object available to a block
- # that can then add columns to it, following the same format as add_column. See example above. The options hash is for
- # fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition.
+ # * <tt>create_table(name, options)</tt> Creates a table called +name+ and
+ # makes the table object available to a block that can then add columns to it,
+ # following the same format as add_column. See example above. The options hash
+ # is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
+ # table definition.
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
- # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+ to +new_name+.
- # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
+ # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
+ # to +new_name+.
+ # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
+ # to the table called +table_name+
# named +column_name+ specified to be one of the following types:
- # <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
- # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be specified by passing an
- # +options+ hash like <tt>{ :default => 11 }</tt>. Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g. <tt>{ :limit => 50, :null => false }</tt>)
- # -- see ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
- # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
- # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
- # parameters as add_column.
- # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
- # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index with the name of the column. Other options include
- # <tt>:name</tt> and <tt>:unique</tt> (e.g. <tt>{ :name => "users_name_index", :unique => true }</tt>).
- # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified by +index_name+.
+ # <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
+ # <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
+ # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
+ # specified by passing an +options+ hash like <tt>{ :default => 11 }</tt>.
+ # Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
+ # <tt>{ :limit => 50, :null => false }</tt>) -- see
+ # ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
+ # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
+ # a column but keeps the type and content.
+ # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
+ # the column to a different type using the same parameters as add_column.
+ # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named
+ # +column_name+ from the table called +table_name+.
+ # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
+ # with the name of the column. Other options include
+ # <tt>:name</tt> and <tt>:unique</tt> (e.g.
+ # <tt>{ :name => "users_name_index", :unique => true }</tt>).
+ # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified
+ # by +index_name+.
#
# == Irreversible transformations
#
- # Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
- # an <tt>ActiveRecord::IrreversibleMigration</tt> exception in their +down+ method.
+ # Some transformations are destructive in a manner that cannot be reversed.
+ # Migrations of that kind should raise an <tt>ActiveRecord::IrreversibleMigration</tt>
+ # exception in their +down+ method.
#
# == Running migrations from within Rails
#
@@ -110,13 +134,15 @@ module ActiveRecord
# rails generate migration MyNewMigration
#
# where MyNewMigration is the name of your migration. The generator will
- # create an empty migration file <tt>timestamp_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
- # directory where <tt>timestamp</tt> is the UTC formatted date and time that the migration was generated.
+ # create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
+ # in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
+ # UTC formatted date and time that the migration was generated.
#
# You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
# MyNewMigration.
#
# There is a special syntactic shortcut to generate migrations that add fields to a table.
+ #
# rails generate migration add_fieldname_to_tablename fieldname:string
#
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
@@ -191,9 +217,10 @@ module ActiveRecord
#
# == Using a model after changing its table
#
- # Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
- # to make a call to Base#reset_column_information in order to ensure that the model has the latest column data from
- # after the new column was added. Example:
+ # Sometimes you'll want to add a column in a migration and populate it
+ # immediately after. In that case, you'll need to make a call to
+ # <tt>Base#reset_column_information</tt> in order to ensure that the model has the
+ # latest column data from after the new column was added. Example:
#
# class AddPeopleSalary < ActiveRecord::Migration
# def self.up
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 3d8f4a030b..c010dac64e 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -4,11 +4,12 @@ require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Named \Scopes
module NamedScope
extend ActiveSupport::Concern
module ClassMethods
- # Returns an anonymous scope.
+ # Returns an anonymous \scope.
#
# posts = Post.scoped
# posts.size # Fires "select count(*) from posts" and returns the count
@@ -18,14 +19,15 @@ module ActiveRecord
# fruits = fruits.where(:colour => 'red') if options[:red_only]
# fruits = fruits.limit(10) if limited?
#
- # Anonymous \scopes tend to be useful when procedurally generating complex queries, where passing
- # intermediate values (scopes) around as first-class objects is convenient.
+ # Anonymous \scopes tend to be useful when procedurally generating complex
+ # queries, where passing intermediate values (\scopes) around as first-class
+ # objects is convenient.
#
- # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
- def scoped(options = {}, &block)
+ # You can define a \scope that applies to all finders using
+ # ActiveRecord::Base.default_scope.
+ def scoped(options = nil)
if options.present?
- relation = scoped.apply_finder_options(options)
- block_given? ? relation.extending(Module.new(&block)) : relation
+ scoped.apply_finder_options(options)
else
current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone
end
@@ -35,7 +37,7 @@ module ActiveRecord
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
end
- # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
+ # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
# such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
#
# class Shirt < ActiveRecord::Base
@@ -85,18 +87,22 @@ module ActiveRecord
# end
def scope(name, scope_options = {}, &block)
name = name.to_sym
+ valid_scope_name?(name)
- if !scopes[name] && respond_to?(name, true)
- logger.warn "Creating scope :#{name}. " \
- "Overwriting existing method #{self.name}.#{name}."
- end
+ extension = Module.new(&block) if block_given?
scopes[name] = lambda do |*args|
options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
- relation = scoped
- relation = options.is_a?(Hash) ? relation.apply_finder_options(options) : scoped.merge(options) if options
- block_given? ? relation.extending(Module.new(&block)) : relation
+ relation = if options.is_a?(Hash)
+ scoped.apply_finder_options(options)
+ elsif options
+ scoped.merge(options)
+ else
+ scoped
+ end
+
+ extension ? relation.extending(extension) : relation
end
singleton_class.send :define_method, name, &scopes[name]
@@ -106,7 +112,15 @@ module ActiveRecord
ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
scope(*args, &block)
end
- end
+ protected
+
+ def valid_scope_name?(name)
+ if !scopes[name] && respond_to?(name, true)
+ logger.warn "Creating scope :#{name}. " \
+ "Overwriting existing method #{self.name}.#{name}."
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index eb9e792dd8..cf8c5aaf84 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -15,7 +15,7 @@ module ActiveRecord
self.nested_attributes_options = {}
end
- # == Nested Attributes
+ # = Active Record Nested Attributes
#
# Nested attributes allow you to save attributes on associated records
# through the parent. By default nested attribute updating is turned off,
@@ -25,6 +25,7 @@ module ActiveRecord
#
# The attribute writer is named after the association, which means that
# in the following example, two new methods are added to your model:
+ #
# <tt>author_attributes=(attributes)</tt> and
# <tt>pages_attributes=(attributes)</tt>.
#
@@ -132,7 +133,7 @@ module ActiveRecord
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
#
- # Alternatively, :reject_if also accepts a symbol for using methods:
+ # Alternatively, :reject_if also accepts a symbol for using methods:
#
# class Member < ActiveRecord::Base
# has_many :posts
@@ -144,7 +145,7 @@ module ActiveRecord
# accepts_nested_attributes_for :posts, :reject_if => :reject_posts
#
# def reject_posts(attributed)
- # attributed['title].blank?
+ # attributed['title'].blank?
# end
# end
#
@@ -212,7 +213,7 @@ module ActiveRecord
# that will reject a record where all the attributes are blank.
# [:limit]
# Allows you to specify the maximum number of the associated records that
- # can be processes with the nested attributes. If the size of the
+ # can be processed with the nested attributes. If the size of the
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
# exception is raised. If omitted, any number associations can be processed.
# Note that the :limit option is only applicable to one-to-many associations.
@@ -278,7 +279,7 @@ module ActiveRecord
# Assigns the given attributes to the association.
#
# If update_only is false and the given attributes include an <tt>:id</tt>
- # that matches the existing record’s id, then the existing record will be
+ # that matches the existing record's id, then the existing record will be
# modified. If update_only is true, a new record is only created when no
# object exists. Otherwise a new record will be built.
#
@@ -295,7 +296,9 @@ module ActiveRecord
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
elsif attributes['id']
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+ self.send(association_name.to_s+'=', existing_record)
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
@@ -365,11 +368,16 @@ module ActiveRecord
unless reject_new_record?(association_name, attributes)
association.build(attributes.except(*UNASSIGNABLE_KEYS))
end
+
+ elsif existing_records.count == 0 #Existing record but not yet associated
+ existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
+ association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
- else
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+
end
end
end
@@ -389,7 +397,7 @@ module ActiveRecord
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
- # Determines if a new record should be build by checking for
+ # Determines if a new record should be built by checking for
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
@@ -405,9 +413,5 @@ module ActiveRecord
end
end
- def raise_nested_attributes_record_not_found(association_name, record_id)
- reflection = self.class.reflect_on_association(association_name)
- raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
- end
end
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 0ea7fe7365..5f80bd86df 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ # = Active Record Observer
+ #
# Observer classes respond to lifecycle callbacks to implement trigger-like
# behavior outside the original class. This is a great way to reduce the
# clutter that normally comes when the model class is burdened with
@@ -105,8 +107,9 @@ module ActiveRecord
end
protected
+
def observed_subclasses
- observed_classes.sum([]) { |klass| klass.send(:subclasses) }
+ observed_classes.sum([]) { |klass| klass.send(:descendants) }
end
def observe_callbacks?
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 10788630a5..50166c4385 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -1,6 +1,8 @@
module ActiveRecord
+ # = Active Record Persistence
module Persistence
- # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
+ # Returns true if this object hasn't been saved yet -- that is, a record
+ # for the object doesn't exist in the data store yet; otherwise, returns false.
def new_record?
@new_record
end
@@ -10,7 +12,8 @@ module ActiveRecord
@destroyed
end
- # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed.
+ # Returns if the record is persisted, i.e. it's not a new record and it was
+ # not destroyed.
def persisted?
!(new_record? || destroyed?)
end
@@ -69,8 +72,8 @@ module ActiveRecord
freeze
end
- # Deletes the record in the database and freezes this instance to reflect that no changes should
- # be made (since they can't be persisted).
+ # Deletes the record in the database and freezes this instance to reflect
+ # that no changes should be made (since they can't be persisted).
def destroy
if persisted?
self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
@@ -80,10 +83,13 @@ module ActiveRecord
freeze
end
- # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
- # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
- # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
- # to render that instance using the companies/company partial instead of clients/client.
+ # Returns an instance of the specified +klass+ with the attributes of the
+ # current record. This is mostly useful in relation to single-table
+ # inheritance structures where you want a subclass to appear as the
+ # superclass. This can be used along with record identification in
+ # Action Pack to allow, say, <tt>Client < Company</tt> to do something
+ # like render <tt>:partial => @client.becomes(Company)</tt> to render that
+ # instance using the companies/company partial instead of clients/client.
#
# Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
# instance will affect the other.
@@ -104,14 +110,15 @@ module ActiveRecord
save(:validate => false)
end
- # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
- # fail and false will be returned.
+ # Updates all the attributes from the passed-in Hash and saves the record.
+ # If the object is invalid, the saving will fail and false will be returned.
def update_attributes(attributes)
self.attributes = attributes
save
end
- # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
+ # Updates an object just like Base.update_attributes but calls save! instead
+ # of save so an exception is raised if the record is invalid.
def update_attributes!(attributes)
self.attributes = attributes
save!
@@ -175,7 +182,7 @@ module ActiveRecord
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
- @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
+ @attributes.update(self.class.unscoped { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
@attributes_cache = {}
self
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 093c6c1e55..d9f85a4e5e 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Query Cache
class QueryCache
module ClassMethods
# Enable the query cache within the block if Active Record is configured.
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index a32fb7d399..2808e199fe 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -9,6 +9,7 @@ require "active_model/railtie"
require "action_controller/railtie"
module ActiveRecord
+ # = Active Record Railtie
class Railtie < Rails::Railtie
config.active_record = ActiveSupport::OrderedOptions.new
@@ -25,9 +26,6 @@ module ActiveRecord
load "active_record/railties/databases.rake"
end
- require "active_record/railties/log_subscriber"
- log_subscriber :active_record, ActiveRecord::Railties::LogSubscriber.new
-
initializer "active_record.initialize_timezone" do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
@@ -68,7 +66,6 @@ module ActiveRecord
unless app.config.cache_classes
ActiveSupport.on_load(:active_record) do
ActionDispatch::Callbacks.after do
- ActiveRecord::Base.reset_subclasses
ActiveRecord::Base.clear_reloadable_connections!
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 006e64b115..7882f05d07 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -57,7 +57,7 @@ namespace :db do
end
rescue
case config['adapter']
- when 'mysql'
+ when /mysql/
@charset = ENV['CHARSET'] || 'utf8'
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
@@ -171,6 +171,36 @@ namespace :db do
ActiveRecord::Migrator.run(:down, "db/migrate/", version)
Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
end
+
+ desc "Display status of migrations"
+ task :status => :environment do
+ config = ActiveRecord::Base.configurations[Rails.env || 'development']
+ ActiveRecord::Base.establish_connection(config)
+ unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ puts 'Schema migrations table does not exist yet.'
+ next # means "return" for rake task
+ end
+ db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
+ file_list = []
+ Dir.foreach(File.join(Rails.root, 'db', 'migrate')) do |file|
+ # only files matching "20091231235959_some_name.rb" pattern
+ if match_data = /(\d{14})_(.+)\.rb/.match(file)
+ status = db_list.delete(match_data[1]) ? 'up' : 'down'
+ file_list << [status, match_data[1], match_data[2]]
+ end
+ end
+ # output
+ puts "\ndatabase: #{config['database']}\n\n"
+ puts "#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration Name"
+ puts "-" * 50
+ file_list.each do |file|
+ puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}"
+ end
+ db_list.each do |version|
+ puts "#{'up'.center(8)} #{version.ljust(14)} *** NO FILE ***"
+ end
+ puts
+ end
end
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).'
@@ -194,7 +224,7 @@ namespace :db do
task :charset => :environment do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
- when 'mysql'
+ when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.charset
when 'postgresql'
@@ -212,7 +242,7 @@ namespace :db do
task :collation => :environment do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
- when 'mysql'
+ when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.collation
else
@@ -313,7 +343,7 @@ namespace :db do
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
case abcs[Rails.env]["adapter"]
- when "mysql", "oci", "oracle"
+ when /mysql/, "oci", "oracle"
ActiveRecord::Base.establish_connection(abcs[Rails.env])
File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
@@ -361,7 +391,7 @@ namespace :db do
task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
- when "mysql"
+ when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table|
@@ -395,7 +425,7 @@ namespace :db do
task :purge => :environment do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
- when "mysql"
+ when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
when "postgresql"
@@ -451,7 +481,7 @@ task 'test:prepare' => 'db:test:prepare'
def drop_database(config)
case config['adapter']
- when 'mysql'
+ when /mysql/
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection.drop_database config['database']
when /^sqlite/
diff --git a/activerecord/lib/active_record/railties/log_subscriber.rb b/activerecord/lib/active_record/railties/log_subscriber.rb
deleted file mode 100644
index 31b98bb6ed..0000000000
--- a/activerecord/lib/active_record/railties/log_subscriber.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module ActiveRecord
- module Railties
- class LogSubscriber < Rails::LogSubscriber
- def initialize
- super
- @odd_or_even = false
- end
-
- def sql(event)
- name = '%s (%.1fms)' % [event.payload[:name], event.duration]
- sql = event.payload[:sql].squeeze(' ')
-
- if odd?
- name = color(name, :cyan, true)
- sql = color(sql, nil, true)
- else
- name = color(name, :magenta, true)
- end
-
- debug " #{name} #{sql}"
- end
-
- def odd?
- @odd_or_even = !@odd_or_even
- end
-
- def logger
- ActiveRecord::Base.logger
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0e48e229b3..a82e5d7ed1 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,12 +1,16 @@
module ActiveRecord
+ # = Active Record Reflection
module Reflection # :nodoc:
extend ActiveSupport::Concern
- # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
- # This information can, for example, be used in a form builder that took an Active Record object and created input
- # fields for all of the attributes depending on their type and displayed the associations to other objects.
+ # Reflection allows you to interrogate Active Record classes and objects
+ # about their associations and aggregations. This information can,
+ # for example, be used in a form builder that took an Active Record object
+ # and created input fields for all of the attributes depending on their type
+ # and displayed the associations to other objects.
#
- # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
+ # You can find the interface for the AggregateReflection and AssociationReflection
+ # classes in the abstract MacroReflection class.
module ClassMethods
def create_reflection(macro, name, options, active_record)
case macro
@@ -43,8 +47,11 @@ module ActiveRecord
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
end
- # Returns an array of AssociationReflection objects for all the associations in the class. If you only want to reflect on a
- # certain association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>, <tt>:belongs_to</tt>) for that as the first parameter.
+ # Returns an array of AssociationReflection objects for all the
+ # associations in the class. If you only want to reflect on a certain
+ # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
+ # <tt>:belongs_to</tt>) for that as the first parameter.
+ #
# Example:
#
# Account.reflect_on_all_associations # returns an array of all associations
@@ -71,8 +78,9 @@ module ActiveRecord
end
- # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
- # those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
+ # Abstract base class for AggregateReflection and AssociationReflection that
+ # describes the interface available for both of those classes. Objects of
+ # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
class MacroReflection
attr_reader :active_record
@@ -80,13 +88,15 @@ module ActiveRecord
@macro, @name, @options, @active_record = macro, name, options, active_record
end
- # Returns the name of the macro. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> will return
- # <tt>:balance</tt> or for <tt>has_many :clients</tt> it will return <tt>:clients</tt>.
+ # Returns the name of the macro. For example, <tt>composed_of :balance,
+ # :class_name => 'Money'</tt> will return <tt>:balance</tt> or for
+ # <tt>has_many :clients</tt> it will return <tt>:clients</tt>.
def name
@name
end
- # Returns the macro type. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>:composed_of</tt>
+ # Returns the macro type. For example,
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>:composed_of</tt>
# or for <tt>has_many :clients</tt> will return <tt>:has_many</tt>.
def macro
@macro
@@ -132,11 +142,13 @@ module ActiveRecord
end
- # Holds all the meta-data about an aggregation as it was specified in the Active Record class.
+ # Holds all the meta-data about an aggregation as it was specified in the
+ # Active Record class.
class AggregateReflection < MacroReflection #:nodoc:
end
- # Holds all the meta-data about an association as it was specified in the Active Record class.
+ # Holds all the meta-data about an association as it was specified in the
+ # Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
# Returns the target association's class:
#
@@ -165,14 +177,14 @@ module ActiveRecord
klass.new(*options)
end
- # Creates a new instance of the associated class, and immediates saves it
+ # Creates a new instance of the associated class, and immediately saves it
# with ActiveRecord::Base#save. +options+ will be passed to the class's
# creation method. Returns the newly created object.
def create_association(*options)
klass.create(*options)
end
- # Creates a new instance of the associated class, and immediates saves it
+ # Creates a new instance of the associated class, and immediately saves it
# with ActiveRecord::Base#save!. +options+ will be passed to the class's
# creation method. If the created record doesn't pass validations, then an
# exception will be raised.
@@ -267,10 +279,10 @@ module ActiveRecord
# Returns whether or not the association should be validated as part of
# the parent's validation.
#
- # Unless you explicitely disable validation with
+ # Unless you explicitly disable validation with
# <tt>:validate => false</tt>, it will take place when:
#
- # * you explicitely enable validation; <tt>:validate => true</tt>
+ # * you explicitly enable validation; <tt>:validate => true</tt>
# * you use autosave; <tt>:autosave => true</tt>
# * the association is a +has_many+ association
def validate?
@@ -306,9 +318,12 @@ module ActiveRecord
end
end
- # Holds all the meta-data about a :through association as it was specified in the Active Record class.
+ # Holds all the meta-data about a :through association as it was specified
+ # in the Active Record class.
class ThroughReflection < AssociationReflection #:nodoc:
- # Gets the source of the through reflection. It checks both a singularized and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
+ # Gets the source of the through reflection. It checks both a singularized
+ # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
+ #
# (The <tt>:tags</tt> association on Tagging below.)
#
# class Post < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 99c914d7fc..bc708b573f 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Relation
class Relation
JoinOperation = Struct.new(:relation, :join_class, :on)
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
@@ -9,13 +10,13 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
- delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a
+ delegate :to_xml, :to_json, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
delegate :insert, :to => :arel
attr_reader :table, :klass
attr_accessor :extensions
- def initialize(klass, table, &block)
+ def initialize(klass, table)
@klass, @table = klass, table
@implicit_readonly = nil
@@ -24,12 +25,10 @@ module ActiveRecord
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
@extensions = []
-
- apply_modules(Module.new(&block)) if block_given?
end
def new(*args, &block)
- with_create_scope { @klass.new(*args, &block) }
+ scoping { @klass.new(*args, &block) }
end
def initialize_copy(other)
@@ -39,11 +38,11 @@ module ActiveRecord
alias build new
def create(*args, &block)
- with_create_scope { @klass.create(*args, &block) }
+ scoping { @klass.create(*args, &block) }
end
def create!(*args, &block)
- with_create_scope { @klass.create!(*args, &block) }
+ scoping { @klass.create!(*args, &block) }
end
def respond_to?(method, include_private = false)
@@ -67,7 +66,7 @@ module ActiveRecord
preload += @includes_values unless eager_loading?
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
- # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT.
+ # @readonly_value is true only if set explicitly. @implicit_readonly is true if there are JOINS and no explicit SELECT.
readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
@records.each { |record| record.readonly! } if readonly
@@ -75,10 +74,12 @@ module ActiveRecord
@records
end
+ # Returns size of the records.
def size
loaded? ? @records.length : count
end
+ # Returns true if there are no records.
def empty?
loaded? ? @records.empty? : count.zero?
end
@@ -99,6 +100,25 @@ module ActiveRecord
end
end
+ # Scope all queries to the current scope.
+ #
+ # ==== Example
+ #
+ # Comment.where(:post_id => 1).scoping do
+ # Comment.first #=> SELECT * FROM comments WHERE post_id = 1
+ # end
+ #
+ # Please check unscoped if you want to remove all previous scopes (including
+ # the default_scope) during the execution of a block.
+ def scoping
+ @klass.scoped_methods << self
+ begin
+ yield
+ ensure
+ @klass.scoped_methods.pop
+ end
+ end
+
# Updates all records with details given if they match a set of conditions supplied, limits and order can
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks
@@ -240,8 +260,9 @@ module ActiveRecord
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
#
- # 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.
+ # 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)
conditions ? where(conditions).delete_all : arel.delete.tap { reset }
end
@@ -301,7 +322,6 @@ module ActiveRecord
if where.is_a?(Arel::Predicates::Equality)
hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
end
-
hash
end
end
@@ -332,7 +352,7 @@ module ActiveRecord
elsif @klass.scopes[method]
merge(@klass.send(method, *args, &block))
elsif @klass.respond_to?(method)
- @klass.send(:with_scope, self) { @klass.send(method, *args, &block) }
+ scoping { @klass.send(method, *args, &block) }
elsif arel.respond_to?(method)
arel.send(method, *args, &block)
elsif match = DynamicFinderMatch.match(method)
@@ -351,10 +371,6 @@ module ActiveRecord
private
- def with_create_scope
- @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
- end
-
def references_eager_loaded_tables?
# always convert table names to downcase as in Oracle quoted table names are in uppercase
joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map(&:downcase).uniq
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7a0c9dc612..f39951e16c 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -87,8 +87,8 @@ module ActiveRecord
# person.visits += 1
# person.save!
# end
- def find(*args, &block)
- return to_a.find(&block) if block_given?
+ def find(*args)
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
options = args.extract_options!
@@ -259,8 +259,8 @@ module ActiveRecord
record
end
- def find_with_ids(*ids, &block)
- return to_a.find(&block) if block_given?
+ def find_with_ids(*ids)
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
expects_array = ids.first.kind_of?(Array)
return ids.first if expects_array && ids.first.empty?
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index d0efa2189d..5cea2328e8 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -20,15 +20,13 @@ module ActiveRecord
table = Arel::Table.new(table_name, :engine => @engine)
end
- unless attribute = table[column]
- raise StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
- end
+ attribute = table[column] || Arel::Attribute.new(table, column)
case value
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
values = value.to_a
attribute.in(values)
- when Range
+ when Range, Arel::Relation
attribute.in(value)
else
attribute.eq(value)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 7a48a6596a..4692271266 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,79 +5,92 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern
- included do
- (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method|
- attr_accessor :"#{query_method}_values"
-
- next if [:where, :having, :select].include?(query_method)
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(*args, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
- new_relation.#{query_method}_values += value if value.present?
- new_relation
- end
- CEVAL
- end
+ 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
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def select(*args, &block)
- if block_given?
- to_a.select(&block)
- else
- new_relation = clone
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
- new_relation.select_values += value if value.present?
- new_relation
- end
- end
- CEVAL
-
- [:where, :having].each do |query_method|
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(*args, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- value = build_where(*args)
- new_relation.#{query_method}_values += Array.wrap(value) if value.present?
- new_relation
- end
- CEVAL
- end
+ def includes(*args)
+ args.reject! { |a| a.blank? }
+ clone.tap { |r| r.includes_values += args if args.present? }
+ end
+
+ def eager_load(*args)
+ clone.tap { |r| r.eager_load_values += args if args.present? }
+ end
- ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
- attr_accessor :"#{query_method}_value"
+ def preload(*args)
+ clone.tap { |r| r.preload_values += args if args.present? }
+ end
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(value = true, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- new_relation.#{query_method}_value = value
- new_relation
- end
- CEVAL
+ def select(*args)
+ if block_given?
+ to_a.select { |*block_args| yield(*block_args) }
+ else
+ clone.tap { |r| r.select_values += args if args.present? }
end
end
- def extending(*modules)
- new_relation = clone
- new_relation.send :apply_modules, *modules
- new_relation
+ def group(*args)
+ clone.tap { |r| r.group_values += args if args.present? }
end
- def lock(locks = true, &block)
- relation = clone
- relation.send(:apply_modules, Module.new(&block)) if block_given?
+ def order(*args)
+ clone.tap { |r| r.order_values += args if args.present? }
+ end
+
+ def reorder(*args)
+ clone.tap { |r| r.order_values = args if args.present? }
+ end
+ def joins(*args)
+ args.flatten!
+ clone.tap { |r| r.joins_values += args if args.present? }
+ end
+
+ def where(*args)
+ value = build_where(*args)
+ clone.tap { |r| r.where_values += Array.wrap(value) if value.present? }
+ end
+
+ def having(*args)
+ value = build_where(*args)
+ clone.tap { |r| r.having_values += Array.wrap(value) if value.present? }
+ end
+
+ def limit(value = true)
+ clone.tap { |r| r.limit_value = value }
+ end
+
+ def offset(value = true)
+ clone.tap { |r| r.offset_value = value }
+ end
+
+ def lock(locks = true)
case locks
when String, TrueClass, NilClass
- clone.tap {|new_relation| new_relation.lock_value = locks || true }
+ clone.tap { |r| r.lock_value = locks || true }
else
- clone.tap {|new_relation| new_relation.lock_value = false }
+ clone.tap { |r| r.lock_value = false }
end
end
+ def readonly(value = true)
+ clone.tap { |r| r.readonly_value = value }
+ end
+
+ def create_with(value = true)
+ clone.tap { |r| r.create_with_value = value }
+ end
+
+ def from(value = true)
+ clone.tap { |r| r.from_value = value }
+ end
+
+ def extending(*modules, &block)
+ modules << Module.new(&block) if block_given?
+ clone.tap { |r| r.send(:apply_modules, *modules) }
+ end
+
def reverse_order
order_clause = arel.send(:order_clauses).join(', ')
relation = except(:order)
@@ -116,45 +129,7 @@ module ActiveRecord
def build_arel
arel = table
- 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)}
-
- non_association_joins = (joins - association_joins - stashed_association_joins).reject {|j| j.blank?}
- 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
- arel = arel.join(tj[0], tj[1]).on(*tj[2])
- end
- end
-
- arel = arel.join(custom_joins)
+ arel = build_joins(arel, @joins_values) if @joins_values.present?
@where_values.uniq.each do |where|
next if where.blank?
@@ -168,31 +143,18 @@ module ActiveRecord
end
end
- @having_values.uniq.each do |h|
- arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
- end
+ arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
arel = arel.take(@limit_value) if @limit_value.present?
arel = arel.skip(@offset_value) if @offset_value.present?
- arel = arel.group(*@group_values.uniq.select{|g| g.present?})
-
- arel = arel.order(*@order_values.uniq.select{|o| o.present?}.map(&:to_s))
+ arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
- selects = @select_values.uniq
+ arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
- quoted_table_name = @klass.quoted_table_name
-
- if selects.present?
- selects.each do |s|
- @implicit_readonly = false
- arel = arel.project(s) if s.present?
- end
- else
- arel = arel.project(quoted_table_name + '.*')
- end
+ arel = build_select(arel, @select_values.uniq)
- arel = @from_value.present? ? arel.from(@from_value) : arel.from(quoted_table_name)
+ arel = arel.from(@from_value) if @from_value.present?
case @lock_value
when TrueClass
@@ -221,6 +183,63 @@ module ActiveRecord
private
+ 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)}
+
+ non_association_joins = (joins - association_joins - stashed_association_joins)
+ 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
+
+ def build_select(arel, selects)
+ if selects.present?
+ @implicit_readonly = false
+ # 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
+ if selects.all? { |s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
+ arel.project(*selects)
+ else
+ arel.project(selects.last)
+ end
+ else
+ arel.project(@klass.quoted_table_name + '.*')
+ end
+ end
+
def apply_modules(modules)
values = Array.wrap(modules)
@extensions += values if values.present?
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index bb1f138f5b..7712ad2569 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -6,7 +6,7 @@ module ActiveRecord
merged_relation = clone
return merged_relation unless r
- (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).reject {|m| [:joins, :where].include?(m)}.each do |method|
+ ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - [:joins, :where]).each do |method|
value = r.send(:"#{method}_values")
merged_relation.send(:"#{method}_values=", value) if value.present?
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index a833356d15..e2783087ec 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Schema
+ #
# Allows programmers to programmatically define a schema in a portable
# DSL. This means you can define tables, indexes, etc. without using SQL
# directly, so your applications can more easily support multiple
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index cd54653581..a4757773d8 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -2,6 +2,8 @@ require 'stringio'
require 'active_support/core_ext/big_decimal'
module ActiveRecord
+ # = Active Record Schema Dumper
+ #
# This class is used to dump the database schema for some connection to some
# output format (i.e., ActiveRecord::Schema).
class SchemaDumper #:nodoc:
@@ -39,13 +41,14 @@ module ActiveRecord
define_params = @version ? ":version => #{@version}" : ""
stream.puts <<HEADER
-# This file is auto-generated from the current state of the database. Instead of editing this file,
-# please use the migrations feature of Active Record to incrementally modify your database, and
-# then regenerate this schema definition.
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
#
-# Note that this schema.rb definition is the authoritative source for your database schema. If you need
-# to create the application database on another system, you should be using db:schema:load, not running
-# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
@@ -173,15 +176,15 @@ HEADER
def indexes(table, stream)
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
- statment_parts = [ ('add_index ' + index.table.inspect) ]
- statment_parts << index.columns.inspect
- statment_parts << (':name => ' + index.name.inspect)
- statment_parts << ':unique => true' if index.unique
+ statement_parts = [ ('add_index ' + index.table.inspect) ]
+ statement_parts << index.columns.inspect
+ statement_parts << (':name => ' + index.name.inspect)
+ statement_parts << ':unique => true' if index.unique
index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
- statment_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
+ statement_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
- ' ' + statment_parts.join(', ')
+ ' ' + statement_parts.join(', ')
end
stream.puts add_index_statements.sort.join("\n")
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index b49471f7ab..2d8bd184f3 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,4 +1,5 @@
module ActiveRecord #:nodoc:
+ # = Active Record Serialization
module Serialization
extend ActiveSupport::Concern
include ActiveModel::Serializers::JSON
@@ -22,6 +23,7 @@ module ActiveRecord #:nodoc:
private
# Add associations specified via the <tt>:includes</tt> option.
+ #
# Expects a block that takes as arguments:
# +association+ - name of the association
# +records+ - the association record(s) to be serialized
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 931872eded..b88d550086 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -1,4 +1,6 @@
module ActiveRecord
+ # = Active Record Session Store
+ #
# A session store backed by an Active Record class. A default class is
# provided, but any object duck-typing to an Active Record Session class
# with text +session_id+ and +data+ attributes is sufficient.
@@ -7,6 +9,7 @@ module ActiveRecord
# +id+ (numeric primary key),
# +session_id+ (text, or longtext if your session data exceeds 65K), and
# +data+ (text or longtext; careful if your session data exceeds 65KB).
+ #
# The +session_id+ column should always be indexed for speedy lookups.
# Session data is marshaled to the +data+ column in Base64 format.
# If the data you write is larger than the column's size limit,
@@ -14,9 +17,11 @@ module ActiveRecord
#
# You may configure the table name, primary key, and data column.
# For example, at the end of <tt>config/environment.rb</tt>:
+ #
# ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
# ActiveRecord::SessionStore::Session.primary_key = 'session_id'
# ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
+ #
# Note that setting the primary key to the +session_id+ frees you from
# having a separate +id+ column if you don't want it. However, you must
# set <tt>session.model.id = session.session_id</tt> by hand! A before filter
@@ -29,8 +34,11 @@ module ActiveRecord
# You may provide your own session class implementation, whether a
# feature-packed Active Record or a bare-metal high-performance SQL
# store, by setting
+ #
# ActiveRecord::SessionStore.session_class = MySessionClass
+ #
# You must implement these methods:
+ #
# self.find_by_session_id(session_id)
# initialize(hash_of_session_id_and_data)
# attr_reader :session_id
@@ -310,6 +318,14 @@ module ActiveRecord
sid
end
+ def destroy(env)
+ if sid = current_session_id(env)
+ Base.silence do
+ get_session_model(env, sid).destroy
+ end
+ end
+ end
+
def get_session_model(env, sid)
if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
env[SESSION_RECORD_KEY] = find_session(sid)
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 0a77ad5fd7..e61a378d17 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -1,4 +1,7 @@
module ActiveRecord
+ # = Active Record Test Case
+ #
+ # Defines some test assertions to test against SQL queries.
class TestCase < ActiveSupport::TestCase #:nodoc:
def assert_date_from_db(expected, actual, message = nil)
# SybaseAdapter doesn't have a separate column type just for dates,
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 9fba8f0aca..ffd12d2082 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,11 +1,16 @@
module ActiveRecord
- # Active Record automatically timestamps create and update operations if the table has fields
- # named created_at/created_on or updated_at/updated_on.
+ # = Active Record Timestamp
+ #
+ # Active Record automatically timestamps create and update operations if the
+ # table has fields named <tt>created_at/created_on</tt> or
+ # <tt>updated_at/updated_on</tt>.
+ #
+ # Timestamping can be turned off by setting:
#
- # Timestamping can be turned off by setting
# <tt>ActiveRecord::Base.record_timestamps = false</tt>
#
- # Timestamps are in the local timezone by default but you can use UTC by setting
+ # Timestamps are in the local timezone by default but you can use UTC by setting:
+ #
# <tt>ActiveRecord::Base.default_timezone = :utc</tt>
module Timestamp
extend ActiveSupport::Concern
@@ -16,8 +21,9 @@ module ActiveRecord
end
# Saves the record with the updated_at/on attributes set to the current time.
- # If the save fails because of validation errors, an ActiveRecord::RecordInvalid exception is raised.
- # If an attribute name is passed, that attribute is used for the touch instead of the updated_at/on attributes.
+ # If the save fails because of validation errors, an
+ # ActiveRecord::RecordInvalid exception is raised. If an attribute name is passed,
+ # that attribute is used for the touch instead of the updated_at/on attributes.
#
# Examples:
#
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 620758f5af..a7709b9489 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -11,7 +11,8 @@ module ActiveRecord
included do
define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
end
-
+ # = Active Record Transactions
+ #
# Transactions are protective blocks where SQL statements are only permanent
# if they can all succeed as one atomic action. The classic example is a
# transfer between two accounts where you can only have a deposit if the
@@ -19,7 +20,8 @@ module ActiveRecord
# the database and guard the data against program errors or database
# break-downs. So basically you should use transaction blocks whenever you
# have a number of statements that must be executed together or not at all.
- # Example:
+ #
+ # For example:
#
# ActiveRecord::Base.transaction do
# david.withdrawal(100)
@@ -320,6 +322,7 @@ module ActiveRecord
if @_start_transaction_state[:level] < 1
restore_state = remove_instance_variable(:@_start_transaction_state)
if restore_state
+ @attributes = @attributes.dup if @attributes.frozen?
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
if restore_state[:id]
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index be64e00bd1..b98fd353aa 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -1,6 +1,9 @@
module ActiveRecord
+ # = Active Record Validations
+ #
# Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
# +record+ method to retrieve the record which did not validate.
+ #
# begin
# complex_operation_that_calls_save!_internally
# rescue ActiveRecord::RecordInvalid => invalid
@@ -49,12 +52,12 @@ module ActiveRecord
# Runs all the specified validations and returns true if no errors were added otherwise false.
def valid?(context = nil)
context ||= (new_record? ? :create : :update)
- super(context)
+ output = super(context)
deprecated_callback_method(:validate)
deprecated_callback_method(:"validate_on_#{context}")
- errors.empty?
+ errors.empty? && output
end
protected
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index e41635134c..0b0f5682aa 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -3,7 +3,7 @@ module ActiveRecord
class AssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all?
- record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
+ record.errors.add(attribute, :invalid, options.merge(:value => value))
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 6283bdd0d6..1c9ecc7b1b 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -32,7 +32,7 @@ module ActiveRecord
end
if relation.exists?
- record.errors.add(attribute, :taken, :default => options[:message], :value => value)
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index bbb7c53d86..8ac21c1410 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -1,11 +1,17 @@
class <%= migration_class_name %> < ActiveRecord::Migration
- def self.up<% attributes.each do |attribute| %>
- <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end -%>
- <%- end %>
+ def self.up
+<% attributes.each do |attribute| -%>
+ <%- if migration_action -%>
+ <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end %>
+ <%- end -%>
+<%- end -%>
end
- def self.down<% attributes.reverse.each do |attribute| %>
- <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end -%>
- <%- end %>
+ def self.down
+<% attributes.reverse.each do |attribute| -%>
+ <%- if migration_action -%>
+ <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end %>
+ <%- end -%>
+<%- end -%>
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 539c2517ee..960c29c49c 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -22,7 +22,7 @@ module ActiveRecord
def create_module_file
return if class_path.empty?
- template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb")
+ template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
end
hook_for :test_framework