aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb10
-rw-r--r--activerecord/lib/active_record/associations.rb14
-rw-r--r--activerecord/lib/active_record/associations/association.rb10
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb20
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb14
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb13
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb37
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb32
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_base.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb12
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb8
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb23
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb11
-rw-r--r--activerecord/lib/active_record/autosave_association.rb8
-rw-r--r--activerecord/lib/active_record/base.rb6
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb112
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb76
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb82
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb59
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_handling.rb14
-rw-r--r--activerecord/lib/active_record/core.rb41
-rw-r--r--activerecord/lib/active_record/errors.rb21
-rw-r--r--activerecord/lib/active_record/explain.rb15
-rw-r--r--activerecord/lib/active_record/explain_registry.rb30
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb5
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb20
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb4
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb30
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb64
-rw-r--r--activerecord/lib/active_record/persistence.rb20
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb14
-rw-r--r--activerecord/lib/active_record/railties/databases.rake124
-rw-r--r--activerecord/lib/active_record/reflection.rb39
-rw-r--r--activerecord/lib/active_record/relation.rb46
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb4
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb14
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb41
-rw-r--r--activerecord/lib/active_record/relation/merger.rb32
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb8
-rw-r--r--activerecord/lib/active_record/runtime_registry.rb17
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/scoping.rb58
-rw-r--r--activerecord/lib/active_record/scoping/default.rb23
-rw-r--r--activerecord/lib/active_record/scoping/named.rb9
-rw-r--r--activerecord/lib/active_record/statement_cache.rb26
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb73
-rw-r--r--activerecord/lib/active_record/tasks/firebird_database_tasks.rb56
-rw-r--r--activerecord/lib/active_record/tasks/oracle_database_tasks.rb45
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb48
-rw-r--r--activerecord/lib/active_record/transactions.rb8
-rw-r--r--activerecord/lib/active_record/validations.rb3
-rw-r--r--activerecord/lib/active_record/version.rb13
74 files changed, 1297 insertions, 519 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index c33f03f13f..0330c0f37f 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -35,8 +35,8 @@ module ActiveRecord
autoload :Base
autoload :Callbacks
autoload :Core
- autoload :CounterCache
autoload :ConnectionHandling
+ autoload :CounterCache
autoload :DynamicMatchers
autoload :Explain
autoload :Inheritance
@@ -50,12 +50,14 @@ module ActiveRecord
autoload :Querying
autoload :ReadonlyAttributes
autoload :Reflection
+ autoload :RuntimeRegistry
autoload :Sanitization
autoload :Schema
autoload :SchemaDumper
autoload :SchemaMigration
autoload :Scoping
autoload :Serialization
+ autoload :StatementCache
autoload :Store
autoload :Timestamp
autoload :Transactions
@@ -69,8 +71,8 @@ module ActiveRecord
autoload :Aggregations
autoload :Associations
- autoload :AttributeMethods
autoload :AttributeAssignment
+ autoload :AttributeMethods
autoload :AutosaveAssociation
autoload :Relation
@@ -143,6 +145,10 @@ module ActiveRecord
autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
autoload :PostgreSQLDatabaseTasks,
'active_record/tasks/postgresql_database_tasks'
+
+ autoload :FirebirdDatabaseTasks, 'active_record/tasks/firebird_database_tasks'
+ autoload :SqlserverDatabaseTasks, 'active_record/tasks/sqlserver_database_tasks'
+ autoload :OracleDatabaseTasks, 'active_record/tasks/oracle_database_tasks'
end
autoload :TestCase
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index eb08f72286..5e5995f566 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -988,7 +988,7 @@ module ActiveRecord
# associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
# <tt>:through</tt>, the join records will be deleted, but the associated records won't.
#
- # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by_name('food'))</tt>
+ # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
# you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
# to be removed from the database.
#
@@ -1073,6 +1073,9 @@ module ActiveRecord
# with +attributes+, linked to this object through a foreign key, and that has already
# been saved (if it passed the validation). *Note*: This only works if the base model
# already exists in the DB, not if it is a new (unsaved) record!
+ # [collection.create!(attributes = {})]
+ # Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
+ # if the record is invalid.
#
# (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
@@ -1094,6 +1097,7 @@ module ActiveRecord
# * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
+ # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
# The declaration can also include an options hash to specialize the behavior of the association.
#
# === Options
@@ -1115,11 +1119,11 @@ module ActiveRecord
# similar callbacks may affect the :dependent behavior, and the
# :dependent behavior may affect other callbacks.
#
- # * <tt>:destroy</tt> causes all the associated objects to also be destroyed
- # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
+ # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
- # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
#
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 4c4b0f08e5..db0553ea76 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -30,7 +30,7 @@ module ActiveRecord
reset_scope
end
- # Returns the name of the table of the related class:
+ # Returns the name of the table of the associated class:
#
# post.comments.aliased_table_name # => "comments"
#
@@ -92,7 +92,7 @@ module ActiveRecord
# The scope for this association.
#
# Note that the association_scope is merged into the target_scope only when the
- # scoped method is called. This is because at that point the call may be surrounded
+ # scope method is called. This is because at that point the call may be surrounded
# by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
# actually gets built.
def association_scope
@@ -113,7 +113,7 @@ module ActiveRecord
end
end
- # This class of the target. belongs_to polymorphic overrides this to look at the
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
# polymorphic_type field on the owner.
def klass
reflection.klass
@@ -217,7 +217,8 @@ module ActiveRecord
reflection.inverse_of
end
- # Is this association invertible? Can be redefined by subclasses.
+ # Returns true if inverse association on the given record needs to be set.
+ # This method is redefined by subclasses.
def invertible_for?(record)
inverse_reflection_for(record)
end
@@ -235,6 +236,7 @@ module ActiveRecord
skip_assign = [reflection.foreign_key, reflection.type].compact
attributes = create_scope.except(*(record.changed - skip_assign))
record.assign_attributes(attributes)
+ set_inverse_instance(record)
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index a9525436fb..aa5551fe0c 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -101,6 +101,7 @@ module ActiveRecord
scope.includes! item.includes_values
scope.where_values += item.where_values
+ scope.order_values |= item.order_values
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 9ac561b997..543a0247d1 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -25,9 +25,10 @@ module ActiveRecord::Associations::Builder
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def belongs_to_counter_cache_after_create_for_#{name}
- record = #{name}
- record.class.increment_counter(:#{cache_column}, record.id) unless record.nil?
- @_after_create_counter_called = true
+ if record = #{name}
+ record.class.increment_counter(:#{cache_column}, record.id)
+ @_after_create_counter_called = true
+ end
end
def belongs_to_counter_cache_before_destroy_for_#{name}
@@ -66,8 +67,19 @@ module ActiveRecord::Associations::Builder
def add_touch_callbacks(reflection)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def belongs_to_touch_after_save_or_destroy_for_#{name}
- record = #{name}
+ foreign_key_field = #{reflection.foreign_key.inspect}
+ old_foreign_id = attribute_was(foreign_key_field)
+
+ if old_foreign_id
+ klass = association(#{name.inspect}).klass
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
+ if old_record
+ old_record.touch #{options[:touch].inspect if options[:touch] != true}
+ end
+ end
+
+ record = #{name}
unless record.nil? || record.new_record?
record.touch #{options[:touch].inspect if options[:touch] != true}
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 2385c90c1a..2a00ac1386 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -79,8 +79,20 @@ module ActiveRecord
if block_given?
load_target.find(*args) { |*block_args| yield(*block_args) }
else
- if options[:finder_sql] || options[:inverse_of]
+ if options[:finder_sql]
find_by_scan(*args)
+ elsif options[:inverse_of]
+ args = args.flatten
+ raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args.blank?
+
+ result = find_by_scan(*args)
+
+ result_size = Array(result).size
+ if !result || result_size != args.size
+ scope.raise_record_not_found_exception!(args, result_size, args.size)
+ else
+ result
+ end
else
scope.find(*args)
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index c2add32aa6..56e57cc36e 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -92,7 +92,7 @@ module ActiveRecord
# # => ActiveModel::MissingAttributeError: missing attribute: person_id
#
# *Second:* You can pass a block so it can be used just like Array#select.
- # This build an array of objects from the database for the scope,
+ # This builds an array of objects from the database for the scope,
# converting them into an array and iterating through them using
# Array#select.
#
@@ -228,6 +228,7 @@ module ActiveRecord
def build(attributes = {}, &block)
@association.build(attributes, &block)
end
+ alias_method :new, :build
# Returns a new object of the collection type that has been instantiated with
# attributes, linked to this object and that has already been saved (if it
@@ -303,7 +304,7 @@ module ActiveRecord
@association.concat(*records)
end
- # Replace this collection with +other_array+. This will perform a diff
+ # Replaces this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
#
# class Person < ActiveRecord::Base
@@ -832,8 +833,6 @@ module ActiveRecord
@association.include?(record)
end
- alias_method :new, :build
-
def proxy_association
@association
end
@@ -848,10 +847,8 @@ module ActiveRecord
# Returns a <tt>Relation</tt> object for the records in this association
def scope
- association = @association
-
- @association.scope.extending! do
- define_method(:proxy_association) { association }
+ @association.scope.tap do |scope|
+ scope.proxy_association = @association
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 98bd010f70..920038a543 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -25,9 +25,8 @@ module ActiveRecord
raise_on_type_mismatch!(record) if record
load_target
- # If target and record are nil, or target is equal to record,
- # we don't need to have transaction.
- if (target || record) && target != record
+ return self.target if !(target || record)
+ if (target != record) || record.changed?
transaction_if(save) do
remove_target!(options[:dependent]) if target && !target.destroyed?
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index f40368cfeb..28e081c03c 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -5,10 +5,31 @@ module ActiveRecord
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
- attr_reader :join_parts, :reflections, :alias_tracker, :active_record
-
+ attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
+
+ # base is the base class on which operation is taking place.
+ # associations is the list of associations which are joined using hash, symbol or array.
+ # joins is the list of all string join commnads and arel nodes.
+ #
+ # Example :
+ #
+ # class Physician < ActiveRecord::Base
+ # has_many :appointments
+ # has_many :patients, through: :appointments
+ # end
+ #
+ # If I execute `@physician.patients.to_a` then
+ # base #=> Physician
+ # associations #=> []
+ # joins #=> [#<Arel::Nodes::InnerJoin: ...]
+ #
+ # However if I execute `Physician.joins(:appointments).to_a` then
+ # base #=> Physician
+ # associations #=> [:appointments]
+ # joins #=> []
+ #
def initialize(base, associations, joins)
- @active_record = base
+ @base_klass = base
@table_joins = joins
@join_parts = [JoinBase.new(base)]
@associations = {}
@@ -54,10 +75,12 @@ module ActiveRecord
parent
}.uniq
- remove_duplicate_results!(active_record, records, @associations)
+ remove_duplicate_results!(base_klass, records, @associations)
records
end
+ protected
+
def remove_duplicate_results!(base, records, associations)
case associations
when Symbol, String
@@ -88,8 +111,6 @@ module ActiveRecord
end
end
- protected
-
def cache_joined_association(association)
associations = []
parent = association.parent
@@ -108,8 +129,8 @@ module ActiveRecord
parent ||= join_parts.last
case associations
when Symbol, String
- reflection = parent.reflections[associations.to_s.intern] or
- raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
+ reflection = parent.reflections[associations.intern] or
+ raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
unless join_association = find_join_association(reflection, parent)
@reflections << reflection
join_association = build_join_association(reflection, parent)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 0d3b4dbab1..e4d17451dc 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -55,14 +55,19 @@ module ActiveRecord
def find_parent_in(other_join_dependency)
other_join_dependency.join_parts.detect do |join_part|
- parent == join_part
+ case parent
+ when JoinBase
+ parent.base_klass == join_part.base_klass
+ else
+ parent == join_part
+ end
end
end
- def join_to(relation)
+ def join_to(manager)
tables = @tables.dup
foreign_table = parent_table
- foreign_klass = parent.active_record
+ foreign_klass = parent.base_klass
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
@@ -75,7 +80,7 @@ module ActiveRecord
foreign_key = reflection.foreign_key
when :has_and_belongs_to_many
# Join the join table first...
- relation.from(join(
+ manager.from(join(
table,
table[reflection.foreign_key].
eq(foreign_table[reflection.active_record_primary_key])
@@ -109,15 +114,30 @@ module ActiveRecord
constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
end
- relation.from(join(table, constraint))
+ manager.from(join(table, constraint))
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, reflection.klass
end
- relation
+ manager
end
+ # Builds equality condition.
+ #
+ # Example:
+ #
+ # class Physician < ActiveRecord::Base
+ # has_many :appointments
+ # end
+ #
+ # If I execute `Physician.joins(:appointments).to_a` then
+ # reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
+ # table #=> #<Arel::Table @name="appointments" ...>
+ # key #=> physician_id
+ # foreign_table #=> #<Arel::Table @name="physicians" ...>
+ # foreign_key #=> id
+ #
def build_constraint(reflection, table, key, foreign_table, foreign_key)
constraint = table[key].eq(foreign_table[foreign_key])
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
index 3920e84976..a7dacdbfd6 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
@@ -4,7 +4,7 @@ module ActiveRecord
class JoinBase < JoinPart # :nodoc:
def ==(other)
other.class == self.class &&
- other.active_record == active_record
+ other.base_klass == base_klass
end
def aliased_prefix
@@ -16,7 +16,7 @@ module ActiveRecord
end
def aliased_table_name
- active_record.table_name
+ base_klass.table_name
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 5604687b57..b534569063 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Associations
class JoinDependency # :nodoc:
- # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
+ # A JoinPart represents a part of a JoinDependency. It is inherited
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
# everything else is being joined onto. A JoinAssociation represents an association which
# is joining to the base. A JoinAssociation may result in more than one actual join
@@ -11,12 +11,12 @@ module ActiveRecord
# The Active Record class which this join part is associated 'about'; for a JoinBase
# this is the actual base model, for a JoinAssociation this is the target model of the
# association.
- attr_reader :active_record
+ attr_reader :base_klass
- delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record
+ delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
- def initialize(active_record)
- @active_record = active_record
+ def initialize(base_klass)
+ @base_klass = base_klass
@cached_record = {}
@column_names_with_alias = nil
end
@@ -70,7 +70,7 @@ module ActiveRecord
end
def instantiate(row)
- @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
+ @cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
index 38bc7ce7da..157b627ad5 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
@@ -5,9 +5,13 @@ module ActiveRecord
include ThroughAssociation
def associated_records_by_owner
- super.each do |owner, records|
- records.uniq! if reflection_scope.distinct_value
+ records_by_owner = super
+
+ if reflection_scope.distinct_value
+ records_by_owner.each_value { |records| records.uniq! }
end
+
+ records_by_owner
end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 43520142bf..35f29b37a2 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -14,7 +14,7 @@ module ActiveRecord
def target_scope
scope = super
chain[1..-1].each do |reflection|
- scope = scope.merge(
+ scope.merge!(
reflection.klass.all.with_default_scope.
except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index ecfa556ab4..e536f5ebcc 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -81,7 +81,7 @@ module ActiveRecord
end
def extract_callstack_for_multiparameter_attributes(pairs)
- attributes = { }
+ attributes = {}
pairs.each do |(multiparameter_name, value)|
attribute_name = multiparameter_name.split("(").first
@@ -146,7 +146,7 @@ module ActiveRecord
end
else
# else column is a timestamp, so if Date bits were not provided, error
- validate_missing_parameters!([1,2,3])
+ validate_required_parameters!([1,2,3])
# If Date bits were provided but blank, then return nil
return if blank_date_parameter?
@@ -172,14 +172,14 @@ module ActiveRecord
def read_other(klass)
max_position = extract_max_param
positions = (1..max_position)
- validate_missing_parameters!(positions)
+ validate_required_parameters!(positions)
set_values = values.values_at(*positions)
klass.new(*set_values)
end
# Checks whether some blank date parameter exists. Note that this is different
- # than the validate_missing_parameters! method, since it just checks for blank
+ # than the validate_required_parameters! method, since it just checks for blank
# positions instead of missing ones, and does not raise in case one blank position
# exists. The caller is responsible to handle the case of this returning true.
def blank_date_parameter?
@@ -187,7 +187,7 @@ module ActiveRecord
end
# If some position is not provided, it errors out a missing parameter exception.
- def validate_missing_parameters!(positions)
+ def validate_required_parameters!(positions)
if missing_parameter = positions.detect { |position| !values.key?(position) }
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index aa92343f76..609c6e8cab 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -56,7 +56,7 @@ module ActiveRecord
# # => false
def instance_method_already_implemented?(method_name)
if dangerous_attribute_method?(method_name)
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
+ raise DangerousAttributeError, "#{method_name} is defined by Active Record"
end
if superclass == Base
@@ -163,8 +163,22 @@ module ActiveRecord
# person.respond_to('age?') # => true
# person.respond_to(:nothing) # => false
def respond_to?(name, include_private = false)
+ name = name.to_s
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
- super
+ result = super
+
+ # If the result is false the answer is false.
+ return false unless result
+
+ # If the result is true then check for the select case.
+ # For queries selecting a subset of columns, return false for unselected columns.
+ # We check defined?(@attributes) not to issue warnings if called on objects that
+ # have been allocated but not yet initialized.
+ if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name)
+ return has_attribute?(name)
+ end
+
+ return true
end
# Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
@@ -328,13 +342,14 @@ module ActiveRecord
end
def attribute_method?(attr_name) # :nodoc:
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
defined?(@attributes) && @attributes.include?(attr_name)
end
private
# Returns a Hash of the Arel::Attributes and attribute values that have been
- # type casted for use in an Arel insert/update method.
+ # typecasted for use in an Arel insert/update method.
def arel_attributes_with_values(attribute_names)
attrs = {}
arel_table = self.class.arel_table
@@ -348,7 +363,7 @@ module ActiveRecord
# Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
attribute_names.select do |name|
- column_for_attribute(name) && !pk_attribute?(name) && !readonly_attribute?(name)
+ column_for_attribute(name) && !readonly_attribute?(name)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 25d62fdb85..7f1ebab4cd 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -11,6 +11,12 @@ module ActiveRecord
end
module ClassMethods
+ ##
+ # :method: serialized_attributes
+ #
+ # Returns a hash of all the attributes that have been specified for
+ # serialization as keys and their class restriction as values.
+
# If you have an attribute that needs to be saved to the database as an
# object, and retrieved as the same object, then specify the name of that
# attribute using this method and it will be handled automatically. The
@@ -44,6 +50,7 @@ module ActiveRecord
end
end
+ # *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
def serialized_attributes
message = "Instance level serialized_attributes method is deprecated, please use class level method."
ActiveSupport::Deprecation.warn message
@@ -86,10 +93,10 @@ module ActiveRecord
# This is only added to the model when serialize is called, which
# ensures we do not make things slower when serialization is not used.
- module Behavior #:nodoc:
+ module Behavior # :nodoc:
extend ActiveSupport::Concern
- module ClassMethods
+ module ClassMethods # :nodoc:
def initialize_attributes(attributes, options = {})
serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
super(attributes, options)
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 0df3e57947..44323ce9db 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -62,14 +62,14 @@ module ActiveRecord
# Note that the model is _not_ yet removed from the database:
#
# id = post.author.id
- # Author.find_by_id(id).nil? # => false
+ # Author.find_by(id: id).nil? # => false
#
# post.save
# post.reload.author # => nil
#
# Now it _is_ removed from the database:
#
- # Author.find_by_id(id).nil? # => true
+ # Author.find_by(id: id).nil? # => true
#
# === One-to-many Example
#
@@ -113,14 +113,14 @@ module ActiveRecord
# Note that the model is _not_ yet removed from the database:
#
# id = post.comments.last.id
- # Comment.find_by_id(id).nil? # => false
+ # Comment.find_by(id: id).nil? # => false
#
# post.save
# post.reload.comments.length # => 1
#
# Now it _is_ removed from the database:
#
- # Comment.find_by_id(id).nil? # => true
+ # Comment.find_by(id: id).nil? # => true
module AutosaveAssociation
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index e262401da6..b06add096f 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -160,10 +160,10 @@ module ActiveRecord #:nodoc:
#
# == Dynamic attribute-based finders
#
- # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
+ # Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects
# by simple queries without turning to SQL. They work by appending the name of an attribute
# to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>.
- # Instead of writing <tt>Person.where(user_name: user_name).first</tt>, you just do
+ # Instead of writing <tt>Person.find_by(user_name: user_name)</tt>, you can use
# <tt>Person.find_by_user_name(user_name)</tt>.
#
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
@@ -172,7 +172,7 @@ module ActiveRecord #:nodoc:
#
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
#
- # Person.where(user_name: user_name, password: password).first
+ # Person.find_by(user_name: user_name, password: password)
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
#
# It's even possible to call these dynamic finder methods on relations and named scopes.
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 22226b2f4f..e4c484d64b 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -83,7 +83,7 @@ module ActiveRecord
#
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
# So, use the callback macros when you want to ensure that a certain callback is called for the entire
- # hierarchy, and use the regular overwriteable methods when you want to leave it up to each descendant
+ # hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant
# to decide whether they want to call +super+ and trigger the inherited callbacks.
#
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index f6cdc67b4d..d3d7396c91 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -3,7 +3,6 @@ require 'yaml'
module ActiveRecord
module Coders # :nodoc:
class YAMLColumn # :nodoc:
- RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ]
attr_accessor :object_class
@@ -24,19 +23,15 @@ module ActiveRecord
def load(yaml)
return object_class.new if object_class != Object && yaml.nil?
return yaml unless yaml.is_a?(String) && yaml =~ /^---/
- begin
- obj = YAML.load(yaml)
-
- unless obj.is_a?(object_class) || obj.nil?
- raise SerializationTypeMismatch,
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
- end
- obj ||= object_class.new if object_class != Object
-
- obj
- rescue *RESCUE_ERRORS
- yaml
+ obj = YAML.load(yaml)
+
+ unless obj.is_a?(object_class) || obj.nil?
+ raise SerializationTypeMismatch,
+ "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
end
+ obj ||= object_class.new if object_class != Object
+
+ obj
end
end
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 bf2f945448..816b397fcf 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -253,14 +253,6 @@ module ActiveRecord
@available = Queue.new self
end
- # Hack for tests to be able to add connections. Do not call outside of tests
- def insert_connection_for_test!(c) #:nodoc:
- synchronize do
- @connections << c
- @available.add c
- end
- end
-
# Retrieve the connection associated with the current thread, or call
# #checkout to obtain one if necessary.
#
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 902dbd148e..566550cbe2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -8,41 +8,21 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc:
def string_to_binary(value)
value
end
- def sql_type
- base.type_to_sql(type.to_sym, limit, precision, scale)
- end
-
def primary_key?
- type.to_sym == :primary_key
- end
-
- def to_sql
- column_sql = "#{base.quote_column_name(name)} #{sql_type}"
- column_options = {}
- column_options[:null] = null unless null.nil?
- column_options[:default] = default unless default.nil?
- column_options[:column] = self
- add_column_options!(column_sql, column_options) unless primary_key?
- column_sql
+ primary_key || type.to_sym == :primary_key
end
-
- private
-
- def add_column_options!(sql, options)
- base.add_column_options!(sql, options)
- end
end
# Represents the schema of an SQL table in an abstract way. This class
@@ -68,19 +48,25 @@ module ActiveRecord
class TableDefinition
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
- attr_accessor :columns, :indexes
+ attr_accessor :indexes
+ attr_reader :name, :temporary, :options
- def initialize(base)
- @columns = []
+ def initialize(types, name, temporary, options)
@columns_hash = {}
@indexes = {}
- @base = base
+ @native = types
+ @temporary = temporary
+ @options = options
+ @name = name
end
+ def columns; @columns_hash.values; end
+
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
- def primary_key(name)
- column(name, :primary_key)
+ def primary_key(name, type = :primary_key, options = {})
+ options[:primary_key] = true
+ column(name, type, options)
end
# Returns a ColumnDefinition for the column with name +name+.
@@ -233,20 +219,14 @@ module ActiveRecord
raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
end
- column = self[name] || new_column_definition(@base, name, type)
-
- limit = options.fetch(:limit) do
- native[type][:limit] if native[type].is_a?(Hash)
- end
-
- column.limit = limit
- column.precision = options[:precision]
- column.scale = options[:scale]
- column.default = options[:default]
- column.null = options[:null]
+ @columns_hash[name] = new_column_definition(name, type, options)
self
end
+ def remove_column(name)
+ @columns_hash.delete name.to_s
+ end
+
[:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
define_method column_type do |*args|
options = args.extract_options!
@@ -278,28 +258,31 @@ module ActiveRecord
args.each do |col|
column("#{col}_id", :integer, options)
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
end
end
alias :belongs_to :references
- # Returns a String whose contents are the column definitions
- # concatenated together. This string can then be prepended and appended to
- # to generate the final SQL to create the table.
- def to_sql
- columns.map { |c| c.to_sql } * ', '
- end
+ def new_column_definition(name, type, options) # :nodoc:
+ column = create_column_definition name, type
+ limit = options.fetch(:limit) do
+ native[type][:limit] if native[type].is_a?(Hash)
+ end
- private
- def create_column_definition(base, name, type)
- ColumnDefinition.new base, name, type
+ column.limit = limit
+ column.precision = options[:precision]
+ column.scale = options[:scale]
+ column.default = options[:default]
+ column.null = options[:null]
+ column.first = options[:first]
+ column.after = options[:after]
+ column.primary_key = type == :primary_key || options[:primary_key]
+ column
end
- def new_column_definition(base, name, type)
- definition = create_column_definition base, name, type
- @columns << definition
- @columns_hash[name] = definition
- definition
+ private
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
def primary_key_column_name
@@ -308,7 +291,24 @@ module ActiveRecord
end
def native
- @base.native_database_types
+ @native
+ end
+ end
+
+ class AlterTable # :nodoc:
+ attr_reader :adds
+
+ def initialize(td)
+ @td = td
+ @adds = []
+ end
+
+ def name; @td.name; end
+
+ def add_column(name, type, options)
+ name = name.to_s
+ type = type.to_sym
+ @adds << @td.new_column_definition(name, type, options)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index f587bf8140..cdf0cbe218 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -5,7 +5,7 @@ module ActiveRecord
# The goal of this module is to move Adapter specific column
# definitions to the Adapter instead of having it in the schema
# dumper itself. This code represents the normal case.
- # We can then redefine how certain data types may be handled in the schema dumper on the
+ # We can then redefine how certain data types may be handled in the schema dumper on the
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column, types)
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 cd4409295f..9c0c4e3ef0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -171,14 +171,14 @@ module ActiveRecord
#
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
- td = create_table_definition
+ td = create_table_definition table_name, options[:temporary], options[:options]
unless options[:id] == false
pk = options.fetch(:primary_key) {
Base.get_primary_key table_name.to_s.singularize
}
- td.primary_key pk
+ td.primary_key pk, options.fetch(:id, :primary_key), options
end
yield td if block_given?
@@ -187,11 +187,7 @@ module ActiveRecord
drop_table(table_name, options)
end
- create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
- create_sql << "#{quote_table_name(table_name)} ("
- create_sql << td.to_sql
- create_sql << ") #{options[:options]}"
- execute create_sql
+ execute schema_creation.accept td
td.indexes.each_pair { |c,o| add_index table_name, c, o }
end
@@ -359,9 +355,9 @@ module ActiveRecord
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
def add_column(table_name, column_name, type, options = {})
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
- execute(add_column_sql)
+ at = create_alter_table table_name
+ at.add_column(column_name, type, options)
+ execute schema_creation.accept at
end
# Removes the given columns from the table definition.
@@ -447,7 +443,7 @@ module ActiveRecord
#
# add_index(:suppliers, :name)
#
- # generates
+ # generates:
#
# CREATE INDEX suppliers_name_index ON suppliers(name)
#
@@ -455,7 +451,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true)
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
#
@@ -463,7 +459,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
#
@@ -471,13 +467,13 @@ module ActiveRecord
#
# add_index(:accounts, :name, name: 'by_name', length: 10)
#
- # generates
+ # generates:
#
# CREATE INDEX by_name ON accounts(name(10))
#
# add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
#
- # generates
+ # generates:
#
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
#
@@ -487,7 +483,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
#
- # generates
+ # generates:
#
# CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
#
@@ -497,11 +493,30 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
- # Note: only supported by PostgreSQL.
+ # ====== Creating an index with a specific method
+ #
+ # add_index(:developers, :name, using: 'btree')
+ #
+ # generates:
+ #
+ # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
+ # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
+ #
+ # Note: only supported by PostgreSQL and MySQL
+ #
+ # ====== Creating an index with a specific type
+ #
+ # add_index(:developers, :name, type: :fulltext)
+ #
+ # generates:
+ #
+ # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
+ #
+ # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
def add_index(table_name, column_name, options = {})
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -685,6 +700,9 @@ module ActiveRecord
if options[:null] == false
sql << " NOT NULL"
end
+ if options[:auto_increment] == true
+ sql << " AUTO_INCREMENT"
+ end
end
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
@@ -749,12 +767,21 @@ module ActiveRecord
index_name = index_name(table_name, column: column_names)
if Hash === options # legacy support, since this param was a string
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal)
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
index_type = options[:unique] ? "UNIQUE" : ""
+ index_type = options[:type].to_s if options.key?(:type)
index_name = options[:name].to_s if options.key?(:name)
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+ if options.key?(:algorithm)
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
+ }
+ end
+
+ using = "USING #{options[:using]}" if options[:using].present?
+
if supports_partial_index?
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
@@ -769,6 +796,7 @@ module ActiveRecord
index_type = options
max_index_length = allowed_index_name_length
+ algorithm = using = nil
end
if index_name.length > max_index_length
@@ -779,7 +807,7 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options]
+ [index_name, index_type, index_columns, index_options, algorithm, using]
end
def index_name_for_remove(table_name, options = {})
@@ -829,8 +857,12 @@ module ActiveRecord
end
private
- def create_table_definition
- TableDefinition.new(self)
+ def create_table_definition(name, temporary, options)
+ TableDefinition.new native_database_types, name, temporary, options
+ end
+
+ def create_alter_table(name)
+ AlterTable.new create_table_definition(name, false, {})
end
def update_table_definition(table_name, base)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 7949bcb5ce..26586f0974 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -18,6 +18,7 @@ module ActiveRecord
autoload :ColumnDefinition
autoload :TableDefinition
autoload :Table
+ autoload :AlterTable
end
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
@@ -100,6 +101,79 @@ module ActiveRecord
@visitor = nil
end
+ def valid_type?(type)
+ true
+ end
+
+ class SchemaCreation
+ def initialize(conn)
+ @conn = conn
+ @cache = {}
+ end
+
+ def accept(o)
+ m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}"
+ send m, o
+ end
+
+ private
+
+ def visit_AlterTable(o)
+ sql = "ALTER TABLE #{quote_table_name(o.name)} "
+ sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
+ end
+
+ def visit_AddColumn(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(sql, column_options(o))
+ end
+
+ def visit_ColumnDefinition(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ column_sql = "#{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(column_sql, column_options(o)) unless o.primary_key?
+ column_sql
+ end
+
+ def visit_TableDefinition(o)
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
+ create_sql << "#{quote_table_name(o.name)} ("
+ create_sql << o.columns.map { |c| accept c }.join(', ')
+ create_sql << ") #{o.options}"
+ create_sql
+ end
+
+ def column_options(o)
+ column_options = {}
+ column_options[:null] = o.null unless o.null.nil?
+ column_options[:default] = o.default unless o.default.nil?
+ column_options[:column] = o
+ column_options
+ end
+
+ def quote_column_name(name)
+ @conn.quote_column_name name
+ end
+
+ def quote_table_name(name)
+ @conn.quote_table_name name
+ end
+
+ def type_to_sql(type, limit, precision, scale)
+ @conn.type_to_sql type.to_sym, limit, precision, scale
+ end
+
+ def add_column_options!(column_sql, column_options)
+ @conn.add_column_options! column_sql, column_options
+ column_sql
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
def lease
synchronize do
unless in_use
@@ -212,6 +286,12 @@ module ActiveRecord
[]
end
+ # A list of index algorithms, to be filled by adapters that support them.
+ # MySQL and PostgreSQL have support for them right now.
+ def index_algorithms
+ {}
+ end
+
# QUOTING ==================================================
# Returns a bind substitution value given a +column+ and list of current
@@ -353,7 +433,7 @@ module ActiveRecord
def translate_exception(exception, message)
# override in derived class
- ActiveRecord::StatementInvalid.new(message)
+ ActiveRecord::StatementInvalid.new(message, exception)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index f88f5742a8..2a7b855f95 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -3,13 +3,34 @@ require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+
+ def visit_AddColumn(o)
+ add_column_position!(super, o)
+ end
+
+ def add_column_position!(sql, column)
+ if column.first
+ sql << " FIRST"
+ elsif column.after
+ sql << " AFTER #{quote_column_name(column.after)}"
+ end
+ sql
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
class Column < ConnectionAdapters::Column # :nodoc:
- attr_reader :collation, :strict
+ attr_reader :collation, :strict, :extra
- def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false)
+ def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
@strict = strict
@collation = collation
-
+ @extra = extra
super(name, default, sql_type, null)
end
@@ -61,6 +82,8 @@ module ActiveRecord
def extract_limit(sql_type)
case sql_type
+ when /^enum\((.+)\)/i
+ $1.split(',').map{|enum| enum.strip.length - 2}.max
when /blob|text/i
case sql_type
when /tiny/i
@@ -77,8 +100,6 @@ module ActiveRecord
when /^mediumint/i; 3
when /^smallint/i; 2
when /^tinyint/i; 1
- when /^enum\((.+)\)/i
- $1.split(',').map{|enum| enum.strip.length - 2}.max
else
super
end
@@ -130,6 +151,9 @@ module ActiveRecord
:boolean => { :name => "tinyint", :limit => 1 }
}
+ INDEX_TYPES = [:fulltext, :spatial]
+ INDEX_USINGS = [:btree, :hash]
+
class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
include Arel::Visitors::BindVisitor
end
@@ -187,6 +211,10 @@ module ActiveRecord
NATIVE_DATABASE_TYPES
end
+ def index_algorithms
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
+ end
+
# HELPER METHODS ===========================================
# The two drivers have slightly different ways of yielding hashes of results, so
@@ -196,8 +224,8 @@ module ActiveRecord
end
# Overridden by the adapters to instantiate their specific Column type.
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, extra)
end
# Must return the Mysql error number from the exception, if the exception has an
@@ -259,7 +287,7 @@ module ActiveRecord
end
rescue ActiveRecord::StatementInvalid => exception
if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ raise ActiveRecord::StatementInvalid.new("'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings.", exception.original_exception)
else
raise
end
@@ -337,6 +365,7 @@ module ActiveRecord
def recreate_database(name, options = {})
drop_database(name)
create_database(name, options)
+ reconnect!
end
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
@@ -410,7 +439,11 @@ module ActiveRecord
if current_index != row[:Key_name]
next if row[:Key_name] == 'PRIMARY' # skip the primary key
current_index = row[:Key_name]
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
+
+ mysql_index_type = row[:Index_type].downcase.to_sym
+ index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
+ index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
end
indexes.last.columns << row[:Column_name]
@@ -426,7 +459,7 @@ module ActiveRecord
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
end
end
end
@@ -459,10 +492,6 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
- def add_column(table_name, column_name, type, options = {})
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
- end
-
def change_column_default(table_name, column_name, default)
column = column_for(table_name, column_name)
change_column table_name, column_name, column.sql_type, :default => default
@@ -487,6 +516,11 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
+ def add_index(table_name, column_name, options = {}) #:nodoc:
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
+ end
+
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
case type.to_s
@@ -572,6 +606,10 @@ module ActiveRecord
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
+
protected
# MySQL is too stupid to create a temporary table for use subquery, so we have
@@ -651,6 +689,7 @@ module ActiveRecord
if column = columns(table_name).find { |c| c.name == column_name.to_s }
options[:default] = column.default
options[:null] = column.null
+ options[:auto_increment] = (column.extra == "auto_increment")
else
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index a4b3a0c584..609ccc2ed2 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -161,7 +161,7 @@ module ActiveRecord
def value_to_date(value)
if value.is_a?(String)
- return nil if value.blank?
+ return nil if value.empty?
fast_string_to_date(value) || fallback_string_to_date(value)
elsif value.respond_to?(:to_date)
value.to_date
@@ -172,14 +172,14 @@ module ActiveRecord
def string_to_time(string)
return string unless string.is_a?(String)
- return nil if string.blank?
+ return nil if string.empty?
fast_string_to_time(string) || fallback_string_to_time(string)
end
def string_to_dummy_time(string)
return string unless string.is_a?(String)
- return nil if string.blank?
+ return nil if string.empty?
dummy_time_string = "2000-01-01 #{string}"
@@ -192,7 +192,7 @@ module ActiveRecord
# convert something to a boolean
def value_to_boolean(value)
- if value.is_a?(String) && value.blank?
+ if value.is_a?(String) && value.empty?
nil
else
TRUE_VALUES.include?(value)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 25b8aef617..530a27d099 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -4,7 +4,7 @@ gem 'mysql2', '~> 0.3.10'
require 'mysql2'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
config = config.symbolize_keys
@@ -63,8 +63,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
end
def error_number(exception)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 7544c2a783..f23521430d 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -16,9 +16,9 @@ class Mysql
end
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects.
- def mysql_connection(config) # :nodoc:
+ def mysql_connection(config)
config = config.symbolize_keys
host = config[:host]
port = config[:port]
@@ -150,8 +150,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
end
def error_number(exception) # :nodoc:
@@ -383,7 +383,7 @@ module ActiveRecord
TYPES = {}
- # Register an MySQL +type_id+ with a typcasting object in
+ # Register an MySQL +type_id+ with a typecasting object in
# +type+.
def self.register_type(type_id, type)
TYPES[type_id] = type
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 3d8f0b575c..a9ef11aa83 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -2,6 +2,17 @@ module ActiveRecord
module ConnectionAdapters
class PostgreSQLColumn < Column
module Cast
+ def point_to_string(point)
+ "(#{point[0]},#{point[1]})"
+ end
+
+ def string_to_point(string)
+ if string[0] == '(' && string[-1] == ')'
+ string = string[1...-1]
+ end
+ string.split(',').map{ |v| Float(v) }
+ end
+
def string_to_time(string)
return string unless String === string
@@ -15,6 +26,15 @@ module ActiveRecord
end
end
+ def string_to_bit(value)
+ case value
+ when /^0x/i
+ value[2..-1].hex.to_s(2) # Hexadecimal notation
+ else
+ value # Bit-string notation
+ end
+ end
+
def hstore_to_string(object)
if Hash === object
object.map { |k,v|
@@ -30,8 +50,8 @@ module ActiveRecord
nil
elsif String === string
Hash[string.scan(HstorePair).map { |k,v|
- v = v.upcase == 'NULL' ? nil : v.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
- k = k.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
+ k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
[k,v]
}]
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 68f2f2ca7b..1be116ce10 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -18,8 +18,19 @@ module ActiveRecord
end
end
+ class Bit < Type
+ def type_cast(value)
+ if String === value
+ ConnectionAdapters::PostgreSQLColumn.string_to_bit value
+ else
+ value
+ end
+ end
+ end
+
class Bytea < Type
def type_cast(value)
+ return if value.nil?
PGconn.unescape_bytea value
end
end
@@ -63,6 +74,16 @@ module ActiveRecord
end
end
+ class Point < Type
+ def type_cast(value)
+ if String === value
+ ConnectionAdapters::PostgreSQLColumn.string_to_point value
+ else
+ value
+ end
+ end
+ end
+
class Array < Type
attr_reader :subtype
def initialize(subtype)
@@ -312,14 +333,14 @@ module ActiveRecord
# FIXME: why are we keeping these types as strings?
alias_type 'tsvector', 'text'
alias_type 'interval', 'text'
- alias_type 'bit', 'text'
- alias_type 'varbit', 'text'
alias_type 'macaddr', 'text'
alias_type 'uuid', 'text'
register_type 'money', OID::Money.new
register_type 'bytea', OID::Bytea.new
register_type 'bool', OID::Boolean.new
+ register_type 'bit', OID::Bit.new
+ register_type 'varbit', OID::Bit.new
register_type 'float4', OID::Float.new
alias_type 'float8', 'float4'
@@ -330,6 +351,7 @@ module ActiveRecord
register_type 'time', OID::Time.new
register_type 'path', OID::Identity.new
+ register_type 'point', OID::Point.new
register_type 'polygon', OID::Identity.new
register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 43f991b362..40a3b82839 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -18,27 +18,33 @@ module ActiveRecord
def quote(value, column = nil) #:nodoc:
return super unless column
+ sql_type = type_to_sql(column.type, column.limit, column.precision, column.scale)
+
case value
when Range
- if /range$/ =~ column.sql_type
- "'#{PostgreSQLColumn.range_to_string(value)}'::#{column.sql_type}"
+ if /range$/ =~ sql_type
+ "'#{PostgreSQLColumn.range_to_string(value)}'::#{sql_type}"
else
super
end
when Array
- if column.array
- "'#{PostgreSQLColumn.array_to_string(value, column, self)}'"
+ case sql_type
+ when 'point' then super(PostgreSQLColumn.point_to_string(value))
else
- super
+ if column.array
+ "'#{PostgreSQLColumn.array_to_string(value, column, self).gsub(/'/, "''")}'"
+ else
+ super
+ end
end
when Hash
- case column.sql_type
+ case sql_type
when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
when 'json' then super(PostgreSQLColumn.json_to_string(value), column)
else super
end
when IPAddr
- case column.sql_type
+ case sql_type
when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
else super
end
@@ -51,14 +57,14 @@ module ActiveRecord
super
end
when Numeric
- if column.sql_type == 'money' || [:string, :text].include?(column.type)
+ if sql_type == 'money' || [:string, :text].include?(column.type)
# Not truly string input, so doesn't require (or allow) escape string syntax.
"'#{value}'"
else
super
end
when String
- case column.sql_type
+ case sql_type
when 'bytea' then "'#{escape_bytea(value)}'"
when 'xml' then "xml '#{quote_string(value)}'"
when /^bit/
@@ -90,8 +96,12 @@ module ActiveRecord
super(value, column)
end
when Array
- return super(value, column) unless column.array
- PostgreSQLColumn.array_to_string(value, column, self)
+ case column.sql_type
+ when 'point' then PostgreSQLColumn.point_to_string(value)
+ else
+ return super(value, column) unless column.array
+ PostgreSQLColumn.array_to_string(value, column, self)
+ end
when String
return super(value, column) unless 'bytea' == column.sql_type
{ :value => value, :format => 1 }
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 3bc61c5e0c..d9b807bba4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,6 +1,42 @@
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+
+ def visit_AddColumn(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(sql, column_options(o))
+ end
+
+ def visit_ColumnDefinition(o)
+ sql = super
+ if o.primary_key? && o.type == :uuid
+ sql << " PRIMARY KEY "
+ add_column_options!(sql, column_options(o))
+ end
+ sql
+ end
+
+ def add_column_options!(sql, options)
+ if options[:array] || options[:column].try(:array)
+ sql << '[]'
+ end
+
+ column = options.fetch(:column) { return super }
+ if column.type == :uuid && options[:default] =~ /\(\)/
+ sql << " DEFAULT #{options[:default]}"
+ else
+ super
+ end
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
module SchemaStatements
# Drops the database specified on the +name+ attribute
# and creates it again using the provided +options+.
@@ -120,12 +156,15 @@ module ActiveRecord
column_names = columns.values_at(*indkey).compact
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
+ unless column_names.empty?
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
+ using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
+ IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ end
end.compact
end
@@ -337,10 +376,7 @@ module ActiveRecord
# See TableDefinition#column for details of the options you can use.
def add_column(table_name, column_name, type, options = {})
clear_cache!
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
-
- execute add_column_sql
+ super
end
# Changes the column of a table.
@@ -375,6 +411,11 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
+ def add_index(table_name, column_name, options = {}) #:nodoc:
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ end
+
def remove_index!(table_name, index_name) #:nodoc:
execute "DROP INDEX #{quote_table_name(index_name)}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 940de7e4f6..bf403c3ae0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -16,7 +16,7 @@ require 'pg'
require 'ipaddr'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
:client_encoding, :options, :application_name, :fallback_application_name,
:keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
@@ -24,7 +24,7 @@ module ActiveRecord
:requirepeer, :krbsrvname, :gsslib, :service]
# Establishes a connection to the database that's used by all Active Record objects
- def postgresql_connection(config) # :nodoc:
+ def postgresql_connection(config)
conn_params = config.symbolize_keys
conn_params.delete_if { |_, v| v.nil? }
@@ -80,7 +80,7 @@ module ActiveRecord
when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
$1
# Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
+ when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
$1
# Character types
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
@@ -330,6 +330,13 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+ def primary_key(name, type = :primary_key, options = {})
+ return super unless type == :uuid
+ options[:default] ||= 'uuid_generate_v4()'
+ options[:primary_key] = true
+ column name, type, options
+ end
+
def column(name, type = nil, options = {})
super
column = self[name]
@@ -344,8 +351,8 @@ module ActiveRecord
private
- def create_column_definition(base, name, type)
- ColumnDefinition.new base, name, type
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
end
@@ -426,6 +433,10 @@ module ActiveRecord
true
end
+ def index_algorithms
+ { concurrently: 'CONCURRENTLY' }
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -627,19 +638,6 @@ module ActiveRecord
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
end
- def add_column_options!(sql, options)
- if options[:array] || options[:column].try(:array)
- sql << '[]'
- end
-
- column = options.fetch(:column) { return super }
- if column.type == :uuid && options[:default] =~ /\(\)/
- sql << " DEFAULT #{options[:default]}"
- else
- super
- end
- end
-
# Set the authorized user for this session
def session_auth=(user)
clear_cache!
@@ -669,6 +667,10 @@ module ActiveRecord
@use_insert_returning
end
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
+
protected
# Returns the version of the connected PostgreSQL server.
@@ -713,7 +715,14 @@ module ActiveRecord
# populate composite types
nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ if OID.registered_type? row['typname']
+ # this composite type is explicitly registered
+ vector = OID::NAMES[row['typname']]
+ else
+ # use the default for composite types
+ vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ end
+
OID::TYPE_MAP[row['oid'].to_i] = vector
end
@@ -900,8 +909,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition
- TableDefinition.new(self)
+ def create_table_definition(name, temporary, options)
+ TableDefinition.new native_database_types, name, temporary, options
end
def update_table_definition(table_name, base)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index d3ffee3a8b..7d940fe1c9 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -6,9 +6,9 @@ gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# sqlite3 adapter reuses sqlite_connection.
- def sqlite3_connection(config) # :nodoc:
+ def sqlite3_connection(config)
# Require database.
unless config[:database]
raise ArgumentError, "No database file specified. Missing argument: database"
@@ -458,7 +458,7 @@ module ActiveRecord
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
alter_table(table_name) do |definition|
- definition.columns.delete(definition[column_name])
+ definition.remove_column column_name
end
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index d6d998c7be..a1943dfcb0 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -15,15 +15,15 @@ module ActiveRecord
# Example for SQLite database:
#
# ActiveRecord::Base.establish_connection(
- # adapter: "sqlite",
- # database: "path/to/dbfile"
+ # adapter: "sqlite",
+ # database: "path/to/dbfile"
# )
#
# Also accepts keys as strings (for parsing from YAML for example):
#
# ActiveRecord::Base.establish_connection(
- # "adapter" => "sqlite",
- # "database" => "path/to/dbfile"
+ # "adapter" => "sqlite",
+ # "database" => "path/to/dbfile"
# )
#
# Or a URL:
@@ -54,11 +54,11 @@ module ActiveRecord
end
def connection_id
- Thread.current['ActiveRecord::Base.connection_id']
+ ActiveRecord::RuntimeRegistry.connection_id
end
def connection_id=(connection_id)
- Thread.current['ActiveRecord::Base.connection_id'] = connection_id
+ ActiveRecord::RuntimeRegistry.connection_id = connection_id
end
# Returns the configuration of the associated connection as a hash:
@@ -79,7 +79,7 @@ module ActiveRecord
connection_handler.retrieve_connection(self)
end
- # Returns true if Active Record is connected.
+ # Returns +true+ if Active Record is connected.
def connected?
connection_handler.connected?(self)
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 968dad5844..ba053700f2 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -77,8 +77,17 @@ module ActiveRecord
mattr_accessor :disable_implicit_join_references, instance_writer: false
self.disable_implicit_join_references = false
- class_attribute :connection_handler, instance_writer: false
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+ class_attribute :default_connection_handler, instance_writer: false
+
+ def self.connection_handler
+ ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
+ end
+
+ def self.connection_handler=(handler)
+ ActiveRecord::RuntimeRegistry.connection_handler = handler
+ end
+
+ self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
end
module ClassMethods
@@ -176,6 +185,7 @@ module ActiveRecord
@columns_hash = self.class.column_types.dup
init_internals
+ init_changed_attributes
ensure_proper_type
populate_with_current_scope_attributes
@@ -246,9 +256,7 @@ module ActiveRecord
run_callbacks(:initialize) unless _initialize_callbacks.empty?
@changed_attributes = {}
- self.class.column_defaults.each do |attr, orig_value|
- @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
- end
+ init_changed_attributes
@aggregation_cache = {}
@association_cache = {}
@@ -299,9 +307,11 @@ module ActiveRecord
id.hash
end
- # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
+ # Clone and freeze the attributes hash such that associations are still
+ # accessible, even on destroyed records, but cloned models will not be
+ # frozen.
def freeze
- @attributes.freeze
+ @attributes = @attributes.clone.freeze
self
end
@@ -336,9 +346,15 @@ module ActiveRecord
self.class.connection
end
+ def connection_handler
+ self.class.connection_handler
+ end
+
# Returns the contents of the record as a nicely formatted string.
def inspect
- inspection = if @attributes
+ # We check defined?(@attributes) not to issue warnings if the object is
+ # allocated but not initialized.
+ inspection = if defined?(@attributes) && @attributes
self.class.column_names.collect { |name|
if has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
@@ -434,5 +450,14 @@ module ActiveRecord
@transaction_state = nil
@reflects_state = [false]
end
+
+ def init_changed_attributes
+ # Intentionally avoid using #column_defaults since overridden defaults (as is done in
+ # optimistic locking) won't get written unless they get marked as changed
+ self.class.columns.each do |c|
+ attr, orig_value = c.name, c.default
+ @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index c615d59725..cd31147414 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -57,24 +57,25 @@ module ActiveRecord
class RecordNotDestroyed < ActiveRecordError
end
- # Raised when SQL statement cannot be executed by the database (for example, it's often the case for
- # MySQL when Ruby driver used is too old).
+ # Superclass for all database execution errors.
+ #
+ # Wraps the underlying database error as +original_exception+.
class StatementInvalid < ActiveRecordError
+ attr_reader :original_exception
+
+ def initialize(message, original_exception = nil)
+ super(message)
+ @original_exception = original_exception
+ end
end
# Raised when SQL statement is invalid and the application gets a blank result.
class ThrowResult < ActiveRecordError
end
- # Parent class for all specific exceptions which wrap database driver exceptions
- # provides access to the original exception also.
+ # Defunct wrapper class kept for compatibility.
+ # +StatementInvalid+ wraps the original exception now.
class WrappedDatabaseException < StatementInvalid
- attr_reader :original_exception
-
- def initialize(message, original_exception)
- super(message)
- @original_exception = original_exception
- end
end
# Raised when a record cannot be inserted because it would violate a uniqueness constraint.
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 15736575a2..e65dab07ba 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,22 +1,22 @@
require 'active_support/lazy_load_hooks'
+require 'active_record/explain_registry'
module ActiveRecord
module Explain
- # Relation#explain needs to be able to collect the queries.
+ # Executes the block with the collect flag enabled. Queries are collected
+ # asynchronously by the subscriber and returned.
def collecting_queries_for_explain # :nodoc:
- current = Thread.current
- original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
+ ExplainRegistry.collect = true
yield
- return current[:available_queries_for_explain]
+ ExplainRegistry.queries
ensure
- # Note that the return value above does not depend on this assignment.
- current[:available_queries_for_explain] = original
+ ExplainRegistry.reset
end
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
# Returns a formatted string ready to be logged.
def exec_explain(queries) # :nodoc:
- str = queries && queries.map do |sql, bind|
+ str = queries.map do |sql, bind|
[].tap do |msg|
msg << "EXPLAIN for: #{sql}"
unless bind.empty?
@@ -31,6 +31,7 @@ module ActiveRecord
def str.inspect
self
end
+
str
end
end
diff --git a/activerecord/lib/active_record/explain_registry.rb b/activerecord/lib/active_record/explain_registry.rb
new file mode 100644
index 0000000000..f5cd57e075
--- /dev/null
+++ b/activerecord/lib/active_record/explain_registry.rb
@@ -0,0 +1,30 @@
+require 'active_support/per_thread_registry'
+
+module ActiveRecord
+ # This is a thread locals registry for EXPLAIN. For example
+ #
+ # ActiveRecord::ExplainRegistry.queries
+ #
+ # returns the collected queries local to the current thread.
+ #
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
+ # for further details.
+ class ExplainRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ attr_accessor :queries, :collect
+
+ def initialize
+ reset
+ end
+
+ def collect?
+ @collect
+ end
+
+ def reset
+ @collect = false
+ @queries = []
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 0f927496fb..a3bc56d600 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -1,4 +1,5 @@
require 'active_support/notifications'
+require 'active_record/explain_registry'
module ActiveRecord
class ExplainSubscriber # :nodoc:
@@ -7,8 +8,8 @@ module ActiveRecord
end
def finish(name, id, payload)
- if queries = Thread.current[:available_queries_for_explain]
- queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
+ if ExplainRegistry.collect? && !ignore_payload?(payload)
+ ExplainRegistry.queries << payload.values_at(:sql, :binds)
end
end
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index 11b53275e1..fbd7a4d891 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -24,7 +24,6 @@ module ActiveRecord
rows.each(&block)
end
- RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ] # :nodoc:
private
def rows
@@ -32,7 +31,7 @@ module ActiveRecord
begin
data = YAML.load(render(IO.read(@file)))
- rescue *RESCUE_ERRORS => error
+ rescue ArgumentError, Psych::SyntaxError => error
raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
end
@rows = data ? validate(data).to_a : []
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 2958d08210..45dc26f0ed 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -708,11 +708,18 @@ module ActiveRecord
module TestFixtures
extend ActiveSupport::Concern
- included do
- setup :setup_fixtures
- teardown :teardown_fixtures
+ def before_setup
+ setup_fixtures
+ super
+ end
+
+ def after_teardown
+ super
+ teardown_fixtures
+ end
- class_attribute :fixture_path
+ included do
+ class_attribute :fixture_path, :instance_writer => false
class_attribute :fixture_table_names
class_attribute :fixture_class_names
class_attribute :use_transactional_fixtures
@@ -752,7 +759,7 @@ module ActiveRecord
def fixtures(*fixture_set_names)
if fixture_set_names.first == :all
fixture_set_names = Dir["#{fixture_path}/**/*.{yml}"]
- fixture_set_names.map! { |f| f[(fixture_path.size + 1)..-5] }
+ fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
else
fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
end
@@ -765,8 +772,7 @@ module ActiveRecord
def try_to_load_dependency(file_name)
require_dependency file_name
rescue LoadError => e
- # Let's hope the developer has included it himself
-
+ # Let's hope the developer has included it
# Let's warn in case this is a subdependency, otherwise
# subdependency error messages are totally cryptic
if ActiveRecord::Base.logger
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index f54865c86e..8df76c7f5f 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -174,7 +174,7 @@ module ActiveRecord
if subclass_name.present? && subclass_name != self.name
subclass = subclass_name.safe_constantize
- unless subclasses.include?(subclass)
+ unless descendants.include?(subclass)
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 48c73d7781..2589b2f3da 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -21,7 +21,7 @@ module ActiveRecord
# <tt>resources :users</tt> route. Normally, +user_path+ will
# construct a path with the user object's 'id' in it:
#
- # user = User.find_by_name('Phusion')
+ # user = User.find_by(name: 'Phusion')
# user_path(user) # => "/users/1"
#
# You can override +to_param+ in your model to make +user_path+ construct
@@ -33,7 +33,7 @@ module ActiveRecord
# end
# end
#
- # user = User.find_by_name('Phusion')
+ # user = User.find_by(name: 'Phusion')
# user_path(user) # => "/users/Phusion"
def to_param
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index c1ba524c84..61e5c120df 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -3,11 +3,11 @@ module ActiveRecord
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
def self.runtime=(value)
- Thread.current[:active_record_sql_runtime] = value
+ ActiveRecord::RuntimeRegistry.sql_runtime = value
end
def self.runtime
- Thread.current[:active_record_sql_runtime] ||= 0
+ ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
end
def self.reset_runtime
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 27d37766b7..6c020e1d57 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -102,7 +102,7 @@ module ActiveRecord
# table definition.
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
# * <tt>change_table(name, options)</tt>: Allows to make column alterations to
- # the table called +name+. It makes the table object availabe to a block that
+ # the table called +name+. It makes the table object available to a block that
# can then add/remove columns, indexes or foreign keys to it.
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
# to +new_name+.
@@ -466,7 +466,7 @@ module ActiveRecord
@connection.respond_to?(:reverting) && @connection.reverting
end
- class ReversibleBlockHelper < Struct.new(:reverting)
+ class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc:
def up
yield unless reverting
end
@@ -867,11 +867,15 @@ module ActiveRecord
alias :current :current_migration
def run
- target = migrations.detect { |m| m.version == @target_version }
- raise UnknownMigrationVersionError.new(@target_version) if target.nil?
- unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
- target.migrate(@direction)
- record_version_state_after_migrating(target.version)
+ migration = migrations.detect { |m| m.version == @target_version }
+ raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
+ unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
+ begin
+ execute_migration_in_transaction(migration, @direction)
+ rescue => e
+ canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
+ raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
+ end
end
end
@@ -892,10 +896,7 @@ module ActiveRecord
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
begin
- ddl_transaction(migration) do
- migration.migrate(@direction)
- record_version_state_after_migrating(migration.version)
- end
+ execute_migration_in_transaction(migration, @direction)
rescue => e
canceled_msg = use_transaction?(migration) ? "this and " : ""
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
@@ -932,6 +933,13 @@ module ActiveRecord
migrated.include?(migration.version.to_i)
end
+ def execute_migration_in_transaction(migration, direction)
+ ddl_transaction(migration) do
+ migration.migrate(direction)
+ record_version_state_after_migrating(migration.version)
+ end
+ end
+
def target
migrations.detect { |m| m.version == @target_version }
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 602ab9e2f4..d607f49e2b 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -90,8 +90,9 @@ module ActiveRecord
# accepts_nested_attributes_for :posts
# end
#
- # You can now set or update attributes on an associated post model through
- # the attribute hash.
+ # You can now set or update attributes on the associated posts through
+ # an attribute hash for a member: include the key +:posts_attributes+
+ # with an array of hashes of post attributes as a value.
#
# For each hash that does _not_ have an <tt>id</tt> key a new record will
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
@@ -114,10 +115,10 @@ module ActiveRecord
# hashes if they fail to pass your criteria. For example, the previous
# example could be rewritten as:
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
- # end
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
+ # end
#
# params = { member: {
# name: 'joe', posts_attributes: [
@@ -134,19 +135,19 @@ module ActiveRecord
#
# Alternatively, :reject_if also accepts a symbol for using methods:
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: :new_record?
- # end
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: :new_record?
+ # end
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: :reject_posts
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: :reject_posts
#
- # def reject_posts(attributed)
- # attributed['title'].blank?
- # end
- # end
+ # def reject_posts(attributed)
+ # attributed['title'].blank?
+ # end
+ # end
#
# If the hash contains an <tt>id</tt> key that matches an already
# associated record, the matching record will be modified:
@@ -183,6 +184,29 @@ module ActiveRecord
# member.save
# member.reload.posts.length # => 1
#
+ # Nested attributes for an associated collection can also be passed in
+ # the form of a hash of hashes instead of an array of hashes:
+ #
+ # Member.create(name: 'joe',
+ # posts_attributes: { first: { title: 'Foo' },
+ # second: { title: 'Bar' } })
+ #
+ # has the same effect as
+ #
+ # Member.create(name: 'joe',
+ # posts_attributes: [ { title: 'Foo' },
+ # { title: 'Bar' } ])
+ #
+ # The keys of the hash which is the value for +:posts_attributes+ are
+ # ignored in this case.
+ # However, it is not allowed to use +'id'+ or +:id+ for one of
+ # such keys, otherwise the hash will be wrapped in an array and
+ # interpreted as an attribute hash for a single post.
+ #
+ # Passing attributes for an associated collection in the form of a hash
+ # of hashes can be used with hashes generated from HTTP/HTML parameters,
+ # where there maybe no natural way to submit an array of hashes.
+ #
# === Saving
#
# All changes to models, including the destruction of those marked for
@@ -338,7 +362,7 @@ module ActiveRecord
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
elsif attributes['id'].present?
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
@@ -428,7 +452,7 @@ module ActiveRecord
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
end
else
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
end
end
end
@@ -490,7 +514,7 @@ module ActiveRecord
end
end
- def raise_nested_attributes_record_not_found(association_name, record_id)
+ def raise_nested_attributes_record_not_found!(association_name, record_id)
raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index b25d0601cb..178db07ca1 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -51,7 +51,7 @@ module ActiveRecord
# how this "single-table" inheritance mapping is implemented.
def instantiate(record, column_types = {})
klass = discriminate_class_for_record(record)
- column_types = klass.decorate_columns(column_types)
+ column_types = klass.decorate_columns(column_types.dup)
klass.allocate.init_with('attributes' => record, 'column_types' => column_types)
end
@@ -428,23 +428,11 @@ module ActiveRecord
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def update_record(attribute_names = @attributes.keys)
- attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
- if attributes_with_values.empty?
+ attributes_values = arel_attributes_with_values_for_update(attribute_names)
+ if attributes_values.empty?
0
else
- klass = self.class
- column_hash = klass.connection.schema_cache.columns_hash klass.table_name
- db_columns_with_values = attributes_with_values.map { |attr,value|
- real_column = column_hash[attr.name]
- [real_column, value]
- }
- bind_attrs = attributes_with_values.dup
- bind_attrs.keys.each_with_index do |column, i|
- real_column = db_columns_with_values[i].first
- bind_attrs[column] = klass.connection.substitute_at(real_column, i)
- end
- stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(bind_attrs)
- klass.connection.update stmt, 'SQL', db_columns_with_values
+ self.class.unscoped.update_record attributes_values, id, id_was
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 902fd90c54..f78ccb01aa 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -8,7 +8,7 @@ module ActiveRecord
delegate :find_each, :find_in_batches, :to => :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
- :having, :create_with, :uniq, :distinct, :references, :none, :to => :all
+ :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :to => :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
# Executes a custom SQL query against your database and returns all the results. The results will
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 99117b74c5..e36888d4a8 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -36,6 +36,20 @@ module ActiveRecord
rake_tasks do
require "active_record/base"
+
+ ActiveRecord::Tasks::DatabaseTasks.env = Rails.env
+ ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first
+ ActiveRecord::Tasks::DatabaseTasks.seed_loader = Rails.application
+ ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a
+ ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures'
+
+ if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
+ if engine.paths['db/migrate'].existent
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a
+ end
+ end
+
load "active_record/railties/databases.rake"
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d92e268109..92bef09ff5 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -2,14 +2,8 @@ require 'active_record'
db_namespace = namespace :db do
task :load_config do
- ActiveRecord::Base.configurations = Rails.application.config.database_configuration || {}
- ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
-
- if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
- if engine.paths['db/migrate'].existent
- ActiveRecord::Migrator.migrations_paths += engine.paths['db/migrate'].to_a
- end
- end
+ ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
+ ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
end
namespace :create do
@@ -156,7 +150,7 @@ db_namespace = namespace :db do
begin
puts ActiveRecord::Tasks::DatabaseTasks.collation_current
rescue NoMethodError
- $stderr.puts 'Sorry, your database adapter is not supported yet, feel free to submit a patch'
+ $stderr.puts 'Sorry, your database adapter is not supported yet. Feel free to submit a patch.'
end
end
@@ -166,11 +160,11 @@ db_namespace = namespace :db do
end
# desc "Raises an error if there are pending migrations"
- task :abort_if_pending_migrations => [:environment, :load_config] do
+ task :abort_if_pending_migrations => :environment do
pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
- puts "You have #{pending_migrations.size} pending migrations:"
+ puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
pending_migrations.each do |pending_migration|
puts ' %4d %s' % [pending_migration.version, pending_migration.name]
end
@@ -184,7 +178,7 @@ db_namespace = namespace :db do
desc 'Load the seed data from db/seeds.rb'
task :seed do
db_namespace['abort_if_pending_migrations'].invoke
- Rails.application.load_seed
+ ActiveRecord::Tasks::DatabaseTasks.load_seed
end
namespace :fixtures do
@@ -192,7 +186,15 @@ db_namespace = namespace :db do
task :load => [:environment, :load_config] do
require 'active_record/fixtures'
- base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ base_dir = if ENV['FIXTURES_PATH']
+ STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
+ "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
+ "instead."
+ File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ else
+ ActiveRecord::Tasks::DatabaseTasks.fixtures_path
+ end
+
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
@@ -209,7 +211,16 @@ db_namespace = namespace :db do
puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label
- base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
+ base_dir = if ENV['FIXTURES_PATH']
+ STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
+ "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
+ "instead."
+ File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ else
+ ActiveRecord::Tasks::DatabaseTasks.fixtures_path
+ end
+
+
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML::load(ERB.new(IO.read(file)).result)
data.keys.each do |key|
@@ -228,7 +239,7 @@ db_namespace = namespace :db do
desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
task :dump => [:environment, :load_config] do
require 'active_record/schema_dumper'
- filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
+ filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
File.open(filename, "w:utf-8") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
@@ -237,12 +248,9 @@ db_namespace = namespace :db do
desc 'Load a schema.rb file into the database'
task :load => [:environment, :load_config] do
- file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
- if File.exists?(file)
- load(file)
- else
- abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded}
- end
+ file = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
+ ActiveRecord::Tasks::DatabaseTasks.check_schema_file(file)
+ load(file)
end
task :load_if_ruby => ['db:create', :environment] do
@@ -253,7 +261,7 @@ db_namespace = namespace :db do
desc 'Create a db/schema_cache.dump file.'
task :dump => [:environment, :load_config] do
con = ActiveRecord::Base.connection
- filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+ filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
con.schema_cache.clear!
con.tables.each { |table| con.schema_cache.add(table) }
@@ -262,7 +270,7 @@ db_namespace = namespace :db do
desc 'Clear a db/schema_cache.dump file.'
task :clear => [:environment, :load_config] do
- filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+ filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
FileUtils.rm(filename) if File.exists?(filename)
end
end
@@ -270,32 +278,11 @@ db_namespace = namespace :db do
end
namespace :structure do
- def set_firebird_env(config)
- ENV['ISC_USER'] = config['username'].to_s if config['username']
- ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
- end
-
- def firebird_db_string(config)
- FireRuby::Database.db_string_for(config.symbolize_keys)
- end
-
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
task :dump => [:environment, :load_config] do
- filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
+ filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
- case current_config['adapter']
- when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(current_config)
- File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
- when 'sqlserver'
- `smoscript -s #{current_config['host']} -d #{current_config['database']} -u #{current_config['username']} -p #{current_config['password']} -f #{filename} -A -U`
- when "firebird"
- set_firebird_env(current_config)
- db_string = firebird_db_string(current_config)
- sh "isql -a #{db_string} > #{filename}"
- else
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
- end
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
if ActiveRecord::Base.connection.supports_migrations?
File.open(filename, "a") do |f|
@@ -307,23 +294,10 @@ db_namespace = namespace :db do
# desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
+ filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
+ ActiveRecord::Tasks::DatabaseTasks.check_schema_file(filename)
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
- filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
- case current_config['adapter']
- when 'sqlserver'
- `sqlcmd -S #{current_config['host']} -d #{current_config['database']} -U #{current_config['username']} -P #{current_config['password']} -i #{filename}`
- when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(current_config)
- IO.read(filename).split(";\n\n").each do |ddl|
- ActiveRecord::Base.connection.execute(ddl)
- end
- when 'firebird'
- set_firebird_env(current_config)
- db_string = firebird_db_string(current_config)
- sh "isql -i #{filename} #{db_string}"
- else
- ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
- end
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
end
task :load_if_sql => ['db:create', :environment] do
@@ -378,29 +352,11 @@ db_namespace = namespace :db do
# desc "Empty the test database"
task :purge => [:environment, :load_config] do
- abcs = ActiveRecord::Base.configurations
- case abcs['test']['adapter']
- when 'sqlserver'
- test = abcs.deep_dup['test']
- test_database = test['database']
- test['database'] = 'master'
- ActiveRecord::Base.establish_connection(test)
- ActiveRecord::Base.connection.recreate_database!(test_database)
- when "oci", "oracle"
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl|
- ActiveRecord::Base.connection.execute(ddl)
- end
- when 'firebird'
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database!
- else
- ActiveRecord::Tasks::DatabaseTasks.purge abcs['test']
- end
+ ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
# desc 'Check for pending migrations and load the test schema'
- task :prepare => 'db:abort_if_pending_migrations' do
+ task :prepare do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
end
@@ -426,7 +382,7 @@ namespace :railties do
puts "NOTE: Migration #{migration.basename} from #{name} has been skipped. Migration with the same name already exists."
end
- on_copy = Proc.new do |name, migration, old_path|
+ on_copy = Proc.new do |name, migration|
puts "Copied migration #{migration.basename} from #{name}"
end
@@ -436,5 +392,5 @@ namespace :railties do
end
end
-task 'test:prepare' => 'db:test:prepare'
+task 'test:prepare' => ['db:test:prepare', 'db:test:load', 'db:abort_if_pending_migrations']
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0995750ecd..60eda96f08 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -75,8 +75,13 @@ module ActiveRecord
end
end
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
+ # Base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
+ #
+ # MacroReflection
+ # AggregateReflection
+ # AssociationReflection
+ # ThroughReflection
class MacroReflection
# Returns the name of the macro.
#
@@ -401,6 +406,16 @@ module ActiveRecord
# has_many :tags, through: :taggings
# end
#
+ # class Tagging < ActiveRecord::Base
+ # belongs_to :post
+ # belongs_to :tag
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ #
+ # taggings_reflection = tags_reflection.source_reflection
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
+ #
def source_reflection
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
end
@@ -426,6 +441,17 @@ module ActiveRecord
# The chain is built by recursively calling #chain on the source reflection and the through
# reflection. The base case for the recursion is a normal association, which just returns
# [self] as its #chain.
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, through: :taggings
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # tags_reflection.chain
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
+ # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
+ #
def chain
@chain ||= begin
chain = source_reflection.chain + through_reflection.chain
@@ -496,9 +522,16 @@ module ActiveRecord
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
end
- # Gets an array of possible <tt>:through</tt> source reflection names:
+ # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, through: :taggings
+ # end
#
- # [:singularized, :pluralized]
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # tags_reflection.source_reflection_names
+ # # => [:tag, :tags]
#
def source_reflection_names
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 037097d2dd..913f6f88f2 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,14 +10,14 @@ module ActiveRecord
:extending]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
- :reverse_order, :distinct, :create_with]
+ :reverse_order, :distinct, :create_with, :uniq]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
attr_reader :table, :klass, :loaded
- attr_accessor :default_scoped
+ attr_accessor :default_scoped, :proxy_association
alias :model :klass
alias :loaded? :loaded
alias :default_scoped? :default_scoped
@@ -39,7 +39,7 @@ module ActiveRecord
reset
end
- def insert(values)
+ def insert(values) # :nodoc:
primary_key_value = nil
if primary_key && Hash === values
@@ -56,16 +56,7 @@ module ActiveRecord
im = arel.create_insert
im.into @table
- conn = @klass.connection
-
- substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
- binds = substitutes.map do |arel_attr, value|
- [@klass.columns_hash[arel_attr.name], value]
- end
-
- substitutes.each_with_index do |tuple, i|
- tuple[1] = conn.substitute_at(binds[i][0], i)
- end
+ substitutes, binds = substitute_values values
if values.empty? # empty insert
im.values = Arel.sql(connection.empty_insert_statement_value)
@@ -73,7 +64,7 @@ module ActiveRecord
im.insert substitutes
end
- conn.insert(
+ @klass.connection.insert(
im,
'SQL',
primary_key,
@@ -82,6 +73,29 @@ module ActiveRecord
binds)
end
+ def update_record(values, id, id_was) # :nodoc:
+ substitutes, binds = substitute_values values
+ um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes)
+
+ @klass.connection.update(
+ um,
+ 'SQL',
+ binds)
+ end
+
+ def substitute_values(values) # :nodoc:
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
+ binds = substitutes.map do |arel_attr, value|
+ [@klass.columns_hash[arel_attr.name], value]
+ end
+
+ substitutes.each_with_index do |tuple, i|
+ tuple[1] = @klass.connection.substitute_at(binds[i][0], i)
+ end
+
+ [substitutes, binds]
+ end
+
# Initializes new record from relation while maintaining the current
# scope.
#
@@ -603,7 +617,7 @@ module ActiveRecord
"\n" \
" Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
"\n" \
- "Currently, Active Record recognises the table in the string, and knows to JOIN the " \
+ "Currently, Active Record recognizes the table in the string, and knows to JOIN the " \
"comments table to the query, rather than loading comments in a separate query. " \
"However, doing this without writing a full-blown SQL parser is inherently flawed. " \
"Since we don't want to write an SQL parser, we are removing this functionality. " \
@@ -612,7 +626,7 @@ module ActiveRecord
"\n" \
" Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n" \
"\n" \
- "If you don't rely on implicit join references you can disable the feature entirely" \
+ "If you don't rely on implicit join references you can disable the feature entirely " \
"by setting `config.active_record.disable_implicit_join_references = true`."
)
true
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index be011b22af..64e1ff9a6a 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -82,7 +82,7 @@ module ActiveRecord
# puts values["Drake"]
# # => 43
#
- # drake = Family.find_by_last_name('Drake')
+ # drake = Family.find_by(last_name: 'Drake')
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
# puts values[drake]
# # => 43
@@ -135,7 +135,7 @@ module ActiveRecord
# # SELECT people.id, people.name FROM people
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
#
- # Person.uniq.pluck(:role)
+ # Person.pluck('DISTINCT role')
# # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest']
#
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 00a506c3a7..8d6740246c 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -14,14 +14,14 @@ module ActiveRecord
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
- module ClassSpecificRelation
+ module ClassSpecificRelation # :nodoc:
extend ActiveSupport::Concern
included do
@delegation_mutex = Mutex.new
end
- module ClassMethods
+ module ClassMethods # :nodoc:
def name
superclass.name
end
@@ -37,11 +37,9 @@ module ActiveRecord
end
RUBY
else
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{method}(*args, &block)
- scoping { @klass.send(#{method.inspect}, *args, &block) }
- end
- RUBY
+ define_method method do |*args, &block|
+ scoping { @klass.send(method, *args, &block) }
+ end
end
end
end
@@ -72,7 +70,7 @@ module ActiveRecord
end
end
- module ClassMethods
+ module ClassMethods # :nodoc:
@@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2)
def new(klass, *args)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 14520381c9..72e9272cd7 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -37,7 +37,7 @@ module ActiveRecord
end
# Finds the first record matching the specified conditions. There
- # is no implied ording so if order matters, you should specify it
+ # is no implied ordering so if order matters, you should specify it
# yourself.
#
# If no record is found, returns <tt>nil</tt>.
@@ -130,8 +130,8 @@ module ActiveRecord
last or raise RecordNotFound
end
- # Returns +true+ if a record exists in the table that matches the +id+ or
- # conditions given, or +false+ otherwise. The argument can take six forms:
+ # Returns truthy if a record exists in the table that matches the +id+ or
+ # conditions given, or falsy otherwise. The argument can take six forms:
#
# * Integer - Finds the record with this primary key.
# * String - Finds the record with a primary key corresponding to this
@@ -176,6 +176,28 @@ module ActiveRecord
false
end
+ # This method is called whenever no records are found with either a single
+ # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
+ #
+ # The error message is different depending on whether a single id or
+ # multiple ids are provided. If multiple ids are provided, then the number
+ # of results obtained should be provided in the +result_size+ argument and
+ # the expected number of results should be provided in the +expected_size+
+ # argument.
+ def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
+ conditions = arel.where_sql
+ conditions = " [#{conditions}]" if conditions
+
+ if Array(ids).size == 1
+ error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
+ else
+ error = "Couldn't find all #{@klass.name.pluralize} with IDs "
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
+ end
+
+ raise RecordNotFound, error
+ end
+
protected
def find_with_associations
@@ -259,11 +281,7 @@ module ActiveRecord
relation.bind_values += [[column, id]]
record = relation.take
- unless record
- conditions = arel.where_sql
- conditions = " [#{conditions}]" if conditions
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
- end
+ raise_record_not_found_exception!(id, 0, 1) unless record
record
end
@@ -286,12 +304,7 @@ module ActiveRecord
if result.size == expected_size
result
else
- conditions = arel.where_sql
- conditions = " [#{conditions}]" if conditions
-
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
- error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
- raise RecordNotFound, error
+ raise_record_not_found_exception!(ids, result.size, expected_size)
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index eb23e92fb8..936b83261e 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
class Merger # :nodoc:
- attr_reader :relation, :values
+ attr_reader :relation, :values, :other
def initialize(relation, other)
if other.default_scoped? && other.klass != relation.klass
@@ -48,11 +48,12 @@ module ActiveRecord
@relation = relation
@values = other.values
+ @other = other
end
NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
Relation::MULTI_VALUE_METHODS -
- [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
+ [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -66,12 +67,39 @@ module ActiveRecord
merge_multi_values
merge_single_values
+ merge_joins
relation
end
private
+ def merge_joins
+ return if values[:joins].blank?
+
+ if other.klass == relation.klass
+ relation.joins!(*values[:joins])
+ else
+ joins_dependency, rest = values[:joins].partition do |join|
+ case join
+ when Hash, Symbol, Array
+ true
+ else
+ false
+ end
+ end
+
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass,
+ joins_dependency,
+ [])
+ relation.joins! rest
+
+ join_dependency.join_associations.each do |association|
+ @relation = association.join_relation(relation)
+ end
+ end
+ end
+
def merge_multi_values
relation.where_values = merged_wheres
relation.bind_values = merged_binds
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 257221174b..9fcd2d06c5 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -34,7 +34,6 @@ module ActiveRecord
#
# User.where.not(name: "Jon", role: "admin")
# # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
- #
def not(opts, *rest)
where_value = @scope.send(:build_where, opts, rest).map do |rel|
case rel
@@ -353,7 +352,7 @@ module ActiveRecord
spawn.unscope!(*args)
end
- def unscope!(*args)
+ def unscope!(*args) # :nodoc:
args.flatten!
args.each do |scope|
@@ -552,7 +551,6 @@ module ActiveRecord
# Order.having('SUM(price) > 30').group('user_id')
def having(opts, *rest)
opts.blank? ? self : spawn.having!(opts, *rest)
- spawn.having!(opts, *rest)
end
def having!(opts, *rest) # :nodoc:
@@ -934,9 +932,7 @@ module ActiveRecord
association_joins = buckets[:association_join] || []
stashed_association_joins = buckets[:stashed_join] || []
join_nodes = (buckets[:join_node] || []).uniq
- string_joins = (buckets[:string_join] || []).map { |x|
- x.strip
- }.uniq
+ string_joins = (buckets[:string_join] || []).map { |x| x.strip }.uniq
join_list = join_nodes + custom_join_ast(manager, string_joins)
diff --git a/activerecord/lib/active_record/runtime_registry.rb b/activerecord/lib/active_record/runtime_registry.rb
new file mode 100644
index 0000000000..63e6738622
--- /dev/null
+++ b/activerecord/lib/active_record/runtime_registry.rb
@@ -0,0 +1,17 @@
+require 'active_support/per_thread_registry'
+
+module ActiveRecord
+ # This is a thread locals registry for Active Record. For example:
+ #
+ # ActiveRecord::RuntimeRegistry.connection_handler
+ #
+ # returns the connection handler local to the current thread.
+ #
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
+ # for further details.
+ class RuntimeRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ attr_accessor :connection_handler, :sql_runtime, :connection_id
+ end
+end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index df090b972d..10c6d272cd 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -118,7 +118,7 @@ HEADER
# then dump all non-primary key columns
column_specs = columns.map do |column|
- raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
next if column.name == pk
@connection.column_spec(column, @types)
end.compact
@@ -185,6 +185,10 @@ HEADER
statement_parts << ('where: ' + index.where.inspect) if index.where
+ statement_parts << ('using: ' + index.using.inspect) if index.using
+
+ statement_parts << ('type: ' + index.type.inspect) if index.type
+
' ' + statement_parts.join(', ')
end
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 9746b1c3c2..0cf3d59985 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -1,3 +1,5 @@
+require 'active_support/per_thread_registry'
+
module ActiveRecord
module Scoping
extend ActiveSupport::Concern
@@ -9,11 +11,11 @@ module ActiveRecord
module ClassMethods
def current_scope #:nodoc:
- Thread.current["#{self}_current_scope"]
+ ScopeRegistry.value_for(:current_scope, base_class.to_s)
end
def current_scope=(scope) #:nodoc:
- Thread.current["#{self}_current_scope"] = scope
+ ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
end
end
@@ -24,5 +26,57 @@ module ActiveRecord
send("#{att}=", value) if respond_to?("#{att}=")
end
end
+
+ # This class stores the +:current_scope+ and +:ignore_default_scope+ values
+ # for different classes. The registry is stored as a thread local, which is
+ # accessed through +ScopeRegistry.current+.
+ #
+ # This class allows you to store and get the scope values on different
+ # classes and different types of scopes. For example, if you are attempting
+ # to get the current_scope for the +Board+ model, then you would use the
+ # following code:
+ #
+ # registry = ActiveRecord::Scoping::ScopeRegistry
+ # registry.set_value_for(:current_scope, "Board", some_new_scope)
+ #
+ # Now when you run:
+ #
+ # registry.value_for(:current_scope, "Board")
+ #
+ # You will obtain whatever was defined in +some_new_scope+. The +value_for+
+ # and +set_value_for+ methods are delegated to the current +ScopeRegistry+
+ # object, so the above example code can also be called as:
+ #
+ # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
+ # "Board", some_new_scope)
+ class ScopeRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
+
+ def initialize
+ @registry = Hash.new { |hash, key| hash[key] = {} }
+ end
+
+ # Obtains the value for a given +scope_name+ and +variable_name+.
+ def value_for(scope_type, variable_name)
+ raise_invalid_scope_type!(scope_type)
+ @registry[scope_type][variable_name]
+ end
+
+ # Sets the +value+ for a given +scope_type+ and +variable_name+.
+ def set_value_for(scope_type, variable_name, value)
+ raise_invalid_scope_type!(scope_type)
+ @registry[scope_type][variable_name] = value
+ end
+
+ private
+
+ def raise_invalid_scope_type!(scope_type)
+ if !VALID_SCOPE_TYPES.include?(scope_type)
+ raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 5bd481082e..d37d33d552 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -5,8 +5,17 @@ module ActiveRecord
included do
# Stores the default scope for the class.
- class_attribute :default_scopes, instance_writer: false
+ class_attribute :default_scopes, instance_writer: false, instance_predicate: false
+
self.default_scopes = []
+
+ def self.default_scopes?
+ ActiveSupport::Deprecation.warn(
+ "#default_scopes? is deprecated. Do something like #default_scopes.empty? instead."
+ )
+
+ !!self.default_scopes
+ end
end
module ClassMethods
@@ -27,14 +36,6 @@ module ActiveRecord
# Post.unscoped {
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
- #
- # It is recommended that you use the block form of unscoped because
- # chaining unscoped with +scope+ does not work. Assuming that
- # +published+ is a +scope+, the following two statements
- # are equal: the +default_scope+ is applied on both.
- #
- # Post.unscoped.published
- # Post.published
def unscoped
block_given? ? relation.scoping { yield } : relation
end
@@ -119,11 +120,11 @@ module ActiveRecord
end
def ignore_default_scope? # :nodoc:
- Thread.current["#{self}_ignore_default_scope"]
+ ScopeRegistry.value_for(:ignore_default_scope, self)
end
def ignore_default_scope=(ignore) # :nodoc:
- Thread.current["#{self}_ignore_default_scope"] = ignore
+ ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
end
# The ignore_default_scope flag is used to prevent an infinite recursion
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 12317601b6..da73bead32 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -160,13 +160,8 @@ module ActiveRecord
singleton_class.send(:define_method, name) do |*args|
if body.respond_to?(:call)
- scope = extension ? body.call(*args).extending(extension) : body.call(*args)
-
- if scope
- default_scoped = scope.default_scoped
- scope = relation.merge(scope)
- scope.default_scoped = default_scoped
- end
+ scope = all.scoping { body.call(*args) }
+ scope = scope.extending(extension) if extension
else
scope = body
end
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
new file mode 100644
index 0000000000..dd4ee0c4a0
--- /dev/null
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -0,0 +1,26 @@
+module ActiveRecord
+
+ # Statement cache is used to cache a single statement in order to avoid creating the AST again.
+ # Initializing the cache is done by passing the statement in the initialization block:
+ #
+ # cache = ActiveRecord::StatementCache.new do
+ # Book.where(name: "my book").limit(100)
+ # end
+ #
+ # The cached statement is executed by using the +execute+ method:
+ #
+ # cache.execute
+ #
+ # The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
+ # Database is queried when +to_a+ is called on the relation.
+ class StatementCache
+ def initialize
+ @relation = yield
+ raise ArgumentError.new("Statement cannot be nil") if @relation.nil?
+ end
+
+ def execute
+ @relation.dup.to_a
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 4fa7cf8a7d..3e8b79c7a0 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -3,10 +3,41 @@ module ActiveRecord
class DatabaseAlreadyExists < StandardError; end # :nodoc:
class DatabaseNotSupported < StandardError; end # :nodoc:
- module DatabaseTasks # :nodoc:
+ # <tt>ActiveRecord::Tasks::DatabaseTasks</tt> is a utility class, which encapsulates
+ # logic behind common tasks used to manage database and migrations.
+ #
+ # The tasks defined here are used in rake tasks provided by Active Record.
+ #
+ # In order to use DatabaseTasks, a few config values need to be set. All the needed
+ # config values are set by Rails already, so it's necessary to do it only if you
+ # want to change the defaults or when you want to use Active Record outside of Rails
+ # (in such case after configuring the database tasks, you can also use the rake tasks
+ # defined in Active Record).
+ #
+ #
+ # The possible config values are:
+ #
+ # * +env+: current environment (like Rails.env).
+ # * +database_configuration+: configuration of your databases (as in +config/database.yml+).
+ # * +db_dir+: your +db+ directory.
+ # * +fixtures_path+: a path to fixtures directory.
+ # * +migrations_paths+: a list of paths to directories with migrations.
+ # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method.
+ #
+ # Example usage of +DatabaseTasks+ outside Rails could look as such:
+ #
+ # include ActiveRecord::Tasks
+ # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml'))
+ # DatabaseTasks.db_dir = 'db'
+ # # other settings...
+ #
+ # DatabaseTasks.create_current('production')
+ module DatabaseTasks
extend self
attr_writer :current_config
+ attr_accessor :database_configuration, :migrations_paths, :seed_loader, :db_dir,
+ :fixtures_path, :env
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
@@ -15,12 +46,16 @@ module ActiveRecord
@tasks[pattern] = task
end
- register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
- register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
- register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+ register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
+ register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
+ register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+
+ register_task(/firebird/, ActiveRecord::Tasks::FirebirdDatabaseTasks)
+ register_task(/sqlserver/, ActiveRecord::Tasks::SqlserverDatabaseTasks)
+ register_task(/(oci|oracle)/, ActiveRecord::Tasks::OracleDatabaseTasks)
def current_config(options = {})
- options.reverse_merge! :env => Rails.env
+ options.reverse_merge! :env => env
if options.has_key?(:config)
@current_config = options[:config]
else
@@ -46,7 +81,7 @@ module ActiveRecord
each_local_configuration { |configuration| create configuration }
end
- def create_current(environment = Rails.env)
+ def create_current(environment = env)
each_current_configuration(environment) { |configuration|
create configuration
}
@@ -69,7 +104,7 @@ module ActiveRecord
each_local_configuration { |configuration| drop configuration }
end
- def drop_current(environment = Rails.env)
+ def drop_current(environment = env)
each_current_configuration(environment) { |configuration|
drop configuration
}
@@ -79,7 +114,7 @@ module ActiveRecord
drop database_url_config
end
- def charset_current(environment = Rails.env)
+ def charset_current(environment = env)
charset ActiveRecord::Base.configurations[environment]
end
@@ -88,7 +123,7 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).charset
end
- def collation_current(environment = Rails.env)
+ def collation_current(environment = env)
collation ActiveRecord::Base.configurations[environment]
end
@@ -113,6 +148,24 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
end
+ def check_schema_file(filename)
+ unless File.exists?(filename)
+ message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
+ message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails)
+ Kernel.abort message
+ end
+ end
+
+ def load_seed
+ if seed_loader
+ seed_loader.load_seed
+ else
+ raise "You tried to load seed data, but no seed loader is specified. Please specify seed " +
+ "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" +
+ "Seed loader should respond to load_seed method"
+ end
+ end
+
private
def database_url_config
@@ -130,7 +183,7 @@ module ActiveRecord
def each_current_configuration(environment)
environments = [environment]
- environments << 'test' if environment.development?
+ environments << 'test' if environment == 'development'
configurations = ActiveRecord::Base.configurations.values_at(*environments)
configurations.compact.each do |configuration|
diff --git a/activerecord/lib/active_record/tasks/firebird_database_tasks.rb b/activerecord/lib/active_record/tasks/firebird_database_tasks.rb
new file mode 100644
index 0000000000..98014a38ea
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/firebird_database_tasks.rb
@@ -0,0 +1,56 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class FirebirdDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ establish_connection(:test)
+ connection.recreate_database!
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ set_firebird_env(configuration)
+ db_string = firebird_db_string(configuration)
+ Kernel.system "isql -a #{db_string} > #{filename}"
+ end
+
+ def structure_load(filename)
+ set_firebird_env(configuration)
+ db_string = firebird_db_string(configuration)
+ Kernel.system "isql -i #{filename} #{db_string}"
+ end
+
+ private
+
+ def set_firebird_env(config)
+ ENV['ISC_USER'] = config['username'].to_s if config['username']
+ ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
+ end
+
+ def firebird_db_string(config)
+ FireRuby::Database.db_string_for(config.symbolize_keys)
+ end
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/oracle_database_tasks.rb b/activerecord/lib/active_record/tasks/oracle_database_tasks.rb
new file mode 100644
index 0000000000..de3aa50e5e
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/oracle_database_tasks.rb
@@ -0,0 +1,45 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class OracleDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ establish_connection(:test)
+ connection.structure_drop.split(";\n\n").each { |ddl| connection.execute(ddl) }
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ establish_connection(configuration)
+ File.open(filename, "w:utf-8") { |f| f << connection.structure_dump }
+ end
+
+ def structure_load(filename)
+ establish_connection(configuration)
+ IO.read(filename).split(";\n\n").each { |ddl| connection.execute(ddl) }
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 0b1b030516..4413330fab 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -59,7 +59,7 @@ module ActiveRecord
def structure_load(filename)
set_psql_env
- Kernel.system("psql -f #{filename} #{configuration['database']}")
+ Kernel.system("psql -q -f #{filename} #{configuration['database']}")
end
private
diff --git a/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb
new file mode 100644
index 0000000000..c718ee03a8
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb
@@ -0,0 +1,48 @@
+require 'shellwords'
+
+module ActiveRecord
+ module Tasks # :nodoc:
+ class SqlserverDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ test = configuration.deep_dup
+ test_database = test['database']
+ test['database'] = 'master'
+ establish_connection(test)
+ connection.recreate_database!(test_database)
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ Kernel.system("smoscript -s #{configuration['host']} -d #{configuration['database']} -u #{configuration['username']} -p #{configuration['password']} -f #{filename} -A -U")
+ end
+
+ def structure_load(filename)
+ Kernel.system("sqlcmd -S #{configuration['host']} -d #{configuration['database']} -U #{configuration['username']} -P #{configuration['password']} -i #{filename}")
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 4dbd668fcf..a5955ccba4 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -339,8 +339,12 @@ module ActiveRecord
# Save the new record state and id of a record so it can be restored later if a transaction fails.
def remember_transaction_record_state #:nodoc:
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
- @_start_transaction_state[:new_record] = @new_record
- @_start_transaction_state[:destroyed] = @destroyed
+ unless @_start_transaction_state.include?(:new_record)
+ @_start_transaction_state[:new_record] = @new_record
+ end
+ unless @_start_transaction_state.include?(:destroyed)
+ @_start_transaction_state[:destroyed] = @destroyed
+ end
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
@_start_transaction_state[:frozen?] = @attributes.frozen?
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 3706885881..26dca415ff 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -74,8 +74,7 @@ module ActiveRecord
protected
def perform_validations(options={}) # :nodoc:
- perform_validation = options[:validate] != false
- perform_validation ? valid?(options[:context]) : true
+ options[:validate] == false || valid?(options[:context])
end
end
end
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index c0471bb506..de5fd05468 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,10 +1,11 @@
module ActiveRecord
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActiveRecord as a Gem::Version
+ def self.version
+ Gem::Version.new "4.1.0.beta"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveRecord.version.segments
+ STRING = ActiveRecord.version.to_s
end
end