TDD with Monorail – Part 4

In part 4 I am going to look at extending the functionality of the user registration page. I am going to focus on capturing the data from the form, passing that data back to the server, persisting it and then informing the user of the results of the save. The result of this should be a small system that extends from the UI right the way to the database and back again.

Before we get into the code I just wanted to point out somethings about the application structure. Although the aim of this exercise is to ultimately end up with a functioning website, the website is not really the main focus of my development activities. The web project is really just a front end that sits on top of an engine/API, namely the core assembly. The core project is the thing that is actually doing most of the work and is therefore the part of the application that i am most concerned with. In order to keep the UI as abstracted from the core as possible I will need to be careful not to allow domain types to leak into the UI layer. Doing this means that I should, in theory, be able to swap UI’s in and out as I see fit. Using message objects is one way of abstracting actual domain implementation away from the UI. I think my messages will take the form of specific request and responses e.g. I will create SaveUserRequest and SaveUserResponse classes for the registration request and responses. These types will be the link between the UI and the core projects and are the beginnings of an API. I think this diagram should clarify my explanation of the application architecture.

The application shown with dependencies between the layers:

Dependencies

and here are those message classes:

In addition to defining these two classes I have created an IValidatable interface which my SaveUserRequest implements. The reason for this is that I will be assuming that the SaveUserRequest objects will be coming from unknown sources therefore I am going to validate all requests before proceeding. The SaveUserRequest is deliberately thin at the moment, I will add more properties to it as required, but at this stage I am really just interested in getting all the layers functioning. Notice that my response class does not implement the IValidatable interface. The responses are generated by my core application so I am guaranteeing that all responses are valid just on the strength that I create them. I may revisit this assumption as things progress.

I am going to start driving out this particular bit of functionality this by creating a unit test around a ‘save’ action in the UsersController. The save action will accept a SaveUserRequest object which will be populated with the data from the client and persist the data in the users table in the database. I already have an architecture in mind for the layers underneath the controllers so I will use a series of tests to try and flesh some of it out. Hopefully, once we are done this will get us all the way down to the database.

I know that I want to keep my controller’s actions as thin as possible, as I have already stated, the only concern that I want them to have is presentation. In order to keep to this, the persistence and mapping functionality will have to be delegated to another layer in the application. At this stage of development I do not know exactly how that other layer might function so I am going to use Rhino Mocks to mock its’ functionality. This allows me to have a very focused unit test as I don’t need to worry about the implementation of the controllers dependencies. For the sake of the unit test I can assume that the dependencies work as expected thanks to the mock implementation.

I have chosen to use a service layer between my controllers and the rest of the application. They will act as a kind of gateway between the UI and the application. My current thinking it that I will probably need a service for each domain type, we’ll see how this turns out, anyway, here is the test I ended up with…

[Test]
public void Should_save_the_user_and_recieve_a_valid_response()
{
    var saveUserRequest = new SaveUserRequest();
    var saveUserResponse = new SaveUserResponse(true);

    using (mockery.Record())
    {
        Expect.Call(service.Save(saveUserRequest)).Return(saveUserResponse);
    }

    using (mockery.Playback())
    {
        controller.Save(saveUserRequest);
    }

    Assert.That(controller.PropertyBag["saveUserResponse"], Is.InstanceOfType(typeof(SaveUserResponse)));
}

As I am writing this application test first this test is dictating the implementation of my action, after creating all the types required by the test and running it, the implementation was the easy bit!

Here is the action:

[AccessibleThrough(Verb.Post)]
public void Save([DataBindAttribute("saveUserRequest")] ISaveUserRequest saveUserRequest)
{
    PropertyBag["saveUserResponse"] = userService.Save(saveUserRequest);
}

Now we have a functioning action that gets a SaveUserRequest passed in, calls save on the UsersService and adds a SaveUserResponse object into the property bag. Good. The next stage is to look at the UserService, currently this doesn’t exist as all I had to do to satisfy the test was to create an interface with a save method on it.

I think the service layer will form the topic for the next post.

Just time for one quick aside…

Form validation

I have chosen to use jQuery’s excellent validation plugin, I chose it as it offers a non-invasive mechanism to validate my HTML form elements. By this I mean that I do not have to mess with my form elements or the code that creates them to hook in some client side validation. Using the rules mechanism provided by jQuery’s validation plugin I can setup exactly how my forms should be validated without getting in the way.

Here is an example of rules in action in my registration view:

#capturefor(script)
    $(document).ready(function(){
        $("#saveUserRequestForm").validate({
            rules: {
                "saveUserRequest.FirstName": "required minlength(2) maxlength(255)",
                "saveUserRequest.Surname": "required minlength(2) maxlength(255)",
                "saveUserRequest.Email": "required email",
                "saveUserRequest.Phone": "required digits",
                "saveUserRequest.Password": "required minlength(6)",
                "confirm_password": {
                    equalTo: "#password"
                }
            }
        });
    });
#end

#capturefor(title)
  $resx.Headings_Registration
#end

$Form.FormTag("save.pnx", "%{id='saveUserRequestForm'}")
    $Form.LabelFor("saveUserRequest.FirstName", "$resx.Labels_FirstName") $Form.TextField("saveUserRequest.FirstName")
    $Form.LabelFor("saveUserRequest.Surname", "$resx.Labels_Surname") $Form.TextField("saveUserRequest.Surname")
    $Form.LabelFor("saveUserRequest.Email", "$resx.Labels_Email") $Form.TextField("saveUserRequest.Email")
    $Form.LabelFor("saveUserRequest.Phone", "$resx.Labels_Phone") $Form.TextField("saveUserRequest.Phone")
    $Form.LabelFor("saveUserRequest.Password", "$resx.Labels_Password") $Form.PasswordField("saveUserRequest.Password")
    $Form.LabelFor("confirm_password", "$resx.Labels_ConfirmPassword") $Form.PasswordField("confirm_password")
    $Form.Submit("$commonResx.Buttons_Submit", "%{id='submit'}")
</form>

As you can see my actual form elements are left well alone and the javascript validation just sits on top! – Nice.

You’ll notice that I am not using the FormHelper to write the closing form tag. This is because Monorail adds a bunch of validation code for the form that assumes that prototype.js is present on the client. I would rather Monorail didn’t spit out all of this javascript by default. Does anyone know a way of turning this off? I have had a look in the code and couldn’t see anything obvious.

jQuery’s validation plugin can be found at: http://plugins.jquery.com/project/validate

Advertisements

~ by Tim on 4 August 2008.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

 
%d bloggers like this: