Ruby on Rails environment workflow implementation

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 RORLine .

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="The archive " x1="969" y1="148" x2="1129" y2="285" enter="" />
    <state name="Department Manager approval " right="Leadership " enter="" leave="" x1="343" y1="179" x2="453" y2="253" />
    <state name="General Manager approval " right="Manager approval " enter="" leave="" x1="566" y1="34" x2="668" y2="98" />
    <state name="Administrative approval " right="Administrative approval " enter="" leave="" x1="717" y1="191" x2="870" y2="244" />
    <trasit name="" condition="" from="Start " to="Department Manager approval " />
    <trasit name="Greater than or equal to 3 days " condition="@form.b5!=nil &amp;&amp; @form.b5 &gt;=3" from="Department Manager approval " to="General Manager approval " />
    <trasit name="" condition="@form.b5 == nil || @form.b5 &lt;3" from="Department Manager approval " to="Administrative approval " />
    <trasit name="" condition="" from="General Manager approval " to="Administrative approval " />
    <trasit name="" condition="" from="Administrative approval " to="End " />
</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
  	
    #Storage of all States, including start and end, State placed in the first, the end state placed last 
    @states = Array.new
    @trasits = Array.new
  	
    #Load an XML document 
    doc = Document.new(xmlstr)
  	
    #Start resolution doc documents 
    root = doc.root
  	
    #Resolution start status node 
    root.elements.each("start") {|element|
      start = State.start
      start.name = "Start "
      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
    }
  	
    #Resolve all status node 
    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
    }
  	
    #Resolution to end the State of the node 
    root.elements.each("end") {|element|
      end_node = State.new
      end_node.name = "End "
      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
    }
    #Resolve all circulation 
    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

##The State in the workflow 

require "Trasit"
class State
  attr_accessor :name, :leave, :enter, :right, :trasits, :guest_trasits
  attr_accessor :x1, :x2, :y1, :y2
  def initialize
    #From this state of the circulation 
    @trasits = Array.new
    
    #From other States to this state of circulation 
    @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 "
      start
    end
  end
end


#Trasit.rb

class Trasit
	attr_accessor :condition, :name, :from, :to
	
	#New conversion classes, from ,toAre State class object 
	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

//The workflow table 
CREATE TABLE `ytwg_workflow` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(100) default NULL,          //Workflow name 
  `content` longtext,                                   //Workflow content, the designer to save the XML file 
  `publish_time` datetime default NULL,     //Release time 
  `formtable` varchar(30) default NULL,      //Form data stored in the table, each workflow once separate building a database table storing form data 
  `position` int(11) default NULL,                 //Sort position 
  `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`)
) 

//The workflow status form interface tables 
CREATE TABLE `ytwg_stateinterface` (
  `id` int(11) NOT NULL auto_increment,
  `flowid` int(11) default NULL,                     //The workflow ID 
  `name` varchar(100) default NULL,            //The state name 
  `content` longtext,                                     //Form, the form designer to save the XML file 
  `publish_time` datetime default NULL,       //Release time 
  `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`)
) 

//Forms processing record table 
CREATE TABLE `ytwg_formhistory` (
  `id` int(11) NOT NULL auto_increment,
  `userid` int(11) default NULL,                    //User ID 
  `flowid` int(11) default NULL,                     //The workflow ID 
  `formid` int(11) default NULL,                     //The form ID 
  `process_time` datetime default NULL,      //Processing time 
  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

#Publishing workflows 
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] = "Exists workflow, upload failed "
      render :action => 'new'
      return
    end
    
    @ytwg_workflow = YtwgWorkflow.new()
    @ytwg_workflow.name = name
    begin
      @ytwg_workflow.content = content
    rescue
      flash[:error] = "Upload file illegal "
      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] = 'Add workflow success '
      redirect_to :action => 'list'
    else
      flash[:error] = "Add workflow to fail "
      render :action => 'new'
    end
  end

  #Uploading table definition template, on the basis of this form dynamically generated database table 
  def upload_formtable
    stream = params[:content]
    content = stream.read
    helper = XMLHelper.new
    helper.ReadFromString(content)
    formtable = helper.tables[0]
    if !formtable
      flash[:notice] = "Upload file format error "
      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         #The ID of the process initiator 
      t.column "flowid", :integer         #The ID of the workflow 
      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] = "Table successfully "
    redirect_to :action=>"listinterface"
  end

  #Upload status node form interface 
  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] = "Upload status interface successfully "
    redirect_to :action=>"listinterface"
  end

  #The user clicks on a workflow connection, you see your own workflow has been initiated. 
  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=>"There is no upload workflow interface "
    end
  end

  #User-initiated or approving a form 
  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 = 'Start '
      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 = 'Start '
    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=>"There is no upload start interface "
      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

  #The user finished a form click the submit 
  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 = 'Start '
      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

 #Wait for me to handle processes 
  def show_waiting_form
    @forms = get_wait_form(params[:id])
    render :layout=>false
  end

 #Get some kind of documents for the current login's approval of 
  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 == "End "
      
      conditions = []
      conditions << "_state='#{state.name}'"
      
      #If you can transfer from more than one State to the State, wait for all State after you do this State can execute 
      if state.guest_trasits.size == 1      #Only you can go from one State to here 
        conditions << " _state like '%,#{state.name}'"
        conditions << "_state like '#{state.name},%'"
      end

      if state.right == "Leadership "
        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.
  • del.icio.us
  • StumbleUpon
  • Digg
  • TwitThis
  • Mixx
  • Technorati
  • Facebook
  • NewsVine
  • Reddit
  • Google
  • LinkedIn
  • YahooMyWeb

Related Posts of Ruby on Rails environment workflow implementation

  • The EJB3 Persistence

    EJB3 persistence with Hibernate is very similar to the mechanism: Environment: Server: JBOSS5.0 Database: MySQL5.0 1. Set up a data source First of all, in jboss-5.0.0.GA \ server \ default \ deploy, the establishment of a database used to connect the dat

  • hibernate to use the principle of

    The use of hibernate, implementation of data persistence. Has the following several processes. One configuration database connection information. Hibernate.config 2 configuration mapping. 3 use: the use of the process are the following steps: 3.1: Ge ...

  • Servlet brief introduction

    Servlet brief introduction: Servlet is a small application server Are used to complete the B / S architecture, the client requests the response to treatment Platform independence, performance, able to run thread Servlet API for Servlet provides the s ...

  • can not be represented as java.sql.Timestamp

    Development of procedures for the use of hibernate when, some time there is no need to fill in the fields, but after the hibernate query time reported "Java.sql.SQLException: Value'0000-00-00 'can not be represented as java.sql.Timestamp ...

  • Spring2.0 + hibernate3.1 + log4j + mysql demo

    applicationContext.xml Non-attachment jar package, necessary friends can send an email to todd.liangt @ gmail.com

  • Struts2 + hibernate + spring problem user log in

    dao layer services layer action jsp <tr> <td align="center"> <b> user name: </ b> </ td> <td> <s: textfield name = "czyNumber" cssClass = "textstyle" theme = "simple" size = &q

  • Based on JDBC, JPA Annotation achieve simple CRUD Generic Dao

    The origin of ideas are pretty long history of reasons: [Use iBATIS history] The use of iBATIS has been a long time, the system is to use the CRUD template tool to generate the code, although there are tools to generate, but looked at a lot of CRUD the Sq

  • Hibernate secondary cache

    Hibernate cache: 2-bit cache, also known as process-level cache or SessionFactory level cache, secondary cache can be shared by all of the session Cache configuration and the use of: Will echcache.xml (the document code in hibernate package directory ...

  • Hibernate's lazy strategy

    hibernate Lazy strategy can be used in: <class> tag, it can be true / false Tags can <PROPERTY> values true / false type of necessary tools to enhance <set> <list> can tag values true / false / extra <many-to-one> <on ...

blog comments powered by Disqus
Recent
Recent Entries
Tag Cloud
Random Entries