Monday, January 30, 2006
Blog Movin'
Moving my blog to DevPinoy.
Tuesday, December 06, 2005
Fun with ActiveRecord
Wow. ActiveRecord is fun :)
It's still got some rough edges, but generally it really can speed up development. I haven't gotten around to using the ActiveRecord Generator software since I'd like to experience first-hand how to code this thing manually.
Anyway just in case you don't know, ActiveRecord is an implementation of the active record pattern documented by Martin Fowler in his book Patterns of Enterprise Application Architecture. It is actually a "port" of sorts from the Ruby on Rails Active Record implementation. Basically you have a class corresponding to a table, with each member/property corresponding to a field on that database table. The operations are either member or static methods. Well at least that's how it is implemented in Castle's ActiveRecord.
So how does it hold up during actual development? At present ActiveRecord's documentation leaves a lot to be desired; it's somehow better to just peruse the examples provided in the download. I tried creating a MonoRail app that uses classes descended from ActiveRecord's ActiveRecordBase class and it sure was easy. I even did NUnit tests for it to make sure it really does what it's supposed to.
Ok so where do I start? Let's take a simple table from a database. In my case I took a table called "usergroup" and I created a class named UserGroup. This class will be a subclass of ActiveRecordBase and will contain member properties corresponding to fields in the usergroup table. Here's how my UserGroup.cs code looks:
As you can see it's got member properties and static methods Find(), FindByName() and FindAll(). ActiveRecord is actually a wrapper on top of NHibernate so I made use of NHibernate's expression facility in the FindByName() method.
With ActiveRecord, I can just instantiate the UserGroup class then Save() it to the database, or I can Update() or Delete(). It's pretty nifty. In my case I dropped that UserGroup.cs in my Testing\Models directory. Now on to the test class TestUserGroup.cs (in the Testing\Tests directory):
Before I ran the test I prepopulated the UserGroup table with dummy data. It's referenced in the test code that I expect to Find() a record with a primary key (int) equal to 1. Anyway if you wish to run this code as well make sure you note that.
Ok so what else are we missing...oh we require an appconfig.xml file as seen in the SetUp() method above! Ok so here goes:
The appconfig.xml (of course you can name it whatever you want) is used by my test classes (in the Testing\Tests directory). Here you can see that we specified that the driver it uses in the ODBC driver. The particular dialect I wish to use is MS SQL Server 2000's dialect since that's the particular database I'm using. I also have the ODBC connection string there pointing to the System DSN named "myODBCDSN". The good thing with having a separate configuration for tests is that I can point my tests to a scratch database and the actual web application to a different database.
Ok, so now I need to create my NAnt targets:
In my case I have the DLL dependencies in the Testing\lib directory (defined in the NAnt property variable ${libDir}) and the data model classes in Testing\Models (defined in the property variable ${modelDir}). Note the six (!) DLLs required just to be able to come up with ActiveRecordBase-based classes. The output DLL is named Testing.Model.dll and is placed in the Testing\bin directory (variable ${binDir}).
Now for the NUnit target:
Again, note the DLL dependencies. Finally I need to craft a .config file for the test classes. In my case it's named Testing.Tests.dll.config and it's expected to be found in the bin directory:
The reason why I'm noting this is because for some reason I'm not familiar with, it insists on having a log4net section in the configuration file. Here I used the log4net appender RollingFileAppender. Anyway if you're not interested in viewing the log file produced by NHibernate through log4net, I suggest you just have a <log4net /> tag just to appease it.
There! Now I can build my model class(es) using:
And test them using:
As you can see in the test code, it's really easy to use ActiveRecord once you get past the configuration file stuff. :P What we did wasn't really test-driven development (since we did the UserGroup.cs code before we came up with the TestUserGroup.cs test class!) but in practice I create the model class first containing only properties and attributes, then I TDD the methods one by one. At least since the NAnt targets and configuration files are already in place, it's much easier to create new model classes, specify them in the NAnt "model" target, create a new test class for that model, specify it in the NAnt "modelTest" target then do the model methods TDD-style. Rinse, lather, repeat. :)
Later I'll post about how I tied them into the controllers for the web application. You'll see first-hand how MonoRail really speeds up .NET web application development. :)
It's still got some rough edges, but generally it really can speed up development. I haven't gotten around to using the ActiveRecord Generator software since I'd like to experience first-hand how to code this thing manually.
Anyway just in case you don't know, ActiveRecord is an implementation of the active record pattern documented by Martin Fowler in his book Patterns of Enterprise Application Architecture. It is actually a "port" of sorts from the Ruby on Rails Active Record implementation. Basically you have a class corresponding to a table, with each member/property corresponding to a field on that database table. The operations are either member or static methods. Well at least that's how it is implemented in Castle's ActiveRecord.
So how does it hold up during actual development? At present ActiveRecord's documentation leaves a lot to be desired; it's somehow better to just peruse the examples provided in the download. I tried creating a MonoRail app that uses classes descended from ActiveRecord's ActiveRecordBase class and it sure was easy. I even did NUnit tests for it to make sure it really does what it's supposed to.
Ok so where do I start? Let's take a simple table from a database. In my case I took a table called "usergroup" and I created a class named UserGroup. This class will be a subclass of ActiveRecordBase and will contain member properties corresponding to fields in the usergroup table. Here's how my UserGroup.cs code looks:
using System;
using System.Collections;
using Castle.ActiveRecord;
using NHibernate.Expression;
namespace Testing.Model
{
[ActiveRecord("usergroup")]
public class UserGroup : ActiveRecordBase
{
private int _userGroupKey;
private string _userGroupName;
private string _userGroupType;
private string _description;
private string _eMailAddress;
private string _password;
private string _lastName;
private string _firstName;
private string _role;
public UserGroup()
{
}
[PrimaryKey(PrimaryKeyType.Native)]
public int UserGroupKey
{
get { return _userGroupKey; }
set { _userGroupKey = value; }
}
[Property]
public string UserGroupName
{
get { return _userGroupName; }
set { _userGroupName = value; }
}
[Property]
public string UserGroupType
{
get { return _userGroupType; }
set { _userGroupType = value; }
}
[Property]
public string Description
{
get { return _description; }
set { _description = value; }
}
[Property]
public string EMailAddress
{
get { return _eMailAddress; }
set { _eMailAddress = value; }
}
[Property]
public string Password
{
get { return _password; }
set { _password = value; }
}
[Property]
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
[Property]
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
[Property]
public string Role
{
get { return _role; }
set { _role = value; }
}
public static UserGroup Find(int id)
{
return (UserGroup) ActiveRecordBase.FindByPrimaryKey(typeof(UserGroup), id);
}
public static UserGroup FindByName(string username)
{
UserGroup[] userArray = (UserGroup[]) ActiveRecordBase.FindAll(typeof(UserGroup), Expression.Eq("UserGroupName", username));
if (userArray != null) {
if (userArray.Length > 0) {
return userArray[0];
}
}
return null;
}
public static UserGroup[] FindAll()
{
return (UserGroup[]) ActiveRecordBase.FindAll(typeof(UserGroup));
}
}
}
As you can see it's got member properties and static methods Find(), FindByName() and FindAll(). ActiveRecord is actually a wrapper on top of NHibernate so I made use of NHibernate's expression facility in the FindByName() method.
With ActiveRecord, I can just instantiate the UserGroup class then Save() it to the database, or I can Update() or Delete(). It's pretty nifty. In my case I dropped that UserGroup.cs in my Testing\Models directory. Now on to the test class TestUserGroup.cs (in the Testing\Tests directory):
using NUnit.Framework;
using Testing.Model;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework.Config;
using System;
using System.Collections;
namespace Testing.Tests
{
[TestFixture]
public class TestUserGroup
{
[SetUp]
public void SetUp()
{
ActiveRecordStarter.Initialize(new XmlConfigurationSource("../Tests/appconfig.xml"), typeof(UserGroup));
}
[Test]
public void TestCreate()
{
UserGroup user = new UserGroup();
Assert.IsNotNull(user);
}
[Test]
public void TestFind()
{
UserGroup user = UserGroup.Find(1);
Assert.IsNotNull(user);
Assert.AreEqual("Admin", user.UserGroupName);
}
[Test]
public void TestFindName()
{
UserGroup user = UserGroup.FindByName("Admin");
Assert.IsNotNull(user);
Assert.AreEqual("Admin", user.UserGroupName);
Console.WriteLine("name = " + user.UserGroupName);
Console.WriteLine("description = " + user.Description);
Console.WriteLine("email = " + user.EMailAddress);
}
[Test]
public void TestSave()
{
UserGroup user = new UserGroup();
user.UserGroupName = "newuser";
user.UserGroupType = "U";
user.Description = "This is a new user";
user.EMailAddress = "myemail@gmail.com";
user.Save();
user = null;
user = UserGroup.FindByName("newuser");
Assert.IsNotNull(user);
Assert.AreEqual("newuser", user.UserGroupName);
user.Delete();
user = UserGroup.FindByName("newuser");
Assert.IsNull(user);
}
[Test]
public void TestFindAll()
{
UserGroup[] users = UserGroup.FindAll();
Assert.IsNotNull(users);
Assert.IsTrue(users.Length > 0);
Console.WriteLine("There are " + users.Length.ToString() + " users in the array.");
}
}
}
Before I ran the test I prepopulated the UserGroup table with dummy data. It's referenced in the test code that I expect to Find() a record with a primary key (int) equal to 1. Anyway if you wish to run this code as well make sure you note that.
Ok so what else are we missing...oh we require an appconfig.xml file as seen in the SetUp() method above! Ok so here goes:
<activerecord>
<config>
<add
key="hibernate.connection.driver_class"
value="NHibernate.Driver.OdbcDriver" />
<add
key="hibernate.dialect"
value="NHibernate.Dialect.MsSql2000Dialect" />
<add
key="hibernate.connection.provider"
value="NHibernate.Connection.DriverConnectionProvider" />
<add
key="hibernate.connection.connection_string"
value="DSN=myODBCDSN;UID=myUSER;PWD=myPASS;" />
</config>
</activerecord>
The appconfig.xml (of course you can name it whatever you want) is used by my test classes (in the Testing\Tests directory). Here you can see that we specified that the driver it uses in the ODBC driver. The particular dialect I wish to use is MS SQL Server 2000's dialect since that's the particular database I'm using. I also have the ODBC connection string there pointing to the System DSN named "myODBCDSN". The good thing with having a separate configuration for tests is that I can point my tests to a scratch database and the actual web application to a different database.
Ok, so now I need to create my NAnt targets:
<target name="model" description="Testing model build">
<csc target="library" output="${binDir}\Testing.Model.dll" debug="${debug}">
<references>
<include name="${libDir}\HashCodeProvider.dll" />
<include name="${libDir}\Iesi.Collections.dll" />
<include name="${libDir}\log4net.dll" />
<include name="${libDir}\NHibernate.dll" />
<include name="${libDir}\Castle.ActiveRecord.dll" />
<include name="${libDir}\Castle.Model.dll" />
</references>
<sources basedir="${modelDir}">
<include name="UserGroup.cs" />
</sources>
</csc>
</target>
In my case I have the DLL dependencies in the Testing\lib directory (defined in the NAnt property variable ${libDir}) and the data model classes in Testing\Models (defined in the property variable ${modelDir}). Note the six (!) DLLs required just to be able to come up with ActiveRecordBase-based classes. The output DLL is named Testing.Model.dll and is placed in the Testing\bin directory (variable ${binDir}).
Now for the NUnit target:
<target name="modelTest" description="Testing model TDD" depends="model">
<csc target="library" output="${binDir}\Testing.Tests.dll" debug="${debug}">
<references>
<include name="${nunitDir}\NUnit.Framework.dll" />
<include name="${binDir}\Testing.Model.dll" />
<include name="${libDir}\Castle.ActiveRecord.dll" />
<include name="${libDir}\Castle.Model.dll" />
</references>
<sources basedir="${testDir}">
<include name="TestUserGroup.cs" />
<include name="TestRegisteredUser.cs" />
</sources>
</csc>
<nunit2>
<formatter type="Plain" />
<test assemblyname="${binDir}\Testing.Tests.dll" />
</nunit2>
</target>
Again, note the DLL dependencies. Finally I need to craft a .config file for the test classes. In my case it's named Testing.Tests.dll.config and it's expected to be found in the bin directory:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="rollingFile" type="log4net.Appender.RollingFileAppender, log4net">
<param name="File" value="log.txt" />
<param name="AppendToFile" value="true" />
<param name="RollingStyle" value="Date" />
<param name="DatePattern" value="yyyy.MM.dd" />
<param name="StaticLogFileName" value="true" />
<layout type="log4net.Layout.PatternLayout, log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] (%X{auth}) - %m%n" />
</layout>
</appender>
<root>
<priority value="ALL" />
<appender-ref ref="rollingFile" />
</root>
</log4net>
</configuration>
The reason why I'm noting this is because for some reason I'm not familiar with, it insists on having a log4net section in the configuration file. Here I used the log4net appender RollingFileAppender. Anyway if you're not interested in viewing the log file produced by NHibernate through log4net, I suggest you just have a <log4net /> tag just to appease it.
There! Now I can build my model class(es) using:
D:\Testing>nant model
And test them using:
D:\Testing>nant modelTest
As you can see in the test code, it's really easy to use ActiveRecord once you get past the configuration file stuff. :P What we did wasn't really test-driven development (since we did the UserGroup.cs code before we came up with the TestUserGroup.cs test class!) but in practice I create the model class first containing only properties and attributes, then I TDD the methods one by one. At least since the NAnt targets and configuration files are already in place, it's much easier to create new model classes, specify them in the NAnt "model" target, create a new test class for that model, specify it in the NAnt "modelTest" target then do the model methods TDD-style. Rinse, lather, repeat. :)
Later I'll post about how I tied them into the controllers for the web application. You'll see first-hand how MonoRail really speeds up .NET web application development. :)
It's not a roadblock
Thanks to the CastleProject mailing list, it was pointed out to me that prepending an exclamation mark (!) before a variable prevents the display of the variable name if the variable contains null. So instead of saying:
I should be putting in:
It was mentioned in the Velocity Template Language guide, which I overlooked :)
${user.LastName}I should be putting in:
$!{user.LastName}It was mentioned in the Velocity Template Language guide, which I overlooked :)
Ugh, hit a roadblock...
I was already able to create ActiveRecord-based model classes and even test cases using NUnit; however I hit a roadblock.
I discovered that NVelocity templates, if fed a null property or variable, renders the whole variable/property name (with dollar sign and all) in the HTML output instead of a (my expected) blank. Is this a feature, or a bug?
I just don't find it acceptable to put in #set and #if template statements just to catch the null properties and render them as blanks...any input on this, people? Or is it time to edit the NVelocity source? :P
Too bad...since I was just starting to discover the joys of coding skeletal controller classes and having a site structure up in record time...!
I discovered that NVelocity templates, if fed a null property or variable, renders the whole variable/property name (with dollar sign and all) in the HTML output instead of a (my expected) blank. Is this a feature, or a bug?
I just don't find it acceptable to put in #set and #if template statements just to catch the null properties and render them as blanks...any input on this, people? Or is it time to edit the NVelocity source? :P
Too bad...since I was just starting to discover the joys of coding skeletal controller classes and having a site structure up in record time...!
Monday, December 05, 2005
MonoRail QuickStart
I find this project interesting. However the wiki seems to have outdated information and it was quite annoying to find inconsistencies between its documentation pages and the video demos (and even the downloadable files!). So I guess I'd like to help out by documenting my experience in starting out with MonoRail (and coming up with a skeleton/site template) through this simple blog. :)
Warning though: I am no MonoRail expert; I'm just starting out! The things I'll put in here are the stuff that worked for me, so if we have similar system configuration (WinXP SP2, NAnt 0.85) this might actually work for you.
Anyway...first step for me was to download the MonoRail-Samples.zip file. It's got a "SampleSite" directory. Just unzip it somewhere in your computer, we'll refer to it later.
Next step is to create the web directory for development. I called my directory "Testing" and I put it somewhere in my computer. Now I create empty directories within the "Testing" directory:
Remember the "SampleSite" sample? Copy the DLLs found in its bin directory (except for the SampleSite.dll file of course) to the Testing\bin directory.
Now I'll also create a web.config file within the Testing directory:
and the sample Testing.build (NAnt) file:
Note here that my NAnt distribution is in C:\NAnt and I'm using version 1.1 of the .NET Framework.
Now that my build file is ready and so is my web.config, I code my simple controller (Testing\Controllers\HomeController.cs):
and view (Testing\Views\home\index.vm):
Now open up a command prompt window and unleash nant on the build file:
So I end up with a Testing.dll file inside the Testing\bin directory. Final step is to open the IIS snap-in (Administrative Tools) and create a virtual directory pointing to the Testing directory. In addition I make sure I map the .rails extension to the aspnet_isapi.dll. Now the site should work :)
Now we have a skeleton/framework and a working MonoRail web site. In the next couple of days I'll be posting my experience with doing the ActiveRecord pattern and using it in a real-world web app port :)
Warning though: I am no MonoRail expert; I'm just starting out! The things I'll put in here are the stuff that worked for me, so if we have similar system configuration (WinXP SP2, NAnt 0.85) this might actually work for you.
Anyway...first step for me was to download the MonoRail-Samples.zip file. It's got a "SampleSite" directory. Just unzip it somewhere in your computer, we'll refer to it later.
Next step is to create the web directory for development. I called my directory "Testing" and I put it somewhere in my computer. Now I create empty directories within the "Testing" directory:
- bin
- Controllers
- Models
- Views
Remember the "SampleSite" sample? Copy the DLLs found in its bin directory (except for the SampleSite.dll file of course) to the Testing\bin directory.
Now I'll also create a web.config file within the Testing directory:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="monoRail"
type="Castle.MonoRail.Engine.Configuration.MonoRailSectionHandler, Castle.MonoRail.Engine" />
</configSections>
<monoRail>
<controllers>
<assembly>Testing</assembly>
</controllers>
<viewEngine viewPathRoot="Views"
customEngine="Castle.MonoRail.Framework.Views.CompositeView.CompositeViewEngine, Castle.MonoRail.Framework.Views.CompositeView" />
</monoRail>
<system.web>
<httpHandlers>
<add verb="*" path="*.rails"
type="Castle.MonoRail.Engine.MonoRailHttpHandlerFactory, Castle.MonoRail.Engine" />
<add verb="*" path="*.vm" type="System.Web.HttpForbiddenHandler" />
</httpHandlers>
</system.web>
</configuration>
and the sample Testing.build (NAnt) file:
<?xml version="1.0"?>
<project name="Testing" default="build" basedir=".">
<description>Test MonoRail project</description>
<property name="debug" value="true" overwrite="false" />
<property name="myDir" value="." />
<property name="nunitDir" value="C:\nant\bin\lib\net\1.1" />
<property name="binDir" value="bin" />
<property name="controllerDir" value="Controllers" />
<target name="clean" description="remove output">
<delete>
<fileset>
<include name="${binDir}\Testing.dll" />
</fileset>
</delete>
</target>
<target name="build" description="Testing build">
<csc target="library" output="${binDir}\Testing.dll" debug="${debug}">
<references>
<include name="${binDir}\Castle.MonoRail.Framework.dll" />
<include name="${binDir}\Castle.MonoRail.Framework.Views.CompositeView.dll" />
<include name="${binDir}\Castle.MonoRail.Framework.Views.NVelocity.dll" />
<include name="${binDir}\Castle.MonoRail.Engine.dll" />
<include name="${binDir}\Commons.dll" />
<include name="${binDir}\log4net.dll" />
<include name="${binDir}\NVelocity.dll" />
</references>
<sources basedir="${controllerDir}">
<include name="HomeController.cs" />
</sources>
</csc>
</target>
</project>
Note here that my NAnt distribution is in C:\NAnt and I'm using version 1.1 of the .NET Framework.
Now that my build file is ready and so is my web.config, I code my simple controller (Testing\Controllers\HomeController.cs):
// HomeController.cs
using Castle.MonoRail.Framework;
namespace Testing.Controllers
{
public class HomeController : Controller
{
public void Index()
{
PropertyBag.Add("name", "User");
}
}
}
and view (Testing\Views\home\index.vm):
<html>
<head>
<title>Testing</title>
</head>
<body>
Welcome to MonoRail, ${name}
</body>
</html>
Now open up a command prompt window and unleash nant on the build file:
D:\Testing>nant
So I end up with a Testing.dll file inside the Testing\bin directory. Final step is to open the IIS snap-in (Administrative Tools) and create a virtual directory pointing to the Testing directory. In addition I make sure I map the .rails extension to the aspnet_isapi.dll. Now the site should work :)
Now we have a skeleton/framework and a working MonoRail web site. In the next couple of days I'll be posting my experience with doing the ActiveRecord pattern and using it in a real-world web app port :)