aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG35
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb9
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb6
-rw-r--r--activerecord/lib/active_record/base.rb85
-rw-r--r--activerecord/lib/active_record/fixtures.rb575
-rw-r--r--activerecord/lib/active_record/identity_map.rb10
-rw-r--r--activerecord/lib/active_record/observer.rb4
-rw-r--r--activerecord/lib/active_record/query_cache.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake6
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb24
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb11
-rw-r--r--activerecord/test/cases/base_test.rb135
-rw-r--r--activerecord/test/cases/fixtures_test.rb42
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/identity_map_test.rb35
-rw-r--r--activerecord/test/cases/lifecycle_test.rb25
-rw-r--r--activerecord/test/cases/query_cache_test.rb11
-rw-r--r--activerecord/test/fixtures/mateys.yml4
-rw-r--r--activerecord/test/fixtures/parrots_pirates.yml8
23 files changed, 655 insertions, 381 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 9ff29f1155..a03751a6c1 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,20 @@
*Rails 3.1.0 (unreleased)*
+* AR#new, AR#create and AR#update_attributes all accept a second hash as option that allows you
+ to specify which role to consider when assigning attributes. This is built on top of ActiveModel's
+ new mass assignment capabilities:
+
+ class Post < ActiveRecord::Base
+ attr_accessible :title
+ attr_accessible :title, :published_at, :as => :admin
+ end
+
+ Post.new(params[:post], :as => :admin)
+
+ assign_attributes() with similar API was also added and attributes=(params, guard) was deprecated.
+
+ [Josh Kalderimis]
+
* default_scope can take a block, lambda, or any other object which responds to `call` for lazy
evaluation:
@@ -22,25 +37,7 @@
[Jon Leighton]
-* Calling 'default_scope' multiple times in a class (including when a superclass calls
- 'default_scope') is deprecated. The current behavior is that this will merge the default
- scopes together:
-
- class Post < ActiveRecord::Base # Rails 3.1
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:published => true, :hidden => false)
- end
-
- In Rails 3.2, the behavior will be changed to overwrite previous scopes:
-
- class Post < ActiveRecord::Base # Rails 3.2
- default_scope where(:published => true)
- default_scope where(:hidden => false)
- # The default scope is now: where(:hidden => false)
- end
-
- If you wish to merge default scopes in special ways, it is recommended to define your default
+* If you wish to merge default scopes in special ways, it is recommended to define your default
scope as a class method and use the standard techniques for sharing code (inheritance, mixins,
etc.):
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 9d4cbbc150..3a5035305b 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -23,5 +23,5 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('arel', '~> 2.1.0')
- s.add_dependency('tzinfo', '~> 0.3.23')
+ s.add_dependency('tzinfo', '~> 0.3.27')
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 388173c1fb..adfc71d435 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -64,9 +64,12 @@ module ActiveRecord
def method_missing(method, *args, &block)
match = DynamicFinderMatch.match(method)
- if match && match.creator?
- attributes = match.attribute_names
- return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
+ if match && match.instantiator?
+ record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
+ @association.send :set_owner_attributes, r
+ @association.send :add_to_target, r
+ yield(r) if block_given?
+ end
end
if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method))
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 0a666598ed..c32753782f 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -91,12 +91,12 @@ module ActiveRecord
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
- relation.from(join(table, constraint))
-
unless conditions[i].empty?
- relation.where(sanitize(conditions[i], table))
+ constraint = constraint.and(sanitize(conditions[i], table))
end
+ relation.from(join(table, constraint))
+
# The current table in this iteration becomes the foreign table in the next
foreign_table = table
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 58a056bce9..78318b1be0 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -830,6 +830,10 @@ module ActiveRecord #:nodoc:
@symbolized_base_class ||= base_class.to_s.to_sym
end
+ def symbolized_sti_name
+ @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
+ end
+
# Returns the base AR subclass that this class descends from. If A
# extends AR::Base, A.base_class will return A. If B descends from A
# through some arbitrarily deep hierarchy, B.base_class will return A.
@@ -1943,32 +1947,9 @@ MSG
errors = []
callstack.each do |name, values_with_empty_parameters|
begin
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
- # in order to allow a date to be set without a year, we must keep the empty values.
- # Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
- values = values_with_empty_parameters.reject { |v| v.nil? }
-
- if values.empty?
- send(name + "=", nil)
- else
-
- value = if Time == klass
- instantiate_time_object(name, values)
- elsif Date == klass
- begin
- values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
- Date.new(*values)
- rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
- instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
- end
- else
- klass.new(*values)
- end
-
- send(name + "=", value)
- end
+ send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
end
end
unless errors.empty?
@@ -1976,19 +1957,65 @@ MSG
end
end
+ def read_value_from_parameter(name, values_hash_from_param)
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ if values_hash_from_param.values.all?{|v|v.nil?}
+ nil
+ elsif klass == Time
+ read_time_parameter_value(name, values_hash_from_param)
+ elsif klass == Date
+ read_date_parameter_value(name, values_hash_from_param)
+ else
+ read_other_parameter_value(klass, name, values_hash_from_param)
+ end
+ end
+
+ def read_time_parameter_value(name, values_hash_from_param)
+ # If Date bits were not provided, error
+ raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
+ set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
+ # If Date bits were provided but blank, then default to 1
+ # If Time bits are not there, then default to 0
+ [1,1,1,0,0,0].each_with_index{|v,i| set_values[i] = set_values[i].blank? ? v : set_values[i]}
+ instantiate_time_object(name, set_values)
+ end
+
+ def read_date_parameter_value(name, values_hash_from_param)
+ set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]}
+ begin
+ Date.new(*set_values)
+ rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
+ instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
+ end
+ end
+
+ def read_other_parameter_value(klass, name, values_hash_from_param)
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
+ values = (1..max_position).collect do |position|
+ raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
+ values_hash_from_param[position]
+ end
+ klass.new(*values)
+ end
+
+ def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
+ [values_hash_from_param.keys.max,upper_cap].min
+ end
+
def extract_callstack_for_multiparameter_attributes(pairs)
attributes = { }
for pair in pairs
multiparameter_name, value = pair
attribute_name = multiparameter_name.split("(").first
- attributes[attribute_name] = [] unless attributes.include?(attribute_name)
+ attributes[attribute_name] = {} unless attributes.include?(attribute_name)
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
- attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
end
- attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
+ attributes
end
def type_cast_attribute_value(multiparameter_name, value)
@@ -1996,7 +2023,7 @@ MSG
end
def find_parameter_position(multiparameter_name)
- multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end
# Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 96fea741e0..0e3ed7aac7 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -423,8 +423,8 @@ class FixturesFileNotFound < StandardError; end
# to the rescue:
#
# george_reginald:
-# monkey_id: <%= Fixtures.identify(:reginald) %>
-# pirate_id: <%= Fixtures.identify(:george) %>
+# monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %>
+# pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %>
#
# == Support for YAML defaults
#
@@ -444,369 +444,374 @@ class FixturesFileNotFound < StandardError; end
#
# Any fixture labeled "DEFAULTS" is safely ignored.
-class Fixtures
- MAX_ID = 2 ** 30 - 1
+Fixture = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixture', 'ActiveRecord::Fixture')
+Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixtures', 'ActiveRecord::Fixtures')
- @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
+module ActiveRecord
+ class Fixtures
+ MAX_ID = 2 ** 30 - 1
- def self.find_table_name(table_name) # :nodoc:
- ActiveRecord::Base.pluralize_table_names ?
- table_name.to_s.singularize.camelize :
- table_name.to_s.camelize
- end
+ @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
- def self.reset_cache
- @@all_cached_fixtures.clear
- end
+ def self.find_table_name(table_name) # :nodoc:
+ ActiveRecord::Base.pluralize_table_names ?
+ table_name.to_s.singularize.camelize :
+ table_name.to_s.camelize
+ end
- def self.cache_for_connection(connection)
- @@all_cached_fixtures[connection]
- end
+ def self.reset_cache
+ @@all_cached_fixtures.clear
+ end
- def self.fixture_is_cached?(connection, table_name)
- cache_for_connection(connection)[table_name]
- end
+ def self.cache_for_connection(connection)
+ @@all_cached_fixtures[connection]
+ end
- def self.cached_fixtures(connection, keys_to_fetch = nil)
- if keys_to_fetch
- cache_for_connection(connection).values_at(*keys_to_fetch)
- else
- cache_for_connection(connection).values
+ def self.fixture_is_cached?(connection, table_name)
+ cache_for_connection(connection)[table_name]
end
- end
- def self.cache_fixtures(connection, fixtures_map)
- cache_for_connection(connection).update(fixtures_map)
- end
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
+ if keys_to_fetch
+ cache_for_connection(connection).values_at(*keys_to_fetch)
+ else
+ cache_for_connection(connection).values
+ end
+ end
+
+ def self.cache_fixtures(connection, fixtures_map)
+ cache_for_connection(connection).update(fixtures_map)
+ end
- def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
- if load_instances
- fixtures.each do |name, fixture|
- begin
- object.instance_variable_set "@#{name}", fixture.find
- rescue FixtureClassNotFound
- nil
+ def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
+ if load_instances
+ fixtures.each do |name, fixture|
+ begin
+ object.instance_variable_set "@#{name}", fixture.find
+ rescue FixtureClassNotFound
+ nil
+ end
end
end
end
- end
- def self.instantiate_all_loaded_fixtures(object, load_instances = true)
- all_loaded_fixtures.each do |table_name, fixtures|
- Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
+ all_loaded_fixtures.each do |table_name, fixtures|
+ ActiveRecord::Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ end
end
- end
- cattr_accessor :all_loaded_fixtures
- self.all_loaded_fixtures = {}
+ cattr_accessor :all_loaded_fixtures
+ self.all_loaded_fixtures = {}
- def self.create_fixtures(fixtures_directory, table_names, class_names = {})
- table_names = [table_names].flatten.map { |n| n.to_s }
- table_names.each { |n|
- class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
- }
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
+ table_names = [table_names].flatten.map { |n| n.to_s }
+ table_names.each { |n|
+ class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
+ }
- # FIXME: Apparently JK uses this.
- connection = block_given? ? yield : ActiveRecord::Base.connection
+ # FIXME: Apparently JK uses this.
+ connection = block_given? ? yield : ActiveRecord::Base.connection
- files_to_read = table_names.reject { |table_name|
- fixture_is_cached?(connection, table_name)
- }
+ files_to_read = table_names.reject { |table_name|
+ fixture_is_cached?(connection, table_name)
+ }
- unless files_to_read.empty?
- connection.disable_referential_integrity do
- fixtures_map = {}
+ unless files_to_read.empty?
+ connection.disable_referential_integrity do
+ fixtures_map = {}
- fixture_files = files_to_read.map do |path|
- table_name = path.tr '/', '_'
+ fixture_files = files_to_read.map do |path|
+ table_name = path.tr '/', '_'
- fixtures_map[path] = Fixtures.new(
- connection,
- table_name,
- class_names[table_name.to_sym] || table_name.classify,
- File.join(fixtures_directory, path))
- end
+ fixtures_map[path] = ActiveRecord::Fixtures.new(
+ connection,
+ table_name,
+ class_names[table_name.to_sym] || table_name.classify,
+ File.join(fixtures_directory, path))
+ end
- all_loaded_fixtures.update(fixtures_map)
+ all_loaded_fixtures.update(fixtures_map)
- connection.transaction(:requires_new => true) do
- fixture_files.each do |ff|
- conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
- table_rows = ff.table_rows
+ connection.transaction(:requires_new => true) do
+ fixture_files.each do |ff|
+ conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
+ table_rows = ff.table_rows
- table_rows.keys.each do |table|
- conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
- end
+ table_rows.keys.each do |table|
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ end
- table_rows.each do |table_name,rows|
- rows.each do |row|
- conn.insert_fixture(row, table_name)
+ table_rows.each do |table_name,rows|
+ rows.each do |row|
+ conn.insert_fixture(row, table_name)
+ end
end
end
- end
- # Cap primary key sequences to max(pk).
- if connection.respond_to?(:reset_pk_sequence!)
- table_names.each do |table_name|
- connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ # Cap primary key sequences to max(pk).
+ if connection.respond_to?(:reset_pk_sequence!)
+ table_names.each do |table_name|
+ connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ end
end
end
- end
- cache_fixtures(connection, fixtures_map)
+ cache_fixtures(connection, fixtures_map)
+ end
end
+ cached_fixtures(connection, table_names)
end
- cached_fixtures(connection, table_names)
- end
-
- # Returns a consistent, platform-independent identifier for +label+.
- # Identifiers are positive integers less than 2^32.
- def self.identify(label)
- Zlib.crc32(label.to_s) % MAX_ID
- end
- attr_reader :table_name, :name, :fixtures, :model_class
-
- def initialize(connection, table_name, class_name, fixture_path)
- @connection = connection
- @table_name = table_name
- @fixture_path = fixture_path
- @name = table_name # preserve fixture base name
- @class_name = class_name
-
- @fixtures = ActiveSupport::OrderedHash.new
- @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
-
- # Should be an AR::Base type class
- if class_name.is_a?(Class)
- @table_name = class_name.table_name
- @connection = class_name.connection
- @model_class = class_name
- else
- @model_class = class_name.constantize rescue nil
+ # Returns a consistent, platform-independent identifier for +label+.
+ # Identifiers are positive integers less than 2^32.
+ def self.identify(label)
+ Zlib.crc32(label.to_s) % MAX_ID
end
- read_fixture_files
- end
+ attr_reader :table_name, :name, :fixtures, :model_class
- def [](x)
- fixtures[x]
- end
+ def initialize(connection, table_name, class_name, fixture_path)
+ @connection = connection
+ @table_name = table_name
+ @fixture_path = fixture_path
+ @name = table_name # preserve fixture base name
+ @class_name = class_name
- def []=(k,v)
- fixtures[k] = v
- end
+ @fixtures = ActiveSupport::OrderedHash.new
+ @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
- def each(&block)
- fixtures.each(&block)
- end
+ # Should be an AR::Base type class
+ if class_name.is_a?(Class)
+ @table_name = class_name.table_name
+ @connection = class_name.connection
+ @model_class = class_name
+ else
+ @model_class = class_name.constantize rescue nil
+ end
- def size
- fixtures.size
- end
+ read_fixture_files
+ end
- # Return a hash of rows to be inserted. The key is the table, the value is
- # a list of rows to insert to that table.
- def table_rows
- now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
- now = now.to_s(:db)
+ def [](x)
+ fixtures[x]
+ end
- # allow a standard key to be used for doing defaults in YAML
- fixtures.delete('DEFAULTS')
+ def []=(k,v)
+ fixtures[k] = v
+ end
- # track any join tables we need to insert later
- rows = Hash.new { |h,table| h[table] = [] }
+ def each(&block)
+ fixtures.each(&block)
+ end
- rows[table_name] = fixtures.map do |label, fixture|
- row = fixture.to_hash
+ def size
+ fixtures.size
+ end
- if model_class && model_class < ActiveRecord::Base
- # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
- if model_class.record_timestamps
- timestamp_column_names.each do |name|
- row[name] = now unless row.key?(name)
- end
- end
+ # Return a hash of rows to be inserted. The key is the table, the value is
+ # a list of rows to insert to that table.
+ def table_rows
+ now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
+ now = now.to_s(:db)
- # interpolate the fixture label
- row.each do |key, value|
- row[key] = label if value == "$LABEL"
- end
+ # allow a standard key to be used for doing defaults in YAML
+ fixtures.delete('DEFAULTS')
- # generate a primary key if necessary
- if has_primary_key_column? && !row.include?(primary_key_name)
- row[primary_key_name] = Fixtures.identify(label)
- end
+ # track any join tables we need to insert later
+ rows = Hash.new { |h,table| h[table] = [] }
+
+ rows[table_name] = fixtures.map do |label, fixture|
+ row = fixture.to_hash
- # If STI is used, find the correct subclass for association reflection
- reflection_class =
- if row.include?(inheritance_column_name)
- row[inheritance_column_name].constantize rescue model_class
- else
- model_class
+ if model_class && model_class < ActiveRecord::Base
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
+ if model_class.record_timestamps
+ timestamp_column_names.each do |name|
+ row[name] = now unless row.key?(name)
+ end
end
- reflection_class.reflect_on_all_associations.each do |association|
- case association.macro
- when :belongs_to
- # Do not replace association name with association foreign key if they are named the same
- fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+ # interpolate the fixture label
+ row.each do |key, value|
+ row[key] = label if value == "$LABEL"
+ end
- if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
- if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
- # support polymorphic belongs_to as "label (Type)"
- row[association.foreign_type] = $1
- end
+ # generate a primary key if necessary
+ if has_primary_key_column? && !row.include?(primary_key_name)
+ row[primary_key_name] = ActiveRecord::Fixtures.identify(label)
+ end
- row[fk_name] = Fixtures.identify(value)
+ # If STI is used, find the correct subclass for association reflection
+ reflection_class =
+ if row.include?(inheritance_column_name)
+ row[inheritance_column_name].constantize rescue model_class
+ else
+ model_class
end
- when :has_and_belongs_to_many
- if (targets = row.delete(association.name.to_s))
- targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
- table_name = association.options[:join_table]
- rows[table_name].concat targets.map { |target|
- { association.foreign_key => row[primary_key_name],
- association.association_foreign_key => Fixtures.identify(target) }
- }
+
+ reflection_class.reflect_on_all_associations.each do |association|
+ case association.macro
+ when :belongs_to
+ # Do not replace association name with association foreign key if they are named the same
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
+ if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ # support polymorphic belongs_to as "label (Type)"
+ row[association.foreign_type] = $1
+ end
+
+ row[fk_name] = ActiveRecord::Fixtures.identify(value)
+ end
+ when :has_and_belongs_to_many
+ if (targets = row.delete(association.name.to_s))
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
+ table_name = association.options[:join_table]
+ rows[table_name].concat targets.map { |target|
+ { association.foreign_key => row[primary_key_name],
+ association.association_foreign_key => ActiveRecord::Fixtures.identify(target) }
+ }
+ end
end
end
end
- end
-
- row
- end
- rows
- end
- private
- def primary_key_name
- @primary_key_name ||= model_class && model_class.primary_key
+ row
+ end
+ rows
end
- def has_primary_key_column?
- @has_primary_key_column ||= primary_key_name &&
- model_class.columns.any? { |c| c.name == primary_key_name }
- end
+ private
+ def primary_key_name
+ @primary_key_name ||= model_class && model_class.primary_key
+ end
- def timestamp_column_names
- @timestamp_column_names ||=
- %w(created_at created_on updated_at updated_on) & column_names
- end
+ def has_primary_key_column?
+ @has_primary_key_column ||= primary_key_name &&
+ model_class.columns.any? { |c| c.name == primary_key_name }
+ end
- def inheritance_column_name
- @inheritance_column_name ||= model_class && model_class.inheritance_column
- end
+ def timestamp_column_names
+ @timestamp_column_names ||=
+ %w(created_at created_on updated_at updated_on) & column_names
+ end
- def column_names
- @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
- end
+ def inheritance_column_name
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
+ end
- def read_fixture_files
- if File.file?(yaml_file_path)
- read_yaml_fixture_files
- elsif File.file?(csv_file_path)
- read_csv_fixture_files
- else
- raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
+ def column_names
+ @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
end
- end
- def read_yaml_fixture_files
- yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
- File.file?(f)
- } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
-
- if yaml = parse_yaml_string(yaml_string)
- # If the file is an ordered map, extract its children.
- yaml_value =
- if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
- yaml.value
- else
- [yaml]
- end
+ def read_fixture_files
+ if File.file?(yaml_file_path)
+ read_yaml_fixture_files
+ elsif File.file?(csv_file_path)
+ read_csv_fixture_files
+ else
+ raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
+ end
+ end
- yaml_value.each do |fixture|
- raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
- fixture.each do |name, data|
- unless data
- raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ def read_yaml_fixture_files
+ yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
+ File.file?(f)
+ } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
+
+ if yaml = parse_yaml_string(yaml_string)
+ # If the file is an ordered map, extract its children.
+ yaml_value =
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
+ yaml.value
+ else
+ [yaml]
end
- fixtures[name] = Fixture.new(data, model_class)
+ yaml_value.each do |fixture|
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
+ fixture.each do |name, data|
+ unless data
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ end
+
+ fixtures[name] = ActiveRecord::Fixture.new(data, model_class)
+ end
end
end
end
- end
- def read_csv_fixture_files
- reader = CSV.parse(erb_render(IO.read(csv_file_path)))
- header = reader.shift
- i = 0
- reader.each do |row|
- data = {}
- row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
- fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
+ def read_csv_fixture_files
+ reader = CSV.parse(erb_render(IO.read(csv_file_path)))
+ header = reader.shift
+ i = 0
+ reader.each do |row|
+ data = {}
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
+ fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = ActiveRecord::Fixture.new(data, model_class)
+ end
end
- end
- def yaml_file_path
- "#{@fixture_path}.yml"
- end
+ def yaml_file_path
+ "#{@fixture_path}.yml"
+ end
- def csv_file_path
- @fixture_path + ".csv"
- end
+ def csv_file_path
+ @fixture_path + ".csv"
+ end
- def yaml_fixtures_key(path)
- File.basename(@fixture_path).split(".").first
- end
+ def yaml_fixtures_key(path)
+ File.basename(@fixture_path).split(".").first
+ end
- def parse_yaml_string(fixture_content)
- YAML::load(erb_render(fixture_content))
- rescue => error
- raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. 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}"
- end
+ def parse_yaml_string(fixture_content)
+ YAML::load(erb_render(fixture_content))
+ rescue => error
+ raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. 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}"
+ end
- def erb_render(fixture_content)
- ERB.new(fixture_content).result
- end
-end
+ def erb_render(fixture_content)
+ ERB.new(fixture_content).result
+ end
+ end
-class Fixture #:nodoc:
- include Enumerable
+ class Fixture #:nodoc:
+ include Enumerable
- class FixtureError < StandardError #:nodoc:
- end
+ class FixtureError < StandardError #:nodoc:
+ end
- class FormatError < FixtureError #:nodoc:
- end
+ class FormatError < FixtureError #:nodoc:
+ end
- attr_reader :model_class, :fixture
+ attr_reader :model_class, :fixture
- def initialize(fixture, model_class)
- @fixture = fixture
- @model_class = model_class
- end
+ def initialize(fixture, model_class)
+ @fixture = fixture
+ @model_class = model_class
+ end
- def class_name
- model_class.name if model_class
- end
+ def class_name
+ model_class.name if model_class
+ end
- def each
- fixture.each { |item| yield item }
- end
+ def each
+ fixture.each { |item| yield item }
+ end
- def [](key)
- fixture[key]
- end
+ def [](key)
+ fixture[key]
+ end
- alias :to_hash :fixture
+ alias :to_hash :fixture
- def find
- if model_class
- model_class.find(fixture[model_class.primary_key])
- else
- raise FixtureClassNotFound, "No class attached to find."
+ def find
+ if model_class
+ model_class.find(fixture[model_class.primary_key])
+ else
+ raise FixtureClassNotFound, "No class attached to find."
+ end
end
end
end
@@ -832,7 +837,7 @@ module ActiveRecord
self.pre_loaded_fixtures = false
self.fixture_class_names = Hash.new do |h, table_name|
- h[table_name] = Fixtures.find_table_name(table_name)
+ h[table_name] = ActiveRecord::Fixtures.find_table_name(table_name)
end
end
@@ -944,7 +949,7 @@ module ActiveRecord
ActiveRecord::Base.connection.begin_db_transaction
# Load fixtures for every test.
else
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
@@already_loaded_fixtures[self.class] = nil
@loaded_fixtures = load_fixtures
end
@@ -957,7 +962,7 @@ module ActiveRecord
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
unless run_in_transaction?
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
end
# Rollback changes if a transaction is active.
@@ -970,7 +975,7 @@ module ActiveRecord
private
def load_fixtures
- fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
+ fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
Hash[fixtures.map { |f| [f.name, f] }]
end
@@ -979,16 +984,16 @@ module ActiveRecord
def instantiate_fixtures
if pre_loaded_fixtures
- raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
+ raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::Fixtures.all_loaded_fixtures.empty?
unless @@required_fixture_classes
- self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
+ self.class.require_fixture_classes ActiveRecord::Fixtures.all_loaded_fixtures.keys
@@required_fixture_classes = true
end
- Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
+ ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
else
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
@loaded_fixtures.each do |fixture_name, fixtures|
- Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
+ ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
end
end
end
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index 9eb47ad99f..f88ead9ca0 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -49,7 +49,7 @@ module ActiveRecord
end
def get(klass, primary_key)
- record = repository[klass.symbolized_base_class][primary_key]
+ record = repository[klass.symbolized_sti_name][primary_key]
if record.is_a?(klass)
ActiveSupport::Notifications.instrument("identity.active_record",
@@ -64,15 +64,15 @@ module ActiveRecord
end
def add(record)
- repository[record.class.symbolized_base_class][record.id] = record
+ repository[record.class.symbolized_sti_name][record.id] = record
end
def remove(record)
- repository[record.class.symbolized_base_class].delete(record.id)
+ repository[record.class.symbolized_sti_name].delete(record.id)
end
- def remove_by_id(symbolized_base_class, id)
- repository[symbolized_base_class].delete(id)
+ def remove_by_id(symbolized_sti_name, id)
+ repository[symbolized_sti_name].delete(id)
end
def clear
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 0893d7e337..c723436330 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -110,8 +110,8 @@ module ActiveRecord
next unless respond_to?(callback)
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
unless klass.respond_to?(callback_meth)
- klass.send(:define_method, callback_meth) do
- observer.send(callback, self)
+ klass.send(:define_method, callback_meth) do |&block|
+ observer.send(callback, self, &block)
end
klass.send(callback, callback_meth)
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 929998eb85..4e61671473 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -40,6 +40,7 @@ module ActiveRecord
def close
@target.close if @target.respond_to?(:close)
ensure
+ ActiveRecord::Base.connection.clear_query_cache
unless @original_cache_value
ActiveRecord::Base.connection.disable_query_cache!
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 5703fac033..c6bc040f9f 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -305,7 +305,7 @@ db_namespace = namespace :db do
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
- Fixtures.create_fixtures(fixtures_dir, fixture_file)
+ ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
end
end
@@ -316,13 +316,13 @@ db_namespace = namespace :db do
label, id = ENV['LABEL'], ENV['ID']
raise 'LABEL or ID required' if label.blank? && id.blank?
- puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label
+ puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::Fixtures.identify(label)}.) if label
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML::load(ERB.new(IO.read(file)).result)
data.keys.each do |key|
- key_id = Fixtures.identify(key)
+ key_id = ActiveRecord::Fixtures.identify(key)
if key == label || key_id == id.to_i
puts "#{file}: #{key} (#{key_id})"
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 0667be7d23..2c20dd997f 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module ActiveRecord
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 43015098c9..292c7efebb 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
private
# custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
def create_test_fixtures(*fixture_names)
- Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
end
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 1efa7deaeb..752b864818 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -138,7 +138,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
private
# custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
def create_test_fixtures(*fixture_names)
- Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
end
# custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 007f11b535..247decc67b 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -605,6 +605,30 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
end
+ def test_find_or_initialize_updates_collection_size
+ number_of_clients = companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
+ end
+
+ def test_find_or_create_with_hash
+ post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_with_one_attribute_followed_by_hash
+ post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_should_work_with_block
+ post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert post.persisted?
+ end
+
def test_deleting
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index e2228228a3..124693f7c9 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -34,6 +34,17 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_no_match(/JOIN/i, sql)
end
+ def test_join_conditions_added_to_join_clause
+ sql = Author.joins(:essays).to_sql
+ assert_match(/writer_type.*?=.*?Author/i, sql)
+ assert_no_match(/WHERE/i, sql)
+ end
+
+ def test_join_conditions_allow_nil_associations
+ authors = Author.includes(:essays).where(:essays => {:id => nil})
+ assert_equal 2, authors.count
+ end
+
def test_find_with_implicit_inner_joins_honors_readonly_without_select
authors = Author.joins(:posts).to_a
assert !authors.empty?, "expected authors to be non-empty"
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5ee3b2d776..1775ba9999 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -575,6 +575,29 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
+ def test_multiparameter_attributes_on_time_with_no_date
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_invalid_time_params
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "2004", "written_on(5i)" => "36", "written_on(6i)" => "64",
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
def test_multiparameter_attributes_on_time_with_old_date
attributes = {
"written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
@@ -586,6 +609,82 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db)
end
+ def test_multiparameter_attributes_on_time_will_raise_on_big_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_raise_on_small_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
+ "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 0, topic.written_on.hour
+ assert_equal 12, topic.written_on.min
+ assert_equal 2, topic.written_on.sec
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 16, topic.written_on.hour
+ assert_equal 24, topic.written_on.min
+ assert_equal 0, topic.written_on.sec
+ end
+ def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal 1, topic.written_on.year
+ assert_equal 1, topic.written_on.month
+ assert_equal 1, topic.written_on.day
+ assert_equal 16, topic.written_on.hour
+ assert_equal 12, topic.written_on.min
+ assert_equal 02, topic.written_on.sec
+ end
+
def test_multiparameter_attributes_on_time_with_utc
ActiveRecord::Base.default_timezone = :utc
attributes = {
@@ -692,6 +791,42 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal address, customer.address
end
+ def test_multiparameter_assignment_of_aggregation_out_of_order
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
+ customer.attributes = attributes
+ assert_equal address, customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_missing_values
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ end
+ assert_equal("address", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_blank_values
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ assert_equal Address.new(nil, "The City", "The Country"), customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_large_index
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country }
+ customer.attributes = attributes
+ end
+ assert_equal("address", ex.errors[0].attribute)
+ end
+
def test_attributes_on_dummy_time
# Oracle, and Sybase do not have a TIME datatype.
return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 3e20155210..2bf192e2c6 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -36,7 +36,7 @@ class FixturesTest < ActiveRecord::TestCase
FIXTURES.each do |name|
fixtures = nil
assert_nothing_raised { fixtures = create_fixtures(name).first }
- assert_kind_of(Fixtures, fixtures)
+ assert_kind_of(ActiveRecord::Fixtures, fixtures)
fixtures.each { |_name, fixture|
fixture.each { |key, value|
assert_match(MATCH_ATTRIBUTE_NAME, key)
@@ -46,7 +46,7 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_create_fixtures
- Fixtures.create_fixtures(FIXTURES_ROOT, "parrots")
+ ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots")
assert Parrot.find_by_name('Curious George'), 'George is in the database'
end
@@ -54,7 +54,7 @@ class FixturesTest < ActiveRecord::TestCase
fixtures_array = nil
assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) }
assert_kind_of(Array, fixtures_array)
- fixtures_array.each { |fixtures| assert_kind_of(Fixtures, fixtures) }
+ fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::Fixtures, fixtures) }
end
def test_attributes
@@ -75,7 +75,7 @@ class FixturesTest < ActiveRecord::TestCase
if ActiveRecord::Base.connection.supports_migrations?
def test_inserts_with_pre_and_suffix
# Reset cache to make finds on the new table work
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t|
t.column :title, :string
@@ -150,11 +150,11 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_empty_yaml_fixture
- assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts")
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts")
end
def test_empty_yaml_fixture_with_a_comment_in_it
- assert_not_nil Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies")
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies")
end
def test_nonexistent_fixture_file
@@ -164,23 +164,23 @@ class FixturesTest < ActiveRecord::TestCase
assert Dir[nonexistent_fixture_path+"*"].empty?
assert_raise(FixturesFileNotFound) do
- Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path)
+ ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path)
end
end
def test_dirty_dirty_yaml_file
- assert_raise(Fixture::FormatError) do
- Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses")
+ assert_raise(ActiveRecord::Fixture::FormatError) do
+ ActiveRecord::Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses")
end
end
def test_empty_csv_fixtures
- assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts")
+ assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts")
end
def test_omap_fixtures
assert_nothing_raised do
- fixtures = Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered")
+ fixtures = ActiveRecord::Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered")
i = 0
fixtures.each do |name, fixture|
@@ -220,7 +220,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!)
def setup
@instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')]
- Fixtures.reset_cache # make sure tables get reinitialized
+ ActiveRecord::Fixtures.reset_cache # make sure tables get reinitialized
end
def test_resets_to_min_pk_with_specified_pk_and_sequence
@@ -524,13 +524,13 @@ class FasterFixturesTest < ActiveRecord::TestCase
def load_extra_fixture(name)
fixture = create_fixtures(name).first
- assert fixture.is_a?(Fixtures)
+ assert fixture.is_a?(ActiveRecord::Fixtures)
@loaded_fixtures[fixture.table_name] = fixture
end
def test_cache
- assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories')
- assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors')
+ assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories')
+ assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors')
assert_no_queries do
create_fixtures('categories')
@@ -538,7 +538,7 @@ class FasterFixturesTest < ActiveRecord::TestCase
end
load_extra_fixture('posts')
- assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts')
+ assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts')
self.class.setup_fixture_accessors('posts')
assert_equal 'Welcome to the weblog', posts(:welcome).title
end
@@ -548,17 +548,17 @@ class FoxyFixturesTest < ActiveRecord::TestCase
fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users"
def test_identifies_strings
- assert_equal(Fixtures.identify("foo"), Fixtures.identify("foo"))
- assert_not_equal(Fixtures.identify("foo"), Fixtures.identify("FOO"))
+ assert_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("foo"))
+ assert_not_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("FOO"))
end
def test_identifies_symbols
- assert_equal(Fixtures.identify(:foo), Fixtures.identify(:foo))
+ assert_equal(ActiveRecord::Fixtures.identify(:foo), ActiveRecord::Fixtures.identify(:foo))
end
def test_identifies_consistently
- assert_equal 207281424, Fixtures.identify(:ruby)
- assert_equal 1066363776, Fixtures.identify(:sapphire_2)
+ assert_equal 207281424, ActiveRecord::Fixtures.identify(:ruby)
+ assert_equal 1066363776, ActiveRecord::Fixtures.identify(:sapphire_2)
end
TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on)
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index fd20f1b120..fbb4ee6f7b 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -104,7 +104,7 @@ class ActiveSupport::TestCase
self.use_transactional_fixtures = true
def create_fixtures(*table_names, &block)
- Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block)
+ ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block)
end
end
diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb
index 649715fbb5..a0e16400d2 100644
--- a/activerecord/test/cases/identity_map_test.rb
+++ b/activerecord/test/cases/identity_map_test.rb
@@ -129,6 +129,41 @@ class IdentityMapTest < ActiveRecord::TestCase
end
##############################################################################
+ # Tests checking if IM is functioning properly on classes with multiple #
+ # types of inheritance #
+ ##############################################################################
+
+ def test_inherited_without_type_attribute_without_identity_map
+ ActiveRecord::IdentityMap.without do
+ p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
+ p2 = Pirate.find(p1.id)
+ assert_not_same(p1, p2)
+ end
+ end
+
+ def test_inherited_with_type_attribute_without_identity_map
+ ActiveRecord::IdentityMap.without do
+ c = comments(:sub_special_comment)
+ c1 = SubSpecialComment.find(c.id)
+ c2 = Comment.find(c.id)
+ assert_same(c1.class, c2.class)
+ end
+ end
+
+ def test_inherited_without_type_attribute
+ p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
+ p2 = Pirate.find(p1.id)
+ assert_not_same(p1, p2)
+ end
+
+ def test_inherited_with_type_attribute
+ c = comments(:sub_special_comment)
+ c1 = SubSpecialComment.find(c.id)
+ c2 = Comment.find(c.id)
+ assert_same(c1, c2)
+ end
+
+ ##############################################################################
# Tests checking dirty attribute behaviour with IM #
##############################################################################
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 6cd8494c9e..643e949087 100644
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -107,6 +107,23 @@ class ValidatedCommentObserver < ActiveRecord::Observer
end
end
+
+class AroundTopic < Topic
+end
+
+class AroundTopicObserver < ActiveRecord::Observer
+ observe :around_topic
+ def topic_ids
+ @topic_ids ||= []
+ end
+
+ def around_save(topic)
+ topic_ids << topic.id
+ yield(topic)
+ topic_ids << topic.id
+ end
+end
+
class LifecycleTest < ActiveRecord::TestCase
fixtures :topics, :developers, :minimalistics
@@ -206,6 +223,14 @@ class LifecycleTest < ActiveRecord::TestCase
assert_equal developer, SalaryChecker.instance.last_saved
end
+ test "around filter from observer should accept block" do
+ observer = AroundTopicObserver.instance
+ topic = AroundTopic.new
+ topic.save
+ assert_nil observer.topic_ids.first
+ assert_not_nil observer.topic_ids.last
+ end
+
def test_observer_is_called_once
observer = DeveloperObserver.instance # activate
observer.calls.clear
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index b2e40c6b22..a61180cfaf 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -65,6 +65,17 @@ class QueryCacheTest < ActiveRecord::TestCase
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
end
+ def test_cache_clear_after_close
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ Post.find(:first)
+ }
+ body = mw.call({}).last
+
+ assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty'
+ body.close
+ assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
+ end
+
def test_find_queries
assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) }
end
diff --git a/activerecord/test/fixtures/mateys.yml b/activerecord/test/fixtures/mateys.yml
index 9ecdd4ecd5..d3690955fc 100644
--- a/activerecord/test/fixtures/mateys.yml
+++ b/activerecord/test/fixtures/mateys.yml
@@ -1,4 +1,4 @@
blackbeard_to_redbeard:
- pirate_id: <%= Fixtures.identify(:blackbeard) %>
- target_id: <%= Fixtures.identify(:redbeard) %>
+ pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %>
+ target_id: <%= ActiveRecord::Fixtures.identify(:redbeard) %>
weight: 10
diff --git a/activerecord/test/fixtures/parrots_pirates.yml b/activerecord/test/fixtures/parrots_pirates.yml
index 6b17a37d68..66472243c7 100644
--- a/activerecord/test/fixtures/parrots_pirates.yml
+++ b/activerecord/test/fixtures/parrots_pirates.yml
@@ -1,7 +1,7 @@
george_blackbeard:
- parrot_id: <%= Fixtures.identify(:george) %>
- pirate_id: <%= Fixtures.identify(:blackbeard) %>
+ parrot_id: <%= ActiveRecord::Fixtures.identify(:george) %>
+ pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %>
louis_blackbeard:
- parrot_id: <%= Fixtures.identify(:louis) %>
- pirate_id: <%= Fixtures.identify(:blackbeard) %>
+ parrot_id: <%= ActiveRecord::Fixtures.identify(:louis) %>
+ pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %>