diff options
55 files changed, 522 insertions, 303 deletions
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 88f973e62d..0be04b70a1 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -59,7 +59,7 @@ module ActionController #:nodoc: key = fragment_cache_key(key) instrument_fragment_cache :write_fragment, key do - content = content.html_safe.to_str if content.respond_to?(:html_safe) + content = content.to_str cache_store.write(key, content, options) end content diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 936fd548b9..dbe3206808 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -50,11 +50,11 @@ module ActionDispatch # Only this middleware cares about RoutingError. So, let's just raise # it here. if headers['X-Cascade'] == 'pass' - exception = ActionController::RoutingError.new("No route matches #{env['PATH_INFO'].inspect}") + raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}" end rescue Exception => exception + raise exception if env['action_dispatch.show_exceptions'] == false end - raise exception if env['action_dispatch.show_exceptions'] == false exception ? render_exception(env, exception) : [status, headers, body] end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 03bfe178e5..683fa19380 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -450,7 +450,7 @@ module ActionDispatch end def raise_routing_error - raise ActionController::RoutingError.new("No route matches #{options.inspect}") + raise ActionController::RoutingError, "No route matches #{options.inspect}" end def different_controller? diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 4026f7a40e..d4f16d4b36 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -156,10 +156,8 @@ module ActionView # The instance of ActionView::Base that is used by +render+. def view @view ||= begin - view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller) + view = @controller.view_context view.singleton_class.send :include, _helpers - view.singleton_class.send :include, @controller._routes.url_helpers - view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash" view.extend(Locals) view.locals = self.locals view.output_buffer = self.output_buffer diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index a745999622..203515d508 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -116,6 +116,27 @@ module ActionView end end + class ControllerHelperMethod < ActionView::TestCase + module SomeHelper + def some_method + render :partial => 'test/from_helper' + end + end + + helper SomeHelper + + test "can call a helper method defined on the current controller from a helper" do + @controller.singleton_class.class_eval <<-EOF, __FILE__, __LINE__ + 1 + def render_from_helper + 'controller_helper_method' + end + EOF + @controller.class.helper_method :render_from_helper + + assert_equal 'controller_helper_method', some_method + end + end + class AssignsTest < ActionView::TestCase setup do ActiveSupport::Deprecation.stubs(:warn) diff --git a/activerecord/Rakefile b/activerecord/Rakefile index f9b77c1799..064734f8e2 100755 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -41,7 +41,7 @@ namespace :test do end end -%w( mysql mysql2 postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| +%w( mysql mysql2 postgresql sqlite3 sqlite3_mem firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| Rake::TestTask.new("test_#{adapter}") { |t| connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}" adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 5aad2b4558..68aaff175a 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -126,21 +126,21 @@ module ActiveRecord parent_records.each do |parent_record| association_proxy = parent_record.send(reflection_name) association_proxy.loaded - association_proxy.target.push(*Array.wrap(associated_record)) + association_proxy.target.concat(Array.wrap(associated_record)) association_proxy.send(:set_inverse_instance, associated_record) end end def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record) parent_records.each do |parent_record| - parent_record.send("set_#{reflection_name}_target", associated_record) + parent_record.send(:association_proxy, reflection_name).target = associated_record end end - def set_association_collection_records(id_to_record_map, reflection_name, associated_records, key) + def set_association_collection_records(id_to_parent_map, reflection_name, associated_records, key) associated_records.each do |associated_record| - mapped_records = id_to_record_map[associated_record[key].to_s] - add_preloaded_records_to_collection(mapped_records, reflection_name, associated_record) + parent_records = id_to_parent_map[associated_record[key].to_s] + add_preloaded_records_to_collection(parent_records, reflection_name, associated_record) end end @@ -158,14 +158,17 @@ module ActiveRecord seen_keys[seen_key] = true mapped_records = id_to_record_map[seen_key] mapped_records.each do |mapped_record| - association_proxy = mapped_record.send("set_#{reflection_name}_target", associated_record) + association_proxy = mapped_record.send(:association_proxy, reflection_name) + association_proxy.target = associated_record association_proxy.send(:set_inverse_instance, associated_record) end end id_to_record_map.each do |id, records| next if seen_keys.include?(id.to_s) - records.each {|record| record.send("set_#{reflection_name}_target", nil) } + records.each do |record| + record.send(:association_proxy, reflection_name).target = nil + end end end @@ -196,7 +199,6 @@ module ActiveRecord right = Arel::Table.new(options[:join_table]).alias('t0') - join_condition = left[reflection.klass.primary_key].eq( right[reflection.association_foreign_key]) @@ -218,24 +220,35 @@ module ActiveRecord custom_conditions = append_conditions(reflection, preload_options) - all_associated_records = associated_records(ids) do |some_ids| + klass = associated_records_proxy.klass + + associated_records(ids) { |some_ids| method = in_or_equal(some_ids) conditions = right[reflection.foreign_key].send(*method) conditions = custom_conditions.inject(conditions) do |ast, cond| ast.and cond end - associated_records_proxy.where(conditions).to_a - end - - set_association_collection_records(id_to_record_map, reflection.name, all_associated_records, 'the_parent_record_id') + relation = associated_records_proxy.where(conditions) + klass.connection.select_all(relation.arel.to_sql, 'SQL', relation.bind_values) + }.map! { |row| + parent_records = id_to_record_map[row['the_parent_record_id'].to_s] + associated_record = klass.instantiate row + add_preloaded_records_to_collection( + parent_records, reflection.name, associated_record) + associated_record + } end def preload_has_one_association(records, reflection, preload_options={}) - return if records.first.send("loaded_#{reflection.name}?") + return if records.first.send(:association_proxy, reflection.name).loaded? id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key]) options = reflection.options - records.each {|record| record.send("set_#{reflection.name}_target", nil)} + + records.each do |record| + record.send(:association_proxy, reflection.name).target = nil + end + if options[:through] through_records = preload_through_records(records, reflection, options[:through]) @@ -317,7 +330,7 @@ module ActiveRecord end def preload_belongs_to_association(records, reflection, preload_options={}) - return if records.first.send("loaded_#{reflection.name}?") + return if records.first.send(:association_proxy, reflection.name).loaded? options = reflection.options klasses_and_ids = {} @@ -415,7 +428,7 @@ module ActiveRecord in_clause_length = connection.in_clause_length || ids.size records = [] ids.each_slice(in_clause_length) do |some_ids| - records += yield(some_ids) + records.concat yield(some_ids) end records end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3e7c9a370d..a03d1bbb06 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -137,6 +137,22 @@ module ActiveRecord # :nodoc: attr_reader :association_cache + protected + + # Returns the proxy for the given association name, instantiating it if it doesn't + # already exist + def association_proxy(name) + association = association_instance_get(name) + + if association.nil? + reflection = self.class.reflect_on_association(name) + association = reflection.proxy_class.new(self, reflection) + association_instance_set(name, association) + end + + association + end + private # Returns the specified association instance if it responds to :loaded?, nil otherwise. def association_instance_get(name) @@ -332,26 +348,31 @@ module ActiveRecord # === One-to-one associations # # * Assigning an object to a +has_one+ association automatically saves that object and - # the object being replaced (if there is one), in order to update their primary - # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>). - # * If either of these saves fail (due to one of the objects being invalid) the assignment - # statement returns +false+ and the assignment is cancelled. + # the object being replaced (if there is one), in order to update their foreign + # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>). + # * If either of these saves fail (due to one of the objects being invalid), an + # <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is + # cancelled. # * If you wish to assign an object to a +has_one+ association without saving it, - # use the <tt>association.build</tt> method (documented below). + # use the <tt>build_association</tt> method (documented below). The object being + # replaced will still be saved to update its foreign key. # * Assigning an object to a +belongs_to+ association does not save the object, since - # the foreign key field belongs on the parent. It does not save the parent either. + # the foreign key field belongs on the parent. It does not save the parent either. # # === Collections # # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically - # saves that object, except if the parent object (the owner of the collection) is not yet - # stored in the database. + # saves that object, except if the parent object (the owner of the collection) is not yet + # stored in the database. # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) - # fails, then <tt>push</tt> returns +false+. + # fails, then <tt>push</tt> returns +false+. + # * If saving fails while replacing the collection (via <tt>association=</tt>), an + # <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is + # cancelled. # * You can add an object to a collection without automatically saving it by using the - # <tt>collection.build</tt> method (documented below). + # <tt>collection.build</tt> method (documented below). # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically - # saved when the parent is saved. + # saved when the parent is saved. # # === Association callbacks # @@ -998,12 +1019,7 @@ module ActiveRecord reflection = create_has_many_reflection(association_id, options, &extension) configure_dependency_for_has_many(reflection) add_association_callbacks(reflection.name, reflection.options) - - if options[:through] - collection_accessor_methods(reflection, HasManyThroughAssociation) - else - collection_accessor_methods(reflection, HasManyAssociation) - end + collection_accessor_methods(reflection) end # Specifies a one-to-one association with another class. This method should only be used @@ -1021,8 +1037,7 @@ module ActiveRecord # [build_association(attributes = {})] # Returns a new object of the associated type that has been instantiated # with +attributes+ and linked to this object through a foreign key, but has not - # yet been saved. <b>Note:</b> This ONLY works if an association already exists. - # It will NOT work if the association is +nil+. + # yet been saved. # [create_association(attributes = {})] # Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that @@ -1115,14 +1130,12 @@ module ActiveRecord def has_one(association_id, options = {}) if options[:through] reflection = create_has_one_through_reflection(association_id, options) - association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation) else reflection = create_has_one_reflection(association_id, options) - association_accessor_methods(reflection, HasOneAssociation) - association_constructor_method(:build, reflection, HasOneAssociation) - association_constructor_method(:create, reflection, HasOneAssociation) + association_constructor_methods(reflection) configure_dependency_for_has_one(reflection) end + association_accessor_methods(reflection) end # Specifies a one-to-one association with another class. This method should only be used @@ -1239,12 +1252,10 @@ module ActiveRecord def belongs_to(association_id, options = {}) reflection = create_belongs_to_reflection(association_id, options) - if reflection.options[:polymorphic] - association_accessor_methods(reflection, BelongsToPolymorphicAssociation) - else - association_accessor_methods(reflection, BelongsToAssociation) - association_constructor_method(:build, reflection, BelongsToAssociation) - association_constructor_method(:create, reflection, BelongsToAssociation) + association_accessor_methods(reflection) + + unless reflection.options[:polymorphic] + association_constructor_methods(reflection) end add_counter_cache_callbacks(reflection) if options[:counter_cache] @@ -1429,7 +1440,7 @@ module ActiveRecord # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}' def has_and_belongs_to_many(association_id, options = {}, &extension) reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) - collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) + collection_accessor_methods(reflection) # Don't use a before_destroy callback since users' before_destroy # callbacks will be executed after the association is wiped out. @@ -1461,15 +1472,10 @@ module ActiveRecord table_name_prefix + join_table + table_name_suffix end - def association_accessor_methods(reflection, association_proxy_class) + def association_accessor_methods(reflection) redefine_method(reflection.name) do |*params| force_reload = params.first unless params.empty? - association = association_instance_get(reflection.name) - - if association.nil? - association = association_proxy_class.new(self, reflection) - association_instance_set(reflection.name, association) - end + association = association_proxy(reflection.name) if force_reload reflection.klass.uncached { association.reload } @@ -1480,40 +1486,15 @@ module ActiveRecord association.target.nil? ? nil : association end - redefine_method("loaded_#{reflection.name}?") do - association = association_instance_get(reflection.name) - association && association.loaded? - end - redefine_method("#{reflection.name}=") do |record| - association = association_instance_get(reflection.name) - - if association.nil? - association = association_proxy_class.new(self, reflection) - association_instance_set(reflection.name, association) - end - - association.replace(record) - association.target.nil? ? nil : association - end - - redefine_method("set_#{reflection.name}_target") do |target| - association = association_proxy_class.new(self, reflection) - association.target = target - association.loaded - association_instance_set(reflection.name, association) + association_proxy(reflection.name).replace(record) end end - def collection_reader_method(reflection, association_proxy_class) + def collection_reader_method(reflection) redefine_method(reflection.name) do |*params| force_reload = params.first unless params.empty? - association = association_instance_get(reflection.name) - - unless association - association = association_proxy_class.new(self, reflection) - association_instance_set(reflection.name, association) - end + association = association_proxy(reflection.name) if force_reload reflection.klass.uncached { association.reload } @@ -1533,15 +1514,12 @@ module ActiveRecord end end - def collection_accessor_methods(reflection, association_proxy_class, writer = true) - collection_reader_method(reflection, association_proxy_class) + def collection_accessor_methods(reflection, writer = true) + collection_reader_method(reflection) if writer redefine_method("#{reflection.name}=") do |new_value| - # Loads proxy class instance (defined in collection_reader_method) if not already loaded - association = send(reflection.name) - association.replace(new_value) - association + association_proxy(reflection.name).replace(new_value) end redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| @@ -1553,17 +1531,18 @@ module ActiveRecord end end - def association_constructor_method(constructor, reflection, association_proxy_class) - redefine_method("#{constructor}_#{reflection.name}") do |*params| - attributes = params.first unless params.empty? - association = association_instance_get(reflection.name) - - unless association - association = association_proxy_class.new(self, reflection) - association_instance_set(reflection.name, association) + def association_constructor_methods(reflection) + constructors = { + "build_#{reflection.name}" => "build", + "create_#{reflection.name}" => "create" + } + constructors["create_#{reflection.name}!"] = "create!" if reflection.macro == :has_one + + constructors.each do |name, proxy_name| + redefine_method(name) do |*params| + attributes = params.first unless params.empty? + association_proxy(reflection.name).send(proxy_name, attributes) end - - association.send(constructor, attributes) end end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index daec8493ac..3d8a23fdca 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -314,7 +314,11 @@ module ActiveRecord transaction do delete(@target - other_array) - concat(other_array - @target) + + unless concat(other_array - @target) + raise RecordNotSaved, "Failed to replace #{@reflection.name} because one or more of the " + "new records could not be saved." + end end end diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 294e1cab50..e4a449d4f4 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -213,7 +213,8 @@ module ActiveRecord # Set the inverse association, if possible def set_inverse_instance(record) if record && invertible_for?(record) - record.send("set_#{inverse_reflection_for(record).name}_target", @owner) + inverse = record.send(:association_proxy, inverse_reflection_for(record).name) + inverse.target = @owner end end @@ -259,23 +260,6 @@ module ActiveRecord end end - private - # Forwards any missing method call to the \target. - def method_missing(method, *args) - if load_target - unless @target.respond_to?(method) - message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" - raise NoMethodError, message - end - - if block_given? - @target.send(method, *args) { |*block_args| yield(*block_args) } - else - @target.send(method, *args) - end - end - end - # Loads the \target if needed and returns it. # # This method is abstract in the sense that it relies on +find_target+, @@ -299,6 +283,18 @@ module ActiveRecord reset end + private + + # Forwards any missing method call to the \target. + def method_missing(method, *args, &block) + if load_target + return super unless @target.respond_to?(method) + @target.send(method, *args, &block) + end + rescue NoMethodError => e + raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@target}") + end + # Should be true if there is a foreign key present on the @owner which # references the target. This is used to determine whether we can load # the target if the @owner is currently a new record (and therefore diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb index 6263c4e3b0..cb3edafab1 100644 --- a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb +++ b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb @@ -223,7 +223,8 @@ module ActiveRecord end def set_target_and_inverse(join_part, association, record) - association_proxy = record.send("set_#{join_part.reflection.name}_target", association) + association_proxy = record.send(:association_proxy, join_part.reflection.name) + association_proxy.target = association association_proxy.send(:set_inverse_instance, association) end end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 739bb919c5..c29ab8dcec 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -7,7 +7,7 @@ module ActiveRecord end def create!(attributes = {}) - new_record(:create_association!, attributes) + build(attributes).tap { |record| record.save! } end def build(attributes = {}) @@ -19,23 +19,25 @@ module ActiveRecord raise_on_type_mismatch(record) unless record.nil? load_target - if @target && @target != record - remove_target(save && @reflection.options[:dependent]) - end + @reflection.klass.transaction do + if @target && @target != record + remove_target!(@reflection.options[:dependent]) + end + + if record + set_inverse_instance(record) + set_owner_attributes(record) - if record - set_owner_attributes(record) - set_inverse_instance(record) + if @owner.persisted? && save && !record.save + nullify_owner_attributes(record) + set_owner_attributes(@target) + raise RecordNotSaved, "Failed to save the new associated #{@reflection.name}." + end + end end @target = record loaded - - if @owner.persisted? && record && save - record.save && self - else - record && self - end end private @@ -49,21 +51,32 @@ module ActiveRecord alias creation_attributes construct_owner_attributes + # The reason that the save param for replace is false, if for create (not just build), + # is because the setting of the foreign keys is actually handled by the scoping, and + # so they are set straight away and do not need to be updated within replace. def new_record(method, attributes) record = scoped.scoping { @reflection.send(method, attributes) } replace(record, false) record end - def remove_target(method) - case method - when :delete, :destroy + def remove_target!(method) + if [:delete, :destroy].include?(method) @target.send(method) else - @target[@reflection.foreign_key] = nil - @target.save if @target.persisted? && @owner.persisted? + nullify_owner_attributes(@target) + + if @target.persisted? && @owner.persisted? && !@target.save + set_owner_attributes(@target) + raise RecordNotSaved, "Failed to remove the existing associated #{@reflection.name}. " + + "The record failed to save when after its foreign key was set to nil." + end end end + + def nullify_owner_attributes(record) + record[@reflection.foreign_key] = nil + end end end end diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index 11fa40a5c4..59a704b7bf 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -13,9 +13,8 @@ module ActiveRecord private def create_through_record(new_value) - proxy = @owner.send(@reflection.through_reflection.name) || - @owner.send(:association_instance_get, @reflection.through_reflection.name) - record = proxy.target + proxy = @owner.send(:association_proxy, @reflection.through_reflection.name) + record = proxy.send(:load_target) if record && !new_value record.destroy @@ -31,10 +30,6 @@ module ActiveRecord end end end - - def find_target - scoped.first - end end end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 1079094bbf..dde52269d4 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -880,6 +880,16 @@ module ActiveRecord #:nodoc: record end + + # Finder methods must instantiate through this method to work with the + # single-table inheritance model that makes it possible to create + # objects of different types from the same table. + def instantiate(record) # :nodoc: + model = find_sti_class(record[inheritance_column]).allocate + model.init_with('attributes' => record) + model + end + private def relation #:nodoc: @@ -892,15 +902,6 @@ module ActiveRecord #:nodoc: end end - # Finder methods must instantiate through this method to work with the - # single-table inheritance model that makes it possible to create - # objects of different types from the same table. - def instantiate(record) - model = find_sti_class(record[inheritance_column]).allocate - model.init_with('attributes' => record) - model - end - def find_sti_class(type_name) if type_name.blank? || !columns_hash.include?(inheritance_column) self @@ -1577,7 +1578,7 @@ MSG # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings). def attribute_present?(attribute) - !read_attribute(attribute).blank? + !_read_attribute(attribute).blank? end # Returns the column object for the named attribute. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 0282493219..5ff5813699 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -77,8 +77,8 @@ module ActiveRecord false end - # Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite - # does not. + # Does this adapter support savepoints? PostgreSQL and MySQL do, + # SQLite < 3.6.8 does not. def supports_savepoints? false end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index bf599a95f7..b04383d5bf 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -62,6 +62,10 @@ module ActiveRecord sqlite_version >= '2.0.0' end + def supports_savepoints? + sqlite_version >= '3.6.8' + end + # Returns +true+ when the connection adapter supports prepared statement # caching, otherwise returns +false+ def supports_statement_cache? @@ -189,6 +193,18 @@ module ActiveRecord exec_query(sql, name).rows end + def create_savepoint + execute("SAVEPOINT #{current_savepoint_name}") + end + + def rollback_to_savepoint + execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") + end + + def release_savepoint + execute("RELEASE SAVEPOINT #{current_savepoint_name}") + end + def begin_db_transaction #:nodoc: @connection.transaction end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 937efe395f..ceeb0ec39d 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -313,6 +313,31 @@ module ActiveRecord macro == :belongs_to end + def proxy_class + case macro + when :belongs_to + if options[:polymorphic] + Associations::BelongsToPolymorphicAssociation + else + Associations::BelongsToAssociation + end + when :has_and_belongs_to_many + Associations::HasAndBelongsToManyAssociation + when :has_many + if options[:through] + Associations::HasManyThroughAssociation + else + Associations::HasManyAssociation + end + when :has_one + if options[:through] + Associations::HasOneThroughAssociation + else + Associations::HasOneAssociation + end + end + end + private def derive_class_name class_name = name.to_s.camelize diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index e9e451ec5c..b75a65e3ca 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -282,15 +282,11 @@ module ActiveRecord end def type_cast_calculated_value(value, column, operation = nil) - if value.is_a?(String) || value.nil? - case operation - when 'count' then value.to_i - when 'sum' then type_cast_using_column(value || '0', column) - when 'average' then value.try(:to_d) - else type_cast_using_column(value, column) - end - else - type_cast_using_column(value, column) + case operation + when 'count' then value.to_i + when 'sum' then type_cast_using_column(value || '0', column) + when 'average' then value.try(:to_d) + else type_cast_using_column(value, column) end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index e30b481fe1..a893c0ad85 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -83,7 +83,7 @@ HEADER # first dump primary key column if @connection.respond_to?(:pk_and_sequence_for) - pk, pk_seq = @connection.pk_and_sequence_for(table) + pk, _ = @connection.pk_and_sequence_for(table) elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end diff --git a/activerecord/test/cases/associations/association_proxy_test.rb b/activerecord/test/cases/associations/association_proxy_test.rb new file mode 100644 index 0000000000..55d8da4c4e --- /dev/null +++ b/activerecord/test/cases/associations/association_proxy_test.rb @@ -0,0 +1,52 @@ +require "cases/helper" + +module ActiveRecord + module Associations + class AsssociationProxyTest < ActiveRecord::TestCase + class FakeOwner + attr_accessor :new_record + alias :new_record? :new_record + + def initialize + @new_record = false + end + end + + class FakeReflection < Struct.new(:options, :klass) + def initialize options = {}, klass = nil + super + end + + def check_validity! + true + end + end + + class FakeTarget + end + + class FakeTargetProxy < AssociationProxy + def association_scope + true + end + + def find_target + FakeTarget.new + end + end + + def test_method_missing_error + reflection = FakeReflection.new({}, Object.new) + owner = FakeOwner.new + proxy = FakeTargetProxy.new(owner, reflection) + + exception = assert_raises(NoMethodError) do + proxy.omg + end + + assert_match('omg', exception.message) + assert_match(FakeTarget.name, exception.message) + end + end + end +end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 1cb29a0fa1..4c4891dcaf 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -146,6 +146,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm" end + def test_with_polymorphic_and_condition + sponsor = Sponsor.create + member = Member.create :name => "Bert" + sponsor.sponsorable = member + + assert_equal member, sponsor.sponsorable + assert_nil sponsor.sponsorable_with_conditions + end + def test_with_select assert_equal Company.find(2).firm_with_select.attributes.size, 1 assert_equal Company.find(2, :include => :firm_with_select ).firm_with_select.attributes.size, 1 diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 2b7ad3642a..1ce91d7211 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -975,6 +975,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !firm.clients.include?(:first_client) end + def test_replace_failure + firm = companies(:first_firm) + account = Account.new + orig_accounts = firm.accounts.to_a + + assert !account.valid? + assert !orig_accounts.empty? + assert_raise ActiveRecord::RecordNotSaved do + firm.accounts = [account] + end + assert_equal orig_accounts, firm.accounts + end + def test_get_ids assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 8203534a37..d9b6694dd8 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -2,9 +2,12 @@ require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' +require 'models/ship' +require 'models/pirate' class HasOneAssociationsTest < ActiveRecord::TestCase - fixtures :accounts, :companies, :developers, :projects, :developers_projects + self.use_transactional_fixtures = false unless supports_savepoints? + fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates def setup Account.destroyed_account_ids.clear @@ -91,18 +94,18 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_nullification_on_association_change firm = companies(:rails_core) old_account_id = firm.account.id - firm.account = Account.new + firm.account = Account.new(:credit_limit => 5) # account is dependent with nullify, therefore its firm_id should be nil assert_nil Account.find(old_account_id).firm_id end def test_association_change_calls_delete - companies(:first_firm).deletable_account = Account.new + companies(:first_firm).deletable_account = Account.new(:credit_limit => 5) assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id] end def test_association_change_calls_destroy - companies(:first_firm).account = Account.new + companies(:first_firm).account = Account.new(:credit_limit => 5) assert_equal [companies(:first_firm).id], Account.destroyed_account_ids[companies(:first_firm).id] end @@ -164,18 +167,27 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal account, firm.account end - def test_build_association_twice_without_saving_affects_nothing - count_of_account = Account.count - firm = Firm.find(:first) - firm.build_account("credit_limit" => 1000) - firm.build_account("credit_limit" => 2000) + def test_create_association + firm = Firm.create(:name => "GlobalMegaCorp") + account = firm.create_account(:credit_limit => 1000) + assert_equal account, firm.reload.account + end - assert_equal count_of_account, Account.count + def test_create_association_with_bang + firm = Firm.create(:name => "GlobalMegaCorp") + account = firm.create_account!(:credit_limit => 1000) + assert_equal account, firm.reload.account end - def test_create_association + def test_create_association_with_bang_failing firm = Firm.create(:name => "GlobalMegaCorp") - account = firm.create_account(:credit_limit => 1000) + assert_raise ActiveRecord::RecordInvalid do + firm.create_account! + end + account = firm.account + assert_not_nil account + account.credit_limit = 5 + account.save assert_equal account, firm.reload.account end @@ -189,17 +201,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal account, firm.account end - def test_failing_build_association - firm = Firm.new("name" => "GlobalMegaCorp") - firm.save - - firm.account = account = Account.new - assert_equal account, firm.account - assert !account.save - assert_equal account, firm.account - assert_equal ["can't be empty"], account.errors["credit_limit"] - end - def test_create firm = Firm.new("name" => "GlobalMegaCorp") firm.save @@ -293,4 +294,51 @@ class HasOneAssociationsTest < ActiveRecord::TestCase new_account = companies(:first_firm).build_account(:firm_name => 'Account') assert_equal new_account.firm_name, "Account" end + + def test_creation_failure_without_dependent_option + pirate = pirates(:blackbeard) + orig_ship = pirate.ship.target + + assert_equal ships(:black_pearl), orig_ship + new_ship = pirate.create_ship + assert_not_equal ships(:black_pearl), new_ship + assert_equal new_ship, pirate.ship + assert new_ship.new_record? + assert_nil orig_ship.pirate_id + assert !orig_ship.changed? # check it was saved + end + + def test_creation_failure_with_dependent_option + pirate = pirates(:blackbeard).becomes(DestructivePirate) + orig_ship = pirate.dependent_ship.target + + new_ship = pirate.create_dependent_ship + assert new_ship.new_record? + assert orig_ship.destroyed? + end + + def test_replacement_failure_due_to_existing_record_should_raise_error + pirate = pirates(:blackbeard) + pirate.ship.name = nil + + assert !pirate.ship.valid? + assert_raise(ActiveRecord::RecordNotSaved) do + pirate.ship = ships(:interceptor) + end + assert_equal ships(:black_pearl), pirate.ship + assert_equal pirate.id, pirate.ship.pirate_id + end + + def test_replacement_failure_due_to_new_record_should_raise_error + pirate = pirates(:blackbeard) + new_ship = Ship.new + + assert_raise(ActiveRecord::RecordNotSaved) do + pirate.ship = new_ship + end + assert_equal ships(:black_pearl), pirate.ship + assert_equal pirate.id, pirate.ship.pirate_id + assert_equal pirate.id, ships(:black_pearl).reload.pirate_id + assert_nil new_ship.pirate_id + end end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 0afbef5c87..91d3025468 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -197,7 +197,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase MemberDetail.find(:all, :include => :member_type) end @new_detail = @member_details[0] - assert @new_detail.loaded_member_type? + assert @new_detail.send(:association_proxy, :member_type).loaded? assert_not_nil assert_no_queries { @new_detail.member_type } end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 7e9c190f42..c50fcd3f33 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -13,7 +13,8 @@ require 'models/book' require 'models/citation' class AssociationsJoinModelTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? + fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books, # Reload edges table from fixtures as otherwise repeated test was failing :edges @@ -522,7 +523,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_has_many_through_collection_size_uses_counter_cache_if_it_exists author = authors(:david) - author.stubs(:read_attribute).with('comments_count').returns(100) + author.stubs(:_read_attribute).with('comments_count').returns(100) assert_equal 100, author.comments.size assert !author.comments.loaded? end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 71fd3fd836..11c0c5b0ef 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -90,7 +90,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas firm = Firm.find(:first) assert firm.valid? - firm.account = Account.new + firm.build_account assert !firm.account.valid? assert !firm.valid? @@ -102,7 +102,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas firm = Firm.find(:first) assert firm.valid? - firm.unvalidated_account = Account.new + firm.build_unvalidated_account assert !firm.unvalidated_account.valid? assert firm.valid? @@ -572,7 +572,7 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase end class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @@ -797,7 +797,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @@ -917,7 +917,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase end class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @ship = Ship.create(:name => 'Nights Dirty Lightning') @@ -1164,7 +1164,7 @@ module AutosaveAssociationOnACollectionAssociationTests end class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @association_name = :birds @@ -1178,7 +1178,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase end class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @association_name = :parrots @@ -1193,7 +1193,7 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T end class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @@ -1209,7 +1209,7 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te end class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @@ -1230,7 +1230,7 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes end class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @@ -1250,7 +1250,7 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord:: end class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @@ -1272,7 +1272,7 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test end class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @pirate = Pirate.new diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 5cb8485b4b..644c9cb528 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -23,6 +23,11 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 53.0, value end + def test_should_return_decimal_average_of_integer_field + value = Account.average(:id) + assert_equal 3.5, value + end + def test_should_return_nil_as_average assert_nil NumericData.average(:bank_balance) end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index f9bbc5299b..2cc993b6ed 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -26,6 +26,15 @@ def current_adapter?(*types) end end +def in_memory_db? + current_adapter?(:SQLiteAdapter) && + ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:" +end + +def supports_savepoints? + ActiveRecord::Base.connection.supports_savepoints? +end + def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz yield @@ -100,11 +109,11 @@ class ActiveSupport::TestCase end end -# silence verbose schema loading -original_stdout = $stdout -$stdout = StringIO.new +def load_schema + # silence verbose schema loading + original_stdout = $stdout + $stdout = StringIO.new -begin adapter_name = ActiveRecord::Base.connection.adapter_name.downcase adapter_specific_schema_file = SCHEMA_ROOT + "/#{adapter_name}_specific_schema.rb" @@ -117,6 +126,8 @@ ensure $stdout = original_stdout end +load_schema + class << Time unless method_defined? :now_before_time_travel alias_method :now_before_time_travel, :now @@ -133,4 +144,3 @@ class << Time @now = nil end end - diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index f9678cb0c5..c5a204b335 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -19,11 +19,6 @@ end class OptimisticLockingTest < ActiveRecord::TestCase fixtures :people, :legacy_things, :references - # need to disable transactional fixtures, because otherwise the sqlite3 - # adapter (at least) chokes when we try and change the schema in the middle - # of a test (see test_increment_counter_*). - self.use_transactional_fixtures = false - def test_lock_existing p1 = Person.find(1) p2 = Person.find(1) @@ -152,6 +147,33 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal "unchangeable name", p.first_name end + def test_quote_table_name + ref = references(:michael_magician) + ref.favourite = !ref.favourite + assert ref.save + end + + # Useful for partial updates, don't only update the lock_version if there + # is nothing else being updated. + def test_update_without_attributes_does_not_only_update_lock_version + assert_nothing_raised do + p1 = Person.create!(:first_name => 'anika') + lock_version = p1.lock_version + p1.save + p1.reload + assert_equal lock_version, p1.lock_version + end + end +end + +class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase + fixtures :people, :legacy_things, :references + + # need to disable transactional fixtures, because otherwise the sqlite3 + # adapter (at least) chokes when we try and change the schema in the middle + # of a test (see test_increment_counter_*). + self.use_transactional_fixtures = false + { :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model| define_method("test_increment_counter_updates_#{name}") do counter_test model, 1 do |id| @@ -198,24 +220,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) } end - def test_quote_table_name - ref = references(:michael_magician) - ref.favourite = !ref.favourite - assert ref.save - end - - # Useful for partial updates, don't only update the lock_version if there - # is nothing else being updated. - def test_update_without_attributes_does_not_only_update_lock_version - assert_nothing_raised do - p1 = Person.create!(:first_name => 'anika') - lock_version = p1.lock_version - p1.save - p1.reload - assert_equal lock_version, p1.lock_version - end - end - private def add_counter_column_to(model, col='test_count') @@ -252,9 +256,9 @@ end # TODO: The Sybase, and OpenBase adapters currently have no support for pessimistic locking -unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) +unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? class PessimisticLockingTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? fixtures :people, :readers def setup diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 1a65045ded..a5a9965c3a 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -14,7 +14,7 @@ if ActiveRecord::Base.connection.supports_migrations? class Reminder < ActiveRecord::Base; end class ActiveRecord::Migration - class <<self + class << self attr_accessor :message_count end @@ -2083,4 +2083,3 @@ if ActiveRecord::Base.connection.supports_migrations? end end end - diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index bd51388e05..36e2c62fbb 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -6,7 +6,7 @@ require 'models/bird' require_dependency 'models/course' class MultipleDbTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @courses = create_fixtures("courses") { Course.retrieve_connection } diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index d290afc1dd..e1f938be84 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -859,7 +859,7 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase end class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @pirate = Pirate.create!(:catchphrase => "My baby takes tha mornin' train!") @@ -899,7 +899,7 @@ class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRe end class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @ship = Ship.create!(:name => "The good ship Dollypop") diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index de5fa140ba..6269437b14 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -137,4 +137,4 @@ class PooledConnectionsTest < ActiveRecord::TestCase def add_record(name) ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name } end -end unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name +end unless current_adapter?(:FrontBase) || in_memory_db? diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 33916c4e46..53aefc7b58 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -63,7 +63,7 @@ class QueryCacheTest < ActiveRecord::TestCase # Oracle adapter returns count() as Fixnum or Float if current_adapter?(:OracleAdapter) assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") - elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5' || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter) + elsif current_adapter?(:SQLite3Adapter) && SQLite3::VERSION > '1.2.5' || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter) # Future versions of the sqlite3 adapter will return numeric assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb index 6f1c170a0c..f906bda8c3 100644 --- a/activerecord/test/cases/session_store/session_test.rb +++ b/activerecord/test/cases/session_store/session_test.rb @@ -5,7 +5,7 @@ require 'active_record/session_store' module ActiveRecord class SessionStore class SessionTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? && ActiveRecord::Base.connection.supports_ddl_transactions? def setup super diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb index 23ad10f3f9..f85fb4e5da 100644 --- a/activerecord/test/cases/unconnected_test.rb +++ b/activerecord/test/cases/unconnected_test.rb @@ -4,7 +4,7 @@ class TestRecord < ActiveRecord::Base end class TestUnconnectedAdapter < ActiveRecord::TestCase - self.use_transactional_fixtures = false + self.use_transactional_fixtures = false unless supports_savepoints? def setup @underlying = ActiveRecord::Base.connection @@ -14,6 +14,7 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase def teardown @underlying = nil ActiveRecord::Base.establish_connection(@specification) + load_schema if in_memory_db? end def test_connection_no_longer_established diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 679d67553b..b4f3dd034c 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -6,19 +6,6 @@ require 'models/warehouse_thing' require 'models/guid' require 'models/event' -# The following methods in Topic are used in test_conditional_validation_* -class Topic - has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" - has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id" -end - -class UniqueReply < Reply - validates_uniqueness_of :content, :scope => 'parent_id' -end - -class SillyUniqueReply < UniqueReply -end - class Wizard < ActiveRecord::Base self.abstract_class = true diff --git a/activerecord/test/connections/native_sqlite3/in_memory_connection.rb b/activerecord/test/connections/native_sqlite3/in_memory_connection.rb deleted file mode 100644 index 6aba9719bb..0000000000 --- a/activerecord/test/connections/native_sqlite3/in_memory_connection.rb +++ /dev/null @@ -1,18 +0,0 @@ -print "Using native SQLite3\n" -require_dependency 'models/course' -require 'logger' -ActiveRecord::Base.logger = Logger.new("debug.log") - -class SqliteError < StandardError -end - -def make_connection(clazz, db_definitions_file) - clazz.establish_connection(:adapter => 'sqlite3', :database => ':memory:') - File.read(SCHEMA_ROOT + "/#{db_definitions_file}").split(';').each do |command| - clazz.connection.execute(command) unless command.strip.empty? - end -end - -make_connection(ActiveRecord::Base, 'sqlite.sql') -make_connection(Course, 'sqlite2.sql') -load(SCHEMA_ROOT + "/schema.rb") diff --git a/activerecord/test/connections/native_sqlite3_mem/connection.rb b/activerecord/test/connections/native_sqlite3_mem/connection.rb new file mode 100644 index 0000000000..14e10900d1 --- /dev/null +++ b/activerecord/test/connections/native_sqlite3_mem/connection.rb @@ -0,0 +1,19 @@ +# This file connects to an in-memory SQLite3 database, which is a very fast way to run the tests. +# The downside is that disconnect from the database results in the database effectively being +# wiped. For this reason, pooled_connections_test.rb is disabled when using an in-memory database. + +print "Using native SQLite3 (in memory)\n" +require_dependency 'models/course' +require 'logger' +ActiveRecord::Base.logger = Logger.new("debug.log") + +class SqliteError < StandardError +end + +def make_connection(clazz) + ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite3', :database => ':memory:' } } + clazz.establish_connection(clazz.name) +end + +make_connection(ActiveRecord::Base) +make_connection(Course) diff --git a/activerecord/test/fixtures/ships.yml b/activerecord/test/fixtures/ships.yml index 137055aad1..df914262b3 100644 --- a/activerecord/test/fixtures/ships.yml +++ b/activerecord/test/fixtures/ships.yml @@ -1,5 +1,6 @@ black_pearl: name: "Black Pearl" + pirate: blackbeard interceptor: id: 2 name: "Interceptor" diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index f2c45053e7..b0490f754e 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -78,3 +78,7 @@ class Pirate < ActiveRecord::Base ship_log << "#{callback}_#{record.class.name.downcase}_#{record.id || '<new>'}" end end + +class DestructivePirate < Pirate + has_one :dependent_ship, :class_name => 'Ship', :foreign_key => :pirate_id, :dependent => :destroy +end diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index 110d540120..6adfe0ae3c 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -10,6 +10,13 @@ class Reply < Topic attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title end +class UniqueReply < Reply + validates_uniqueness_of :content, :scope => 'parent_id' +end + +class SillyUniqueReply < UniqueReply +end + class WrongReply < Reply validate :errors_on_empty_content validate :title_is_wrong_create, :on => :create diff --git a/activerecord/test/models/sponsor.rb b/activerecord/test/models/sponsor.rb index 7e5a1dc38b..aa4a3638fd 100644 --- a/activerecord/test/models/sponsor.rb +++ b/activerecord/test/models/sponsor.rb @@ -2,4 +2,6 @@ class Sponsor < ActiveRecord::Base belongs_to :sponsor_club, :class_name => "Club", :foreign_key => "club_id" belongs_to :sponsorable, :polymorphic => true belongs_to :thing, :polymorphic => true, :foreign_type => :sponsorable_type, :foreign_key => :sponsorable_id + belongs_to :sponsorable_with_conditions, :polymorphic => true, + :foreign_type => 'sponsorable_type', :foreign_key => 'sponsorable_id', :conditions => {:name => 'Ernie'} end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 6496f36f7e..6440dbe8ab 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -45,6 +45,10 @@ class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id" has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title" + + has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" + has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id" + serialize :content before_create :default_written_on diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index f7f03f4d95..3720dbb8b8 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -18,6 +18,10 @@ class BigDecimal end end + def to_d + self + end + DEFAULT_STRING_FORMAT = 'F' def to_formatted_s(format = DEFAULT_STRING_FORMAT) _original_to_s(format) diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 092f936961..06d868a3b0 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,5 +1,5 @@ require 'date' -require 'active_support/inflector' +require 'active_support/inflector/methods' require 'active_support/core_ext/date/zones' class Date diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index 8e267c76c4..029b8c41b4 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -1,4 +1,4 @@ -require 'active_support/inflector' +require 'active_support/inflector/methods' require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/date_time/calculations' require 'active_support/values/time_zone' diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index d4ae3131ec..49ac18d245 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -1,4 +1,4 @@ -require 'active_support/inflector' +require 'active_support/inflector/methods' require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/values/time_zone' diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 970536a594..a65fcafb44 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -1,4 +1,4 @@ -require 'active_support/inflector' +require 'active_support/inflector/methods' module ActiveSupport module Deprecation diff --git a/activesupport/test/core_ext/bigdecimal.rb b/activesupport/test/core_ext/bigdecimal_test.rb index 9faad9146f..d592973d7a 100644 --- a/activesupport/test/core_ext/bigdecimal.rb +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -1,10 +1,17 @@ require 'abstract_unit' +require 'bigdecimal' +require 'active_support/core_ext/big_decimal' class BigDecimalTest < Test::Unit::TestCase def test_to_yaml assert_equal("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) - assert_equal("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) - assert_equal("--- .NaN\n", BigDecimal.new('NaN').to_yaml) + assert_equal("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) + assert_equal("--- .NaN\n", BigDecimal.new('NaN').to_yaml) assert_equal("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) end -end
\ No newline at end of file + + def test_to_d + bd = BigDecimal.new '10' + assert_equal bd, bd.to_d + end +end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 74223dd7f2..a0479d45ac 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -4,6 +4,7 @@ require 'bigdecimal' require 'active_support/core_ext/string/access' require 'active_support/ordered_hash' require 'active_support/core_ext/object/conversions' +require 'active_support/inflections' class HashExtTest < Test::Unit::TestCase class IndifferentHash < HashWithIndifferentAccess diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index e0494de6e4..7469ae70fd 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -1,5 +1,6 @@ # encoding: utf-8 require 'abstract_unit' +require 'active_support/core_ext/string/inflections' require 'active_support/json' class TestJSONEncoding < Test::Unit::TestCase diff --git a/ci/ci_build.rb b/ci/ci_build.rb index fd884b3b2e..964e2d4eb8 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -35,7 +35,7 @@ cd "#{root_dir}/activesupport" do puts "[CruiseControl] Building Active Support" puts build_results[:activesupport] = rake 'test' - # build_results[:activesupport_isolated] = rake 'test:isolated' + build_results[:activesupport_isolated] = rake 'test:isolated' end system "sudo rm -R #{root_dir}/railties/tmp" @@ -51,7 +51,7 @@ cd "#{root_dir}/actionpack" do puts "[CruiseControl] Building Action Pack" puts build_results[:actionpack] = rake 'test' - # build_results[:actionpack_isolated] = rake 'test:isolated' + build_results[:actionpack_isolated] = rake 'test:isolated' end cd "#{root_dir}/actionmailer" do @@ -59,7 +59,7 @@ cd "#{root_dir}/actionmailer" do puts "[CruiseControl] Building Action Mailer" puts build_results[:actionmailer] = rake 'test' - # build_results[:actionmailer_isolated] = rake 'test:isolated' + build_results[:actionmailer_isolated] = rake 'test:isolated' end cd "#{root_dir}/activemodel" do @@ -67,7 +67,7 @@ cd "#{root_dir}/activemodel" do puts "[CruiseControl] Building Active Model" puts build_results[:activemodel] = rake 'test' - # build_results[:activemodel_isolated] = rake 'test:isolated' + build_results[:activemodel_isolated] = rake 'test:isolated' end rm_f "#{root_dir}/activeresource/debug.log" @@ -76,7 +76,7 @@ cd "#{root_dir}/activeresource" do puts "[CruiseControl] Building Active Resource" puts build_results[:activeresource] = rake 'test' - # build_results[:activeresource_isolated] = rake 'test:isolated' + build_results[:activeresource_isolated] = rake 'test:isolated' end rm_f "#{root_dir}/activerecord/debug.log" @@ -85,7 +85,7 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with MySQL" puts build_results[:activerecord_mysql] = rake 'mysql:rebuild_databases', 'mysql:test' - # build_results[:activerecord_mysql_isolated] = rake 'mysql:rebuild_databases', 'mysql:isolated_test' + build_results[:activerecord_mysql_isolated] = rake 'mysql:rebuild_databases', 'mysql:isolated_test' end cd "#{root_dir}/activerecord" do @@ -93,7 +93,7 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with MySQL2" puts build_results[:activerecord_mysql2] = rake 'mysql:rebuild_databases', 'mysql2:test' - # build_results[:activerecord_mysql2_isolated] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test' + build_results[:activerecord_mysql2_isolated] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test' end cd "#{root_dir}/activerecord" do @@ -101,7 +101,7 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with PostgreSQL" puts build_results[:activerecord_postgresql8] = rake 'postgresql:rebuild_databases', 'postgresql:test' - # build_results[:activerecord_postgresql8_isolated] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test' + build_results[:activerecord_postgresql8_isolated] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test' end cd "#{root_dir}/activerecord" do @@ -109,7 +109,7 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with SQLite 3" puts build_results[:activerecord_sqlite3] = rake 'sqlite3:test' - # build_results[:activerecord_sqlite3_isolated] = rake 'sqlite3:isolated_test' + build_results[:activerecord_sqlite3_isolated] = rake 'sqlite3:isolated_test' end diff --git a/railties/Rakefile b/railties/Rakefile index 1b469543cc..26fa0bf6a5 100755 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -18,7 +18,7 @@ namespace :test do Dir["test/#{dir}/*_test.rb"].each do |file| next true if file.include?("fixtures") ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) - system(ruby, '-Itest', "-I#{File.dirname(__FILE__)}/../activesupport/lib", file) + sh(ruby, '-Itest', "-I#{File.dirname(__FILE__)}/../activesupport/lib", file) end end end diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index a162bdb66f..3eb79d57c8 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -294,7 +294,7 @@ YAML boot_rails - expected = %W( + expected_locales = %W( #{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml #{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml #{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml @@ -304,11 +304,11 @@ YAML #{app_path}/app/locales/en.yml ).map { |path| File.expand_path(path) } - actual = I18n.load_path.map { |path| File.expand_path(path) }.find_all do |p| - p =~ /^#{RAILS_FRAMEWORK_ROOT}/ || p =~ /^#{@plugin.path}/ || p =~ /^#{app_path}/ - end + actual_locales = I18n.load_path.map { |path| + File.expand_path(path) + } & expected_locales # remove locales external to Rails - assert_equal expected, actual + assert_equal expected_locales, actual_locales assert_equal "2", I18n.t(:foo) assert_equal "1", I18n.t(:bar) |