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/attribute.rb54
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb13
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb35
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb30
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb23
-rw-r--r--activerecord/lib/active_record/fixtures.rb39
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb8
9 files changed, 129 insertions, 77 deletions
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 74b44810d4..3c4c8f10ec 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -5,8 +5,8 @@ module ActiveRecord
FromDatabase.new(name, value, type)
end
- def from_user(name, value, type)
- FromUser.new(name, value, type)
+ def from_user(name, value, type, original_attribute = nil)
+ FromUser.new(name, value, type, original_attribute)
end
def with_cast_value(name, value, type)
@@ -26,10 +26,11 @@ module ActiveRecord
# This method should not be called directly.
# Use #from_database or #from_user
- def initialize(name, value_before_type_cast, type)
+ def initialize(name, value_before_type_cast, type, original_attribute = nil)
@name = name
@value_before_type_cast = value_before_type_cast
@type = type
+ @original_attribute = original_attribute
end
def value
@@ -38,21 +39,33 @@ module ActiveRecord
@value
end
+ def original_value
+ if assigned?
+ original_attribute.original_value
+ else
+ type_cast(value_before_type_cast)
+ end
+ end
+
def value_for_database
type.serialize(value)
end
- def changed_from?(old_value)
- type.changed?(old_value, value, value_before_type_cast)
+ def changed?
+ changed_from_assignment? || changed_in_place?
+ end
+
+ def changed_in_place?
+ has_been_read? && type.changed_in_place?(original_value_for_database, value)
end
- def changed_in_place_from?(old_value)
- has_been_read? && type.changed_in_place?(old_value, value)
+ def forgetting_assignment
+ with_value_from_database(value_for_database)
end
def with_value_from_user(value)
type.assert_valid_value(value)
- self.class.from_user(name, value, type)
+ self.class.from_user(name, value, type, self)
end
def with_value_from_database(value)
@@ -64,7 +77,7 @@ module ActiveRecord
end
def with_type(type)
- self.class.new(name, value_before_type_cast, type)
+ self.class.new(name, value_before_type_cast, type, original_attribute)
end
def type_cast(*)
@@ -97,16 +110,39 @@ module ActiveRecord
protected
+ attr_reader :original_attribute
+ alias_method :assigned?, :original_attribute
+
def initialize_dup(other)
if defined?(@value) && @value.duplicable?
@value = @value.dup
end
end
+ def changed_from_assignment?
+ assigned? && type.changed?(original_value, value, value_before_type_cast)
+ end
+
+ def original_value_for_database
+ if assigned?
+ original_attribute.original_value_for_database
+ else
+ _original_value_for_database
+ end
+ end
+
+ def _original_value_for_database
+ value_for_database
+ end
+
class FromDatabase < Attribute # :nodoc:
def type_cast(value)
type.deserialize(value)
end
+
+ def _original_value_for_database
+ value_before_type_cast
+ end
end
class FromUser < Attribute # :nodoc:
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
index 501590cf0e..fb8ad9163e 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -4,8 +4,7 @@ module ActiveRecord
class Attribute # :nodoc:
class UserProvidedDefault < FromUser
def initialize(name, value, type, database_default)
- super(name, value, type)
- @database_default = database_default
+ super(name, value, type, database_default)
end
def type_cast(value)
@@ -16,17 +15,9 @@ module ActiveRecord
end
end
- def changed_from?(old_value)
- super || changed_from?(database_default.value)
- end
-
def with_type(type)
- self.class.new(name, value_before_type_cast, type, database_default)
+ self.class.new(name, value_before_type_cast, type, original_attribute)
end
-
- protected
-
- attr_reader :database_default
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 43c15841c2..e8a782ed13 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -35,23 +35,22 @@ module ActiveRecord
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
- clear_changes_information
+ @mutation_tracker = nil
+ @previous_mutation_tracker = nil
+ @changed_attributes = HashWithIndifferentAccess.new
end
end
- def init_internals
- super
- @mutation_tracker = AttributeMutationTracker.new(@attributes, @attributes.dup)
- end
-
def initialize_dup(other) # :nodoc:
super
- @mutation_tracker = AttributeMutationTracker.new(@attributes,
- self.class._default_attributes.deep_dup)
+ @attributes = self.class._default_attributes.map do |attr|
+ attr.with_value_from_user(@attributes.fetch_value(attr.name))
+ end
+ @mutation_tracker = nil
end
def changes_applied
- @previous_mutation_tracker = @mutation_tracker
+ @previous_mutation_tracker = mutation_tracker
@changed_attributes = HashWithIndifferentAccess.new
store_original_attributes
end
@@ -81,7 +80,7 @@ module ActiveRecord
if defined?(@cached_changed_attributes)
@cached_changed_attributes
else
- super.reverse_merge(@mutation_tracker.changed_values).freeze
+ super.reverse_merge(mutation_tracker.changed_values).freeze
end
end
@@ -96,17 +95,24 @@ module ActiveRecord
end
def attribute_changed_in_place?(attr_name)
- @mutation_tracker.changed_in_place?(attr_name)
+ mutation_tracker.changed_in_place?(attr_name)
end
private
+ def mutation_tracker
+ unless defined?(@mutation_tracker)
+ @mutation_tracker = nil
+ end
+ @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
+ end
+
def changes_include?(attr_name)
- super || @mutation_tracker.changed?(attr_name)
+ super || mutation_tracker.changed?(attr_name)
end
def clear_attribute_change(attr_name)
- @mutation_tracker.forget_change(attr_name)
+ mutation_tracker.forget_change(attr_name)
end
def _update_record(*)
@@ -122,7 +128,8 @@ module ActiveRecord
end
def store_original_attributes
- @mutation_tracker = @mutation_tracker.now_tracking(@attributes)
+ @attributes = @attributes.map(&:forgetting_assignment)
+ @mutation_tracker = nil
end
def previous_mutation_tracker
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
index 08eb8ba7ac..ba7348918b 100644
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb
@@ -1,14 +1,13 @@
module ActiveRecord
class AttributeMutationTracker # :nodoc:
- def initialize(attributes, original_attributes)
+ def initialize(attributes)
@attributes = attributes
- @original_attributes = original_attributes
end
def changed_values
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
if changed?(attr_name)
- result[attr_name] = original_attributes.fetch_value(attr_name)
+ result[attr_name] = attributes[attr_name].original_value
end
end
end
@@ -16,49 +15,34 @@ module ActiveRecord
def changes
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
if changed?(attr_name)
- result[attr_name] = [original_attributes.fetch_value(attr_name), attributes.fetch_value(attr_name)]
+ result[attr_name] = [attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
end
end
end
def changed?(attr_name)
attr_name = attr_name.to_s
- modified?(attr_name) || changed_in_place?(attr_name)
+ attributes[attr_name].changed?
end
def changed_in_place?(attr_name)
- original_database_value = original_attributes[attr_name].value_before_type_cast
- attributes[attr_name].changed_in_place_from?(original_database_value)
+ attributes[attr_name].changed_in_place?
end
def forget_change(attr_name)
attr_name = attr_name.to_s
- original_attributes[attr_name] = attributes[attr_name].dup
- end
-
- def now_tracking(new_attributes)
- AttributeMutationTracker.new(new_attributes, clean_copy_of(new_attributes))
+ attributes[attr_name] = attributes[attr_name].forgetting_assignment
end
protected
- attr_reader :attributes, :original_attributes
+ attr_reader :attributes
private
def attr_names
attributes.keys
end
-
- def modified?(attr_name)
- attributes[attr_name].changed_from?(original_attributes.fetch_value(attr_name))
- end
-
- def clean_copy_of(attributes)
- attributes.map do |attr|
- attr.with_value_from_database(attr.value_for_database)
- end
- end
end
class NullMutationTracker # :nodoc:
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index d6b661ff76..2fc5e410f9 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -42,7 +42,7 @@ module ActiveRecord
#
# ActiveRecord::Base.establish_connection(:production)
#
- # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
+ # The exceptions +AdapterNotSpecified+, +AdapterNotFound+ and +ArgumentError+
# may be returned on an error.
def establish_connection(spec = nil)
spec ||= DEFAULT_ENV.call.to_sym
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index 8132310c91..f969556c50 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -17,24 +17,39 @@ module ActiveRecord
def initialize(file)
@file = file
- @rows = nil
end
def each(&block)
rows.each(&block)
end
+ def model_class
+ config_row['model_class']
+ end
private
def rows
- return @rows if @rows
+ @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == '_fixture' }
+ end
+
+ def config_row
+ @config_row ||= begin
+ row = raw_rows.find { |fixture_name, _| fixture_name == '_fixture' }
+ if row
+ row.last
+ else
+ {'model_class': nil}
+ end
+ end
+ end
- begin
+ def raw_rows
+ @raw_rows ||= begin
data = YAML.load(render(IO.read(@file)))
+ data ? validate(data).to_a : []
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 : []
end
def render(content)
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index f1dc56df63..59df3c78f3 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -395,6 +395,20 @@ module ActiveRecord
# <<: *DEFAULTS
#
# Any fixture labeled "DEFAULTS" is safely ignored.
+ #
+ # == Configure the fixture model class
+ #
+ # It's possible to set the fixture's model class directly in the YAML file.
+ # This is helpful when fixtures are loaded outside tests and
+ # +set_fixture_class+ is not available (e.g.
+ # when running <tt>rake db:fixtures:load</tt>).
+ #
+ # _fixture:
+ # model_class: User
+ # david:
+ # name: David
+ #
+ # Any fixtures labeled "_fixture" are safely ignored.
class FixtureSet
#--
# An instance of FixtureSet is normally stored in a single YAML file and
@@ -578,21 +592,16 @@ module ActiveRecord
@name = name
@path = path
@config = config
- @model_class = nil
- if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
- @model_class = class_name
- else
- @model_class = class_name.safe_constantize if class_name
- end
+ self.model_class = class_name
+
+ @fixtures = read_fixture_files(path)
@connection = connection
@table_name = ( model_class.respond_to?(:table_name) ?
model_class.table_name :
self.class.default_fixture_table_name(name, config) )
-
- @fixtures = read_fixture_files path, @model_class
end
def [](x)
@@ -761,13 +770,25 @@ module ActiveRecord
@column_names ||= @connection.columns(@table_name).collect(&:name)
end
- def read_fixture_files(path, model_class)
+ def model_class=(class_name)
+ if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
+ @model_class = class_name
+ else
+ @model_class = class_name.safe_constantize if class_name
+ end
+ end
+
+ # Loads the fixtures from the YAML file at +path+.
+ # If the file sets the +model_class+ and current instance value is not set,
+ # it uses the file value.
+ def read_fixture_files(path)
yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f|
::File.file?(f)
} + [yaml_file_path(path)]
yaml_files.each_with_object({}) do |file, fixtures|
FixtureSet::File.open(file) do |fh|
+ self.model_class ||= fh.model_class if fh.model_class
fh.each do |fixture_name, row|
fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 7b53f6e5a0..3f02f73a5a 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -211,7 +211,7 @@ module ActiveRecord
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@mutation_tracker", @mutation_tracker)
+ became.instance_variable_set("@mutation_tracker", @mutation_tracker) if defined?(@mutation_tracker)
became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index eb53a18f0f..ccb0ab18ae 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -558,11 +558,8 @@ module ActiveRecord
end
def where!(opts, *rest) # :nodoc:
- if Hash === opts
- opts = sanitize_forbidden_attributes(opts)
- references!(PredicateBuilder.references(opts))
- end
-
+ opts = sanitize_forbidden_attributes(opts)
+ references!(PredicateBuilder.references(opts)) if Hash === opts
self.where_clause += where_clause_factory.build(opts, rest)
self
end
@@ -619,6 +616,7 @@ module ActiveRecord
end
def having!(opts, *rest) # :nodoc:
+ opts = sanitize_forbidden_attributes(opts)
references!(PredicateBuilder.references(opts)) if Hash === opts
self.having_clause += having_clause_factory.build(opts, rest)