diff options
Diffstat (limited to 'activerecord/lib')
17 files changed, 127 insertions, 131 deletions
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index a9f6aaafef..4daafedcfb 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -27,14 +27,12 @@ module ActiveRecord throw(:abort) end + when :destroy + # No point in executing the counter update since we're going to destroy the parent anyway + load_target.each { |t| t.destroyed_by_association = reflection } + destroy_all else - if options[:dependent] == :destroy - # No point in executing the counter update since we're going to destroy the parent anyway - load_target.each { |t| t.destroyed_by_association = reflection } - destroy_all - else - delete_all - end + delete_all end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 78bfcf34a9..eadd73aab0 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -209,13 +209,13 @@ module ActiveRecord # end # # person = Person.new - # person.respond_to(:name) # => true - # person.respond_to(:name=) # => true - # person.respond_to(:name?) # => true - # person.respond_to('age') # => true - # person.respond_to('age=') # => true - # person.respond_to('age?') # => true - # person.respond_to(:nothing) # => false + # person.respond_to?(:name) # => true + # person.respond_to?(:name=) # => true + # person.respond_to?(:name?) # => true + # person.respond_to?('age') # => true + # person.respond_to?('age=') # => true + # person.respond_to?('age?') # => true + # person.respond_to?(:nothing) # => false def respond_to?(name, include_private = false) return false unless super diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 519de271c3..3211b6eaeb 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -34,10 +34,10 @@ module ActiveRecord # is not passed, the previous default value (if any) will be used. # Otherwise, the default will be +nil+. # - # +array+ (PG only) specifies that the type should be an array (see the + # +array+ (PostgreSQL only) specifies that the type should be an array (see the # examples below). # - # +range+ (PG only) specifies that the type should be a range (see the + # +range+ (PostgreSQL only) specifies that the type should be a range (see the # examples below). # # ==== Examples 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 c341773be1..9b74c3a10f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -74,7 +74,7 @@ module ActiveRecord #-- # Synchronization policy: # * all public methods can be called outside +synchronize+ - # * access to these i-vars needs to be in +synchronize+: + # * access to these instance variables needs to be in +synchronize+: # * @connections # * @now_connecting # * private methods that require being called in a +synchronize+ blocks @@ -329,13 +329,13 @@ module ActiveRecord # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 - # The cache of threads mapped to reserved connections, the sole purpose - # of the cache is to speed-up +connection+ method, it is not the authoritative - # registry of which thread owns which connection, that is tracked by - # +connection.owner+ attr on each +connection+ instance. + # This variable tracks the cache of threads mapped to reserved connections, with the + # sole purpose of speeding up the +connection+ method. It is not the authoritative + # registry of which thread owns which connection. Connection ownership is tracked by + # the +connection.owner+ attr on each +connection+ instance. # The invariant works like this: if there is mapping of <tt>thread => conn</tt>, - # then that +thread+ does indeed own that +conn+, however an absence of a such - # mapping does not mean that the +thread+ doesn't own the said connection, in + # then that +thread+ does indeed own that +conn+. However, an absence of a such + # mapping does not mean that the +thread+ doesn't own the said connection. In # that case +conn.owner+ attr should be consulted. # Access and modification of +@thread_cached_conns+ does not require # synchronization. @@ -364,10 +364,10 @@ module ActiveRecord @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout end - # Is there an open connection that is being used for the current thread? + # Returns true if there is an open connection being used for the current thread. # # This method only works for connections that have been obtained through - # #connection or #with_connection methods, connections obtained through + # #connection or #with_connection methods. Connections obtained through # #checkout will not be detected by #active_connection? def active_connection? @thread_cached_conns[connection_cache_key(Thread.current)] @@ -415,7 +415,10 @@ module ActiveRecord with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do synchronize do @connections.each do |conn| - checkin conn + if conn.in_use? + conn.steal! + checkin conn + end conn.disconnect! end @connections = [] @@ -426,9 +429,9 @@ module ActiveRecord # Disconnects all connections in the pool, and clears the pool. # - # The pool first tries to gain ownership of all connections, if unable to + # The pool first tries to gain ownership of all connections. If unable to # do so within a timeout interval (default duration is - # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool is forcefully + # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully # disconnected without any regard for other connection owning threads. def disconnect! disconnect(false) @@ -447,7 +450,10 @@ module ActiveRecord with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do synchronize do @connections.each do |conn| - checkin conn + if conn.in_use? + conn.steal! + checkin conn + end conn.disconnect! if conn.requires_reloading? end @connections.delete_if(&:requires_reloading?) @@ -474,9 +480,9 @@ module ActiveRecord # Clears the cache which maps classes and re-connects connections that # require reloading. # - # The pool first tries to gain ownership of all connections, if unable to + # The pool first tries to gain ownership of all connections. If unable to # do so within a timeout interval (default duration is - # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool forcefully + # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool forcefully # clears the cache and reloads connections without any regard for other # connection owning threads. def clear_reloadable_connections! @@ -530,20 +536,20 @@ module ActiveRecord @available.delete conn # @available.any_waiting? => true means that prior to removing this - # conn, the pool was at its max size (@connections.size == @size) - # this would mean that any threads stuck waiting in the queue wouldn't + # conn, the pool was at its max size (@connections.size == @size). + # This would mean that any threads stuck waiting in the queue wouldn't # know they could checkout_new_connection, so let's do it for them. # Because condition-wait loop is encapsulated in the Queue class # (that in turn is oblivious to ConnectionPool implementation), threads - # that are "stuck" there are helpless, they have no way of creating + # that are "stuck" there are helpless. They have no way of creating # new connections and are completely reliant on us feeding available # connections into the Queue. needs_new_connection = @available.any_waiting? end # This is intentionally done outside of the synchronized section as we - # would like not to hold the main mutex while checking out new connections, - # thus there is some chance that needs_new_connection information is now + # would like not to hold the main mutex while checking out new connections. + # Thus there is some chance that needs_new_connection information is now # stale, we can live with that (bulk_make_new_connections will make # sure not to exceed the pool's @size limit). bulk_make_new_connections(1) if needs_new_connection @@ -556,17 +562,17 @@ module ActiveRecord stale_connections = synchronize do @connections.select do |conn| conn.in_use? && !conn.owner.alive? + end.each do |conn| + conn.steal! end end stale_connections.each do |conn| - synchronize do - if conn.active? - conn.reset! - checkin conn - else - remove conn - end + if conn.active? + conn.reset! + checkin conn + else + remove conn end end end @@ -698,7 +704,7 @@ module ActiveRecord def acquire_connection(checkout_timeout) # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to # +conn.lease+ the returned connection (and to do this in a +synchronized+ - # section), this is not the cleanest implementation, as ideally we would + # section). This is not the cleanest implementation, as ideally we would # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+ # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections # of the said methods and avoid an additional +synchronize+ overhead. @@ -822,7 +828,7 @@ module ActiveRecord # should use. # # The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge - # about the model. The model, needs to pass a specification name to the handler, + # about the model. The model needs to pass a specification name to the handler, # in order to lookup the correct connection pool. class ConnectionHandler def initialize @@ -890,7 +896,7 @@ module ActiveRecord # Remove the connection for this class. This will close the active # connection and the defined connection (if they exist). The result - # can be used as an argument for establish_connection, for easily + # can be used as an argument for #establish_connection, for easily # re-establishing the connection. def remove_connection(spec_name) if pool = owner_to_pool.delete(spec_name) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index eec0bc8518..396cb0b07a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -179,7 +179,7 @@ module ActiveRecord # A Symbol can be used to specify the type of the generated primary key column. # [<tt>:primary_key</tt>] # The name of the primary key, if one is to be added automatically. - # Defaults to +id+. If <tt>:id</tt> is false this option is ignored. + # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored. # # Note that Active Record models will automatically detect their # primary key. This can be avoided by using @@ -305,9 +305,9 @@ module ActiveRecord # # Creates a table called 'assemblies_parts' with no id. # create_join_table(:assemblies, :parts) # - # You can pass a +options+ hash can include the following keys: + # You can pass an +options+ hash which can include the following keys: # [<tt>:table_name</tt>] - # Sets the table name overriding the default + # Sets the table name, overriding the default. # [<tt>:column_options</tt>] # Any extra options you want appended to the columns definition. # [<tt>:options</tt>] @@ -433,7 +433,7 @@ module ActiveRecord # t.remove_index :company_id # end # - # See also Table for details on all of the various column transformation. + # See also Table for details on all of the various column transformations. def change_table(table_name, options = {}) if supports_bulk_alter? && options[:bulk] recorder = ActiveRecord::Migration::CommandRecorder.new(self) @@ -483,10 +483,10 @@ module ActiveRecord # # Available options are (none of these exists by default): # * <tt>:limit</tt> - - # Requests a maximum column length. This is number of characters for a <tt>:string</tt> column + # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns. # * <tt>:default</tt> - - # The column's default value. Use nil for NULL. + # The column's default value. Use +nil+ for +NULL+. # * <tt>:null</tt> - # Allows or disallows +NULL+ values in the column. This option could # have been named <tt>:null_allowed</tt>. @@ -495,7 +495,7 @@ module ActiveRecord # * <tt>:scale</tt> - # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns. # - # Note: The precision is the total number of significant digits + # Note: The precision is the total number of significant digits, # and the scale is the number of digits that can be stored following # the decimal point. For example, the number 123.45 has a precision of 5 # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can @@ -564,7 +564,7 @@ module ActiveRecord # # The +type+ and +options+ parameters will be ignored if present. It can be helpful # to provide these in a migration's +change+ method so it can be reverted. - # In that case, +type+ and +options+ will be used by add_column. + # In that case, +type+ and +options+ will be used by #add_column. def remove_column(table_name, column_name, type = nil, options = {}) execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}" end @@ -952,13 +952,13 @@ module ActiveRecord # Checks to see if a foreign key exists on a table for a given foreign key definition. # - # # Check a foreign key exists + # # Checks to see if a foreign key exists. # foreign_key_exists?(:accounts, :branches) # - # # Check a foreign key on a specified column exists + # # Checks to see if a foreign key on a specified column exists. # foreign_key_exists?(:accounts, column: :owner_id) # - # # Check a foreign key with a custom name exists + # # Checks to see if a foreign key with a custom name exists. # foreign_key_exists?(:accounts, name: "special_fk_name") # def foreign_key_exists?(from_table, options_or_to_table = {}) @@ -1081,7 +1081,7 @@ module ActiveRecord end # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT. - # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they + # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they # require the order columns appear in the SELECT. # # columns_for_distinct("posts.id", ["posts.created_at desc"]) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index d4b9e301bc..5747e4d1ee 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -184,7 +184,30 @@ module ActiveRecord # this method must only be called while holding connection pool's mutex def expire - @owner = nil + if in_use? + if @owner != Thread.current + raise ActiveRecordError, "Cannot expire connection, " << + "it is owned by a different thread: #{@owner}. " << + "Current thread: #{Thread.current}." + end + + @owner = nil + else + raise ActiveRecordError, 'Cannot expire connection, it is not currently leased.' + end + end + + # this method must only be called while holding connection pool's mutex (and a desire for segfaults) + def steal! # :nodoc: + if in_use? + if @owner != Thread.current + pool.send :remove_connection_from_thread_cache, self, @owner + + @owner = Thread.current + end + else + raise ActiveRecordError, 'Cannot steal connection, it is not currently leased.' + end end def unprepared_statement diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 718a6c5b91..3e77b92141 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -460,7 +460,6 @@ module ActiveRecord # it can be helpful to provide these in a migration's +change+ method so it can be reverted. # In that case, +options+ and the block will be used by create_table. def drop_table(table_name, options = {}) - create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name) execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end @@ -515,19 +514,21 @@ module ActiveRecord schema, name = extract_schema_qualified_name(table_name) - fk_info = select_all <<-SQL.strip_heredoc - SELECT fk.referenced_table_name as 'to_table' - ,fk.referenced_column_name as 'primary_key' - ,fk.column_name as 'column' - ,fk.constraint_name as 'name' + fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA') + SELECT fk.referenced_table_name AS 'to_table', + fk.referenced_column_name AS 'primary_key', + fk.column_name AS 'column', + fk.constraint_name AS 'name', + rc.update_rule AS 'on_update', + rc.delete_rule AS 'on_delete' FROM information_schema.key_column_usage fk - WHERE fk.referenced_column_name is not null + JOIN information_schema.referential_constraints rc + USING (constraint_schema, constraint_name) + WHERE fk.referenced_column_name IS NOT NULL AND fk.table_schema = #{quote(schema)} AND fk.table_name = #{quote(name)} SQL - create_table_info = create_table_info(table_name) - fk_info.map do |row| options = { column: row['column'], @@ -535,8 +536,8 @@ module ActiveRecord primary_key: row['primary_key'] } - options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE") - options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE") + options[:on_update] = extract_foreign_key_action(row['on_update']) + options[:on_delete] = extract_foreign_key_action(row['on_delete']) ForeignKeyDefinition.new(table_name, row['to_table'], options) end @@ -891,21 +892,15 @@ module ActiveRecord end end - def extract_foreign_key_action(structure, name, action) # :nodoc: - if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/ - case $1 - when 'CASCADE'; :cascade - when 'SET NULL'; :nullify - end + def extract_foreign_key_action(specifier) # :nodoc: + case specifier + when 'CASCADE'; :cascade + when 'SET NULL'; :nullify end end - def create_table_info_cache # :nodoc: - @create_table_info_cache ||= {} - end - def create_table_info(table_name) # :nodoc: - create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] end def create_table_definition(*args) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb index 13c9b6cbd9..87f0ff7d85 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -13,19 +13,6 @@ module ActiveRecord result end - # Returns a record hash with the column names as keys and column values - # as values. - def select_one(arel, name = nil, binds = []) - arel, binds = binds_from_relation(arel, binds) - @connection.query_options.merge!(as: :hash) - select_result(to_sql(arel, binds), name, binds) do |result| - @connection.next_result while @connection.more_results? - result.first - end - ensure - @connection.query_options.merge!(as: :array) - end - # Returns an array of arrays containing the field values. # Order is the same as that returned by +columns+. def select_rows(sql, name = nil, binds = []) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index f6860b9aba..45507e206a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -579,7 +579,7 @@ module ActiveRecord end def foreign_keys(table_name) - fk_info = select_all <<-SQL.strip_heredoc + fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA') SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete FROM pg_constraint c JOIN pg_class t1 ON c.conrelid = t1.oid diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 17a5dc1d1b..8b649eab0f 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -14,10 +14,6 @@ module ActiveRecord "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}" end - def original_table_name - "#{table_name_prefix}active_record_internal_metadatas#{table_name_suffix}" - end - def []=(key, value) find_or_initialize_by(key: key).update_attributes!(value: value) end @@ -30,17 +26,8 @@ module ActiveRecord ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) } end - def original_table_exists? - # This method will be removed in Rails 5.1 - # Since it is only necessary when `active_record_internal_metadatas` could exist - ActiveSupport::Deprecation.silence { connection.table_exists?(original_table_name) } - end - # Creates an internal metadata table with columns +key+ and +value+ def create_table - if original_table_exists? - connection.rename_table(original_table_name, table_name) - end unless table_exists? key_options = connection.internal_string_options_for_primary_key diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index afed5e5e85..510e8a6e43 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -479,7 +479,12 @@ module ActiveRecord # ball.touch(:updated_at) # => raises ActiveRecordError # def touch(*names, time: nil) - raise ActiveRecordError, "cannot touch on a new record object" unless persisted? + unless persisted? + raise ActiveRecordError, <<-MSG.squish + cannot touch on a new or destroyed record object. Consider using + persisted?, new_record?, or destroyed? before touching + MSG + end time ||= current_time_from_proper_timezone attributes = timestamp_attributes_for_update_in_model diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7a1552856b..93baa882ad 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -65,7 +65,7 @@ module ActiveRecord @klass.connection.insert( im, 'SQL', - primary_key, + primary_key || false, primary_key_value, nil, binds) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index ecce949370..e5496c02b2 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -15,11 +15,11 @@ module ActiveRecord @table = table @handlers = [] - register_handler(BasicObject, BasicObjectHandler.new(self)) + register_handler(BasicObject, BasicObjectHandler.new) register_handler(Class, ClassHandler.new(self)) register_handler(Base, BaseHandler.new(self)) - register_handler(Range, RangeHandler.new(self)) - register_handler(RangeHandler::RangeWithBinds, RangeHandler.new(self)) + register_handler(Range, RangeHandler.new) + register_handler(RangeHandler::RangeWithBinds, RangeHandler.new) register_handler(Relation, RelationHandler.new) register_handler(Array, ArrayHandler.new(self)) register_handler(AssociationQueryValue, AssociationQueryHandler.new(self)) diff --git a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb index 6cec75dc0a..79cde00303 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/basic_object_handler.rb @@ -1,17 +1,9 @@ module ActiveRecord class PredicateBuilder class BasicObjectHandler # :nodoc: - def initialize(predicate_builder) - @predicate_builder = predicate_builder - end - def call(attribute, value) attribute.eq(value) end - - protected - - attr_reader :predicate_builder end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb index 306d4694ae..5db778e19c 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb @@ -3,10 +3,6 @@ module ActiveRecord class RangeHandler # :nodoc: RangeWithBinds = Struct.new(:begin, :end, :exclude_end?) - def initialize(predicate_builder) - @predicate_builder = predicate_builder - end - def call(attribute, value) if value.begin.respond_to?(:infinite?) && value.begin.infinite? if value.end.respond_to?(:infinite?) && value.end.infinite? @@ -24,10 +20,6 @@ module ActiveRecord attribute.between(value) end end - - protected - - attr_reader :predicate_builder end end end diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 8e6cd6c82f..b9fd0f5326 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -75,8 +75,14 @@ module ActiveRecord hash_rows[idx] end + def first + return nil if @rows.empty? + Hash[@columns.zip(@rows.first)] + end + def last - hash_rows.last + return nil if @rows.empty? + Hash[@columns.zip(@rows.last)] end def cast_values(type_overrides = {}) # :nodoc: diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb index 9a80a63e28..598873ea3a 100644 --- a/activerecord/lib/active_record/touch_later.rb +++ b/activerecord/lib/active_record/touch_later.rb @@ -8,7 +8,12 @@ module ActiveRecord end def touch_later(*names) # :nodoc: - raise ActiveRecordError, "cannot touch on a new record object" unless persisted? + unless persisted? + raise ActiveRecordError, <<-MSG.squish + cannot touch on a new or destroyed record object. Consider using + persisted?, new_record?, or destroyed? before touching + MSG + end @_defer_touch_attrs ||= timestamp_attributes_for_update_in_model @_defer_touch_attrs |= names |