diff options
author | Xavier Noria <fxn@hashref.com> | 2010-04-28 13:41:21 -0700 |
---|---|---|
committer | Xavier Noria <fxn@hashref.com> | 2010-04-28 13:41:21 -0700 |
commit | b9ab4c780af82c1c60d63c50f040a55da5bfa8db (patch) | |
tree | 4df8981047957fce17c10ea5c6aac7955aef2a4d /activerecord | |
parent | 8f1a5bfee1305f193bb659c010ca7e2272c12051 (diff) | |
parent | 22184930ea323872c73542767d447bbbd7878c96 (diff) | |
download | rails-b9ab4c780af82c1c60d63c50f040a55da5bfa8db.tar.gz rails-b9ab4c780af82c1c60d63c50f040a55da5bfa8db.tar.bz2 rails-b9ab4c780af82c1c60d63c50f040a55da5bfa8db.zip |
Merge commit 'rails/master'
Diffstat (limited to 'activerecord')
22 files changed, 186 insertions, 187 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index c0c4df5035..fcb0e31f79 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,9 @@ *Rails 3.0.0 [beta 4/release candidate] (unreleased)* +* Destroy uses optimistic locking. If lock_version on the record you're destroying doesn't match lock_version in the database, a StaleObjectError is raised. #1966 [Curtis Hawthorne] + +* PostgreSQL: drop support for old postgres driver. Use pg 0.9.0 or later. [Jeremy Kemper] + * Observers can prevent records from saving by returning false, just like before_save and friends. #4087 [Mislav Marohnić] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d94cc03938..6dbee9f4bf 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1500,7 +1500,16 @@ module ActiveRecord when :destroy method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym define_method(method_name) do - send(reflection.name).each { |o| o.destroy } + send(reflection.name).each do |o| + # No point in executing the counter update since we're going to destroy the parent anyway + counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym + if(o.respond_to? counter_method) then + class << o + self + end.send(:define_method, counter_method, Proc.new {}) + end + o.destroy + end end before_destroy method_name when :delete_all diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index fd24dcddc3..2d7cfad80d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1844,8 +1844,7 @@ module ActiveRecord #:nodoc: # user.is_admin? # => true def attributes=(new_attributes, guard_protected_attributes = true) return if new_attributes.nil? - attributes = new_attributes.dup - attributes.stringify_keys! + attributes = new_attributes.stringify_keys multi_parameter_attributes = [] attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 8649f96498..d7b5bf8e31 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -13,12 +13,12 @@ module ActiveRecord when String, ActiveSupport::Multibyte::Chars value = value.to_s if column && column.type == :binary && column.class.respond_to?(:string_to_binary) - "#{quoted_string_prefix}'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode) + "'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode) elsif column && [:integer, :float].include?(column.type) value = column.type == :integer ? value.to_i : value.to_f value.to_s else - "#{quoted_string_prefix}'#{quote_string(value)}'" # ' (for ruby-mode) + "'#{quote_string(value)}'" # ' (for ruby-mode) end when NilClass then "NULL" when TrueClass then (column && column.type == :integer ? '1' : quoted_true) @@ -30,7 +30,7 @@ module ActiveRecord if value.acts_like?(:date) || value.acts_like?(:time) "'#{quoted_date(value)}'" else - "#{quoted_string_prefix}'#{quote_string(value.to_yaml)}'" + "'#{quote_string(value.to_yaml)}'" end end end @@ -67,10 +67,6 @@ module ActiveRecord value end.to_s(:db) end - - def quoted_string_prefix - '' - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index ceb1adc9e0..74fed4ad62 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -2,26 +2,12 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_support/core_ext/kernel/requires' require 'active_support/core_ext/object/blank' -begin - require_library_or_gem 'pg' -rescue LoadError => e - begin - require_library_or_gem 'postgres' - class PGresult - alias_method :nfields, :num_fields unless self.method_defined?(:nfields) - alias_method :ntuples, :num_tuples unless self.method_defined?(:ntuples) - alias_method :ftype, :type unless self.method_defined?(:ftype) - alias_method :cmd_tuples, :cmdtuples unless self.method_defined?(:cmd_tuples) - end - rescue LoadError - raise e - end -end - module ActiveRecord class Base # Establishes a connection to the database that's used by all Active Record objects def self.postgresql_connection(config) # :nodoc: + require 'pg' + config = config.symbolize_keys host = config[:host] port = config[:port] || 5432 @@ -277,20 +263,12 @@ module ActiveRecord true end - # Does PostgreSQL support standard conforming strings? - def supports_standard_conforming_strings? - # Temporarily set the client message level above error to prevent unintentional - # error messages in the logs when working on a PostgreSQL database server that - # does not support standard conforming strings. - client_min_messages_old = client_min_messages - self.client_min_messages = 'panic' - - # postgres-pr does not raise an exception when client_min_messages is set higher - # than error and "SHOW standard_conforming_strings" fails, but returns an empty - # PGresult instead. - has_support = query('SHOW standard_conforming_strings')[0][0] rescue false - self.client_min_messages = client_min_messages_old - has_support + # Enable standard-conforming strings if available. + def set_standard_conforming_strings + old, self.client_min_messages = client_min_messages, 'panic' + execute('SET standard_conforming_strings = on') rescue nil + ensure + self.client_min_messages = old end def supports_insert_with_returning? @@ -314,85 +292,23 @@ module ActiveRecord # QUOTING ================================================== # Escapes binary strings for bytea input to the database. - def escape_bytea(original_value) - if @connection.respond_to?(:escape_bytea) - self.class.instance_eval do - define_method(:escape_bytea) do |value| - @connection.escape_bytea(value) if value - end - end - elsif PGconn.respond_to?(:escape_bytea) - self.class.instance_eval do - define_method(:escape_bytea) do |value| - PGconn.escape_bytea(value) if value - end - end - else - self.class.instance_eval do - define_method(:escape_bytea) do |value| - if value - result = '' - value.each_byte { |c| result << sprintf('\\\\%03o', c) } - result - end - end - end - end - escape_bytea(original_value) + def escape_bytea(value) + @connection.escape_bytea(value) if value end # Unescapes bytea output from a database to the binary string it represents. # NOTE: This is NOT an inverse of escape_bytea! This is only to be used # on escaped binary output from database drive. - def unescape_bytea(original_value) - # In each case, check if the value actually is escaped PostgreSQL bytea output - # or an unescaped Active Record attribute that was just written. - if PGconn.respond_to?(:unescape_bytea) - self.class.instance_eval do - define_method(:unescape_bytea) do |value| - if value =~ /\\\d{3}/ - PGconn.unescape_bytea(value) - else - value - end - end - end - else - self.class.instance_eval do - define_method(:unescape_bytea) do |value| - if value =~ /\\\d{3}/ - result = '' - i, max = 0, value.size - while i < max - char = value[i] - if char == ?\\ - if value[i+1] == ?\\ - char = ?\\ - i += 1 - else - char = value[i+1..i+3].oct - i += 3 - end - end - result << char - i += 1 - end - result - else - value - end - end - end - end - unescape_bytea(original_value) + def unescape_bytea(value) + @connection.unescape_bytea(value) if value end # Quotes PostgreSQL-specific data types for SQL input. def quote(value, column = nil) #:nodoc: if value.kind_of?(String) && column && column.type == :binary - "#{quoted_string_prefix}'#{escape_bytea(value)}'" + "'#{escape_bytea(value)}'" elsif value.kind_of?(String) && column && column.sql_type == 'xml' - "xml E'#{quote_string(value)}'" + "xml '#{quote_string(value)}'" elsif value.kind_of?(Numeric) && column && column.sql_type == 'money' # Not truly string input, so doesn't require (or allow) escape string syntax. "'#{value.to_s}'" @@ -408,28 +324,9 @@ module ActiveRecord end end - # Quotes strings for use in SQL input in the postgres driver for better performance. - def quote_string(original_value) #:nodoc: - if @connection.respond_to?(:escape) - self.class.instance_eval do - define_method(:quote_string) do |s| - @connection.escape(s) - end - end - elsif PGconn.respond_to?(:escape) - self.class.instance_eval do - define_method(:quote_string) do |s| - PGconn.escape(s) - end - end - else - # There are some incorrectly compiled postgres drivers out there - # that don't define PGconn.escape. - self.class.instance_eval do - remove_method(:quote_string) - end - end - quote_string(original_value) + # Quotes strings for use in SQL input. + def quote_string(s) #:nodoc: + @connection.escape(s) end # Checks the following cases: @@ -1005,22 +902,11 @@ module ActiveRecord # Ignore async_exec and async_query when using postgres-pr. @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec) - # Use escape string syntax if available. We cannot do this lazily when encountering - # the first string, because that could then break any transactions in progress. - # See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html - # If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't - # support escape string syntax. Don't override the inherited quoted_string_prefix. - if supports_standard_conforming_strings? - self.class.instance_eval do - define_method(:quoted_string_prefix) { 'E' } - end - end - # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision # should know about this but can't detect it there, so deal with it here. - PostgreSQLColumn.money_precision = - (postgresql_version >= 80300) ? 19 : 10 + PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10 + configure_connection end @@ -1036,7 +922,10 @@ module ActiveRecord end self.client_min_messages = @config[:min_messages] if @config[:min_messages] self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] - + + # Use standard-conforming strings if available so we don't have to do the E'...' dance. + set_standard_conforming_strings + # If using ActiveRecord's time zone support configure the connection to return # TIMESTAMP WITH ZONE types in UTC. execute("SET time zone 'UTC'") if ActiveRecord::Base.default_timezone == :utc diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 22f0e60083..0bc49c1daa 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -3,8 +3,9 @@ require 'yaml' require 'csv' require 'zlib' require 'active_support/dependencies' -require 'active_support/core_ext/logger' +require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/logger' if RUBY_VERSION < '1.9' module YAML #:nodoc: @@ -492,6 +493,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) 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?('/') } connection = block_given? ? yield : ActiveRecord::Base.connection table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) } @@ -502,7 +504,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) fixtures_map = {} fixtures = table_names_to_fetch.map do |table_name| - fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s)) + fixtures_map[table_name] = Fixtures.new(connection, table_name.tr('/', '_'), class_names[table_name.tr('/', '_').to_sym], File.join(fixtures_directory, table_name)) end all_loaded_fixtures.update(fixtures_map) @@ -836,8 +838,8 @@ module ActiveRecord def fixtures(*table_names) if table_names.first == :all - table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"] - table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') } + table_names = Dir["#{fixture_path}/**/*.{yml,csv}"] + table_names.map! { |f| f[(fixture_path.size + 1)..-5] } else table_names = table_names.flatten.map { |n| n.to_s } end @@ -868,9 +870,9 @@ module ActiveRecord end def setup_fixture_accessors(table_names = nil) - table_names = [table_names] if table_names && !table_names.respond_to?(:each) - (table_names || fixture_table_names).each do |table_name| - table_name = table_name.to_s.tr('.', '_') + table_names = Array.wrap(table_names || fixture_table_names) + table_names.each do |table_name| + table_name = table_name.to_s.tr('./', '_') define_method(table_name) do |*fixtures| force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 9044ca418b..60ad23f38c 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -23,6 +23,16 @@ module ActiveRecord # p2.first_name = "should fail" # p2.save # Raises a ActiveRecord::StaleObjectError # + # Optimistic locking will also check for stale data when objects are destroyed. Example: + # + # p1 = Person.find(1) + # p2 = Person.find(1) + # + # p1.first_name = "Michael" + # p1.save + # + # p2.destroy # Raises a ActiveRecord::StaleObjectError + # # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, # or otherwise apply the business logic needed to resolve the conflict. # @@ -39,6 +49,7 @@ module ActiveRecord self.lock_optimistically = true alias_method_chain :update, :lock + alias_method_chain :destroy, :lock alias_method_chain :attributes_from_column_definition, :lock class << self @@ -88,7 +99,7 @@ module ActiveRecord unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to update a stale object" + raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}" end affected_rows @@ -100,6 +111,28 @@ module ActiveRecord end end + def destroy_with_lock #:nodoc: + return destroy_without_lock unless locking_enabled? + + unless new_record? + lock_col = self.class.locking_column + previous_value = send(lock_col).to_i + + affected_rows = connection.delete( + "DELETE FROM #{self.class.quoted_table_name} " + + "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " + + "AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}", + "#{self.class.name} Destroy" + ) + + unless affected_rows == 1 + raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}" + end + end + + freeze + end + module ClassMethods DEFAULT_LOCKING_COLUMN = 'lock_version' diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 70a460d41d..6718b4a69d 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/hash/indifferent_access' module ActiveRecord module NestedAttributes #:nodoc: diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 878a4dac09..f3d21d4969 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -70,27 +70,23 @@ module ActiveRecord end end - initializer "active_record.add_observer_hook", :after=>"active_record.set_configs" do |app| + initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app| ActiveSupport.on_load(:active_record) do - ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do - ActiveRecord::Base.instantiate_observers + unless app.config.cache_classes + ActionDispatch::Callbacks.after do + ActiveRecord::Base.reset_subclasses + ActiveRecord::Base.clear_reloadable_connections! + end end end end - initializer "active_record.instantiate_observers", :after=>"active_record.initialize_database" do + config.after_initialize do ActiveSupport.on_load(:active_record) do instantiate_observers - end - end - initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app| - ActiveSupport.on_load(:active_record) do - unless app.config.cache_classes - ActionDispatch::Callbacks.after do - ActiveRecord::Base.reset_subclasses - ActiveRecord::Base.clear_reloadable_connections! - end + ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do + ActiveRecord::Base.instantiate_observers end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 2b53afc68b..cb7eade0ab 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -258,8 +258,8 @@ namespace :db do base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir - (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file| - Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*')) + (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir["#{fixtures_dir}/**/*.{yml,csv}"]).each do |fixture_file| + Fixtures.create_fixtures(fixtures_dir, fixture_file[(fixtures_dir.size + 1)..-5]) end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index a26f1c0ac8..3514d0a259 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/hash/indifferent_access' module ActiveRecord module FinderMethods diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 09332418d5..58af930446 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -187,15 +187,13 @@ module ActiveRecord def build_where(*args) return if args.blank? - builder = PredicateBuilder.new(table.engine) - opts = args.first case opts when String, Array @klass.send(:sanitize_sql, args.size > 1 ? args : opts) when Hash attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts) - builder.build_from_hash(attributes, table) + PredicateBuilder.new(table.engine).build_from_hash(attributes, table) else opts end diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 2e85959b1e..255b03433d 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -182,17 +182,6 @@ module ActiveRecord #:nodoc: options[:except] |= Array.wrap(@serializable.class.inheritance_column) end - def serializable_attributes - serializable_attribute_names.collect { |name| Attribute.new(name, @serializable) } - end - - def serializable_method_attributes - Array.wrap(options[:methods]).inject([]) do |method_attributes, name| - method_attributes << MethodAttribute.new(name.to_s, @serializable) if @serializable.respond_to?(name.to_s) - method_attributes - end - end - def add_associations(association, records, opts) if records.is_a?(Enumerable) tag = reformat_name(association.to_s) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 9e88ec8016..77b2b748b1 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -566,8 +566,8 @@ class FinderTest < ActiveRecord::TestCase end def test_string_sanitation - assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") - assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table") + assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") + assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table") end def test_count diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index d24283fe4e..3ce23209cc 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -16,6 +16,9 @@ require 'models/treasure' require 'models/matey' require 'models/ship' require 'models/book' +require 'models/admin' +require 'models/admin/account' +require 'models/admin/user' class FixturesTest < ActiveRecord::TestCase self.use_instantiated_fixtures = true @@ -507,7 +510,7 @@ class FasterFixturesTest < ActiveRecord::TestCase end class FoxyFixturesTest < ActiveRecord::TestCase - fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers + 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")) @@ -629,6 +632,11 @@ class FoxyFixturesTest < ActiveRecord::TestCase assert_kind_of DeadParrot, parrots(:polly) assert_equal pirates(:blackbeard), parrots(:polly).killer end + + def test_namespaced_models + assert admin_accounts(:signals37).users.include?(admin_users(:david)) + assert_equal 2, admin_accounts(:signals37).users.size + end end class ActiveSupportSubclassWithFixturesTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index dfaecf35cf..aa2d9527f9 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -38,6 +38,25 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end + # See Lighthouse ticket #1966 + def test_lock_destroy + p1 = Person.find(1) + p2 = Person.find(1) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.first_name = 'stu' + p1.save! + assert_equal 1, p1.lock_version + assert_equal 0, p2.lock_version + + assert_raises(ActiveRecord::StaleObjectError) { p2.destroy } + + assert p1.destroy + assert_equal true, p1.frozen? + assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) } + end + def test_lock_repeating p1 = Person.find(1) p2 = Person.find(1) @@ -150,6 +169,32 @@ class OptimisticLockingTest < ActiveRecord::TestCase end end end + + # See Lighthouse ticket #1966 + def test_destroy_dependents + # Establish dependent relationship between People and LegacyThing + add_counter_column_to(Person, 'legacy_things_count') + LegacyThing.connection.add_column LegacyThing.table_name, 'person_id', :integer + LegacyThing.reset_column_information + LegacyThing.class_eval do + belongs_to :person, :counter_cache => true + end + Person.class_eval do + has_many :legacy_things, :dependent => :destroy + end + + # Make sure that counter incrementing doesn't cause problems + p1 = Person.new(:first_name => 'fjord') + p1.save! + t = LegacyThing.new(:person => p1) + t.save! + p1.reload + assert_equal 1, p1.legacy_things_count + assert p1.destroy + assert_equal true, p1.frozen? + assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) } + assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) } + end def test_quote_table_name ref = references(:michael_magician) @@ -168,11 +213,11 @@ class OptimisticLockingTest < ActiveRecord::TestCase private - def add_counter_column_to(model) - model.connection.add_column model.table_name, :test_count, :integer, :null => false, :default => 0 + def add_counter_column_to(model, col='test_count') + model.connection.add_column model.table_name, col, :integer, :null => false, :default => 0 model.reset_column_information # OpenBase does not set a value to existing rows when adding a not null default column - model.update_all(:test_count => 0) if current_adapter?(:OpenBaseAdapter) + model.update_all(col => 0) if current_adapter?(:OpenBaseAdapter) end def remove_counter_column_from(model) diff --git a/activerecord/test/fixtures/admin/accounts.yml b/activerecord/test/fixtures/admin/accounts.yml new file mode 100644 index 0000000000..9e341a15af --- /dev/null +++ b/activerecord/test/fixtures/admin/accounts.yml @@ -0,0 +1,2 @@ +signals37: + name: 37signals diff --git a/activerecord/test/fixtures/admin/users.yml b/activerecord/test/fixtures/admin/users.yml new file mode 100644 index 0000000000..6f11f2509e --- /dev/null +++ b/activerecord/test/fixtures/admin/users.yml @@ -0,0 +1,7 @@ +david: + name: David + account: signals37 + +jamis: + name: Jamis + account: signals37 diff --git a/activerecord/test/models/admin.rb b/activerecord/test/models/admin.rb new file mode 100644 index 0000000000..00e69fbed8 --- /dev/null +++ b/activerecord/test/models/admin.rb @@ -0,0 +1,5 @@ +module Admin + def self.table_name_prefix + 'admin_' + end +end
\ No newline at end of file diff --git a/activerecord/test/models/admin/account.rb b/activerecord/test/models/admin/account.rb new file mode 100644 index 0000000000..46de28aae1 --- /dev/null +++ b/activerecord/test/models/admin/account.rb @@ -0,0 +1,3 @@ +class Admin::Account < ActiveRecord::Base + has_many :users +end
\ No newline at end of file diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb new file mode 100644 index 0000000000..74bb21551e --- /dev/null +++ b/activerecord/test/models/admin/user.rb @@ -0,0 +1,3 @@ +class Admin::User < ActiveRecord::Base + belongs_to :account +end
\ No newline at end of file diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 7a0cf550e0..f5fba2f87d 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -26,6 +26,15 @@ ActiveRecord::Schema.define do t.integer :credit_limit end + create_table :admin_accounts, :force => true do |t| + t.string :name + end + + create_table :admin_users, :force => true do |t| + t.string :name + t.references :account + end + create_table :audit_logs, :force => true do |t| t.column :message, :string, :null=>false t.column :developer_id, :integer, :null=>false |