如何用YAML动态创建可配置状态机?Rails员工离职工作流需求
基于YAML配置的动态离职工作流实现方案(Rails)
针对多组织专属的动态状态机需求,直接用aasm的静态定义确实受限,下面提供一套灵活的自定义实现方案,支持从YAML加载组织专属配置,动态处理状态转换。
一、设计YAML配置结构
每个组织的离职流程配置独立存储,包含状态列表、事件转换规则、触发动作、权限控制等核心信息:
# 示例:某组织的离职流程配置 states: - initiated # 离职申请发起 - exit_interview_done # 离职面谈完成 - assets_returned # 资产归还 - finance_approved # 财务结清 - finalized # 离职完成 events: mark_interview_done: from: initiated to: exit_interview_done allowed_roles: [hr, department_manager] # 允许触发的角色 triggers: - send_interview_notice_to_hr # 触发的动作(对应服务类) confirm_assets_return: from: exit_interview_done to: assets_returned allowed_roles: [admin, hr] approve_finance: from: assets_returned to: finance_approved triggers: - generate_finance_clearance_doc finalize_termination: from: finance_approved to: finalized triggers: - deactivate_employee_account - send_termination_letter
二、配置存储方案
两种可选方式,根据业务需求选择:
- 数据库存储(推荐,支持动态修改):给
Organization模型添加workflow_config字段(类型text),直接存储YAML文本。 - 文件系统存储:在
config/organizations/下按组织slug创建子目录,存放对应YAML文件(适合需要版本控制的场景)。
三、封装动态状态机服务类
核心逻辑放在服务类中,负责加载配置、验证权限、执行状态转换和触发动作:
# app/services/dynamic_termination_workflow.rb class DynamicTerminationWorkflow attr_reader :employee, :config def initialize(employee) @employee = employee @config = load_organization_config end # 加载组织专属配置(示例:从数据库加载) def load_organization_config raw_config = employee.organization.workflow_config return default_config if raw_config.blank? YAML.safe_load(raw_config, aliases: true) || default_config end # 兜底默认配置,防止组织未配置时出错 def default_config YAML.safe_load(<<~YML) states: - initiated - finalized events: finalize: from: initiated to: finalized YML end # 检查是否允许触发指定事件 def can_trigger?(event_name, actor) event = config[:events][event_name.to_sym] return false unless event current_state = employee.termination_state.to_sym # 检查当前状态是否在允许的转换起点 return false unless event[:from].include?(current_state) # 检查角色权限(如果配置了) event[:allowed_roles] ? event[:allowed_roles].include?(actor.role.to_sym) : true end # 执行事件触发逻辑 def trigger(event_name, actor) return false unless can_trigger?(event_name, actor) event = config[:events][event_name.to_sym] new_state = event[:to] # 事务保证状态转换和动作执行的原子性 ActiveRecord::Base.transaction do employee.update!(termination_state: new_state) execute_triggers(event[:triggers], actor) if event[:triggers] end true rescue StandardError => e Rails.logger.error "Workflow trigger failed: #{e.message}" false end private # 执行事件触发的动作(调用对应服务类) def execute_triggers(triggers, actor) triggers.each do |trigger| service_class = "#{trigger.camelize}Service".constantize service_class.call(employee: employee, actor: actor) end end end
四、在Employee模型中集成
给员工模型添加快捷方法,简化调用:
# app/models/employee.rb class Employee < ApplicationRecord belongs_to :organization enum termination_state: -> { DynamicTerminationWorkflow.new(self).config[:states].map { |s| [s, s] }.to_h } def termination_workflow @termination_workflow ||= DynamicTerminationWorkflow.new(self) end # 快捷触发事件 def trigger_termination_event(event_name, actor) termination_workflow.trigger(event_name, actor) end # 快捷检查权限 def can_trigger_termination_event?(event_name, actor) termination_workflow.can_trigger?(event_name, actor) end end
五、业务代码中使用示例
在控制器或业务逻辑中直接调用:
# 示例:HR标记离职面谈完成 employee = Employee.find(params[:id]) current_actor = current_user if employee.can_trigger_termination_event?(:mark_interview_done, current_actor) employee.trigger_termination_event(:mark_interview_done, current_actor) redirect_to employee_path(employee), notice: "离职面谈已标记完成" else redirect_to employee_path(employee), alert: "无权限执行此操作" end
六、可选优化点
- 配置验证:添加配置格式校验逻辑,防止无效YAML导致系统出错。
- 配置缓存:用
Rails.cache缓存组织配置,减少数据库或文件读取次数。 - 版本控制:给组织配置添加历史版本记录,支持回滚操作。
内容的提问来源于stack exchange,提问作者Mukul Jangid
相关产品推荐
相关产品推荐

