Friday, February 20, 2009

Defining Enum Tables

Back in January I posted about "Defining Many To Many" tables; and now (to take a note from the Colbert Report), here is Part 2 in my infinite part series...

Better Know an ORM Programmatic Definition: Defining Enum Tables!

The idea here is that we want to generate Enums from database tables; so for each table we specify as an Enum table, we want create an Enum and populate it's values from the rows in that table. As always, the goal is to make this solution be as generic as possible. We want this to be able to work on pretty much any database we throw at it, we want it to check for any usual pitfalls, and of course we want what we generate to be as useful as possible! Let's begin with an example...

Example Table (Input)

Table Name: StarTrek
Columns: Id (int), Name (string), Captain (string)
Row 1: "Original", "James Tiberius Kirk"
Row 2: "Animated", "James Tiberius Kirk"
Row 3: "TNG", "Jean-Luc Picard"
Row 4: "DS9", "Benjamin Sisko"
Row 5: "Voyager", "Kathryn Janeway"
Row 6: "Enterprise", "Scott Bakula? Seriously?"

Example Enum (Output)

public enum StarTrek : int
{
    /// <summary>James Tiberius Kirk</summary>
    Original = 1,
...etc...
    /// <summary>Scott Bakula? Seriously?</summary>
    Enterprise = 6
}

So, what is the logic?

1) Explicitly identify select the table.

While associations can be determined by examining keys and other qualities, Enum tables just don't have enough unique qualities to identify in that manner; thus we will want to explicitly choose our Enum tables. For this task I recommend a Regex for Enum table names; however you could always use a good old fashion table list. Now that we have identified that the table SHOULD be an Enum, we need to determine if it CAN be an Enum table...

2) The table must have a primary key.

Enums values can be assigned a numeric value, so to allow for association mapping and logical comparisons it's a good idea that our generated Enums are assigned meaningful values at generation time. NOTE: While assigning a numeric value to Enum values can server many different purposes, the following was specifically chosen because it allows for Enums to act as database associations for business entity objects.

2.A) The table must have a primary key composed of a single column. (It's hard to have a composite key that evaluates to a single numerical value.)

2.B) The primary key column must be of a number type. (This is so that the Enum values can be assigned to the key value.)

3) The table must have a column to specify the values.

Well if the table is the Enum itself, where are the values going to come from? You have to chose which column the value is going to come out of! Again I recommend using a Regex to find this column by name, but if that fails (or if you are feeling lazy) you could default to taking the first column of a string type.

4) The table must have at least one row.

Firstly, this is because there's not a lot of use for an empty Enum; but also, some languages (such as VB) don't support it.

Additionally

When generating the enums, it might come in handy to generate the description for each value as well (as we did in the example above); so, in the code below is an extra function for finding that description with (surprise surprise) a Regex.

And finally, here is what your code might look like...

public static Regex EnumTableNameRegex = new Regex("(E|e)num$", RegexOptions.Compile);
public static Regex EnumValueColumnRegex = new Regex("((V|v)alue)|((N|n)ame)|((T|t)ype(C|c)ode)", RegexOptions.Compile);
public static Regex EnumDescriptionColumnRegex = new Regex("(D|d)esc", RegexOptions.Compile);

public bool IsEnum(TableSchema table)
{
    return EnumTableNameRegex.IsMatch(table.Name)                            // 1) Matches the enum regex.
        && table.PrimaryKey != null                                                          // 2) Has a Primary Key...
        && table.PrimaryKey.MemberColumns.Count == 1                         // a) ...that is a single column...
        && IsNumeric(table.PrimaryKey.MemberColumns[0].SystemType)   // b) ...of a number type.
        && !string.IsNullOrEmpty(GetEnumNameColumnName(table))         // 3) Contains a column for name.
        && table.GetTableData().Rows.Count > 0;                                      // 4) Must have at least one row.
}

private bool IsNumeric(Type t)
{
    return t == typeof(byte)
           || t == typeof(sbyte)
           || t == typeof(short)
           || t == typeof(ushort)
           || t == typeof(int)
           || t == typeof(uint)
           || t == typeof(long)
           || t == typeof(ulong);
}

public string GetEnumValueColumn(TableSchema table)
{
    string result = GetEnumColumn(table, EnumValueColumnRegex);

    // If no Regex match found, use first column of type string.
    if (string.IsNullOrEmpty(result))
        foreach (ColumnSchema column in table.Columns)
            if (column.SystemType == typeof(string))
            {
                result = column.Name;
                break;
            }

    return result;
}

private string GetEnumColumn(TableSchema table, Regex regex)
{
    string result = string.Empty;

    foreach (ColumnSchema column in table.Columns)
        if (regex.IsMatch(column.Name))
        {
            result = column.Name;
            break;
        }

    return result;
}

public string GetEnumDescriptionColumn(TableSchema table)
{
    return GetEnumColumnName(table, EnumDescriptionExpressions);
}

Monday, January 19, 2009

5.1 Preview: Insert Class Merge Strategy

Coming in v5.1, the Insert Class Merge Strategy will be CodeSmith's third native Merge Strategy. The premise is simple: Insert your template output into a previously existing class in the output file.

At first this may sound very similar to the Insert Region Merge Strategy, and indeed it did start out that way; however, this Merge Strategy has many additional settings and features that separate it from it's other fellow Merge Strategies, and that make it a very robust and powerful tool.

I think the best way to describe this Merge Strategy is through example; but before we can do that, we must first go over it's configuration options...

Configuration Options

Language (String, Required)
Only supports C# and VB.

ClassName (String, Required)
Name of the class to insert into.

PreserveClassAttributes (Boolean, defaults to False)
Whether or not the the merge should preserve the existing classes attributes.
By default, the merge tries to replace the entire existing class, which includes the attributes on the top of class; this option leaves the attributes from the top of the original class.

OnlyInsertMatchingClass (Boolean, defaults to False)
Insert the whole template output, or just the matching class.

MergeImports (Boolean, defaults to False)
Merge the import/using statements of the existing file and generated output.

NotFoundAction (Enum, defaults to None)
What to do if the class is not found in the existing file. There are three options...
None: Don't merge anything, just leave the existing file as is.
InsertAtBottom: Append the output of the template to the bottom of existing file.
InsertInParent. Insert the output of the template at the bottom of a speficied parent section (specified by the NotFoundParent property).

NotFoundParent (String, no default)
If you specified InsertInParent for the NotFoundAction configuration, you must specify a name for the parent region.
This can be the name of a Region or a Class.

Example Configuration...

Language: C#
ClassName: "Pet"
PreserveClassAttributes: True
OnlyInsertMatchingClass: True
MergeImports: True

...Existing File...

using System;
using System.ComponentModel.DataAnnotations;

namespace Petshop
{
    [ScaffoldTable(true)]
    public class Pet
    {
        public int Age { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

...Generated Output...

using System;
using System.Text;

namespace Petshop
{
    public class Pet
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string FullName
        {
            get { return String.Format("{0} {1}", FirstName, LastName); }
        }
    }
}

...Insert Class Merge Strategy Result!

using System;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace Petshop
{
    [ScaffoldTable(true)]
    public class Pet
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string FullName
        {
            get { return String.Format("{0} {1}", FirstName, LastName); }
        }
    }
}

Thursday, January 8, 2009

Defining Many To Many

There are a lot of simple tasks that we humans can do with little effort, but when put into logic, can often become quite difficult to define. A great example of this is Many To Many Table Associations.

So, how do you programatically take a table and tell if it is a many to many association? My train of thought started off simple: the table has two foreign key columns. Then the flaws started rolling in...

  1. Regular tables could have two for foreign keys.
  2. It can't just be two columns, there could be time stamps or version numbers.
  3. It can't just be two key columns, because there could be composite keys.
  4. It may or may not have a primary key.
  5. The primary key could be a composite key on the foreign keys.

...it seems that I had taken my human brain (specifically it's mad pattern recognition skillz) for granted! :) So after a brief discussion in the office (and on our forums), we came up with the following logic:

  1. Table must have Two ForeignKeys.
  2. All columns must be either...
    1. Member of the Primary Key.
    2. Member of a Foreign Key.
    3. A DateTime stamp (CreateDate, EditDate, etc).
    4. Match our Row Version regular expression.

Of course, there could always be other things out there that we didn't think of. In this world there are many technologies, with many conventions, used by many programmers, all unique in there own way. So, unfortunately, there is no truly simple answer, nor is there a perfect solution...however, that is why we here at CodeSmith always try to be as generic and flexible as possible in our designs! Also, it's why we love to use things like Extended Properties, and how we get our last criteria:

0) Bypass logic if table contains Extended Property for ManyToMany

So, finally, here is what the code might look like...

Note: This is (a slightly modified version of) what is in our NHibernate templates.

public static bool IsManyToMany(TableSchema table)
{
    // 0) Bypass logic if table contains Extended Property for ManyToMany
    if (table.ExtendedProperties.Contains("CS_ManyToMany"))
        return true;

    // 1) Table must have Two ForeignKeys.
    // 2) All columns must be either...
    //    a) Member of the Primary Key.
    //    b) Member of a Foreign Key.
    //    c) A DateTime stamp (CreateDate, EditDate, etc).
    //    d) Name matches Version Regex.

    if(table.ForeignKeys.Count != 2)
        return false;

    bool result = true;
    versionRegex = new Regex("(V|v)ersion");

    foreach (ColumnSchema column in table.Columns)
    {
        if (!( column.IsForeignKeyMember
            || column.IsPrimaryKeyMember
            || column.SystemType.Equals(typeof(DateTime))
            || versionRegex.IsMatch(column.Name)))
        {
            result = false;
            break;
        }
    }

    return result;
}

Tuesday, December 16, 2008

Automated Testing w/ Selenium!

We here at CodeSmith recently started using Selenium to test our webapps.

If you haven't heard of Selenium, or you have been thinking about trying to create tests for any of your websites or web apps but didn't know where to start, go check it out! (Some links can be found at the bottom of the post.)

While Selenium is a great tool, and the guys at ThoughtWorks did a great job, Selenium definitely lacking in the same department as most all of it's open source brethren: documentation. Thus, this is my attempt to try and help out!

Our Automation Goals

Once we got our unit tests working, we wanted a quick and easy way to automate them; the goal was to be able to open N-Unit, run tests, and not have to worry about configuration and server connections. Also, this worked out well for running our unit tests with our build for continuous integration!

Disclaimer: For large scale or enterprise testing, there are better ways of accomplishing this (such as leaving the server running on a dedicated testing box), but we were just looking for a quick and easy way to run our tests!

Our Automation Implementation 

We are just running the Selenium Server out of the Test Fixture Setup; we launch it as a process, run our tests, then close it in the Test Fixture Tear Down.

First, we created a class to pull my settings from the App.Config...

public static class SeleniumAppSettings
{
    public static string ServerHost
    {
        get { return ConfigurationManager.AppSettings["SeleniumServerHost"] ?? "localhost"; } 
    }

...etc, etc, I only mention this so you know where I'm getting the values later!

Second, we create an abstract class for our Tests to inherit from...

public abstract class SeleniumTestBase
{
    private Process _process;
    protected ISelenium selenium;

    [TestFixtureSetUp]
    public void TestFixtureSetUp()
    {
        string arguments = String.Format("-jar \"{0}\" -firefoxProfileTemplate \"{1}\"",
            SeleniumAppSettings.ServerJar,
            SeleniumAppSettings.FirefoxProfile);
        _process = Process.Start("java", arguments);

        selenium = new DefaultSelenium(
            SeleniumAppSettings.ServerHost,
            SeleniumAppSettings.ServerPort,
            SeleniumAppSettings.Browser,
            SeleniumAppSettings.URL);
        selenium.Start();
    }

    [TestFixtureTearDown]
    public void TestFixtureTearDown()
    {
        if (selenium != null)
        {
            try
            {
                // Close Browser
                selenium.Stop();
                // Close Server
                selenium.ShutDownSeleniumServer();
            }
            catch { }
        }

        // Confirm server is closed.
        try
        {
            if (!_process.HasExited)
                _process.CloseMainWindow();
            if (!_process.HasExited)
                _process.Kill();
            _process.Close();
        }
        catch { }
    }
}

Third, we just created a class for whatever we wanted to test...

[TestFixture]
public class WebsiteTest : SeleniumTestBase
{
    [Test]
    public void MainPageTest()
    {
        selenium.Open("/");
        selenium.WaitForPageToLoad(SeleniumAppSettings.TimeOut);
        Assert.IsTrue(selenium.GetTitle().Equals("My Website"));
        Assert.IsTrue(selenium.IsTextPresent("Hello world!"));
    }
}

As long as it inherits the base it will take care of managing the server!

Firefox 3.0

A great feature with Selenium is that you can run your tests against multiple browsers just by customing your start up parameters. (...yes, my example is hard coded to use Firefox, see my next section for why!) So, many are disappointed that Selenium does not natively support Firefox 3.0...

However! There is a very simple, and very reliable work around! Here's what you need to do...

  1. Open selenium-server.jar (You can do this by using Winrar.)
  2. Recursively traverse the customProfileDirCUSTFFCHROME and customProfileDirCUSTFF directorys...
  3. ... and edit all the install.rdf files...
  4. ...update the <em:maxVersion> values from 2.0.0.* to 4.0.0.*

...this will allow Selenium to run the latest Firefox!

(My thanks to Mikhail Koryak for blogging about this fix!)

Persisting Custom SSH Certificates

When testing one of our websites we kept running into a problem: our test domains did not have legitimate SSH certificates, so we were unable to test our https pages!

Why/how was this a problem? When Selenium launches it creates a clean browser profile, this way no cookies plugins or other thing will interferer with the tests. However, this also means that no temporary certificates were being persisted from test to test!

The Solution

Create a custom Firefox profile, and tell Selenium to use that when it launches.

Close your Firefox browser and Run "firefox.exe -profileManager". This will allow you to create another Firefox profile (I named mine "Selenium"). Then launch that profile, go to the website you want to test, and store a permanent SSH certificate for it.

Above you may have noticed that I used the "-firefoxProfileTemplate" argument when launching the Selenium server, this tells the server to load the specified profile rather than creating a new one.

Note: The changes made to your profile while Selenium is running will not be saved! This is good because no cookies or other such things will be left behind to intefear with future tests. However, it has the slight draw back (for us at least) of cause Selenium to reinstall its Firefox plugins every time it runs. ...meh!

(Thanks to Chris Pitts for blogging about something very similar to this!)

Helpful Links

Selenium Homepage
Selenium IDE Homepage
Selenium Video Example/Tutorial
Selenium on Wikipedia

Real Time Web Analytics