Amberleaf Update: Bill Tracking for Domains and Hosting

Amberleaf Billing

It’s been a long time coming, but today finally saw the launch of Amberleaf’s bill tracking for domains and hosting accounts. How to implement billing had been troubling me for a while, but I finally decided to keep it simple.

So, I spun a branch in git and started coding. I gave myself a time limit of 3 days to get something built and released. This proved a useful tool in keeping me focussed. Despite the inevitable “if only it just did this…” going through my mind, pledging my deadline in public helped me to stick to it!

I designed the billing system to simply track income and bills that were due to be paid. No complex invoice generation or sending invoices to clients – that’s better left to specialist tools like Freshbooks or Invoice Machine. The plan is to eventually let Amberleaf interact with these services to generate invoices.

If you’ve got an Amberleaf account, just sign in to see the new billing functionality. If you’ve not yet signed up, Amberleaf is completely free during the beta, so why not register an account to check out the new features? There’ll also be a limited free account once Amberleaf goes live!

I’d love to hear your feedback if you’re using Amberleaf to manage your domains and hosting accounts – you can send feedback directly from Amberleaf using the Send Feedback tab, or tweet @cblunt.

Amberleaf: Manage Your Domains & Hosting – Public Beta Now Open.

Amberleaf Front Page

After several months of evening development and jumping into Rails at the deep-end, I’m happy to jump into 2010 by opening public beta accounts for Amberleaf – and every account is free for the duration of the beta!

Amberleaf is built to remind you of upcoming renewals for domains and SSL certificate expiries, and hosting account renewals, and simply to remind you when to bill your customers. It is the evolution of HostManager, an OS X app I wrote back in my university days.

Who uses Amberleaf?

Anybody who has a domain! Whether you own or manage a single domain, or several hundred hosting accounts on behalf of your clients, Amberleaf is designed to make your life easier, by providing:

  • Automatic WHOIS lookup for domains, with more TLDs being added.
  • Email notification of upcoming expiries and renewals.
  • Private RSS feed of your upcoming notifications.
  • Simple client management, with the ability to import your existing clients’ vCards.
  • Keep a history of notes for your domains and clients, such as when special discounts are due to end.

What do you think?

Amberleaf is completely free for the duration of the beta, and signup only takes a couple of minutes; I’d love to hear your feedback and comments.

Sign up for Amberleaf now or find out more.

If you’ve already signed up, you can email comments to chris at chrisblunt dot com; ping me on twitter (@cblunt), or send feedback directly from Amberleaf using the feedback tab at the bottom of every page.

If you’d like to see what’s coming in future releases, and get involved in Amberleaf’s development, check out the public tracker at trac.chrisblunt.com.

Core Data: Cached Transient Properites

In writing HostManager 2.0, I’ve come up against a lot of problems caused by the dark magic that is Cocoa bindings and key-value observing. One of the most troublesome of these has been with transient (calculated) properties.

For example, a Client managed object can get a count of owned domains that are expiring soon. This is a transient propety, accessed through a key of expiringDomains. It simply returns an NSSet which is built from a fetchRequest.

Previously, I was running this fetchRequest each time the key was accessed. This was admittedly inefficient, but seemed functional – at least until you saved the document.

Something crazy happens when a Core Data document is saved – all the objects in a context are released (faulted). There is a good reason for this, as it frees up any memory that was being used to temporarily store the managed objects. However, it caused very strange results to appear in my transient properties; namely, they would all vanish.

Even forcing the fetch request to refresh didn’t pick up any objects from the context (I’m not sure why as I would have expected the faulted objects to be ‘refired’). So I took the plunge and refactored the Client object to cache its expiringDomains value. After some extensive testing and finger-crossing, I’m fairly sure this is the correct way to handle transient relationship properties:

CustomManagedObject.h

@interface CustomManagedObject : NSManagedObject {
ame NSSet *transientRelationship;
}
 
- (NSSet *)transientRelationship;

CustomManagedObject.m

#import "CustomManagedObject.h"
 
@implementation CustomManagedObject
 
- (void)awakeFromFetch
{
ame [super awakeFromFetch];
 
ame // set managed object to observe changes to the context, this is necessary
ame // to monitor changes to the context that occur elsewhere in the app
ame [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refresh:) name:NSManagedObjectContextObjectsDidChangeNotification object:[self managedObjectContext]];
 
ame // clear the cache after managed object is fetched into the context
ame if( transientRelationship != nil )
ame [transientRelationship release], transientRelationship = nil;}
 
- (void)refresh:(NSNotification *)notification
{
ame // ... perform check to see if update is neccessary, assume it is...
 
ame if( refreshNeeded ) {
ame if( expiringDomains != nil )
ame gDomains release], expiringDomains = nil;
ame }
}
 
- (NSSet *)transientRelationship
{
ame [self willAccessValueForKey:@"transientRelationship"]
ame
ame if( transientRelationship == nil ) {SFetchRequest *aFetchRequest = [[[NSFetchRequest alloc] init] autorelease];
 
ame [aFetchRequest setEntity:[NSEntityDescription entityForName:@"DestinationEntity" inManagedObjectContext:[self managedObjectContext]]];
 
ame // optionally, set a predicate, e.g. owner == self
ame [aFetchRequest setPredicate:[NSPredicate predicateWithFormat:@"predicateString"]];
 
ame NSArray *results = [[self managedObjectContext] executeFetchRequest:aFetchRequest error:nil];
 
ame transientRelationship = [[NSSet setWithArray:results] retain];
ame }
ame
ame [self didAccessValueForKey:@"transientRelationship"]
 
ame return transientRelationship;
}

Unfaulting Core Data Objects After Load

Hours of searching for an answer were beginning to lead me to the conclusion that it was impossible to unfault transient properties of an NSManagedObject after loading them from a datafile.

The Problem: A ‘smart’ group to show expiring domains based on their (transient, calculated) isExpiring property worked until the document was saved. On saving, the objects in the context became faults and the smart group no longer worked. This was also true of re-loading a saved document – the domain’s isExpiring property would not be unfaulted.

ame

The Solution: It’s a horrible solution, but seemingly the only way to get the faults to fire (calling [domain will/didAccessValueForKey:@"isExpiring"] did nothing). On awakening the smart group from a fetch, a quick NSFetchRequest is created to fetch all the Domain objects and unfault their @”data” propety. This is no doubt a dirty piece of code, but seems to force the calculated properties to be unfaulted.

- (void)awakeFromFetch
{
ame [super awakeFromFetch];
ame [self commonAwake];
ame
ame NSError *error;
ame NSFetchRequest *fr = [[[NSFetchRequest alloc] init] autorelease];
ame [fr setEntity:[[self fetchRequest] entity]];
ame
ame NSArray *results = [[self managedObjectContext]
ame SEnumerator *enumerator = [results objectEnumerator];
ame id object;
ame
ame while( object = [enumerator nextObject] ) {
ame NSLog( [object valueForKey:@"domainName"] );
ame [object willChangeValueForKey:@"data"];
ame [object didChangeValueForKey:@"data"];
ame }
}

With this now working, I can finally continue implementing the business logic in HM2.0, and stop worrying about the intricacies of Core Data and the dark art of Bindings.