Stories on maker education and innovation 

Home open source Using Ruby and Code Generation To Save Time
formats

Using Ruby and Code Generation To Save Time

Speed

Working in information systems as a programmer, you often find yourself writing boring repetitious code over and over again. Think about it. Let’s imagine you are writing code to access the following table. How many times will you type those property names?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE user(
id VARCHAR(50) ,
user_name VARCHAR(50) ,
first_name VARCHAR(140) ,
last_name VARCHAR(140) ,
zip VARCHAR(20) ,
email VARCHAR(140) ,
enable_flag INT ,
password VARCHAR(140) ,
created_by VARCHAR(50) ,
updated_by VARCHAR(50) ,
created_at INT ,
updated_at INT ,
PRIMARY KEY ( id )
);

In typical business applications, you will repeat the column names of this table in a data transfer object, a repository class, unit test code, validation code and controller classes. It’s not the most exciting code, it, however, needs to get done. Inspired by talks by Kathleen Dollard, consultant and code generation promoter in the .NET community, I decided to build a small code generation tool for myself that would help save me time. I wanted to share parts of my code generation experiments with you. I hope the case study helps you in designing your own code generation strategy.

Building a Simple Code Generation Tool

To give myself a simple start, I decided to express my entities using XML Schema Definition(XSD), an open XML standard for describing the structure of information. XSD is pretty easy to code by hand. In the .NET world, it’s pretty simple to generate a XSD from your database using DataAdapters or other methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="user">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="id" type="xs:string"/> 
      <xs:element name="user_name" type="xs:string"/>
      <xs:element name="first_name" type="xs:string"/>
      <xs:element name="last_name" type="xs:string"/>
      <xs:element name="zip" type="xs:string"/>
      <xs:element name="email" type="xs:string"/>
      <xs:element name="enable_flag" type="xs:int"/>
      <xs:element name="password" type="xs:string"/>
      <xs:element name="created_by" type="xs:string"/>
      <xs:element name="updated_by" type="xs:string"/>
      <xs:element name="created_at" type="xs:dateTime"/>
      <xs:element name="updated_at" type="xs:dateTime"/>     
      </xs:sequence>
  </xs:complexType>
</xs:element>

For this example, we will store the XSD in a file called “user.xsd.”

Using IronRuby, I created a class that would convert an entity expressed in XSD into code. IronRuby is an open source implementation of the Ruby language for the .NET framework. The IronRuby project enables programmers to blend the capabilities of the .NET framework and Ruby. When the “CodeGen” class is constructed, you provide a schema file, table name, a template, and namespace references.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/env ruby
load_assembly "System.Data"
include System::Data
require 'erb'

class CodeGen
  def camel_case(s)
    s.gsub(/(?<=_|^)(\w)/){$1.upcase}.gsub(/(?:_)(\w)/,'\1')
  end

  def initialize(schema_file, table_name, template_file, ui_namespace, model_namespace)
    if schema_file == NIL
      raise ArgumentError.new("schema_file is NIL")
    end

    if table_name == NIL
      raise ArgumentError.new("table_name is NIL")
    end

    if template_file == NIL
      raise ArgumentError.new("template_file is NIL")
    end
   
    if ui_namespace == NIL
        raise ArgumentError.new("ui_namespace is required.")
    end

    if model_namespace == NIL
        raise ArgumentError.new("model_namespace is required.")
    end
       
    if !File.exists?(schema_file)
      raise ArgumentError.new("Schema file does not exist: #{schema_file}" )
    end

    if !File.exists?(template_file)
      raise ArgumentError.new("template_file does not exist: #{template_file}" )
    end

    # read schema file....
    @data_set = System::Data::DataSet.new()
    @data_set.ReadXml(schema_file)
   
    #get table ref....
    @table = @data_set.Tables[table_name]
    @entity_name = @table.TableName.capitalize
    @table_name = @table.TableName
    @ui_namespace = ui_namespace
    @model_namespace = model_namespace
    @last_col = @table.Columns[@table.Columns.Count - 1].ColumnName
   
   
    #load ERB templates ....
    # http://stackoverflow.com/questions/980547/how-do-i-execute-ruby-template-files-erb-without-a-web-server-from-command-line    
    @template = ERB.new File.new(template_file).read, nil, ">"
       
    end
 
  def generate()
      return @template.result(binding)
  end
 
  def get_human_db_ref(strDbRef)
    strDbRef.capitalize.gsub("_"," ")
  end
   
end

I do acknowledge that we could have built this code generation framework using other technologies like T4, another template technology available for .NET. I, however, enjoyed getting to learn Ruby and ERB. Ruby is just a fun dynamic language! It’s nice that you can quickly edit the templates without compiling.

In the following lines, we create a “DataSet” object to read the schema data into memory. This enables the code generation templates to know the columns defined for the entity and their respective data types.

1
2
@data_set = System::Data::DataSet.new()
@data_set.ReadXml(schema_file)

Our templates that we will write depend upon several pieces of data: the schema data, entity name, table name, and other properties. We store these elements as properties on the “CodeGen” object. By doing this, the properties become available to the ERB template system.

1
2
3
4
@table = @data_set.Tables[table_name]
@entity_name = @table.TableName.capitalize
@table_name = @table.TableName
...

In the following line, we use a ERB template to convert the table data into code.

1
@template = ERB.new File.new(template_file).read, nil, ">"

The following code, shows a code template for generation an entity class in C#. The ERB template utilizes the standard ADO.NET methods available on a DataTable class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

namespace <%=@model_namespace%>
{
    public class <%= @entity_name %>
    {

        <% for col in @table.Columns %>
        <%= col.DataType.to_s() %> <%= camel_case(col.ColumnName.to_s()) %>{get; set; }
        <% end %>      
       
        public <%= @entity_name %> ()
        {

        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env ruby
require 'CodeGen'
require 'fileutils'

def rscg(schema_file, table_name, template_file, output_file, ui_namespace, model_namespace)
  code_gen = CodeGen.new(schema_file, table_name, template_file, ui_namespace, model_namespace)
  output = code_gen.generate()
  aFile = File.new(output_file, "w")
  aFile.write(output)
  aFile.close
end

rscg("user.xsd", "user", "buildEntity.erb", "c:\\output\\#{table}.cs", "MyUINameSpace", "MyModelNameSpace")

RSCG stands for “real simple code generation.” To execute the code generation process on one entity, you need to provide the XSD file, a table name, a template file name, an output file, and name space references.

1
rscg("user.xsd", "user", "buildEntity.erb", "c:\\output\\#{table}.cs", "MyUINameSpace", "MyModelNameSpace")

In researching this blog post, I discovered many more potential code generation solutions. The listing include commercial offerings and open source options.

http://en.wikipedia.org/wiki/Comparison_of_code_generation_tools

On my weekend programming projects, this simple code generation idea has saved me lots of time. I’m currently adjusting my framework to generate unit test stubs, repository classes, entity classes, user interface, I hope you find the ideas helpful on your own projects.

Featured Posts on InspiredToEducate.NET

Photo from http://netdna.webdesignerdepot.com/uploads/2013/02/featured20@wdd2x.jpg

 
 Share on Facebook Share on Twitter Share on Reddit Share on LinkedIn
No Comments  comments 
© Inspired To Educate
credit