diff options
Diffstat (limited to 'activerecord')
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) %> |