diff options
Diffstat (limited to 'activerecord/lib')
33 files changed, 294 insertions, 308 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5aa9e26fa6..b74ff62c09 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -135,11 +135,13 @@ module ActiveRecord autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many' end - autoload :Preloader, 'active_record/associations/preloader' - autoload :JoinDependency, 'active_record/associations/join_dependency' - autoload :AssociationScope, 'active_record/associations/association_scope' - autoload :AliasTracker, 'active_record/associations/alias_tracker' - autoload :JoinHelper, 'active_record/associations/join_helper' + eager_autoload do + autoload :Preloader, 'active_record/associations/preloader' + autoload :JoinDependency, 'active_record/associations/join_dependency' + autoload :AssociationScope, 'active_record/associations/association_scope' + autoload :AliasTracker, 'active_record/associations/alias_tracker' + autoload :JoinHelper, 'active_record/associations/join_helper' + end # Clears out the association cache. def clear_association_cache #:nodoc: diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 59c1bad559..ab0d888b16 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -231,7 +231,8 @@ module ActiveRecord def build_record(attributes, options) reflection.build_association(attributes, options) do |record| - attributes = create_scope.except(*(record.changed - [reflection.foreign_key])) + skip_assign = [reflection.foreign_key, reflection.type].compact + attributes = create_scope.except(*(record.changed - skip_assign)) record.assign_attributes(attributes, :without_protection => true) end end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index f9cffa40c8..226639ad37 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -33,6 +33,18 @@ module ActiveRecord private + def column_for(table_name, column_name) + columns = alias_tracker.connection.schema_cache.columns_hash[table_name] + columns[column_name] + end + + def bind(scope, column, value) + substitute = alias_tracker.connection.substitute_at( + column, scope.bind_values.length) + scope.bind_values += [[column, value]] + substitute + end + def add_constraints(scope) tables = construct_tables @@ -67,7 +79,9 @@ module ActiveRecord conditions = self.conditions[i] if reflection == chain.last - scope = scope.where(table[key].eq(owner[foreign_key])) + column = column_for(table.table_name, key.to_s) + bind_val = bind(scope, column, owner[foreign_key]) + scope = scope.where(table[key].eq(bind_val)) if reflection.type scope = scope.where(table[reflection.type].eq(owner.class.base_class.name)) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 3604d98015..65e882867e 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -190,6 +190,8 @@ module ActiveRecord # association, it will be used for the query. Otherwise, construct options and pass them with # scope to the target class's +count+. def count(column_name = nil, count_options = {}) + return 0 if owner.new_record? + column_name, count_options = nil, column_name if column_name.is_a?(Hash) if options[:counter_sql] || options[:finder_sql] @@ -407,7 +409,7 @@ module ActiveRecord if mem_index mem_record = memory.delete_at(mem_index) - (record.attribute_names - mem_record.changes.keys).each do |name| + ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name| mem_record[name] = record[name] end @@ -569,7 +571,9 @@ module ActiveRecord args.shift if args.first.is_a?(Hash) && args.first.empty? collection = fetch_first_or_last_using_find?(args) ? scoped : load_target - collection.send(type, *args).tap {|it| set_inverse_instance it } + collection.send(type, *args).tap do |record| + set_inverse_instance record if record.is_a? ActiveRecord::Base + end end 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 501ebe7c5b..56f9013d61 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -11,7 +11,7 @@ module ActiveRecord # If target and record are nil, or target is equal to record, # we don't need to have transaction. if (target || record) && target != record - reflection.klass.transaction do + transaction_if(save) do remove_target!(options[:dependent]) if target && !target.destroyed? if record @@ -70,6 +70,14 @@ module ActiveRecord def nullify_owner_attributes(record) record[reflection.foreign_key] = nil end + + def transaction_if(value) + if value + reflection.klass.transaction { yield } + else + yield + end + end end end end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index fafed94ff2..12cfdd4622 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -30,17 +30,21 @@ module ActiveRecord # option references an association's column), it will fallback to the table # join strategy. class Preloader #:nodoc: - autoload :Association, 'active_record/associations/preloader/association' - autoload :SingularAssociation, 'active_record/associations/preloader/singular_association' - autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association' - autoload :ThroughAssociation, 'active_record/associations/preloader/through_association' + extend ActiveSupport::Autoload - autoload :HasMany, 'active_record/associations/preloader/has_many' - autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through' - autoload :HasOne, 'active_record/associations/preloader/has_one' - autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through' - autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many' - autoload :BelongsTo, 'active_record/associations/preloader/belongs_to' + eager_autoload do + autoload :Association, 'active_record/associations/preloader/association' + autoload :SingularAssociation, 'active_record/associations/preloader/singular_association' + autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association' + autoload :ThroughAssociation, 'active_record/associations/preloader/through_association' + + autoload :HasMany, 'active_record/associations/preloader/has_many' + autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through' + autoload :HasOne, 'active_record/associations/preloader/has_one' + autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through' + autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many' + autoload :BelongsTo, 'active_record/associations/preloader/belongs_to' + end attr_reader :records, :associations, :options, :model diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index ed863a9696..9cd8954496 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -55,12 +55,12 @@ module ActiveRecord # The attribute already has an unsaved change. if attribute_changed?(attr) old = @changed_attributes[attr] - @changed_attributes.delete(attr) unless field_changed?(attr, old, value) + @changed_attributes.delete(attr) unless _field_changed?(attr, old, value) else old = clone_attribute_value(:read_attribute, attr) # Save Time objects as TimeWithZone if time_zone_aware_attributes == true old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old) - @changed_attributes[attr] = old if field_changed?(attr, old, value) + @changed_attributes[attr] = old if _field_changed?(attr, old, value) end # Carry on. @@ -77,7 +77,7 @@ module ActiveRecord end end - def field_changed?(attr, old, value) + def _field_changed?(attr, old, value) if column = column_for_attribute(attr) if column.number? && (changes_from_nil_to_empty_string?(column, old, value) || changes_from_zero_to_string?(old, value)) diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 00023b0b6c..cf4c35cf84 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -97,6 +97,16 @@ module ActiveRecord super end end + + def attributes_before_type_cast + super.dup.tap do |attributes| + self.class.serialized_attributes.each_key do |key| + if attributes.key?(key) + attributes[key] = attributes[key].unserialized_value + end + end + end + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 4c3d5eed4c..39d81cf6ef 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -37,6 +37,7 @@ module ActiveRecord if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) method_body, line = <<-EOV, __LINE__ + 1 def #{attr_name}=(original_time) + original_time = nil if original_time.blank? time = original_time unless time.acts_like?(:time) time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b51bb5cc5e..62c8110274 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -450,12 +450,12 @@ module ActiveRecord #:nodoc: private def relation #:nodoc: - @relation ||= Relation.new(self, arel_table) + relation = Relation.new(self, arel_table) if finder_needs_type_condition? - @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) + relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) else - @relation + relation end end end @@ -489,7 +489,6 @@ module ActiveRecord #:nodoc: @marked_for_destruction = false @previously_changed = {} @changed_attributes = {} - @relation = nil ensure_proper_type @@ -544,7 +543,7 @@ module ActiveRecord #:nodoc: @changed_attributes = {} self.class.column_defaults.each do |attr, orig_value| - @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr]) + @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr]) end @aggregation_cache = {} diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 235efc12f6..6cb97bc550 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -54,144 +54,16 @@ module ActiveRecord # your database connection configuration: # # * +pool+: number indicating size of connection pool (default 5) - # * +wait_timeout+: number of seconds to block and wait for a connection - # before giving up and raising a timeout error (default 5 seconds). + # * +checkout _timeout+: number of seconds to block and wait for a + # connection before giving up and raising a timeout error + # (default 5 seconds). ('wait_timeout' supported for backwards + # compatibility, but conflicts with key used for different purpose + # by mysql2 adapter). class ConnectionPool - - # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool - # with which it shares a Monitor. But could be a generic Queue. - # - # The Queue in stdlib's 'thread' could replace this class except - # stdlib's doesn't support waiting with a timeout. - class Queue - def initialize(lock = Monitor.new) - @lock = lock - @cond = @lock.new_cond - @num_waiting = 0 - @queue = [] - end - - # Test if any threads are currently waiting on the queue. - def any_waiting? - synchronize do - @num_waiting > 0 - end - end - - # Return the number of threads currently waiting on this - # queue. - def num_waiting - synchronize do - @num_waiting - end - end - - # Add +element+ to the queue. Never blocks. - def add(element) - synchronize do - @queue.push element - @cond.signal - end - end - - # If +element+ is in the queue, remove and return it, or nil. - def delete(element) - synchronize do - @queue.delete(element) - end - end - - # Remove all elements from the queue. - def clear - synchronize do - @queue.clear - end - end - - # Remove the head of the queue. - # - # If +timeout+ is not given, remove and return the head the - # queue if the number of available elements is strictly - # greater than the number of threads currently waiting (that - # is, don't jump ahead in line). Otherwise, return nil. - # - # If +timeout+ is given, block if it there is no element - # available, waiting up to +timeout+ seconds for an element to - # become available. - # - # Raises: - # - ConnectionTimeoutError if +timeout+ is given and no element - # becomes available after +timeout+ seconds, - def poll(timeout = nil) - synchronize do - if timeout - no_wait_poll || wait_poll(timeout) - else - no_wait_poll - end - end - end - - private - - def synchronize(&block) - @lock.synchronize(&block) - end - - # Test if the queue currently contains any elements. - def any? - !@queue.empty? - end - - # A thread can remove an element from the queue without - # waiting if an only if the number of currently available - # connections is strictly greater than the number of waiting - # threads. - def can_remove_no_wait? - @queue.size > @num_waiting - end - - # Removes and returns the head of the queue if possible, or nil. - def remove - @queue.shift - end - - # Remove and return the head the queue if the number of - # available elements is strictly greater than the number of - # threads currently waiting. Otherwise, return nil. - def no_wait_poll - remove if can_remove_no_wait? - end - - # Waits on the queue up to +timeout+ seconds, then removes and - # returns the head of the queue. - def wait_poll(timeout) - @num_waiting += 1 - - t0 = Time.now - elapsed = 0 - loop do - @cond.wait(timeout - elapsed) - - return remove if any? - - elapsed = Time.now - t0 - - if elapsed >= timeout - msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' % - [timeout, elapsed] - raise ConnectionTimeoutError, msg - end - end - ensure - @num_waiting -= 1 - end - end - include MonitorMixin attr_accessor :automatic_reconnect - attr_reader :spec, :connections, :size + attr_reader :spec, :connections # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification # object which describes database connection information (e.g. adapter, @@ -207,23 +79,17 @@ module ActiveRecord # The cache of reserved connections mapped to threads @reserved_connections = {} - @timeout = spec.config[:wait_timeout] || 5 + @queue = new_cond + # 'wait_timeout', the backward-compatible key, conflicts with spec key + # used by mysql2 for something entirely different, checkout_timeout + # preferred to avoid conflict and allow independent values. + @timeout = spec.config[:checkout_timeout] || spec.config[:wait_timeout] || 5 # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 @connections = [] @automatic_reconnect = true - - @available = Queue.new self - end - - # Hack for tests to be able to add connections. Do not call outside of tests - def insert_connection_for_test!(c) #:nodoc: - synchronize do - @connections << c - @available.add c - end end # Retrieve the connection associated with the current thread, or call @@ -250,7 +116,7 @@ module ActiveRecord # #release_connection releases the connection-thread association # and returns the connection to the pool. def release_connection(with_id = current_connection_id) - conn = @reserved_connections.delete(with_id) + conn = synchronize { @reserved_connections.delete(with_id) } checkin conn if conn end @@ -279,7 +145,6 @@ module ActiveRecord conn.disconnect! end @connections = [] - @available.clear end end @@ -291,15 +156,9 @@ module ActiveRecord checkin conn conn.disconnect! if conn.requires_reloading? end - @connections.delete_if do |conn| conn.requires_reloading? end - @available.clear - @connections.each do |conn| - @available.add conn - end - end end @@ -363,22 +222,58 @@ connection. For example: ActiveRecord::Base.connection.close # Check-out a database connection from the pool, indicating that you want # to use it. You should call #checkin when you no longer need this. # - # This is done by either returning and leasing existing connection, or by - # creating a new connection and leasing it. - # - # If all connections are leased and the pool is at capacity (meaning the - # number of currently leased connections is greater than or equal to the - # size limit set), an ActiveRecord::PoolFullError exception will be raised. + # This is done by either returning an existing connection, or by creating + # a new connection. If the maximum number of connections for this pool has + # already been reached, but the pool is empty (i.e. they're all being used), + # then this method will wait until a thread has checked in a connection. + # The wait time is bounded however: if no connection can be checked out + # within the timeout specified for this pool, then a ConnectionTimeoutError + # exception will be raised. # # Returns: an AbstractAdapter object. # # Raises: - # - PoolFullError: no connection can be obtained from the pool. + # - ConnectionTimeoutError: no connection can be obtained from the pool + # within the timeout period. def checkout synchronize do - conn = acquire_connection - conn.lease - checkout_and_verify(conn) + waited_time = 0 + + loop do + conn = @connections.find { |c| c.lease } + + unless conn + if @connections.size < @size + conn = checkout_new_connection + conn.lease + end + end + + if conn + checkout_and_verify conn + return conn + end + + if waited_time >= @timeout + raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout} (waited #{waited_time} seconds). The max pool size is currently #{@size}; consider increasing it." + end + + # Sometimes our wait can end because a connection is available, + # but another thread can snatch it up first. If timeout hasn't + # passed but no connection is avail, looks like that happened -- + # loop and wait again, for the time remaining on our timeout. + before_wait = Time.now + @queue.wait( [@timeout - waited_time, 0].max ) + waited_time += (Time.now - before_wait) + + # Will go away in Rails 4, when we don't clean up + # after leaked connections automatically anymore. Right now, clean + # up after we've returned from a 'wait' if it looks like it's + # needed, then loop and try again. + if(active_connections.size >= @connections.size) + clear_stale_cached_connections! + end + end end end @@ -391,40 +286,10 @@ connection. For example: ActiveRecord::Base.connection.close synchronize do conn.run_callbacks :checkin do conn.expire + @queue.signal end release conn - - @available.add conn - end - end - - # Acquire a connection by one of 1) immediately removing one - # from the queue of available connections, 2) creating a new - # connection if the pool is not at capacity, 3) waiting on the - # queue for a connection to become available (first calling - # clear_stale_cached_connections! to clean up leaked connections, - # this cleanup will prob be going away in Rails4). - # - # Raises: - # - ConnectionTimeoutError if a connection could not be acquired - def acquire_connection - if conn = @available.poll - conn - elsif @connections.size < @size - checkout_new_connection - else - # this conditional clear_stale will go away in Rails 4, when we don't - # clean up after leaked connections automatically anymore. Right now, - # clean up after we've returned from a 'wait' if it looks like it's - # needed before trying to wait for a connection. - synchronize do - if(active_connections.size >= @connections.size) - clear_stale_cached_connections! - end - end - - @available.poll(@timeout) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index 6c8a102caf..04ab7e0a43 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -67,7 +67,8 @@ module ActiveRecord :port => config.port, :database => config.path.sub(%r{^/},""), :host => config.host } - spec.reject!{ |_,value| !value } + spec.reject!{ |_,value| value.blank? } + spec.map { |key,value| spec[key] = URI.unescape(value) if value.is_a?(String) } if config.query options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys spec.merge!(options) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index db99c3fbef..c49aed7069 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -4,6 +4,7 @@ module ActiveRecord # Converts an arel AST to SQL def to_sql(arel, binds = []) if arel.respond_to?(:ast) + binds = binds.dup visitor.accept(arel.ast) do quote(*binds.shift.reverse) end @@ -20,14 +21,14 @@ module ActiveRecord # Returns a record hash with the column names as keys and column values # as values. - def select_one(arel, name = nil) - result = select_all(arel, name) + def select_one(arel, name = nil, binds = []) + result = select_all(arel, name, binds) result.first if result end # Returns a single value from a record - def select_value(arel, name = nil) - if result = select_one(arel, name) + def select_value(arel, name = nil, binds = []) + if result = select_one(arel, name, binds) result.values.first end end @@ -266,7 +267,7 @@ module ActiveRecord # Inserts the given fixture into the table. Overridden in adapters that require # something beyond a simple insert (eg. Oracle). def insert_fixture(fixture, table_name) - columns = Hash[columns(table_name).map { |c| [c.name, c] }] + columns = schema_cache.columns_hash(table_name) key_list = [] value_list = fixture.map do |name, value| diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index d38e8464c5..e6269c78cf 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -75,7 +75,7 @@ module ActiveRecord case type when :string, :text then value - when :integer then value.to_i + when :integer then klass.value_to_integer(value) when :float then value.to_f when :decimal then klass.value_to_decimal(value) when :datetime, :timestamp then klass.string_to_time(value) @@ -92,7 +92,7 @@ module ActiveRecord case type when :string, :text then var_name - when :integer then "(#{var_name}.to_i)" + when :integer then "#{klass}.value_to_integer(#{var_name})" when :float then "#{var_name}.to_f" when :decimal then "#{klass}.value_to_decimal(#{var_name})" when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})" @@ -168,6 +168,17 @@ module ActiveRecord end end + # Used to convert values to integer. + # handle the case when an integer column is used to store boolean values + def value_to_integer(value) + case value + when TrueClass, FalseClass + value ? 1 : 0 + else + value.to_i + end + end + # convert something to a BigDecimal def value_to_decimal(value) # Using .class is faster than .is_a? and diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c132692249..4850b68051 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -2,7 +2,7 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' require 'active_record/connection_adapters/statement_pool' require 'active_support/core_ext/hash/keys' -gem 'mysql', '~> 2.8.1' +gem 'mysql', '~> 2.8' require 'mysql' class Mysql diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5108f4113f..afc363c9b2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -145,11 +145,8 @@ module ActiveRecord when /\A\(?(-?\d+(\.\d*)?\)?)\z/ $1 # Character types - when /\A'(.*)'::(?:character varying|bpchar|text)\z/m + when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m $1 - # Character types (8.1 formatting) - when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m - $1.gsub(/\\(\d\d\d)/) { $1.oct.chr } # Binary data types when /\A'(.*)'::bytea\z/m $1 @@ -965,16 +962,13 @@ module ActiveRecord end_sql if result.nil? or result.empty? - # If that fails, try parsing the primary key's default value. - # Support the 7.x and 8.0 nextval('foo'::text) as well as - # the 8.1+ nextval('foo'::regclass). result = query(<<-end_sql, 'SCHEMA')[0] SELECT attr.attname, CASE - WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN - substr(split_part(def.adsrc, '''', 2), - strpos(split_part(def.adsrc, '''', 2), '.')+1) - ELSE split_part(def.adsrc, '''', 2) + WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN + substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), + strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1) + ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) END FROM pg_class t JOIN pg_attribute attr ON (t.oid = attrelid) @@ -982,7 +976,7 @@ module ActiveRecord JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1]) WHERE t.oid = '#{quote_table_name(table)}'::regclass AND cons.contype = 'p' - AND def.adsrc ~* 'nextval' + AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval' end_sql end @@ -999,7 +993,7 @@ module ActiveRecord INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1] WHERE cons.contype = 'p' - AND dep.refobjid = '#{table}'::regclass + AND dep.refobjid = '#{quote_table_name(table)}'::regclass end_sql row && row.first @@ -1084,6 +1078,13 @@ module ActiveRecord when nil, 0..0x3fffffff; super(type) else raise(ActiveRecordError, "No binary type has byte size #{limit}.") end + when 'text' + # PostgreSQL doesn't support limits on text columns. + # The hard limit is 1Gb, according to section 8.3 in the manual. + case limit + when nil, 0..0x3fffffff; super(type) + else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") + end when 'integer' return 'integer' unless limit @@ -1282,7 +1283,8 @@ module ActiveRecord # - ::regclass is a function that gives the id for a table name def column_definitions(table_name) #:nodoc: exec_query(<<-end_sql, 'SCHEMA').rows - SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull + SELECT a.attname, format_type(a.atttypid, a.atttypmod), + pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod FROM pg_attribute a LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 4e8932a695..bc8d24a03f 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -1,7 +1,7 @@ module ActiveRecord module ConnectionAdapters class SchemaCache - attr_reader :columns, :columns_hash, :primary_keys, :tables + attr_reader :primary_keys, :tables attr_reader :connection def initialize(conn) @@ -30,6 +30,25 @@ module ActiveRecord @tables[name] = connection.table_exists?(name) end + # Get the columns for a table + def columns(table = nil) + if table + @columns[table] + else + @columns + end + end + + # Get the columns for a table as a hash, key is the column name + # value is the column object. + def columns_hash(table = nil) + if table + @columns_hash[table] + else + @columns_hash + end + end + # Clears out internal caches def clear! @columns.clear diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 731f7e2049..e80b465bab 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -205,7 +205,7 @@ module ActiveRecord value = super if column.type == :string && value.encoding == Encoding::ASCII_8BIT logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger - value.encode! 'utf-8' + value = value.encode Encoding::UTF_8 end value end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 0ba54e6248..1ee465ab21 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -25,6 +25,10 @@ module ActiveRecord self.name end + if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection + has_many_association = has_many_association.through_reflection + end + foreign_key = has_many_association.foreign_key.to_s child_class = has_many_association.klass belongs_to = child_class.reflect_on_all_associations(:belongs_to) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 7deac2588a..b2881991d5 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -102,6 +102,8 @@ module ActiveRecord def destroy #:nodoc: return super unless locking_enabled? + destroy_associations + if persisted? table = self.class.arel_table lock_col = self.class.locking_column diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index d2065d701f..05091654c0 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -277,13 +277,14 @@ module ActiveRecord type = (reflection.collection? ? :collection : :one_to_one) + # remove_possible_method :pirate_attributes= + # # def pirate_attributes=(attributes) # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options) # end class_eval <<-eoruby, __FILE__, __LINE__ + 1 - if method_defined?(:#{association_name}_attributes=) - remove_method(:#{association_name}_attributes=) - end + remove_possible_method(:#{association_name}_attributes=) + def #{association_name}_attributes=(attributes) assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options) end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 8a3c3fed7d..fd3380a53c 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -193,8 +193,12 @@ module ActiveRecord name = name.to_s raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name) raise ActiveRecordError, "can not update on a new record object" unless persisted? + + updated_count = self.class.update_all({ name => value }, self.class.primary_key => id) + raw_write_attribute(name, value) - self.class.update_all({ name => value }, self.class.primary_key => id) == 1 + + updated_count == 1 end # Updates the attributes of the model from the passed-in hash and saves the diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 466d148901..2156889a0f 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -6,19 +6,19 @@ module ActiveRecord module ClassMethods # Enable the query cache within the block if Active Record is configured. def cache(&block) - if ActiveRecord::Base.configurations.blank? - yield - else + if ActiveRecord::Base.connected? connection.cache(&block) + else + yield end end # Disable the query cache within the block if Active Record is configured. def uncached(&block) - if ActiveRecord::Base.configurations.blank? - yield - else + if ActiveRecord::Base.connected? connection.uncached(&block) + else + yield end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index aabd0d0bdb..ae6d9c8653 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -2,6 +2,25 @@ require 'active_support/core_ext/object/inclusion' require 'active_record' db_namespace = namespace :db do + def database_url_config + @database_url_config ||= + ActiveRecord::Base::ConnectionSpecification::Resolver.new(ENV["DATABASE_URL"], {}).spec.config.stringify_keys + end + + def current_config(options = {}) + options = { :env => Rails.env }.merge! options + + if options[:config] + @current_config = options[:config] + else + @current_config ||= if ENV['DATABASE_URL'] + database_url_config + else + ActiveRecord::Base.configurations[options[:env]] + end + end + end + task :load_config do ActiveRecord::Base.configurations = Rails.application.config.database_configuration ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a @@ -35,10 +54,14 @@ db_namespace = namespace :db do end end - desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)' + desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)' task :create => [:load_config, :rails_env] do - configs_for_environment.each { |config| create_database(config) } - ActiveRecord::Base.establish_connection(configs_for_environment.first) + if ENV['DATABASE_URL'] + create_database(database_url_config) + else + configs_for_environment.each { |config| create_database(config) } + ActiveRecord::Base.establish_connection(configs_for_environment.first) + end end def mysql_creation_options(config) @@ -133,9 +156,13 @@ db_namespace = namespace :db do end end - desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)' + desc 'Drops the database using DATABASE_URL or the current Rails.env (use db:drop:all to drop all databases)' task :drop => [:load_config, :rails_env] do - configs_for_environment.each { |config| drop_database_and_rescue(config) } + if ENV['DATABASE_URL'] + drop_database_and_rescue(database_url_config) + else + configs_for_environment.each { |config| drop_database_and_rescue(config) } + end end def local_database?(config, &block) @@ -146,7 +173,6 @@ db_namespace = namespace :db do end end - desc "Migrate the database (options: VERSION=x, VERBOSE=false)." task :migrate => [:environment, :load_config] do ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true @@ -201,8 +227,6 @@ db_namespace = namespace :db do desc 'Display status of migrations' task :status => [:environment, :load_config] do - config = ActiveRecord::Base.configurations[Rails.env] - ActiveRecord::Base.establish_connection(config) unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) puts 'Schema migrations table does not exist yet.' next # means "return" for rake task @@ -222,7 +246,7 @@ db_namespace = namespace :db do ['up', version, '********** NO FILE **********'] end # output - puts "\ndatabase: #{config['database']}\n\n" + puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n" puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" puts "-" * 50 (db_list + file_list).sort_by {|migration| migration[1]}.each do |migration| @@ -314,7 +338,6 @@ db_namespace = namespace :db do task :load => [:environment, :load_config] do require 'active_record/fixtures' - ActiveRecord::Base.establish_connection(Rails.env) base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact @@ -353,7 +376,6 @@ db_namespace = namespace :db do require 'active_record/schema_dumper' filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" File.open(filename, "w:utf-8") do |file| - ActiveRecord::Base.establish_connection(Rails.env) ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) end db_namespace['schema:dump'].reenable @@ -369,7 +391,7 @@ db_namespace = namespace :db do end end - task :load_if_ruby => [:environment, 'db:create'] do + task :load_if_ruby => ['db:create', :environment] do db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby end end @@ -377,25 +399,25 @@ db_namespace = namespace :db do namespace :structure do desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql' task :dump => [:environment, :load_config] do - abcs = ActiveRecord::Base.configurations + config = current_config filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") - case abcs[Rails.env]['adapter'] + case config['adapter'] when /mysql/, 'oci', 'oracle' - ActiveRecord::Base.establish_connection(abcs[Rails.env]) + ActiveRecord::Base.establish_connection(config) File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump } when /postgresql/ - set_psql_env(abcs[Rails.env]) - search_path = abcs[Rails.env]['schema_search_path'] + set_psql_env(config) + search_path = config['schema_search_path'] unless search_path.blank? search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ") end - `pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(abcs[Rails.env]['database'])}` + `pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(config['database'])}` raise 'Error dumping database' if $?.exitstatus == 1 when /sqlite/ - dbfile = abcs[Rails.env]['database'] + dbfile = config['database'] `sqlite3 #{dbfile} .schema > #{filename}` when 'sqlserver' - `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f #{filename} -A -U` + `smoscript -s #{config['host']} -d #{config['database']} -u #{config['username']} -p #{config['password']} -f #{filename} -A -U` when "firebird" set_firebird_env(abcs[Rails.env]) db_string = firebird_db_string(abcs[Rails.env]) @@ -412,40 +434,38 @@ db_namespace = namespace :db do # desc "Recreate the databases from the structure.sql file" task :load => [:environment, :load_config] do - env = ENV['RAILS_ENV'] || 'test' - - abcs = ActiveRecord::Base.configurations + config = current_config filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") - case abcs[env]['adapter'] + case config['adapter'] when /mysql/ - ActiveRecord::Base.establish_connection(abcs[env]) + ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') IO.read(filename).split("\n\n").each do |table| ActiveRecord::Base.connection.execute(table) end when /postgresql/ - set_psql_env(abcs[env]) - `psql -f "#{filename}" #{abcs[env]['database']}` + set_psql_env(config) + `psql -f "#{filename}" #{config['database']}` when /sqlite/ - dbfile = abcs[env]['database'] + dbfile = config['database'] `sqlite3 #{dbfile} < "#{filename}"` when 'sqlserver' - `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}` + `sqlcmd -S #{config['host']} -d #{config['database']} -U #{config['username']} -P #{config['password']} -i #{filename}` when 'oci', 'oracle' - ActiveRecord::Base.establish_connection(abcs[env]) + ActiveRecord::Base.establish_connection(config) IO.read(filename).split(";\n\n").each do |ddl| ActiveRecord::Base.connection.execute(ddl) end when 'firebird' - set_firebird_env(abcs[env]) - db_string = firebird_db_string(abcs[env]) + set_firebird_env(config) + db_string = firebird_db_string(config) sh "isql -i #{filename} #{db_string}" else - raise "Task not supported by '#{abcs[env]['adapter']}'" + raise "Task not supported by '#{config['adapter']}'" end end - task :load_if_sql => [:environment, 'db:create'] do + task :load_if_sql => ['db:create', :environment] do db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql end end @@ -465,10 +485,10 @@ db_namespace = namespace :db do # desc "Recreate the test database from an existent structure.sql file" task :load_structure => 'db:test:purge' do begin - old_env, ENV['RAILS_ENV'] = ENV['RAILS_ENV'], 'test' + current_config(:config => ActiveRecord::Base.configurations['test']) db_namespace["structure:load"].invoke ensure - ENV['RAILS_ENV'] = old_env + current_config(:config => nil) end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 4b3b30d6ed..a727f40194 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -464,7 +464,12 @@ module ActiveRecord node.left.relation.name == table_name } - Hash[equalities.map { |where| [where.left.name, where.right] }] + binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] + + Hash[equalities.map { |where| + name = where.left.name + [name, binds.fetch(name.to_s) { where.right }] + }] end def scope_for_create diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 2fd89882ff..14701f668c 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -59,11 +59,11 @@ module ActiveRecord relation = apply_finder_options(finder_options) end - start = options.delete(:start).to_i + start = options.delete(:start) batch_size = options.delete(:batch_size) || 1000 relation = relation.reorder(batch_order).limit(batch_size) - records = relation.where(table[primary_key].gteq(start)).all + records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a while records.any? records_size = records.size diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 802059db21..45e9e64229 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -178,7 +178,9 @@ module ActiveRecord # def pluck(column_name) column_name = column_name.to_s - klass.connection.select_all(select(column_name).arel).map! do |attributes| + relation = clone + relation.select_values = [column_name] + klass.connection.select_all(relation.arel, nil, bind_values).map! do |attributes| klass.type_cast_attribute(attributes.keys.first, klass.initialize_attributes(attributes)) end end @@ -240,7 +242,8 @@ module ActiveRecord query_builder = relation.arel end - type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation) + result = @klass.connection.select_value(query_builder, nil, relation.bind_values) + type_cast_calculated_value(result, column_for(column_name), operation) end def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: @@ -286,7 +289,7 @@ module ActiveRecord relation = except(:group).group(group) relation.select_values = select_values - calculated_data = @klass.connection.select_all(relation) + calculated_data = @klass.connection.select_all(relation, nil, bind_values) if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index abc67d9c15..57ecabb537 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -199,7 +199,7 @@ module ActiveRecord relation = relation.where(table[primary_key].eq(id)) if id end - connection.select_value(relation, "#{name} Exists") ? true : false + connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false rescue ThrowResult false end @@ -263,7 +263,7 @@ module ActiveRecord conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}] result = where(conditions).send(match.finder) - if match.bang? && result.blank? + if match.bang? && result.nil? raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}" else yield(result) if block_given? @@ -332,7 +332,7 @@ module ActiveRecord substitute = connection.substitute_at(column, @bind_values.length) relation = where(table[primary_key].eq(substitute)) - relation.bind_values = [[column, id]] + relation.bind_values += [[column, id]] record = relation.first unless record diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index c25570d758..3eead56e47 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -22,7 +22,7 @@ module ActiveRecord end end - (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method| + (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order, :binds]).each do |method| value = r.send(:"#{method}_values") merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present? end @@ -31,6 +31,8 @@ module ActiveRecord merged_wheres = @where_values + r.where_values + merged_binds = (@bind_values + r.bind_values).uniq(&:first) + unless @where_values.empty? # Remove duplicates, last one wins. seen = Hash.new { |h,table| h[table] = {} } @@ -47,6 +49,7 @@ module ActiveRecord end merged_relation.where_values = merged_wheres + merged_relation.bind_values = merged_binds (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method| value = r.send(:"#{method}_value") diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 9c50baa647..d6b0265fb3 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -34,7 +34,7 @@ module ActiveRecord if current_scope current_scope.clone else - scope = relation.clone + scope = relation scope.default_scoped = true scope end @@ -48,7 +48,7 @@ module ActiveRecord if current_scope current_scope.scope_for_create else - scope = relation.clone + scope = relation scope.default_scoped = true scope.scope_for_create end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 0c760e9850..ab25dc52f9 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -39,6 +39,7 @@ module ActiveRecord def initialize_dup(other) clear_timestamp_attributes + super end private diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 1b14df70e0..d0b51ef6a7 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -329,6 +329,7 @@ module ActiveRecord @_start_transaction_state[:destroyed] = @destroyed end @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 + @_start_transaction_state[:frozen?] = @attributes.frozen? end # Clear the new record state and id of a record. @@ -345,8 +346,8 @@ module ActiveRecord @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 if @_start_transaction_state[:level] < 1 || force restore_state = remove_instance_variable(:@_start_transaction_state) - was_frozen = @attributes.frozen? - @attributes = @attributes.dup if was_frozen + was_frozen = restore_state[:frozen?] + @attributes = @attributes.dup if @attributes.frozen? @new_record = restore_state[:new_record] @destroyed = restore_state[:destroyed] if restore_state.has_key?(:id) diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 0f91199469..7a45f838c0 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -2,7 +2,7 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 3 MINOR = 2 - TINY = 8 + TINY = 9 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') |