Castle Demo AppQueries and Foreign Keys

time to read 16 min | 3103 words

Okay, one thing that I'm sure that you have noticed is that we have a bug in the DeleteUser method. The problem is that we don't check for foreign keys violations, and that will cause problems down the road (someone tries to delete a user that is assigned to a project, and he suddenly stares at an error page asking "But what did I do?"

Let's try to prevent it before we get a bug report about it. First, we need to make a couple of policy decsisions. There are two ways that a user can be attached to a project. Either the user is  the default assignee, or he is assigned to the project. From my perspective it's perfectly fine to delete a user if he is assigned to a project, the assignment to the project should just go away, but it's bad to remove a user that is the default assignee of a project.

How do I do this? When we first defined the relationship between users and projects, I decided that the class that should be responsible for maintain the association between the objects would be Project class. Because of this, we defined the Projects as the ones responsible for the connection. This ensure that the association is managed properly (otherwise you might get into a case when you save both the user and the project, and each tries to update the same association, which is a waste at best, and can be dangerous at worst.

Because of that, we will not instruct Active Record to deal with that, but do it manually, and here is the result:

    public void DeleteUser([ARFetch("id")]User user)

    {

        if (user == null)

        {

            RenderView("NoSuchUser");

            return;

        }

        if (AssertUserIsNoDefaultAssigneeInAnyProjecT(user)==false)

        {

            return;

        }

        user.Projects.Clear();

        user.Delete();

        RedirectToAction("Users");

    }

As you can see, we Clear() the Projects collection before we delete the user, and that is all. Active Record takes care of the rest.

Now to the AssertUserIsNoDefaultAssigneeInAnyProject() method:

private bool AssertUserIsNoDefaultAssigneeInAnyProject(User user)

    {

        Project[] projectsUserIsDefaultAssigneeIn = Project.FindAll(Expression.Eq("DefaultAssignee", user));

       

        if (projectsUserIsDefaultAssigneeIn.Length > 0)

        {

            List<string> names = new List<string>();

            Array.ForEach(projectsUserIsDefaultAssigneeIn,

                delegate(Project project) { names.Add(project.Name); });

 

            Flash["bad"] = string.

                Format("User {0} is the default assignee for {1}, and so cannot be deleted",

                    user.Name,

                    string.Join(",",names.ToArray()));

            RenderView("GenericMessage");

            return false;

        }

        return true;

    }

 

We ask to get all the projects where this user is the default assignee. We don't deal with SQL, or need to pass the correct Id, or anything like this. We just tell Active Record, "give me all the projects where this user is the default assignee". This is one of the main reasons I like Active Record so much.

We then unpack the projects names to an array, and tell the user he can't delete a user that is the default assignee in a project (we also tell him what the projects are, which would save some frustrations in the future).

This is it, we solved the problem.

But wait, we have another problem, again with users. The problem is with the password field. If the user doesn't change it, it's set to the default "Dummy Password", which is probably not a good thing. Let's see how we can fix it.

First, we remove the "Dummy Password" default from the view, and leave it with an empty password (this will make it easy afterward to tell whatever the user changed the password.

Then, we change the SaveUser() method to this:

    public void SaveUser(

        [ARDataBind("user", AutoLoadUnlessKeyIs = 0,

            Exclude="Password")] User user)

    {

        RenderMessage("Back to Users", "Users");

        try

        {

            string pass = Request.Params["user.Password"];

            if (string.IsNullOrEmpty(pass) == false)

                user.Password = pass;

            user.Save();

            Flash["good"] = string.Format(

                "User {0} was saved successfully.", user.Name);

        }

        catch (Exception e)

        {

            Flash["DataBindErrors"] = user.ValidationErrorMessages;

            Flash["bad"] = e.Message;

        }

    }

The only interesting thing here is the Exclude, where we tell Active Record to not set the Password property. We then manually get the value, check if it is not empty and if it is not, set the password.

More posts in "Castle Demo App" series:

  1. (03 Mar 2006) ViewComponents, Security, Filters and Friends
  2. (01 Mar 2006) Code Update
  3. (01 Mar 2006) Queries and Foreign Keys
  4. (28 Feb 2006) Complex Interactions
  5. (25 Feb 2006) CRUD Operations on Projects
  6. (22 Feb 2006) Let There Be A New User
  7. (22 Feb 2006) Updating our Users
  8. (20 Feb 2006) Getting serious, the first real page
  9. (20 Feb 2006) MonoRail At A Glance
  10. (20 Feb 2006) The First MonoRail Page
  11. (19 Feb 2006) Many To Many Relations
  12. (19 Feb 2006) Lazy Loading and Scopes
  13. (17 Feb 2006) Active Record Relations
  14. (17 Feb 2006) Getting Started With Active Record