Workflow enterprises are essential to the development of an important component. With workflow, customer demand will greatly improve the speed of implementation, taking into account the development of efficiency, flexibility. Java has a number of stability in the area of workflow, Java has become the occupation of the development of a powerful enterprise-level aides. However, ROR field, there is no outstanding work flow occur. RubyForge has some of the work flow on projects, but a closer facie, are targeted at Java Workflow transplant, but can not be the extent practical. The face of this status quo, at my own in 2006 developed a small-scale Ruby job stream, although a small amount of code, but the practicality of it is true that for some real use cases can easily qualified, but also a strong supporting me to continue to the road before ROR行.

Here are my workflow how the implementation.



VC Writing with a workflow designer, this little software is relatively simple, including some simple drag and drop graphics and symbols, such as the beginning and end, a state of flow. For each state can set the permissions can be set for each flow condition. At my research in the field of workflow is not very deep, development of the designer on the principle of practicality, there is no special complexity of implementation. Be able to achieve at the basis of user needs how how easy to do.

Ruby on Rails environment workflow implementation



Save the file to xml format

<?xml version="1.0" encoding="gb2312" ?>
<workflow>
    <start right="" leave="" enter="@form.a2 = @user.truename&#x0D;&#x0A;@form.c2 = @user.department.name" x1="97" y1="156" x2="247" y2="279" />
    <end right="行政归档" x1="969" y1="148" x2="1129" y2="285" enter="" />
    <state name="部门经理审批" right="领导" enter="" leave="" x1="343" y1="179" x2="453" y2="253" />
    <state name="总经理审批" right="经理审批" enter="" leave="" x1="566" y1="34" x2="668" y2="98" />
    <state name="行政审批" right="行政审批" enter="" leave="" x1="717" y1="191" x2="870" y2="244" />
    <trasit name="" condition="" from="开始" to="部门经理审批" />
    <trasit name="大于等于3天" condition="@form.b5!=nil &amp;&amp; @form.b5 &gt;=3" from="部门经理审批" to="总经理审批" />
    <trasit name="" condition="@form.b5 == nil || @form.b5 &lt;3" from="部门经理审批" to="行政审批" />
    <trasit name="" condition="" from="总经理审批" to="行政审批" />
    <trasit name="" condition="" from="行政审批" to="结束" />
</workflow>



And this document to the system, from Ruby to resolve the job flow

Analytical Workflow Ruby code (on the lib directory) as follows:

#Flow.rb

require 'rexml/document'
require "State"
require "Trasit"
require "Flow"
require "pp"

include REXML

class Flow 
  attr_accessor :name, :publish_time
  attr_reader :trasits, :states
  def initialize(name, xmlstr, publish_time)
    @publish_time = publish_time
    @name = name
  	
    #存放所有状态,包括开始状态和结束,开始状态放在第一个,结束状态放在最后
    @states = Array.new
    @trasits = Array.new
  	
    #载入XML文档
    doc = Document.new(xmlstr)
  	
    #开始解析doc文档
    root = doc.root
  	
    #解析开始状态节点
    root.elements.each("start") {|element|
      start = State.start
      start.name = "开始"
      start.enter = element.attributes["enter"].gbk
      start.leave = element.attributes["leave"].gbk
      start.right = element.attributes["right"].gbk
      start.x1 = element.attributes["x1"].to_i
      start.x2 = element.attributes["x2"].to_i
      start.y1 = element.attributes["y1"].to_i
      start.y2 = element.attributes["y2"].to_i
      @states << start
      break
    }
  	
    #解析所有状态节点
    root.elements.each("state") {|element|
      state = State.new
      state.name = element.attributes["name"].gbk
      state.right = element.attributes["right"].gbk
      state.enter = element.attributes["enter"].gbk
      state.leave = element.attributes["leave"].gbk
      state.x1 = element.attributes["x1"].to_i
      state.x2 = element.attributes["x2"].to_i
      state.y1 = element.attributes["y1"].to_i
      state.y2 = element.attributes["y2"].to_i
      @states << state
    }
  	
    #解析结束状态节点
    root.elements.each("end") {|element|
      end_node = State.new
      end_node.name = "结束"
      end_node.right = element.attributes["right"].gbk
      end_node.enter = element.attributes["enter"].gbk
      end_node.x1 = element.attributes["x1"].to_i
      end_node.x2 = element.attributes["x2"].to_i
      end_node.y1 = element.attributes["y1"].to_i
      end_node.y2 = element.attributes["y2"].to_i
      
      @states << end_node
    }
    #解析所有流转
    root.elements.each("trasit") {|element|
      from_name = element.attributes["from"].gbk
      to_name = element.attributes["to"].gbk
  		
      for state in @states
        if state.name == from_name
          from_node = state
        end
        if state.name == to_name
          to_node = state
        end
      end  		
  		
      trasit = Trasit.new(from_node, to_node)
      trasit.name = element.attributes["name"].gbk
      trasit.condition = element.attributes["condition"].gbk
  		
      from_node.trasits << trasit
      to_node.guest_trasits << trasit
      @trasits << trasit	
    }
  end
  
  def start
    @states[0]
  end
  
  def get_state(name)
    for state in @states
      return state if state.name == name
    end
    nil
  end
  
end



#FlowMeta.rb

$LOAD_PATH.unshift(File.dirname(__FILE__))

require "Flow"
require "EncodeUtil"

class FlowMeta
  class << self
    def LoadAllFlows()
      YtLog.info "loading all workflow..."
      $Workflows.clear
      flows = YtwgWorkflow.find(:all)
      for flow in flows
        #LoadWorkFlow(flow.name, flow.content.sub!('<?xml version="1.0" encoding="gb2312" ?>', ''))
        LoadWorkFlow(flow.name, flow.content, flow.publish_time)
      end
    end
		
    def LoadWorkFlow(name, str, publish_time=Time.new)
      YtLog.info name
      $Workflows[name] = Flow.new(name, str, publish_time)
    end
		
    def Remove(name)
      $Workflows.delete(name)
    end
  end
end



#State.rb

##工作流中的状态

require "Trasit"
class State
  attr_accessor :name, :leave, :enter, :right, :trasits, :guest_trasits
  attr_accessor :x1, :x2, :y1, :y2
  def initialize
    #从此状态出发的流转
    @trasits = Array.new
    
    #从其他状态到此状态的流转
    @guest_trasits = Array.new
  end
	
  def trasits
    @trasits
  end
	
  def add_trasit(trasit)
    @trasits << trasit
  end
  
  def add_guest_trasit(trasit)
    @guest_trasits << trasit
  end
	
  class << self
    def start
      start = State.new
      start.name = "开始"
      start
    end
  end
end



#Trasit.rb

class Trasit
	attr_accessor :condition, :name, :from, :to
	
	#新建流转类,from,to均为State类对象
	def initialize(from, to)
		@from = from
		@to = to
	end
end



OK, analytic workflow even completed the mission, 250 line Ruby code, a small, customizable and high even if the workflow engine are completed. Here we look how to use this work flow.



Workflow engine automatically after the completion of the following users will think of flow at each point to see form interface come from? For this feature, I wrote a specialized form design and form analytic engine, analytic engine form xml format can be translated into html form format forms. This form more complex components beyond the scope of this discussion, let us not refer to the time being.



Say what the following database tables, in order to use the workflow engine required three tables set up

//工作流表
CREATE TABLE `ytwg_workflow` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(100) default NULL,          //工作流名称
  `content` longtext,                                   //工作流内容,设计器保存的xml文件
  `publish_time` datetime default NULL,     //发布时间
  `formtable` varchar(30) default NULL,      //表单数据存放的表格,每个工作流建立后会单独建立数据库表,存放表单数据
  `position` int(11) default NULL,                 //排序位置
  `reserved1` varchar(100) default NULL,    
  `reserved2` varchar(100) default NULL,
  `reserved3` varchar(100) default NULL,
  `reserved4` varchar(100) default NULL,
  `reserved5` varchar(100) default NULL,
  `reserved6` varchar(100) default NULL,
  PRIMARY KEY  (`id`)
) 

//工作流状态表单界面表
CREATE TABLE `ytwg_stateinterface` (
  `id` int(11) NOT NULL auto_increment,
  `flowid` int(11) default NULL,                     //工作流id
  `name` varchar(100) default NULL,            //状态名称
  `content` longtext,                                     //表单,表单设计器保存的xml文件
  `publish_time` datetime default NULL,       //发布时间
  `reserved1` varchar(100) default NULL,     
  `reserved2` varchar(100) default NULL,
  `reserved3` varchar(100) default NULL,
  `reserved4` varchar(100) default NULL,
  `reserved5` varchar(100) default NULL,
  `reserved6` varchar(100) default NULL,
  PRIMARY KEY  (`id`)
) 

//表单处理记录表
CREATE TABLE `ytwg_formhistory` (
  `id` int(11) NOT NULL auto_increment,
  `userid` int(11) default NULL,                    //用户id
  `flowid` int(11) default NULL,                     //工作流id
  `formid` int(11) default NULL,                     //表单id
  `process_time` datetime default NULL,      //处理时间
  PRIMARY KEY  (`id`)
) 






Each release of a job stream, the following should work for the dynamic creation of database tables, stored form data. Me through to this workflow template to publish a form to dynamically create the table.





Look below how to use workflow

#发布工作流  
def create
    stream = params[:ytwg_workflow][:content]
    content = stream.read
    name = stream.original_filename[0, stream.original_filename.index(".")]
    if YtwgWorkflow.find(:all, :conditions=>"name='#{name}'").size > 0
      flash[:error] = "存在同名工作流,上传失败"
      render :action => 'new'
      return
    end
    
    @ytwg_workflow = YtwgWorkflow.new()
    @ytwg_workflow.name = name
    begin
      @ytwg_workflow.content = content
    rescue
      flash[:error] = "上传文件非法"
      render :action => 'new'
    end
    @ytwg_workflow.publish_time = Time.new
    if @ytwg_workflow.save
      FlowMeta.LoadWorkFlow(@ytwg_workflow.name, @ytwg_workflow.content.sub!('<?xml version="1.0" encoding="gb2312" ?>', ''))
      flash[:notice] = '添加工作流成功'
      redirect_to :action => 'list'
    else
      flash[:error] = "添加工作流失败"
      render :action => 'new'
    end
  end

  #上传表定义模板,根据这个表单动态生成数据库表
  def upload_formtable
    stream = params[:content]
    content = stream.read
    helper = XMLHelper.new
    helper.ReadFromString(content)
    formtable = helper.tables[0]
    if !formtable
      flash[:notice] = "上传文件格式错误"
      redirect_to :action=>"listinterface"
      return
    end
    conn = ActiveRecord::Base.connection
    conn.create_table "ytwg_#{formtable.GetTableID}", :primary_key=>:id do |t|
      t.column "userid", :integer         #流程发起人的id
      t.column "flowid", :integer         #工作流的id
      Integer(0).upto(formtable.GetRowCount()-1) do |row|
        next if formtable.IsEmptyRow(row)
        Integer(0).upto(formtable.GetColumnCount()-1) do |col|
          next if formtable.IsEmptyCol(col)
          cell = formtable.GetCell(row, col)
          next if !cell.IsStore || !cell.IsEffective
          next if formtable.GetCellDBFieldName(row, col).downcase == "id"
    	     
          t.column "_state", :string, :limit=>30
          t.column "_madetime", :datetime
          t.column "_lastprocesstime", :datetime
          if cell.GetDataType == 1    #CCell.CtNumeric
            t.column formtable.GetCellDBFieldName(row, col).downcase, :float
          elsif cell.GetDataType == 0    #CCell.CtText               
            if cell.IsCheckWidth()
              t.column formtable.GetCellDBFieldName(row, col).downcase, :string, {:limit=>cell.GetTextWidth}
            else
              t.column formtable.GetCellDBFieldName(row, col).downcase, :string, {:limit=>100}
            end     
          elsif cell.GetDataType == 3 #CCell.CtDate
            t.column formtable.GetCellDBFieldName(row, col).downcase, :datetime
          end     	     
        end
      end
    end
    
    flow = YtwgWorkflow.find(params[:id])
    flow.formtable = formtable.GetTableID
    flow.save
    
    flash[:notice] = "建表成功"
    redirect_to :action=>"listinterface"
  end

  #上传状态节点的表单界面
  def uploadinterface
    stream = params[:content]
    content = stream.read
    
    interfaces = YtwgStateinterface.find(:all, :conditions=>"flowid = #{params[:id]} and name = '#{params[:name]}'")
    if interfaces.size > 0
      interface = interfaces[0]
      interface.publish_time = Time.new
    else
      interface = YtwgStateinterface.new
      interface.flowid = params[:id]
      interface.name = params[:name]
      interface.publish_time = Time.new
    end
    interface.content = content  #EncodeUtil.change("UTF-8", "GB2312", content)
    interface.save
    flash[:notice] = "上传状态界面成功"
    redirect_to :action=>"listinterface"
  end

  #用户点击某一工作流连接后,查看自己已经发起的工作流。
  def show_form
    @flow = YtwgWorkflow.find(params[:flowid])
    YtwgForm.set_table_name("ytwg_" + @flow.formtable)
    YtwgForm.reset_column_information() 
    form = YtwgForm.find(params[:formid])
     
    interfaces = YtwgStateinterface.find(:all, :conditions=>"flowid=#{params[:flowid]} and name='#{form._state.split(',')[0]}'")
    if interfaces.size > 0
      helper = XMLHelper.new
      helper.ReadFromString(interfaces[0].content)
      @style = helper.StyleToHTML(helper.tables[0])
      @html = helper.TableToEditHTML(helper.tables[0], helper.dictionFactory, 
        {:record=>form, :encoding=>"gb2312"})
      @historys = YtwgFormhistory.find(:all, :conditions=>"flowid=#{params[:flowid]} and formid = #{params[:formid]}")
    else
      render :text=>"没有上传工作流界面"
    end
  end

  #用户发起或者审批一个表单
  def write_form
    @flow = YtwgWorkflow.find(params[:flowid])
    YtwgForm.set_table_name("ytwg_" + @flow.formtable)
    YtwgForm.reset_column_information() 

    if params[:formid]
      form_record = YtwgForm.find(params[:formid])
      state_name = form_record._state
    else
      form_record = YtwgForm.new
      form_record._state = '开始'
      state_name = form_record._state
    end
    
    states = []
    for state in form_record._state.split(',')
      states << state if checkright(state)
    end
    if states.size > 0
      state_name = states[0]
    else
      state_name = '开始'
    end

    process = FlowProcess.new($Workflows[@flow.name], form_record, state_name)
    process.user = session[:user]
    process.signal_enter
    
    interfaces = YtwgStateinterface.find(:all, :conditions=>"flowid=#{@flow.id} and name = '#{state_name}'")
    if interfaces.size ==0
      render :text=>"没有上传开始界面"
      return
    end
    @start_interface = interfaces[0]
    helper = XMLHelper.new
    helper.ReadFromString(@start_interface.content)
    @style = helper.StyleToHTML(helper.tables[0])
    @html = helper.TableToEditHTML(helper.tables[0], helper.dictionFactory, 
      {:record=>form_record,:encoding=>"gb2312", :script=>helper.script})
    @historys = YtwgFormhistory.find(:all, :conditions=>"flowid=#{params[:flowid]} and formid = #{params[:formid]}") if params[:formid]
  end

  #用户写完一个表单后点击提交
  def update_form
    @flow = YtwgWorkflow.find(params[:id])
    YtwgForm.set_table_name("ytwg_" + @flow.formtable)
    YtwgForm.reset_column_information() 
    if params[:formid]
      form = YtwgForm.find(params[:formid])
      form.update_attributes(params[@flow.formtable])
      
      states = []
      for state in form._state.split(',')
        states << state if check_state_right(@flow.name, state)
      end
      state_name = states[0]
    else
      form = YtwgForm.new(params[@flow.formtable])
      form._madetime = Time.new
      form._state = '开始'
      state_name = form._state
      form.userid = session[:user].id
      form.flowid = @flow.id
    end

    form._lastprocesstime = Time.new    
    process = FlowProcess.new($Workflows[@flow.name], form, state_name)
    process.user = session[:user]
    process.signal_leave
    
    history = YtwgFormhistory.new
    history.userid = session[:user].id
    history.flowid = @flow.id
    history.formid = form.id
    history.process_time = Time.new
    history.save
    redirect_to :action=>'myform', :id=>params[:id]
  end

 #等待我处理的流程
  def show_waiting_form
    @forms = get_wait_form(params[:id])
    render :layout=>false
  end

 #获得某一种单据中等待当前登陆者审批的
  def get_wait_form(flowid)
    forms = []
    flow = YtwgWorkflow.find(flowid)
    if !flow.formtable || flow.formtable.size==0
      return forms
    end
    YtwgForm.set_table_name("ytwg_" + flow.formtable)
    YtwgForm.reset_column_information() 
    for state in $Workflows[flow.name].states
      next if state.name == "结束"
      
      conditions = []
      conditions << "_state='#{state.name}'"
      
      #如果可以从多个状态转移到这个状态,则等待所有状态都执行完此状态才可以执行
      if state.guest_trasits.size == 1      #只可以从一个状态转到这里
        conditions << " _state like '%,#{state.name}'"
        conditions << "_state like '#{state.name},%'"
      end

      if state.right == "领导"
        all_forms = YtwgForm.find(:all, :conditions=>conditions.join(' or '), :order=>"id desc")
        for form in all_forms
          forms << form if YtwgUser.find(form.userid).department.leader_id == session[:user].id rescue nil
        end
      else
        for right in state.right.split(',')
          if checkright(right)
            forms += YtwgForm.find(:all, :conditions=>conditions.join(' or '), :order=>"id desc")
          end
        end
      end
    end
    forms.uniq!
    return forms
  end



With the above function of the core, how to use the basic job flow even understand. In addition there are many additional features required to achieve small, such as export Excel, export PDF, at page display workflow flow chart (I use the VML implementation). Following is a registration form leave the form shows the interface:





The workflow application status:

The current workflow has not yet commercial, there is only one application scenario. The first few months of our company and bought a set of OA, at train salesmen run mouthed puff flower Manager 9800, we bought, and later found the implementation of workflow and simply can not use. Leave a simple application form can not be realized in a custom form and flow, but under me based on my workflow components, rapid development of a set of OA, after a few days on the last line, then side by side perfect, one-month After very little moved. At present, this company is quite satisfied with the OA. Although OA of my outstanding OA than domestic products have a big gap, but this set of workflow components, or at least be able to do the majority of occasions, can not meet for the occasion can also be flexible expansion.



Welcome to everyone for their valuable opinions, the field has at ROR Workflow friends worry everyone can explore.