acts_as_union to the Rescue

2009-09-06 20:00:00 -0400

So here I am, minding my own business on Labor Day, working on the upcoming teams branch of Tempo. Basically, it introduces a new account model that’s distinct from users and projects, ideally facilitating the setup of teams for small organizations. Accordingly, an account has an owner, and every user belongs to an account. In addition, users can be managers for the whole account (new!), or users can be marked as a manager for a particular project assignment.

This all sounds great on paper, but in the Projects controller there arise a number of concerns:

  • The account owner or one of the managers should see all the account’s projects on the projects index, and also the /archive resource collection
  • All other users should only see accounts they are assigned to as managers
  • index should only show active projects and archive should only show inactive projects
  • index and archive both support an id parameter for the XML api, enabling the lookup of an aribitrary collection of managed projects

As you can imagine, this could lead to a lot of spaghetti in a controller method, that you’d want to hide away in a model. Typically, you’d want your User model to set up the various project collections as association (has_many) resources so you can provide find options as needed (such as :conditions => { :is_active => true }). That keeps your redundancy down, but your controller (or a model method) would have to conditionally decide which resource to provide. There was the possibility of adding a method to the user model that handled the conditional logic and passed back the right result set, but it would have been a royal pain to correctly pass through find options, especially given all the various circumstances above..

To make things further complicated, we need to also use these methods for the reporting interface and to make sure users only see the time entries they are supposed to see. That means keeping nonsense and code repetition to a minimum is critical.

That’s where acts_as_union saves the day!

has_many :managed_projects,
:source => :project,
:through => :assignments,
:conditions => [ 'assignments.is_manager = ?', true],
:order => ' ASC'
has_many :owned_projects,
:source => :projects,
:through => :owned_account,
:order => ' ASC'
acts_as_union :manageable_projects, [ :owned_projects, :managed_projects ]

ActsAsUnion is included in our ActsAsNetwork plugin, and it allows us to UNION together multiple associations and operate across them as we would any other association, by supporting the various find_* options we are used to with has_many.

Now I can do any of the following:

@projects = @current_user.manageable_projects
@projects = @current_user.manageable_projects.find(:all, :conditions => { :is_active => false})
@projects = @current_user.manageable_projects.safe_find_from_ids(params[:id])

On a tangential note, safe_find_from_ids is a quick and dirty monkey-patch we throw in an initializer so we can look up a set of ids that returns an empty set instead of raising an exception when no ids are found:

class ActiveRecord::Base
def self.safe_find_from_ids(*args)
ids = args.kind_of?(Array) ? args.flatten : [args]
ids.empty? ? [] : self.find(:all, :conditions => ["#{self.table_name}.id IN (#{ids.collect{|p| '?'}.join(',')})", ids].flatten)

It’s not ideal, but it gets the job done. There may be a better way of doing it in recent versions of Rails, need to look into that.

Happy Labor Day!

Zetetic is the creator of the super-flexible Tempo Time Tracking system.

Video: Command Line Time Tracking

2009-09-03 20:00:00 -0400

Out to lunch

Recently we hired Aaron Aiken’s Trim Trab Creative to put together some videos demonstrating how Tempo works, and the videos are coming out great (check out the main tour vid here). He has a real knack for putting together interesting demos and communicating the concepts at work in a piece of software.

We put a lot of thought and work in Tempo’s interfaces, but sometimes it gets tricky to explain some of the concepts to new users, especially some of the advanced controls that can save you a lot of time. Introducing people to the benefits of using our simple command line for time tracking is one of those tricky things. Aaron hit it out of the park:

Tempo Time Tracking – Text Entry Demo from Zetetic on Vimeo.

Tempo: Spit and Polish, and the Future

2009-09-02 20:00:00 -0400

We’ve been working pretty hard on Tempo since we released the new design this Summer, fixing bugs, refining the interfaces, and making those subtle tweaks that can really make a difference with a piece of software that we use frequently while we work. With that in mind, we’ve got another maintenance update out that should provide some requested changes and fix yet more “things” that needed fixing.

Here’s the full list of what’s changed, then I’ll go into what we’ve got coming down the pipe:

Stat Chart

  • New user utilization chart on Time screen, click the spark-line to activate!
  • Modal charts return! Click stats metrics and spark-line on Reports screen to activate
  • Modal edit of time entry view was a little mussed in IE7
  • After entry edit, form fields were not clearing
  • After entry add, edit, start, stop, etc causes stats on screen to update for accuracy
  • API: setting exclude-tags param in reports search was ignored, now handled like tags
  • Saved Reports with specific end dates were skewed by a time zone / serialization issue
  • Future dates on reports were being improperly set to ‘today’
  • Last active project cannot be archived or deleted (causes mad bugz, interim fix)
  • Batch tag add and remove with no managed entries resulted in an error
  • Saved reports with exclude tags were broken
  • Hide tag auto-suggest when user hits space bar (a relieving fix)
  • Expanded command line entry form
  • Busted link to Mac OS X Dashboard widget

Coming Soon

We’ve been working hard behind the scenes on another major update for Tempo that should dramatically improve our ability to set up teams and collaborate. The current system can be a little bit confusing for first-time users who are looking to set up a Tempo account for their organization and invite their teammates. In the future, the setup process will distinguish between an Account, and a User. An Account will have a distinct name, e.g. Conglomo, and a subdomain for accessing the account (e.g. Billing functions will move to the account and users will be set up directly under the account by the account owner or any users assigned as managers.

Once we’ve got that in place, we plan on putting together an iPhone application (we’ve got some experience doing that now), making some much-needed improvements to our API, and getting to work on some of the integrations people have been asking for.

Oh, we’re cooking up a Gadget for the Windows Sidebar, too!

Hewitt is Right, Arment is Wrong

2009-09-01 20:00:00 -0400

Update: Corrected misspelling of Marco Arment’s surname.

Joe Hewitt, developer of Firebug and Facebook’s iPhone application, writes in an article titled, Innocent Until Proven Guilty, that Apple’s review process for third party iPhone apps in the iTunes App Store should be eliminated entirely. I was impressed with this, as it’s really the elephant in the middle of the room, that few commenters seem to address. The driving justification behind Joe’s short article is quite powerful:

Does that sound scary to you, imagining a world in which any developer can just publish an app to your little touch screen computer without Apple’s saintly reviewers scrubbing it of all evil first? Well, it shouldn’t, because there is this thing called the World Wide Web which already works that way, and it has served millions and millions of people quite well for a long time now.

Indeed, somehow we’ve managed all these years. And while the Internet has brought us various catastrophic influenza and garbage, the iPhone OS is already exposed to those threats, and third party applications are sand-boxed to the point of being a much poorer vector for malicious activity than looking for your classic remote buffer overflow. Hewitt continues, emphasis mine:

If you think that all apps should be held prisoner by Apple until proven safe, you should also be able to convince yourself that this is how the web should work.

I think the issue becomes a bit clearer if you change that statement to read, “that this is how your personal computer should work.”

In any event, there are many other developers out there who’ve been beating the drums of discontent, griping at Apple and earning some page views in the process, but the writing is usually done with an oddly sycophantic bent. Marco Arment, who is prone to such outbursts (wherein he righteously chastises Apple but then repeats over and over how much he loves the company and the iPhone itself, perhaps to prove his bona fides), recently pushed back against Hewitt’s sentiment, offering the article, Apple can’t stop reviewing iPhone apps. His main point, that Apple has no choice but to continue to review all apps lest some rogue iFart yield bad press or screw it up for all of us, rings quite hollow and altogether avoids Hewitt’s challenge: should this be how the Internet works? More to the point, should it be how your personal computer works?

Even if Apple stopped reviewing apps completely, they would still be blamed in the press for the effects of problematic apps. This creates a difficult position: Apple must attempt to be the gatekeeper in a market full of gray areas, but any decisions they make fall under intense scrutiny, and many decisions don’t have an indisputably “good” option.

Apple must attempt to be the gatekeeper? Arment is missing the painfully obvious, that Apple has been getting a ton of negative press, the likes of which they don’t normally ever see, thanks to their asinine review system and their poor behavior as a business partner. And that’s for the best, for App Store developers. It’s important to take off the Apple Fanboy bifocals long enough to look at this as a question of business relationships and investment risk.

Rather than fighting to abolish app review, it’s far more productive to guide and influence Apple, through both public and private interactions, to improve the system that we’re all, including Apple, probably stuck with for a long time.

Appeasement, or perhaps I should say bargaining with no chips, doesn’t seem very productive so far (is it ever?) Sure, it works well enough that popular developers like Arment have options for recourse; under the current system, if you are already a very popular developer with a wide audience, you can publicly embarrass Apple before that audience in order to get half-decent treatment as a business partner, adding value to their product and generating continued additional revenue for them.

Zetetic is the creator of the encrypted iPhone data vault and password manager Strip and the open source encryption-enhanced database engine SQLCipher.

Zetetic.Ldap - Bringing LDAP + LDIF tools to .NET

2009-08-31 20:00:00 -0400

We’ve just released the Zetetic.Ldap library to github, which makes it, to the best of our collective knowledge, the first and therefore most kickin’est .NET library for working with RFC 2849 LDIF, LDAP schema, and LDAP entry change tracking.

LDIF: This file format is still the best and most universal way to move data in or out of directory systems like OpenLDAP, Active Directory, Fedora or Red Hat Directory Server, etc. Zetetic.Ldap makes it easy to write and read directory entries with LDIF.

LdifWriter ldif = new LdifWriter(@"c:\temp\stuff.ldif"); 
ldif.BeginEntry("cn=joe cool,o=zetetic");
ldif.WriteAttr("givenName", "joe");
ldif.WriteAttr("jpegPhoto", File.ReadAllBytes(@"c:\temp\joe-photo.jpeg");
ldif.WriteAttr("dateCreated", DateTime.Now);

Zetetic.Ldap knows how to format binary data, dates, and long fields in the proper way — you don’t have to mess with it!

The same is true for reading entries out of an LDIF file.

LdifEntryReader ldif = new LdifEntryReader(@"c:\temp\stuff.ldif"); 
for (Entry entry = ldif.ReadEntry(); entry != null; entry = ldif.ReadEntry())
Console.WriteLine("Found: {0}", entry.DistinguishedName");

We’ve also included a command-line pivoter program that transforms LDIF files into tab-delimited files, for easy loading into Excel, RDBMS, or what-have-you. This can be incredibly useful for analyzing the data in your directory.

Change tracking: Normally, if you’re using Microsoft’s System.DirectoryServices.Protocols classes, you’ve got to manage the concept of preparing specific LDAP modification commands yourself. It can get pretty complicated, and sometimes you have to resort to trial-and-error to learn the right way to delete an attribute, for example, or rename or move an entry. Zetetic.Ldap provides a wrapper around this complexity, and will track your changes until you’re ready to send them to the server.

MutableEntry entry = MutableEntry.CreateUncommitted("cn=frank grimes,o=snpp", "person");
entry.SetAttr("sn", "Grimes");
MutableEntry entry = new MutableEntry(mySearchResult);

Schema: This part is a little more abstract. Directory servers contain an area that describes the allowable objectClasses (types) and attributes (fields), and those attributes have syntaxes (like string, number, date, etc.). The classes in Zetetic.Ldap.Schema help you work with these data programmatically, so you can quickly discover what the server supports. Moreover, you can find out what kind of value (syntax) each attribute uses conceptually, without delving into the world of OIDs.

Here’s a snippet from the project’s unit tests, showing how to export the basic objectClass definitions from a Microsoft LDAP server.

ISchemaInfo target = new AdsSchemaInfo();
using (LdapConnection conn = new LdapConnection("localhost:20389"))

foreach (ObjectClassSchema o in target.ObjectClasses)
System.Console.WriteLine("oc: {0}", o);
foreach (AttributeSchema a in o.MustHave)
System.Console.WriteLine(" must: {0} as {1}", a, a.LangType);

foreach (AttributeSchema a in o.MayHave)
System.Console.WriteLine(" may : {0} as {1}", a, a.LangType);

We also included some helpers to manage parsing special attributes like GUIDs, or “pwdLastSet” and “accountExpires,” which are usually treated like DateTimes, but stored as 64-bit integers, sometimes outside the range of what DateTime can handle—I always enjoy telling people, “Oh, it’s simply the number of 100-nanosecond intervals since January 1, 1601 UTC!”

So anyway, when you find yourself working with LDAP or LDIF on .NET, grab the release at github, and drop us a line, we’d love to know how you like it.