February 24, 2017·4 min read·engineeringrubyrailsdatabases

Rails 5.0.2's first candidate repairs cached database assumptions

Rails 5.0.2.rc1 repairs statement-cache invalidation and hstore change detection. Both fixes concern assumptions that can remain stale after the application's visible state changes.

A model can change its table name while retaining a cached statement built for the previous table. A database value can retain the same meaning while its serialized key order changes. Rails 5.0.2's first release candidate repairs both cases in Active Record, where cached or serialized representations can outlive the assumptions that made them correct.

Rafael França's candidate announcement asks applications to report regressions before the planned final release. The tagged Active Record changelog dates the candidate to February 24, which is the date used here. The announcement follows on February 25 and describes March 1 as the expected final date if no regressions appear. That remains a plan at this point.

I find Ruby's object model useful because a reader can follow the same basic method-and-object relationships across an application. Framework state underneath those methods can be less obvious. These repairs matter because the visible model operation should determine the query and change-tracking behavior, even when the framework caches intermediate work.

A table-name change must invalidate dependent state

The changelog says table_name= now resets the current statement cache so queries do not continue against the previous table. The tagged implementation converts the new value to a string when present. If a table name is already defined and the value differs, it resets column information when connected.

The setter also clears related derived state, including the quoted table name, Arel table and predicate builder. A sequence name is cleared unless explicitly configured. These values are not independent facts about the model. They depend on which table the model describes, so a table-name assignment has to update more than one instance variable.

The bug illustrates a general requirement of caching with a mutable configuration. A cached query can remain syntactically valid after the table changes. Its validity as SQL does not mean it still expresses the requested operation. In this case, the wrong cached assumption can direct a query to the old table.

This affects code that changes a model's table mapping, not every ordinary query. The maintenance repair is specifically about making that supported state transition invalidate the work derived from the old mapping. It does not introduce a new table-selection API or require an application to rename its database tables.

Hstore change tracking compares deserialized values

The candidate also repairs PostgreSQL hstore handling in changed_in_place?. The tagged hstore type explains the problem in a source comment. Comparing Hash equivalents avoids treating different key orders as different values.

def changed_in_place?(raw_old_value, new_value)
  deserialize(raw_old_value) != new_value
end

The old database representation is converted into the value model before comparison. That matters because serialized order is not necessarily a meaningful change in a hash of key-value associations. A comparison at the wrong level can mark an attribute as changed even when the application sees equivalent content.

The fix does not ignore actual changes to a key or value. It changes the representation used for equality. Once both sides describe the same kind of Ruby value, the comparison can distinguish different content from a different ordering of the same associations.

I value this use of Ruby's ordinary Hash semantics. It keeps persistence bookkeeping aligned with the value the application manipulates. A framework should not require every caller to reason about serialization order merely to understand whether an attribute changed.

Maintenance includes metadata and integration details

Other Active Record fixes in the candidate respect timestamp precision for arrays and handle symbol primary keys in association reflection. Generated migrations now use the configured db/migrate path. These are separate repairs to the relationship between application configuration and the framework's database operations.

The changelog also permits passing a frozen Hash to as_json. This concerns the mutability of an argument supplied by the caller. A method that accepts configuration should not unexpectedly require that the caller leave it mutable when the operation can support a frozen value.

The mysql2 adapter also regains a configurable wait_timeout. That is an adapter configuration repair with a different effect from query construction. Including it in the same candidate shows why a maintenance version cannot be understood solely through one model-level symptom, even within Active Record.

The release remains a candidate across the Rails components listed in the announcement. Its published SHA256 checksums allow comparison with the uploaded gem artifacts, but matching bytes do not establish that a particular application has no regression. The announced feedback period exists to expose application behavior that the framework's own tests may not cover. The concrete Active Record changes provide useful starting points for that feedback, including table remapping and hstore values whose serialized ordering differs while their content remains equal.