Skip to content

Commit

Permalink
add support for foreign key
Browse files Browse the repository at this point in the history
  • Loading branch information
martinfsn committed Oct 4, 2023
1 parent 7ed073e commit 720a97e
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 36 deletions.
9 changes: 6 additions & 3 deletions lib/statesman/multi_state/active_record_macro.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ module ActiveRecordMacro

class_methods do
def has_one_state_machine(field_name, state_machine_klass:, transition_klass:,
transition_name: transition_klass.to_s.underscore.pluralize.to_sym, virtual_attribute_name: "#{field_name}_state_form")
transition_name: transition_klass.to_s.underscore.pluralize.to_sym, virtual_attribute_name: "#{field_name}_state_form",
transition_foreign_key: nil)
state_machine_name = "#{field_name}_state_machine"

# To handle STI, this needs to be done to get the base klass
base_klass = caller_locations.first.label.split(':').last[...-1]

has_many transition_name, class_name: transition_klass.to_s, autosave: false, dependent: :destroy
association_options = { class_name: transition_klass.to_s, autosave: false, dependent: :destroy }.merge(transition_foreign_key ? { foreign_key: transition_foreign_key.to_s } : {} )
has_many transition_name, **association_options

include Statesman::Adapters::CustomActiveRecordQueries[
transition_class: transition_klass.constantize,
Expand Down Expand Up @@ -94,7 +96,8 @@ def save_with_state(**options)
field_name,
nil,
{ state_machine_klass: state_machine_klass, transition_klass: transition_klass,
transition_name: transition_name, virtual_attribute_name: virtual_attribute_name },
transition_name: transition_name, virtual_attribute_name: virtual_attribute_name,
transition_foreign_key: transition_foreign_key },
self
)

Expand Down
17 changes: 17 additions & 0 deletions test/dummy/app/models/foreign_key_status_order_transition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

class ForeignKeyStatusOrderTransition < ApplicationRecord
include Statesman::Adapters::ActiveRecordTransition

belongs_to :order, foreign_key: 'custom_fk_id', inverse_of: :foreign_key_status_order_transitions

after_destroy :update_most_recent, if: :most_recent?

private

def update_most_recent
last_transition = order.foreign_key_status_order_transitions.order(:sort_key).last
return unless last_transition.present?
last_transition.update_column(:most_recent, true)
end
end
4 changes: 4 additions & 0 deletions test/dummy/app/models/order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ class Order < ApplicationRecord
transition_klass: 'AdminStatusOrderTransition'
has_one_state_machine :custom_status, state_machine_klass: 'AdminStatusOrderStateMachine',
transition_klass: 'AdminStatusOrderTransition', transition_name: :transitions, virtual_attribute_name: 'my_attribute'
has_one_state_machine :foreign_key_status, state_machine_klass: 'UserStatusOrderStateMachine',
transition_klass: 'ForeignKeyStatusOrderTransition',
transition_foreign_key: 'custom_fk_id'

end
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class CreateForeignKeyStatusOrderTransitions < ActiveRecord::Migration[7.0]
def change
create_table :foreign_key_status_order_transitions do |t|
t.string :to_state, null: false
t.text :metadata, default: '{}'
t.integer :sort_key, null: false
t.integer :custom_fk_id, null: false
t.boolean :most_recent, null: false

# If you decide not to include an updated timestamp column in your transition
# table, you'll need to configure the `updated_timestamp_column` setting in your
# migration class.
t.timestamps null: false
end

# Foreign keys are optional, but highly recommended
add_foreign_key :foreign_key_status_order_transitions, :orders, column: :custom_fk_id

add_index(:foreign_key_status_order_transitions,
%i(custom_fk_id sort_key),
unique: true,
name: "index_foreign_key_status_order_transitions_parent_sort")
add_index(:foreign_key_status_order_transitions,
%i(custom_fk_id most_recent),
unique: true,
where: "most_recent",
name: "index_foreign_key_status_order_transitions_parent_most_recent")
end
end
69 changes: 39 additions & 30 deletions test/dummy/db/schema.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# frozen_string_literal: true

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
Expand All @@ -12,38 +10,49 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 20_221_005_223_432) do
create_table 'admin_status_order_transitions', force: :cascade do |t|
t.string 'to_state', null: false
t.text 'metadata', default: '{}'
t.integer 'sort_key', null: false
t.integer 'order_id', null: false
t.boolean 'most_recent', null: false
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
t.index %w[order_id most_recent], name: 'index_admin_status_order_transitions_parent_most_recent', unique: true,
where: 'most_recent'
t.index %w[order_id sort_key], name: 'index_admin_status_order_transitions_parent_sort', unique: true
ActiveRecord::Schema[7.0].define(version: 2023_10_04_142606) do
create_table "admin_status_order_transitions", force: :cascade do |t|
t.string "to_state", null: false
t.text "metadata", default: "{}"
t.integer "sort_key", null: false
t.integer "order_id", null: false
t.boolean "most_recent", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["order_id", "most_recent"], name: "index_admin_status_order_transitions_parent_most_recent", unique: true, where: "most_recent"
t.index ["order_id", "sort_key"], name: "index_admin_status_order_transitions_parent_sort", unique: true
end

create_table "foreign_key_status_order_transitions", force: :cascade do |t|
t.string "to_state", null: false
t.text "metadata", default: "{}"
t.integer "sort_key", null: false
t.integer "custom_fk_id", null: false
t.boolean "most_recent", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["custom_fk_id", "most_recent"], name: "index_foreign_key_status_order_transitions_parent_most_recent", unique: true, where: "most_recent"
t.index ["custom_fk_id", "sort_key"], name: "index_foreign_key_status_order_transitions_parent_sort", unique: true
end

create_table 'orders', force: :cascade do |t|
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
create_table "orders", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

create_table 'user_status_order_transitions', force: :cascade do |t|
t.string 'to_state', null: false
t.text 'metadata', default: '{}'
t.integer 'sort_key', null: false
t.integer 'order_id', null: false
t.boolean 'most_recent', null: false
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
t.index %w[order_id most_recent], name: 'index_user_status_order_transitions_parent_most_recent', unique: true,
where: 'most_recent'
t.index %w[order_id sort_key], name: 'index_user_status_order_transitions_parent_sort', unique: true
create_table "user_status_order_transitions", force: :cascade do |t|
t.string "to_state", null: false
t.text "metadata", default: "{}"
t.integer "sort_key", null: false
t.integer "order_id", null: false
t.boolean "most_recent", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["order_id", "most_recent"], name: "index_user_status_order_transitions_parent_most_recent", unique: true, where: "most_recent"
t.index ["order_id", "sort_key"], name: "index_user_status_order_transitions_parent_sort", unique: true
end

add_foreign_key 'admin_status_order_transitions', 'orders'
add_foreign_key 'user_status_order_transitions', 'orders'
add_foreign_key "admin_status_order_transitions", "orders"
add_foreign_key "foreign_key_status_order_transitions", "orders", column: "custom_fk_id"
add_foreign_key "user_status_order_transitions", "orders"
end
9 changes: 6 additions & 3 deletions test/statesman/multi_state/reflection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class ReflectionTest < ActiveSupport::TestCase
reflection = Order.reflect_on_state_machine(:custom_status)
assert_equal :transitions, reflection.options[:transition_name]
assert_equal 'my_attribute', reflection.options[:virtual_attribute_name]

reflection = Order.reflect_on_state_machine(:foreign_key_status)
assert_equal 'custom_fk_id', reflection.options[:transition_foreign_key]
end

test 'reflection on a singular state machine with the same name as a state machine on another model' do
Expand All @@ -34,9 +37,9 @@ class ReflectionTest < ActiveSupport::TestCase
reflections = Order.reflect_on_all_state_machines.sort_by(&:name)

assert_equal [Order], reflections.collect(&:active_record).uniq
assert_equal %i[admin_status custom_status user_status], reflections.collect(&:name)
assert_equal %i[has_one_state_machine has_one_state_machine has_one_state_machine], reflections.collect(&:macro)
assert_equal %i[admin_status_order_transitions transitions user_status_order_transitions], reflections.collect { |reflection|
assert_equal %i[admin_status custom_status foreign_key_status user_status], reflections.collect(&:name)
assert_equal %i[has_one_state_machine has_one_state_machine has_one_state_machine has_one_state_machine], reflections.collect(&:macro)
assert_equal %i[admin_status_order_transitions transitions foreign_key_status_order_transitions user_status_order_transitions], reflections.collect { |reflection|
reflection.options[:transition_name]
}
end
Expand Down

0 comments on commit 720a97e

Please sign in to comment.