pluginaweek / state_machine

Adds support for creating state machines for attributes on any Ruby class
http://www.pluginaweek.org
MIT License
3.74k stars 507 forks source link

Rails 4 partial_writes with state_machine initial value. #248

Closed pda closed 11 years ago

pda commented 11 years ago

Update: fixed in rails/rails#9489

Rails 4 adds (and enables by default) partial inserts to ActiveRecord. This means saving a new record will only write to columns believed to be changed. (CHANGELOG extract below).

state_machine sets its initial value in a way which bypasses ActiveRecord's change/dirty checking, and so that column is not included in the insert.

For a nullable column, this results in a null being stored instead of the initial value. For a not-null column, this results in an insert error.

Proposal

state_machine should set the initial value such that ActiveRecord sees it as a changed attribute.

Does this sound sane? If so I'm happy to look into the implementation.

Reproduction

# Migration
class CreateWidgets < ActiveRecord::Migration
  def change
    create_table :widgets do |t|
      t.string :status, null: false
      t.timestamps
    end
  end
end

# Model
class Widget < ActiveRecord::Base
  state_machine :status, initial: "new"
end

# Without NOT NULL constraint.
w = Widget.new
w.status        # => "new"
w.changes       # => {}
w.save          # => true
w.status        # => "new"
w.reload.status # => nil   <-- vanished!

# With NOT NULL constraint.
Widget.create!  # => ActiveRecord::StatementInvalid:
                # PG::Error: ERROR:
                # null value in column "status" violates
                # not-null constraint
-- Generated SQL in both cases:
INSERT INTO "widgets" ("created_at", "updated_at")
  VALUES ($1, $2)
  RETURNING "id"

ActiveRecord CHANGELOG

From the ActiveRecord CHANGELOG:

Support for partial inserts.

When inserting new records, only the fields which have been changed from the defaults will actually be included in the INSERT statement. The other fields will be populated by the database.

This is more efficient, and also means that it will be safe to remove database columns without getting subsequent errors in running app processes (so long as the code in those processes doesn't contain any references to the removed column).

The partial_updates configuration option is now renamed to partial_writes to reflect the fact that it now impacts both inserts and updates.

Jon Leighton

pda commented 11 years ago

Looks like this has been fixed in rails/rails#9489 (merged since the current 4.0.0.beta1). Also referenced in rails/rails#9460 (closed).