2007-06-28

Windows on iMac

I've been a bit fed up with Mac OS recently. I love the way it looks etc, but I have been disappointed because I find it very hard to find good software for it, and I find it a bit sluggish too. In fact I find it quicker to start FireFox on Windows in a virtual machine that I do to start it on Mac OS X.

Anyway, I decided to install WinXP onto it using Boot Camp from Apple. The software told me I had a file system error that should be repaired first. I ran the Disk Utility app that comes with the OS, for some reason it would only verify the fact that I had an error but would never enable the "Repair" button. Apple support says the solution is......

Format the HD and reinstall the O/S.

In all the years of using Windows (from 3.1) I have never had to do that.

I think now I will make my Windows partition bigger than initially planned. Mac OS will just sit there unused I expect.

2007-06-23

I've changed my mind on MonoRail

It is amazing!

I'll leave that previous post just in case anyone ever needs to create an ECO object instance first and then connect it with an EcoSpace afterwards, but you don't need it for MonoRail!

  public void Join([EcoDataBind("User")]User user)

{

PropertyBag["User"] = user;

if (this.Params["User.FirstName"] != null)

{

GetErrors(user.AsIObject(), Errors);

if (Context.Params["ConfirmEmailAddress"] != user.EmailAddress)

Errors.Add("Email address confirmation does not match email address.");

if (Errors.Count == 0)

{

//TODO EcoSpace.UpdateDatabase();

Redirect("Account", "Home");

}//No errors

}

}


That's all there is to it now! I have created a small set of classes to enable ECO support in MonoRail. Instead of descending your controllers from SmartDispatcherController you will now descend from EcoSmartDispatcherController. This gives the following abilities:

  1. The EcoSpace instance is created automatically for each request.

  2. If the EcoSpace is dirty at the end of the request it is serialized and stored in the Session. Next request it is deserialized from the Session instead of being created new.

  3. You can use the [EcoDataBinder] attribute as displayed above. This will ensure that the object instance of the parameter type is created within the EcoSpace of the controller. No need to modify your business classes at all. Form data is automatically bound to the object, and when posted the binding is automatically reversed.

  4. You can add the ExternalId to your form as a hidden input like so $EcoFormHelper.ExternalId("User", $User).



I really hope I don't find an insurpassable obstacle with this framework, because I can really see myself liking it!

MonoRails, loving it

So I didn't like Ruby on Rails much. More accurately I didn't like the Ruby language or ActiveRecord much, but the "Rails" part I really quite liked! So my investigation continues and I have found myself looking at MonoRails.

MonoRails is what I would have as a child called "a rip off", but these days it is known as a "clone" :-) It's basically a .NET version of Rails, which obviously appeals to me because I liked the Model-View-Controller approach of Rails and I obviously like C#.

MonoRails has its own version of ActiveRecord (which I shall be avoiding) and an interface into NHibernate too (which I haven't looked at in great depth, but it certainly doesn't look as powerful as ECO). So I have been trying to get MonoRails working with ECO instead. Considering I don't know MonoRails at all I am surprised at how quickly I managed to do what I wanted. Take the following controller method as an example, when the user visits localhost/Account/Join the following method will be executed...

public void Join([DataBind("User")] User user)


MonoRails will automatically create an instance of User (my ECO class) and then automatically populate its contents from the form that was posted. The first problem I had here was that ECO classes have no parameterless constructors. I could have gone down the following route:

public void Join(string salutation, string firstName,
string lastName, string emailAddress, string password)


but that would mean I have to create the User instance myself and then populate its properties from the parameters. The problem with this approach is that I am just too damned lazy :-) So, my first hurdle was to allow ECO classes to be constructed + have their properties set without an EcoSpace. I created a base RootClass that all of my business classes will ultimately descend from, and then I did this...

First I created a simple class that implements IContent and stores the values in a Dictionary.
 internal class TemporaryCache : IContent
{
private Dictionary<int, object> MemberValues = new Dictionary<int, object>();
#region IContent Members
object IContent.get_MemberByIndex(int index)
{
return MemberValues[index];
}

void IContent.set_MemberByIndex(int index, object value)
{
MemberValues[index] = value;
}

#endregion

public void ApplyValues(IContent destination)
{
foreach (int currentKey in MemberValues.Keys)
destination.set_MemberByIndex(currentKey, MemberValues[currentKey]);
}
}


All other methods of IContent throw a NotImplementedException, all we need here is get_MemberByIndex and set_MemberByIndex so that simple property values (not associations) may be set before the object has an EcoSpace. At some point we need to update the values in the EcoSpace, which is exactly what ApplyValues does, it just copies its own values across to a target IContent.

Now we need to use this class as our business class’s IContent and implement a parameterless constructor.

  public RootClass()
{
eco_Content = new TemporaryCache();
}


And finally we need at some point after creation + setting the property values to be able to attach this object + its contents to the new EcoSpace.

  public void AttachToEcoSpace(DefaultEcoSpace ecoSpace)
{
if (ecoSpace == null)
throw new ArgumentNullException("ecoSpace");
if (this.eco_Content != null && !(this.eco_Content is TemporaryCache))
throw new InvalidOperationException("EcoSpace already assigned");

TemporaryCache oldContent = (TemporaryCache)eco_Content;
eco_Content = null;
this.Initialize(ecoSpace);
oldContent.ApplyValues(eco_Content);
}


Now my Join() method looks like this:
  public void Join([DataBind("User")] User user)
{
user.AttachToEcoSpace(EcoSpace);
PropertyBag["User"] = user;
if (this.Params["User.FirstName"] != null)
{
user.GetErrors(Errors);
if (Context.Params["ConfirmEmailAddress"] != user.EmailAddress)
Errors.Add("Email address confirmation does not match email address.");
if (Errors.Count == 0)
{
EcoSpace.UpdateDatabase();
Redirect("Account", "Home");
}//No errors
}
}


Standard stuff


I descend all new Controller classes from my own abstract BaseController which merely creates a new EcoSpace at the start of a request and disposes it at the end. This class also exposes an Errors property (List<string>) that I can add errors to.

 public class BaseController : SmartDispatcherController
{
protected readonly List<string> Errors = new List<string>();

protected override void Initialize()
{
base.Initialize();
EcoSpace = new ApplicationEcoSpace();
PropertyBag["Errors"] = Errors;
}

protected override void ReleaseResources()
{
EcoSpace.Dispose();
base.ReleaseResources();
}

private ApplicationEcoSpace ecoSpace;
protected ApplicationEcoSpace EcoSpace
{
get
{
ecoSpace.Active = true;
return ecoSpace;
}
private set
{
ecoSpace = value;
}
}
}

My Account controller then descends from this, like so:
 [Layout("default")]
[Rescue("generalerror")]
public class AccountController : BaseController
{
public void Join([DataBind("User")] User user)
{
user.AttachToEcoSpace(EcoSpace);
PropertyBag["User"] = user;
if (this.Params["User.FirstName"] != null)
{
user.GetErrors(Errors);
if (Context.Params["ConfirmEmailAddress"] != user.EmailAddress)
Errors.Add("Email address confirmation does not match email address.");
if (Errors.Count == 0)
{
EcoSpace.UpdateDatabase();
Redirect("Account", "Home");
}//No errors
}
}
}


Why do I set PropertyBag["User"] = user;? Simple, I can then databind to it in my view...
<h2>Join</h2>
$HtmlHelper.Form("Join.rails")
<table summary="Enter your information">
<caption>Please provide the following information.</caption>
<tbody>
<tr>
<th>
<label for="User.Salutation">Salutation</label>
</th>
<td>$HtmlHelper.InputText("User.Salutation", $User.Salutation)</td>
</tr>
<tr>
<th><label for="User.FirstName">First name</label></th>
<td>$HtmlHelper.InputText("User.FirstName", $User.FirstName)</td>
</tr>
<tr>
<th>
<label for="User.LastName">Last name</label>
</th>
<td>$HtmlHelper.InputText("User.LastName", $User.LastName)</td>
</tr>
<tr>
<th>
<label for="User.EmailAddress">Email address</label>
</th>
<td>$HtmlHelper.InputText("User.EmailAddress", $User.EmailAddress)</td>
</tr>
<tr>
<th>
<label for="ConfirmEmailAddress">Confirm email address</label>
</th>
<td>$HtmlHelper.InputText("ConfirmEmailAddress", $ConfirmEmailAddress)</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">
<input type="Submit" value="Join"/>
</td>
</tr>
</tfoot>
</table>
$HtmlHelper.EndForm

Did you notice the [Layout("default")] on my AccountController? That tells MonoRails to use my default.vm file as the master page for the HTML output. It basically sets up the header information etc and displays any errors, here is how it looks:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<meta name="keywords" content="" />
<title>NotASausage</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<link href="/StyleSheets/Main.css" rel="stylesheet" media="all" type="text/css" />
<link href="/StyleSheets/Menu.css" rel="stylesheet" media="all" type="text/css" />

<link href="/StyleSheets/ThumbnailViewer.css" rel="stylesheet" media="all" type="text/css" />
<script src="/Scripts/ThumbnailViewer.js" type="text/javascript"></script>
</head>
<body>
<div id="MainFrame">
<div id="Header"><img id="HeaderBanner" src="/Images/Banner.jpg" alt="NotASausage" /></div>
<div id="SideBar">
<div id="MainMenu">
<h2>Menu</h2>
<ul>
<li><a href="/default.aspx">Main page</a></li>
</ul>
</div>
</div>
<div id="Contents">
#if ($Errors && $Errors.Count > 0)
<ul class="ErrorList">
#foreach($ErrorMessage in $Errors)
<li>$ErrorMessage</li>
#end
</ul>
#end
$childContent
<div id="Footer">
©Peter Morris - All rights reserved.
</div>
</div>
</body>
</html>

The $childContent indicates where to inject the output of the current page.

How cool is that? I can now model in ECO, generate my business objects, automatically generate my DB, and then finally put together some very simple application actions which will control access to those business objects and display any validation problems back to the user!

So far I like what I see!

2007-06-21

Quantum bugs

I've just been driven mad by a "Quantum bug". What is a quantum bug? Well, it's a bug that doesn't exist unless you look at it :-)

I kept getting a NullReferenceException in my code. It happened whenever I changed a property of a class. I assumed it was something in the framework, but it was in fact in a library of my own. When the property changed it would trigger an observer I had attached, the purpose of this observer is to check if the form that owns my component is the active form, if it is then it records the object that changed; the purpose is to know which objects were modified by which form.

Anyway, to check if the form is active I do this

Form activeForm = System.Windows.Forms.Form.ActiveForm;
Form activeMdiChildForm = activeForm.ActiveMdiChild;
if (activeForm == this.Form || activeMdiChildForm == this.Form)
.....

Now the quantum!

If you are debugging your app Form.ActiveForm will always return null! How useful is that eh? :-) So, when debugging my app I get a NullReferenceException, when I let it run through I don't.

There was me being a good boy, stepping through the code I had just added to check it does what it is supposed to, and Bill punishes me for it!

Damn :-)

2007-06-16

Cocoa, falling at the first hurdle?

I was REALLY looking forward to programming some apps on the Mac using Cocoa and objective-C.

I am reading "Cocoa programming for MAC OS X" by Aaron Hillegas. I had read as far as page 30 when I saw the following:

"Objective-C is a very simple language. It has no visibility specifiers: All methods are public; and all instance variables are protected. (Actually, there are instance specifiers for instance variables, but they are rarely used. The default is protected, and that works nicely.)"


WHAT?

Works nicely? I disagree! It is actually possible to make a class's method private by not including it in the interface declaration (myclass.h) and just adding the implementation file instead, but what about protected methods?

public
I expose as little as needed to ensure the class provides the service it was designed to. The signatures/names of members in my public area change as little as possible so as not to break other people's code. Public members are therefore inflexible.

protected
I am happy to expose a few more methods here. Typically virtual methods so that anyone inheriting from my class can hook in their own code and modify the behaviour of my class. Protected members are a little more flexible to change as fewer lines of code depend on them, but I still try to change them as little as possible.

internal / friend
These are vital to me. They allow me (and only me) access to certain members of other classes. I find this invaluable when I need to have some kind of collaboration between classes in a framework that nobody else should have access to, including descendants of my own classes that are not in the same assembly/module.

private
An absolute necessity. Access to some members would allow a consumer to manipulate members of your class without obeying "the rules". This would include adding items to a private array member without going through the relevant method which might perform other tasks such as


  1. Enforcing read-only.

  2. Checking security permissions.

  3. Executing other methods to enforce proper business procedure.



As you can see, I feel very strongly about encapsulation. It provides flexibility to change the way you provide your service and enforces correct behaviour. When you order new computer from a manufacturer you don't walk along the conveyor belt interfering with the way each element is added do you? You interface with the manufacturer using the approved process (public PlaceOrder) and then leave them to get on with the 100 or so private procedures they go through to fulfill the order.

Never before have I read so little about a language before deciding it is a waste of time proceeding! Maybe encapsulation will be implemented along with the new garbage collection features (I wont go into reference counting) that is coming in the next version of Mac OS X. I'll have to wait and see!

Enough rails for me

That's it, I've had enough of Ruby on Rails!

I like the rails part, it's a very clever approach, but I really dislike the Ruby part!

The final straw occurred yesterday. It's very common in OOP to have the constructor set default values for your object just in case the consumer of your class does not set them. In C# I would do something like this.....

public class Post : MyBaseClass
{
private bool isPost = true;
}

In Ruby I was trying to achieve this simple behaviour...

01: IsPost is set to true
02: A form is displayed with the default value
03: User changes the value
04: My Post instance is updated with the values from the form

I tried to override Initialize() only.

def Initialize
super
is_post = true
end

Now Post.new(params[:post]) is not reachable for some reason.


So in my Post class I did this

def Initialize(* params)
super(params)
is_post = true
end

The problem here is when I try to initialize the values from the form in my controller class.

post = Post.new(params[:post])

This means that the super(params) is called (which sets is_post to the value in the form) and then sets is_post = true. So my value is always true regardless of what the user enters.

So I tried putting "is_post = true" before the call to super, but that throws a null reference exception.

Finally someone told me to merge hashes like this

{:is_post => true}.merge(params);
super(params)

I don't recall the error, but that didn't work either.


I'm sorry, but ANY language that gives me this much hassle just to set a default is not worth it.

C# on rails, now *that* would be nice!

2007-06-01

Installing Rails on a Mac

What a lot of grief!

After trying more simple steps on another site (and failing) I eventually managed to get the following steps to work:

http://hivelogic.com/narrative/articles/ruby-rails-mongrel-mysql-osx


There were however some changes:

01: Make sure you download TextMate. I kept reading it as TextEdit for some reason, so just be aware of that when "mate ~/.bash_login" wont work.

02: One of the steps tells you to type "make ~/.bash_login". I found that adding the suggested text to that file made no difference when you log out/in or close the Terminal window. This was because a file named "~/.bash_profile" existed. If this file exists then you should modify it instead of the one mentioned in the article.

03: When trying to do "rake db:migrate" I would experience the following error:

otherwise you will experience the following error:
dyld: NSLinkModule() error
dyld: Library not loaded: /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib
Referenced from: /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7/lib/mysql.bundle
Reason: image not found
Trace/BPT trap


This is because the latest version of MySql has changed a path from lib/mysql to just lib/. To fix this problem you need to type the following (it's all one line):

sudo install_name_tool -change /usr/local/mysql/lib/mysql/libmysqlclient.15.dylib /usr/local/mysql/lib/libmysqlclient.15.dylib /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7/lib/mysql.bundle

Right then. Now that those Hellish few days are over it's time to get annoyed with trying to write a website I suppose :-)