aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb16
-rw-r--r--activerecord/lib/active_record/attribute.rb56
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb55
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb83
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb27
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb42
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb6
-rw-r--r--activerecord/lib/active_record/core.rb41
-rw-r--r--activerecord/lib/active_record/errors.rb36
-rw-r--r--activerecord/lib/active_record/fixtures.rb18
-rw-r--r--activerecord/lib/active_record/migration.rb13
-rw-r--r--activerecord/lib/active_record/persistence.rb18
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/result.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb6
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb3
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/type.rb1
-rw-r--r--activerecord/lib/active_record/type/mutable.rb16
-rw-r--r--activerecord/lib/active_record/type/serialized.rb10
-rw-r--r--activerecord/lib/active_record/type/value.rb8
34 files changed, 308 insertions, 229 deletions
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index da9b125fd6..175019a72b 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -82,12 +82,16 @@ module ActiveRecord
@through_records[record.object_id] ||= begin
ensure_mutable
- through_record = through_association.build through_scope_attributes
+ through_record = through_association.build(*options_for_through_record)
through_record.send("#{source_reflection.name}=", record)
through_record
end
end
+ def options_for_through_record
+ [through_scope_attributes]
+ end
+
def through_scope_attributes
scope.where_values_hash(through_association.reflection.name.to_s).except!(through_association.reflection.foreign_key)
end
@@ -180,7 +184,11 @@ module ActiveRecord
def through_records_for(record)
attributes = construct_join_attributes(record)
candidates = Array.wrap(through_association.target)
- candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
+ candidates.find_all do |c|
+ attributes.all? do |key, value|
+ c.public_send(key) == value
+ end
+ end
end
def delete_through_records(records)
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index fcf3b219d4..f00fef8b9e 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -41,12 +41,16 @@ module ActiveRecord
def construct_join_attributes(*records)
ensure_mutable
- join_attributes = {
- source_reflection.foreign_key =>
- records.map { |record|
- record.send(source_reflection.association_primary_key(reflection.klass))
- }
- }
+ if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
+ join_attributes = { source_reflection.name => records }
+ else
+ join_attributes = {
+ source_reflection.foreign_key =>
+ records.map { |record|
+ record.send(source_reflection.association_primary_key(reflection.klass))
+ }
+ }
+ end
if options[:source_type]
join_attributes[source_reflection.foreign_type] =
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
new file mode 100644
index 0000000000..f78cde23c5
--- /dev/null
+++ b/activerecord/lib/active_record/attribute.rb
@@ -0,0 +1,56 @@
+module ActiveRecord
+ class Attribute # :nodoc:
+ class << self
+ def from_database(value, type)
+ FromDatabase.new(value, type)
+ end
+
+ def from_user(value, type)
+ FromUser.new(value, type)
+ end
+ end
+
+ attr_reader :value_before_type_cast, :type
+
+ # This method should not be called directly.
+ # Use #from_database or #from_user
+ def initialize(value_before_type_cast, type)
+ @value_before_type_cast = value_before_type_cast
+ @type = type
+ end
+
+ def value
+ # `defined?` is cheaper than `||=` when we get back falsy values
+ @value = type_cast(value_before_type_cast) unless defined?(@value)
+ @value
+ end
+
+ def value_for_database
+ type.type_cast_for_database(value)
+ end
+
+ def type_cast
+ raise NotImplementedError
+ end
+
+ protected
+
+ def initialize_dup(other)
+ if defined?(@value) && @value.duplicable?
+ @value = @value.dup
+ end
+ end
+
+ class FromDatabase < Attribute
+ def type_cast(value)
+ type.type_cast_from_database(value)
+ end
+ end
+
+ class FromUser < Attribute
+ def type_cast(value)
+ type.type_cast_from_user(value)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index e626227e7e..b4d75d6556 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -156,16 +156,6 @@ module ActiveRecord
end
end
- def find_generated_attribute_method(method_name) # :nodoc:
- klass = self
- until klass == Base
- gen_methods = klass.generated_attribute_methods
- return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object)
- klass = klass.superclass
- end
- nil
- end
-
# Returns +true+ if +attribute+ is an attribute method and table exists,
# +false+ otherwise.
#
@@ -216,25 +206,6 @@ module ActiveRecord
end
end
- # If we haven't generated any methods yet, generate them, then
- # see if we've created the method we're looking for.
- def method_missing(method, *args, &block) # :nodoc:
- self.class.define_attribute_methods
- if respond_to_without_attributes?(method)
- # make sure to invoke the correct attribute method, as we might have gotten here via a `super`
- # call in a overwritten attribute method
- if attribute_method = self.class.find_generated_attribute_method(method)
- # this is probably horribly slow, but should only happen at most once for a given AR class
- attribute_method.bind(self).call(*args, &block)
- else
- return super unless respond_to_missing?(method, true)
- send(method, *args, &block)
- end
- else
- super
- end
- end
-
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
# <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
# which will all return +true+. It also define the attribute methods if they have
@@ -252,18 +223,14 @@ module ActiveRecord
# person.respond_to('age?') # => true
# person.respond_to(:nothing) # => false
def respond_to?(name, include_private = false)
+ return false unless super
name = name.to_s
- self.class.define_attribute_methods
- 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?(@raw_attributes) not to issue warnings if called on objects that
+ # We check defined?(@attributes) not to issue warnings if called on objects that
# have been allocated but not yet initialized.
- if defined?(@raw_attributes) && @raw_attributes.any? && self.class.column_names.include?(name)
+ if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
return has_attribute?(name)
end
@@ -280,7 +247,7 @@ module ActiveRecord
# person.has_attribute?('age') # => true
# person.has_attribute?(:nothing) # => false
def has_attribute?(attr_name)
- @raw_attributes.has_key?(attr_name.to_s)
+ @attributes.has_key?(attr_name.to_s)
end
# Returns an array of names for the attributes available on this object.
@@ -292,7 +259,7 @@ module ActiveRecord
# person.attribute_names
# # => ["id", "created_at", "updated_at", "name", "age"]
def attribute_names
- @raw_attributes.keys
+ @attributes.keys
end
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
@@ -400,11 +367,10 @@ module ActiveRecord
protected
- def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
- attribute_names.each do |name|
- attributes[name] = clone_attribute_value(reader_method, name)
+ def clone_attributes # :nodoc:
+ @attributes.each_with_object({}) do |(name, attr), h|
+ h[name] = attr.dup
end
- attributes
end
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
@@ -424,7 +390,7 @@ module ActiveRecord
def attribute_method?(attr_name) # :nodoc:
# We check defined? because Syck calls respond_to? before actually calling initialize.
- defined?(@raw_attributes) && @raw_attributes.include?(attr_name)
+ defined?(@attributes) && @attributes.include?(attr_name)
end
private
@@ -465,9 +431,6 @@ module ActiveRecord
end
def typecasted_attribute_value(name)
- # FIXME: we need @attributes to be used consistently.
- # If the values stored in @attributes were already typecasted, this code
- # could be simplified
read_attribute(name)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index 4365f5a1a1..d057f0941a 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -43,7 +43,9 @@ module ActiveRecord
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
- @raw_attributes[attr_name.to_s]
+ if attr = @attributes[attr_name.to_s]
+ attr.value_before_type_cast
+ end
end
# Returns a hash of attributes before typecasting and deserialization.
@@ -57,7 +59,7 @@ module ActiveRecord
# task.attributes_before_type_cast
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
def attributes_before_type_cast
- @raw_attributes
+ @attributes.each_with_object({}) { |(k, v), h| h[k] = v.value_before_type_cast }
end
private
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index be438aba95..6a5c057384 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -38,12 +38,39 @@ module ActiveRecord
end
end
- def initialize_dup(other) # :nodoc:
- super
- init_changed_attributes
- end
+ def initialize_dup(other) # :nodoc:
+ super
+ init_changed_attributes
+ end
+
+ def changed?
+ super || changed_in_place.any?
+ end
+
+ def changed
+ super | changed_in_place
+ end
+
+ def attribute_changed?(attr_name, options = {})
+ result = super
+ # We can't change "from" something in place. Only setters can define
+ # "from" and "to"
+ result ||= changed_in_place?(attr_name) unless options.key?(:from)
+ result
+ end
+
+ def changes_applied
+ super
+ store_original_raw_attributes
+ end
+
+ def reset_changes
+ super
+ original_raw_attributes.clear
+ end
+
+ private
- private
def initialize_internals_callback
super
init_changed_attributes
@@ -65,11 +92,20 @@ module ActiveRecord
old_value = old_attribute_value(attr)
- result = super(attr, value)
+ result = super
+ store_original_raw_attribute(attr)
save_changed_attribute(attr, old_value)
result
end
+ def raw_write_attribute(attr, value)
+ attr = attr.to_s
+
+ result = super
+ original_raw_attributes[attr] = value
+ result
+ end
+
def save_changed_attribute(attr, old_value)
if attribute_changed?(attr)
changed_attributes.delete(attr) unless _field_changed?(attr, old_value)
@@ -105,6 +141,41 @@ module ActiveRecord
raw_value = read_attribute_before_type_cast(attr)
column_for_attribute(attr).changed?(old_value, new_value, raw_value)
end
+
+ def changed_in_place
+ self.class.attribute_names.select do |attr_name|
+ changed_in_place?(attr_name)
+ end
+ end
+
+ def changed_in_place?(attr_name)
+ type = type_for_attribute(attr_name)
+ old_value = original_raw_attribute(attr_name)
+ value = read_attribute(attr_name)
+ type.changed_in_place?(old_value, value)
+ end
+
+ def original_raw_attribute(attr_name)
+ original_raw_attributes.fetch(attr_name) do
+ read_attribute_before_type_cast(attr_name)
+ end
+ end
+
+ def original_raw_attributes
+ @original_raw_attributes ||= {}
+ end
+
+ def store_original_raw_attribute(attr_name)
+ type = type_for_attribute(attr_name)
+ value = type.type_cast_for_database(read_attribute(attr_name))
+ original_raw_attributes[attr_name] = value
+ end
+
+ def store_original_raw_attributes
+ attribute_names.each do |attr|
+ store_original_raw_attribute(attr)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 7b7e37884b..8c1cc128f7 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -82,25 +82,16 @@ module ActiveRecord
# it has been typecast (for example, "2004-12-12" in a date column is cast
# to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name)
- # If it's cached, just return it
- # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
name = attr_name.to_s
- @attributes[name] || @attributes.fetch(name) {
- column = @column_types_override[name] if @column_types_override
- column ||= @column_types[name]
-
- return @raw_attributes.fetch(name) {
- if name == 'id' && self.class.primary_key != name
- read_attribute(self.class.primary_key)
- end
- } unless column
-
- value = @raw_attributes.fetch(name) {
- return block_given? ? yield(name) : nil
- }
-
- @attributes[name] = column.type_cast_from_database(value)
- }
+ @attributes.fetch(name) {
+ if name == 'id'
+ return read_attribute(self.class.primary_key)
+ elsif block_given? && self.class.columns_hash.key?(name)
+ return yield(name)
+ else
+ return nil
+ end
+ }.value
end
private
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 60debb7d18..734d94865a 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -3,20 +3,7 @@ module ActiveRecord
module Serialization
extend ActiveSupport::Concern
- included do
- # Returns a hash of all the attributes that have been specified for
- # serialization as keys and their class restriction as values.
- class_attribute :serialized_attributes, instance_accessor: false
- self.serialized_attributes = {}
- 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
@@ -50,8 +37,6 @@ module ActiveRecord
# serialize :preferences, Hash
# end
def serialize(attr_name, class_name_or_coder = Object)
- include Behavior
-
coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
class_name_or_coder
else
@@ -61,25 +46,18 @@ module ActiveRecord
decorate_attribute_type(attr_name, :serialize) do |type|
Type::Serialized.new(type, coder)
end
-
- # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
- # has its own hash of own serialized attributes
- self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
- end
- end
-
-
- # 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:
- extend ActiveSupport::Concern
-
- def should_record_timestamps?
- super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?)
end
- def keys_for_partial_write
- super | (attributes.keys & self.class.serialized_attributes.keys)
+ def serialized_attributes
+ ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc)
+ `serialized_attributes` is deprecated without replacement, and will
+ be removed in Rails 5.0.
+ WARNING
+ @serialized_attributes ||= Hash[
+ columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
+ [c.name, c.cast_type.coder]
+ }
+ ]
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index b72a6219b0..246a2cd8ba 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -69,22 +69,19 @@ module ActiveRecord
def write_attribute_with_type_cast(attr_name, value, should_type_cast)
attr_name = attr_name.to_s
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
- @attributes.delete(attr_name)
- column = type_for_attribute(attr_name)
+ type = type_for_attribute(attr_name)
unless has_attribute?(attr_name) || self.class.columns_hash.key?(attr_name)
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
end
- # If we're dealing with a binary column, write the data to the cache
- # so we don't attempt to typecast multiple times.
- if column.binary?
- @attributes[attr_name] = value
- elsif should_type_cast
- @attributes[attr_name] = column.type_cast_from_user(value)
+ if should_type_cast
+ @attributes[attr_name] = Attribute.from_user(value, type)
+ else
+ @attributes[attr_name] = Attribute.from_database(value, type)
end
- @raw_attributes[attr_name] = value
+ value
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 7ff5001796..e8ce00d92b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -193,7 +193,7 @@ module ActiveRecord
# * You are creating a nested (savepoint) transaction
#
# The mysql, mysql2 and postgresql adapters support setting the transaction
- # isolation level. However, support is disabled for mysql versions below 5,
+ # isolation level. However, support is disabled for MySQL versions below 5,
# because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
# which means the isolation level gets persisted outside the transaction.
def transaction(options = {})
@@ -338,8 +338,8 @@ module ActiveRecord
end
# The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
- # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
- # an UPDATE statement, so in the mysql adapters we redefine this to do that.
+ # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
+ # an UPDATE statement, so in the MySQL adapters we redefine this to do that.
def join_to_update(update, select) #:nodoc:
key = update.key
subselect = subquery_for(key, select)
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 ffa6af6d99..22823a8c58 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -788,7 +788,7 @@ module ActiveRecord
return option_strings
end
- # Overridden by the mysql adapter for supporting index lengths
+ # Overridden by the MySQL adapter for supporting index lengths
def quoted_columns_for_index(column_names, options = {})
option_strings = Hash[column_names.map {|name| [name, '']}]
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 200b773172..a9b97d5919 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -75,7 +75,7 @@ module ActiveRecord
end
def has_default?
- return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
super
end
@@ -213,7 +213,7 @@ module ActiveRecord
Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
end
- # Must return the Mysql error number from the exception, if the exception has an
+ # Must return the MySQL error number from the exception, if the exception has an
# error number.
def error_number(exception) # :nodoc:
raise NotImplementedError
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 72c6990ba5..d629fca911 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -16,7 +16,7 @@ module ActiveRecord
attr_reader :name, :cast_type, :null, :sql_type, :default_function
delegate :type, :precision, :scale, :limit, :klass, :accessor,
- :text?, :number?, :binary?, :serialized?, :changed?,
+ :text?, :number?, :binary?, :changed?,
:type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
:type_cast_for_schema,
to: :cast_type
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index b79d1a4458..2fcb085ab2 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -85,7 +85,7 @@ module ActiveRecord
"password" => uri.password,
"port" => uri.port,
"database" => database_from_path,
- "host" => uri.host })
+ "host" => uri.hostname })
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 909bba8c7d..252b82643d 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -409,7 +409,7 @@ module ActiveRecord
stmt.execute(*type_casted_binds.map { |_, val| val })
rescue Mysql::Error => e
# Older versions of MySQL leave the prepared statement in a bad
- # place when an error occurs. To support older mysql versions, we
+ # place when an error occurs. To support older MySQL versions, we
# need to close the statement and delete the statement from the
# cache.
stmt.close
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
index 87817fe8d1..4e7d472d97 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -12,12 +12,16 @@ module ActiveRecord
def type_cast_from_database(value)
if value.is_a?(::String)
- type_cast_array(parse_pg_array(value))
+ type_cast_array(parse_pg_array(value), :type_cast_from_database)
else
super
end
end
+ def type_cast_from_user(value)
+ type_cast_array(value, :type_cast_from_user)
+ end
+
# Loads pg_array_parser if available. String parsing can be
# performed quicker by a native extension, which will not create
# a large amount of Ruby objects that will need to be garbage
@@ -32,11 +36,11 @@ module ActiveRecord
private
- def type_cast_array(value)
+ def type_cast_array(value, method)
if value.is_a?(::Array)
- value.map { |item| type_cast_array(item) }
+ value.map { |item| type_cast_array(item, method) }
else
- @subtype.type_cast_from_database(value)
+ @subtype.public_send(method, value)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
index 36c53d8732..89b203d2b1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
@@ -3,8 +3,9 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Bytea < Type::Binary
- def cast_value(value)
- PGconn.unescape_bytea value
+ def type_cast_from_database(value)
+ return if value.nil?
+ PGconn.unescape_bytea(super)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
index 88de816d4f..0dd4b65333 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
@@ -3,14 +3,12 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Hstore < Type::Value
+ include Type::Mutable
+
def type
:hstore
end
- def type_cast_from_user(value)
- type_cast_from_database(type_cast_for_database(value))
- end
-
def type_cast_from_database(value)
ConnectionAdapters::PostgreSQLColumn.string_to_hstore(value)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
index b4fed1bcab..d1347c7bb5 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
@@ -3,14 +3,12 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Json < Type::Value
+ include Type::Mutable
+
def type
:json
end
- def type_cast_from_user(value)
- type_cast_from_database(type_cast_for_database(value))
- end
-
def type_cast_from_database(value)
ConnectionAdapters::PostgreSQLColumn.string_to_json(value)
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 86b4cc90ea..d39e5fddfe 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -249,16 +249,19 @@ module ActiveRecord
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil, options = {})
- defaults = self.class.raw_column_defaults.dup
- defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
+ defaults = {}
+ self.class.raw_column_defaults.each do |k, v|
+ default = v.duplicable? ? v.dup : v
+ defaults[k] = Attribute.from_database(default, type_for_attribute(k))
+ end
- @raw_attributes = defaults
- @column_types_override = nil
+ @attributes = defaults
@column_types = self.class.column_types
init_internals
initialize_internals_callback
+ self.class.define_attribute_methods
# +options+ argument is only needed to make protected_attributes gem easier to hook.
# Remove it when we drop support to this gem.
init_attributes(attributes, options) if attributes
@@ -278,13 +281,11 @@ module ActiveRecord
# post.init_with('attributes' => { 'title' => 'hello world' })
# post.title # => 'hello world'
def init_with(coder)
- @raw_attributes = coder['raw_attributes']
- @column_types_override = coder['column_types']
+ @attributes = coder['attributes']
@column_types = self.class.column_types
init_internals
- @attributes = coder['attributes'] if coder['attributes']
@new_record = coder['new_record']
self.class.define_attribute_methods
@@ -323,12 +324,9 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
- cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
-
- @raw_attributes = cloned_attributes
- @raw_attributes[self.class.primary_key] = nil
- @attributes = other.clone_attributes(:read_attribute)
- @attributes[self.class.primary_key] = nil
+ pk = self.class.primary_key
+ @attributes = other.clone_attributes
+ @attributes[pk] = Attribute.from_database(nil, type_for_attribute(pk))
run_callbacks(:initialize) unless _initialize_callbacks.empty?
@@ -354,9 +352,9 @@ module ActiveRecord
# Post.new.encode_with(coder)
# coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
- coder['raw_attributes'] = @raw_attributes
+ # FIXME: Remove this when we better serialize attributes
+ coder['raw_attributes'] = attributes_before_type_cast
coder['attributes'] = @attributes
- coder['column_types'] = @column_types_override
coder['new_record'] = new_record?
end
@@ -391,13 +389,13 @@ module ActiveRecord
# accessible, even on destroyed records, but cloned models will not be
# frozen.
def freeze
- @raw_attributes = @raw_attributes.clone.freeze
+ @attributes = @attributes.clone.freeze
self
end
# Returns +true+ if the attributes hash has been frozen.
def frozen?
- @raw_attributes.frozen?
+ @attributes.frozen?
end
# Allows sort on objects
@@ -426,9 +424,9 @@ module ActiveRecord
# Returns the contents of the record as a nicely formatted string.
def inspect
- # We check defined?(@raw_attributes) not to issue warnings if the object is
+ # We check defined?(@attributes) not to issue warnings if the object is
# allocated but not initialized.
- inspection = if defined?(@raw_attributes) && @raw_attributes
+ inspection = if defined?(@attributes) && @attributes
self.class.column_names.collect { |name|
if has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
@@ -527,11 +525,10 @@ module ActiveRecord
def init_internals
pk = self.class.primary_key
- @raw_attributes[pk] = nil unless @raw_attributes.key?(pk)
+ @attributes[pk] ||= Attribute.from_database(nil, type_for_attribute(pk))
@aggregation_cache = {}
@association_cache = {}
- @attributes = {}
@readonly = false
@destroyed = false
@marked_for_destruction = false
@@ -554,7 +551,7 @@ module ActiveRecord
def thaw
if frozen?
- @raw_attributes = @raw_attributes.dup
+ @attributes = @attributes.dup
end
end
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 2ccb1b0702..52c70977ef 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -30,17 +30,18 @@ module ActiveRecord
class SerializationTypeMismatch < ActiveRecordError
end
- # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt>
- # misses adapter field).
+ # Raised when adapter not specified on connection (or configuration file
+ # +config/database.yml+ misses adapter field).
class AdapterNotSpecified < ActiveRecordError
end
- # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
+ # Raised when Active Record cannot find database adapter specified in
+ # +config/database.yml+ or programmatically.
class AdapterNotFound < ActiveRecordError
end
- # Raised when connection to the database could not been established (for example when <tt>connection=</tt>
- # is given a nil object).
+ # Raised when connection to the database could not been established (for
+ # example when +connection=+ is given a nil object).
class ConnectionNotEstablished < ActiveRecordError
end
@@ -82,19 +83,17 @@ module ActiveRecord
class InvalidForeignKey < WrappedDatabaseException
end
- # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example,
- # when using +find+ method)
- # does not match number of expected variables.
+ # Raised when number of bind variables in statement given to +:condition+ key
+ # (for example, when using +find+ method) does not match number of expected
+ # values supplied.
#
- # For example, in
+ # For example, when there are two placeholders with only one value supplied:
#
# Location.where("lat = ? AND lng = ?", 53.7362)
- #
- # two placeholders are given but only one variable to fill them.
class PreparedStatementInvalid < ActiveRecordError
end
- # Raised when a given database does not exist
+ # Raised when a given database does not exist.
class NoDatabaseError < StatementInvalid
end
@@ -102,7 +101,8 @@ module ActiveRecord
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
# the page before the other.
#
- # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
+ # Read more about optimistic locking in ActiveRecord::Locking module
+ # documentation.
class StaleObjectError < ActiveRecordError
attr_reader :record, :attempted_action
@@ -114,8 +114,9 @@ module ActiveRecord
end
- # Raised when association is being configured improperly or
- # user tries to use offset and limit together with has_many or has_and_belongs_to_many associations.
+ # Raised when association is being configured improperly or user tries to use
+ # offset and limit together with +has_many+ or +has_and_belongs_to_many+
+ # associations.
class ConfigurationError < ActiveRecordError
end
@@ -153,7 +154,8 @@ module ActiveRecord
class Rollback < ActiveRecordError
end
- # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
+ # Raised when attribute has a name reserved by Active Record (when attribute
+ # has name of one of Active Record instance methods).
class DangerousAttributeError < ActiveRecordError
end
@@ -171,7 +173,7 @@ module ActiveRecord
end
# Raised when an error occurred while doing a mass assignment to an attribute through the
- # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
+ # +attributes=+ method. The exception has an +attribute+ property that is the name of the
# offending attribute.
class AttributeAssignmentError < ActiveRecordError
attr_reader :exception, :attribute
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 95fcbbe99a..4cd5f92207 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -15,9 +15,10 @@ module ActiveRecord
# They are stored in YAML files, one file per model, which are placed in the directory
# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
- # The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
- # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a fixture file looks
- # like this:
+ # The fixture file ends with the +.yml+ file extension, for example:
+ # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>).
+ #
+ # The format of a fixture file looks like this:
#
# rubyonrails:
# id: 1
@@ -61,8 +62,8 @@ module ActiveRecord
# end
# end
#
- # By default, <tt>test_helper.rb</tt> will load all of your fixtures into your test database,
- # so this test will succeed.
+ # By default, +test_helper.rb+ will load all of your fixtures into your test
+ # database, so this test will succeed.
#
# The testing environment will automatically load the all fixtures into the database before each
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
@@ -375,8 +376,8 @@ module ActiveRecord
# == Support for YAML defaults
#
# You can set and reuse defaults in your fixtures YAML file.
- # This is the same technique used in the <tt>database.yml</tt> file
- # to specify defaults:
+ # This is the same technique used in the +database.yml+ file to specify
+ # defaults:
#
# DEFAULTS: &DEFAULTS
# created_on: <%= 3.weeks.ago.to_s(:db) %>
@@ -392,7 +393,8 @@ module ActiveRecord
# Any fixture labeled "DEFAULTS" is safely ignored.
class FixtureSet
#--
- # An instance of FixtureSet is normally stored in a single YAML file and possibly in a folder with the same name.
+ # An instance of FixtureSet is normally stored in a single YAML file and
+ # possibly in a folder with the same name.
#++
MAX_ID = 2 ** 30 - 1
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 481e5c17e4..01c001e692 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -366,16 +366,19 @@ module ActiveRecord
# This class is used to verify that all migrations have been run before
# loading a web page if config.active_record.migration_error is set to :page_load
class CheckPending
- def initialize(app)
+ def initialize(app, connection = Base.connection)
@app = app
+ @connection = connection
@last_check = 0
end
def call(env)
- mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
- if @last_check < mtime
- ActiveRecord::Migration.check_pending!
- @last_check = mtime
+ if @connection.supports_migrations?
+ mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
+ if @last_check < mtime
+ ActiveRecord::Migration.check_pending!(@connection)
+ @last_check = mtime
+ end
end
@app.call(env)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 3ab8ec4836..5c744762d9 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -48,11 +48,12 @@ module ActiveRecord
# how this "single-table" inheritance mapping is implemented.
def instantiate(attributes, column_types = {})
klass = discriminate_class_for_record(attributes)
- klass.allocate.init_with(
- 'raw_attributes' => attributes,
- 'column_types' => column_types,
- 'new_record' => false,
- )
+
+ attributes = attributes.each_with_object({}) do |(name, value), h|
+ type = column_types.fetch(name) { klass.type_for_attribute(name) }
+ h[name] = Attribute.from_database(value, type)
+ end
+ klass.allocate.init_with('attributes' => attributes, 'new_record' => false)
end
private
@@ -182,7 +183,6 @@ module ActiveRecord
# So any change to the attributes in either instance will affect the other.
def becomes(klass)
became = klass.new
- became.instance_variable_set("@raw_attributes", @raw_attributes)
became.instance_variable_set("@attributes", @attributes)
became.instance_variable_set("@changed_attributes", @changed_attributes) if defined?(@changed_attributes)
became.instance_variable_set("@new_record", new_record?)
@@ -399,11 +399,9 @@ module ActiveRecord
self.class.unscoped { self.class.find(id) }
end
- @raw_attributes.update(fresh_object.instance_variable_get('@raw_attributes'))
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
- @column_types = self.class.column_types
- @column_types_override = fresh_object.instance_variable_get('@column_types_override')
- @attributes = {}
+ @column_types = self.class.column_types
self
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 95a3c70c93..51c96373ee 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -487,7 +487,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
# returns either nil or the inverse association name that it finds.
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
- inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name).to_sym
begin
reflection = klass._reflect_on_association(inverse_name)
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index ae53f66d7a..8c29c69a9b 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -95,7 +95,7 @@ module ActiveRecord
@hash_rows ||=
begin
# We freeze the strings to prevent them getting duped when
- # used as keys in ActiveRecord::Base's @raw_attributes hash
+ # used as keys in ActiveRecord::Base's @attributes hash
columns = @columns.map { |c| c.dup.freeze }
@rows.map { |row|
# In the past we used Hash[columns.zip(row)]
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 1aa93ffbb3..ff70cbed0f 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -107,7 +107,7 @@ module ActiveRecord
end.join(', ')
end
- # Sanitizes a +string+ so that it is safe to use within a sql
+ # Sanitizes a +string+ so that it is safe to use within an SQL
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
def sanitize_sql_like(string, escape_character = "\\")
pattern = Regexp.union(escape_character, "%", "_")
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 019fe2218e..c2484d02ed 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -182,11 +182,7 @@ module ActiveRecord #:nodoc:
klass = @serializable.class
column = klass.columns_hash[name] || Type::Value.new
- type = if column.serialized?
- super
- else
- column.type
- end
+ type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type
{ :text => :string,
:time => :datetime }[type] || type
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index dedbbd3585..649f316ed8 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -6,7 +6,7 @@ module ActiveRecord
# <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.
+ # The tasks defined here are used with 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
@@ -14,7 +14,6 @@ module ActiveRecord
# (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).
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index c755831e6d..644c4852b9 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -124,7 +124,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
end
def root_password
- $stdout.print "Please provide the root password for your mysql installation\n>"
+ $stdout.print "Please provide the root password for your MySQL installation\n>"
$stdin.gets.strip
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index e9b827886a..f1384e0bb2 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -1,3 +1,4 @@
+require 'active_record/type/mutable'
require 'active_record/type/numeric'
require 'active_record/type/time_value'
require 'active_record/type/value'
diff --git a/activerecord/lib/active_record/type/mutable.rb b/activerecord/lib/active_record/type/mutable.rb
new file mode 100644
index 0000000000..64cf4b9b93
--- /dev/null
+++ b/activerecord/lib/active_record/type/mutable.rb
@@ -0,0 +1,16 @@
+module ActiveRecord
+ module Type
+ module Mutable
+ def type_cast_from_user(value)
+ type_cast_from_database(type_cast_for_database(value))
+ end
+
+ # +raw_old_value+ will be the `_before_type_cast` version of the
+ # value (likely a string). +new_value+ will be the current, type
+ # cast value.
+ def changed_in_place?(raw_old_value, new_value) # :nodoc:
+ raw_old_value != type_cast_for_database(new_value)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index e6d84eadc0..42bbed7103 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -1,6 +1,8 @@
module ActiveRecord
module Type
class Serialized < SimpleDelegator # :nodoc:
+ include Mutable
+
attr_reader :subtype, :coder
def initialize(subtype, coder)
@@ -17,10 +19,6 @@ module ActiveRecord
end
end
- def type_cast_from_user(value)
- type_cast_from_database(type_cast_for_database(value))
- end
-
def type_cast_for_database(value)
return if value.nil?
unless is_default_value?(value)
@@ -28,10 +26,6 @@ module ActiveRecord
end
end
- def serialized?
- true
- end
-
def accessor
ActiveRecord::Store::IndifferentHashAccessor
end
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index efcdab1c0e..875fb98c4b 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -44,10 +44,6 @@ module ActiveRecord
false
end
- def serialized?
- false
- end
-
def klass # :nodoc:
end
@@ -60,6 +56,10 @@ module ActiveRecord
old_value != new_value
end
+ def changed_in_place?(*) # :nodoc:
+ false
+ end
+
private
# Takes an input from the database, or from attribute setters,
# and casts it to a type appropriate for this object. This method