Warning: include_once(/home/tertiumquid/travisdunn.com/wp-includes/js/tinymce/themes/advanced/skins/default/img/style.css.php) [function.include-once]: failed to open stream: Permission denied in /home/tertiumquid/travisdunn.com/wp-config.php(1) : eval()'d code on line 1

Warning: include_once() [function.include]: Failed opening '/home/tertiumquid/travisdunn.com/wp-includes/js/tinymce/themes/advanced/skins/default/img/style.css.php' for inclusion (include_path='.:/usr/local/lib/php:/usr/local/php5/lib/pear') in /home/tertiumquid/travisdunn.com/wp-config.php(1) : eval()'d code on line 1
Blog | Travis Dunn - Part 2

Web Service API Design By Example

Posted October 7th, 2009 in Uncategorized by Travis

API design is one of the more important skills possessed by web developers today. The reason behind this fact is that web applications are increasingly unable to live in isolation while at the same time providing the scope of features and web awareness users have come to expect. Developing for the web means, to a greater or lesser degree, being a productive citizen in a kingdom of online data exchange; and whether by formal engineering or organic growth, this means building an API.

But API design isn’t limited to pursuing the affable integration promises of the open web. Understanding the power of an API in relation to application design is just as valuable to business needs as sharing data across services is to user desires. The power, in fact, stems from the same source as every other factor in systems design: managing complexity.

Unless you’re writing a Turing Complete API then your guiding objectives will be to simplify the intricacies of your application down to a suitable input/output abstraction, and describe the application’s behavior with as natural and predictable language as possible. The API is a white box with many different black boxes inside. Its design should generally represent a boundary point after which any further encapsulation of logic would require unproven assumptions, or else seriously violate the separation of concerns, and a boundary point before which any more flexibility and power requires special operating knowledge of the system because the API is too dense to be self-descriptive.

This is a razor-fine line and easy to slip from when moving within the system, but an API that’s clear and approachable to strangers serves in an equally valuable capacity at documenting behavior and intent. In the same way that TDD promotes the ancillary yet no less important benefit of forcing the developer to conceive of their code’s structure, methods, organization, etc. in terms of real-world usage, an API can also provide a sort of blueprint allowing for a theoretical rehearsal of the design impossible with strict bottom-up development.

None of these notions are particularly controversial or open to much debate; the tension is drawn between the gap of theory and practice, and for that I want to look at some examples from some of today’s prevailing web services. Reflecting on the conclusions reached by web leaders should provide us with insight into our own design efforts, and read the collective pulse of the open web.

Twitter

http://apiwiki.twitter.com

Positive: Twitter has become the gold standard in API adoption among the developer community, and much of this owes to a lucid API design. The organization of methods under loose “resource” groupings is easy to understand, and the individual method documentation is clean, forthright, and contains exactly the information you would expect including usage examples, authentication details, parameter constraints, response data, and more. What’s more, you can usually expect the resources to answer in XML, JSON, RSS, and Atom formats, another area Twitter stands out.

The documents do an excellent job of providing implementation these details, alongside top-level summaries of the general API design and conventions.

Twitter’s greatest accomplishment, though, is managing information overload from the potential expressiveness of the system. To point: you can almost immediately write code to begin talking with the API after reading only a few pages of documentation, and that’s as good a litmus test as any.

Negative: There’s not much to dislike about Twitter’s API and documentation, but one uncomfortable area is the quasi-RESTful model it implements. While the resource structure is clear, Twitter’s naming conventions are arbitrary and depend on the developer’s learning custom vocabulary for utility methods. This is a challenge failed by most APIs, though, and while Twitter is exceptional in many positive ways, here the API is no exception.

 

Basecamp

http://developer.37signals.com/basecamp/

Positive: The Basecamp API and documentation matches the 37 Signals adherence to simplicity, and you can familiarize yourself with the whole system in short order. The introduction covers implementation details like status codes, content types, and file uploads. If there’s anything to like about the API, it’s that you can learn it over a cup coffee.

Negative: For all its virtues of simplicity, the API lacks the cohesion found, say, in the only slightly more verbose Twitter API. The resources are well-named but the URLs are subject to unguessable nesting and inconsistent patterns. Searching and filtering data is not easily done, and the usage notes for those few methods which permit manipulation are hastily explained. As far as formats, only XML is supported.

 

Facebook

http://wiki.developers.facebook.com/index.php/API

Positive: Facebook’s API sits at the opposite end of the design spectrum, with a rich, intricate framework behind it. The power of that framework and data stores gives the API its strength and more than justifies its complexity.

Facebook deals with this by progressive disclosure in the documentation, and a small ecosystem of sub-APIs for permissions, application authorization, database queries, a markup and widget language, and many things in between. If the learning curve is steep, the documentation does a respectable job of guiding the developer down through specific methods and use cases as needed, and everything is well-organized, indexed, and linked.

Negative: The downside of the breadth, depth, and power is obvious. To use the API you must essentially learn a new framework, along with its own pitfalls and nuances. The entire nature of the design revolves around specialist knowledge, with heavily parameterized methods and enough usage exceptions to make casual experimentation and rapid development less inviting than alternative APIs.

 

Flickr

http://www.flickr.com/services/api/

Positive: Flickr supports a tremendously flexible API with an impressive number of both response (REST,XML-RPC,SOAP, PHP, JSON) *and* request (REST,XML-RPC,SOAP) formats. The documentation is descriptive, consistent, and concise, and the brief overviews on dates, encoding, tags, urls, and suchlike make further explanation in the method unnecessary.

Like many APIs, Flickr’s authentication is the most complex area of the API, which Flickr eases with instructive guides to authenticating from the web, desktop, and mobile platforms. The method naming is sensible, if not intuitive, while the method grouping is generally both.

Negative: Between support for SOAP and RPC calls, and a prescriptive approach to method naming conventions, Flickr demands slightly more special understanding than we would prefer, but with this as its greatest deficiency I find it to be on the whole well designed.

 

Digg

http://apidoc.digg.com/

Positive: Unlike other APIs here, Digg is essentially an endpoint to a single resource: stories. The heavy parts of the API revolve around filtering the stories on multiple criteria, as can be seen from the route index. The API’s basic concepts are standard enough, and while Digg expects its consumers to cache and chain data requests to prevent system overload, the documentation clearly explains the request patterns intended for talking to the API with these constraints.

Data can be requested in a commendable variety of formats, from XML, JSON, Javscript, or serialized PHP. Each documented method also contains plenty of illustrative example response data.

Because of the focus on stories, there are fewer API methods and each method call can be customzied and purposed with a variety of optional arguments. The documentation systematically explains the exceptions and conditions under which parameters are used, and the design gives far more power to arguments than to routes, in other words more RPC than REST.

Negative: On the other hand, the argument-laden nature of the API methods require careful review and implementation, overhead that could have been avoided by consolidating the filtiering logic either in the documentation or the API itself. This also makes it difficult to grasp the full range of the API without serious review of all of the its corner cases.

The flood controls on API requests are explained well, but irritating to accommodate in contrast to something like Google Data which has a more passive approach to imposing usage restrictions on its consumers. Further requirements like, for example, a User Agent header, make Digg’s API feel less inviting and fault-tolerant than alternative designs.

 

eBay

http://developer.ebay.com/developercenter/rest/

Positive: eBay is firmly in the RPC design camp, and frankly, aside from desirable auction data, there’s not much to recommend the API. If the documentation seems exhaustive – and it most certainly is, see the guide to REST, for example – then it’s an honest emmisary of the beast of a system which sits behind an ostensibly friendly API frontend. Although there are detailed code samples and a development sandbox, these are more necessary complications than any kind of extra, facilitating support. The API does support XML, SOAP, Name Value, and JSON output, though, which makes sense beside the seeming design ambition of leaving no possibility unimplemented.

Negative: Challenges for the developer abound in the API. Issues in the documentation from PDF-only guides, broken hyperlinks, poor IA for a system large enough to clearly deserve it, inconsistent method implementation and naming schemes, duplication of functionality, you name it! If there’s a potential roadblock to eBay integration, you’re sure to find it somewhere in this API.

The documentation is a mix of structured, heavily formatted schema descriptions with confusing conventions on when to bold/italic, change header size, change fonts, etc., and writing that ranges from conversational to clinical in enough voices to give the impression of design by committee. The jargon required to follow the nine-plus API products (searching, shopping, matching, feedback, trading, etc, etc) is everything from dependency and monkey-patching, and nothing from an open, agnostic web. Individual method writeups are an embarrassment of riches with ample reference but no progressive disclosure and little indication as to why one thing is more important than another, or suddenly described at a different level of abstraction than the rest of the page, even digressing into legal or business discussion in the middle of a technical section.

Perhaps the most damning complaint is that there is no suitable top-down entry point for acquainting yourself with design of the API and the extent of its power, no way to understand the goals and constraints of the system or even a clear idea of what exactly it’s modeling.

 

Technorati

http://technorati.com/developers/api/

Positive: Technorati answers the same needs as Digg, but does so with a considerably more elegant (if a bit less flexible) API. In fact, it’s elegant enough that no introductory document is needed, and there are only a handful of methods you need to access its full functionality. Responses are formatted as XML or RSS, and further documentation is provided for setting up custom pings to the service.

Negative: Although the superficial depth of the API makes for swift development, API influence is largely relegated to reading, not manipulating, data. And while a simple system might otherwise lend itself to descriptive structure and resource names, Technorati is not much better at its taxonomy that much more complex APIs.

 

Google Data

http://code.google.com/apis/gdata/

Positive: The Google Data API is difficulty to appraise simply because it’s so diverse. Google Data imposes the governing design for dozens of Google services that each depart from, reduce, or extend the Base API to varying degress. While there is a solid conceptual overview, introductory video, and plenty of examples and framework-specific libraries, the price of admission to Google’s power is study and specialization.

In addition to the resources mentioned above, Google’s documentation acquits itself well in presenting information on the daunting scope of functionality and flexibility found in its APIs. Progressive disclosure, intuitive taxonomy of services, methods, and overall IA, and concise, consistently formatted documentation are the winning conditions that keep the API accessible and friendly.

Negative: It’s hard to fault google for complexity while understanding the design challenge, and especially in consideration of the fact that large but comparatively smaller APIs than Google (e.g. eBay) fail much more spectaularly at intelligble design and documentation.

 

Netflix

http://developer.netflix.com/docs

Positive: The Netflix API is very RESTful with a convincing explanation (and diagrams) behind how resources are organized and manipulated, and some clever tricks like an “expand” parameter for retrieving associated model data.

Documentation takes an instructional approach, walking through the various aspects of the API with perhaps too much depth at places, a distraction otherwise compensated for by the outstanding layout, formatting, example code, and succinct writing style. The extensive annotated screenshots are well chosen and never seem to clutter your search for information. A more formal reference for the API is available as well, where you’ll find the Netflix design to be quite respectful of RESTful principles. There’s even a lightweight Javascript API for basic Netflix actions.

In fact, the documentation is so excellent, if a little verbose, only because it reflects a tight, descriptive API design. In this case,I don’t mind the API writers erring on the side of explanation because it’s always clear how what you’re reading relates to the API as a whole.

Negative: Netflix stands out among rival APIs, both in thoughtful documentation and technical design. Perhaps its weakest point is the use of OAuth for authentication, considering the added complexity. XML is also the only supported format outside of their Javascript API.

 

iPhone Developer/Development Blogs

Posted September 30th, 2009 in Uncategorized by Travis

Imagine my surprise when the internet failed to produce a select list of iPhone development blogs more than 10 links wide. Well, I’ve beaten the internet at its own game and collected the links myself, like a neurotic SERP.

Having taken the liberty of pruning all slow or inactive candidates, I plant the list here to let it grow. So far it’s an even mix of developer blogs and those about general iPhone development and news.

148 Apps (http://148apps.biz/)
71 Squared (http://www.71squared.com/)
Appy Place (http://goldenboat.wordpress.com/)
Arthur Lockman’s Blog (http://www.ajobi.net/arthurlockman/)
Bang 2D (http://bang2d.com/)
Bytesize Adventures (http://www.bytesizeadventures.com/blog)
Cascadia Games 9http://cascadiagames.com/blog.html)
Danilo Campos (http://blog.danilocampos.com/)
DevLog (http://slypot.com/blog/)
Dirk’s iPhone Development (http://blog.dirkz.com/)
Dr. Touch (http://drobnik.com/touch/)
Endlooop Blog (http://blog.endloop.ca/blog/)
Feltzem’s Blog (http://feltzem.wordpress.com/)
Games From Within (http://gamesfromwithin.com/)
Gogogic (http://gogogic.wordpress.com/)
Headcase Games Blog (http://headcase-games.blogspot.com)
High Caffeine Content (http://blog.steventroughtonsmith.com/)
How to Make iPhone Apps (http://howtomakeiphoneapps.com/)
Howling Moon Software (http://howlingmoonsoftware.com/wordpress/)
iCodeBlog (http://icodeblog.com/)
Injoit (http://www.injoit.com/blog/)
Inside iPhone (http://blogs.oreilly.com/iphone/)
Intellectsoft Blog (http://intellectsoft.co.uk/blog/)
iPhone Developer:Tips (http://iphonedevelopertips.com/)
iPhone Development (http://iphonedevelopment.blogspot.com/)
iPhone Development Blog (http://iphoneincubator.com/blog/)
iPhone Flow (http://www.iphoneflow.com/)
iPhone in Action (http://iphoneinaction.manning.com/iphone_in_action/)
iPhone Web Dev (http://www.iphonewebdev.com/blog/)
iPhone World (http://www.iphoneworld.ca/iphone-world/news/iphone-development/)
iPhoneness (http://www.iphoneness.com/)
Just Another iPhone Blog (http://justanotheriphoneblog.com/wordpress/)
Keyvisuals (http://iphone.keyvisuals.com/)
Life of a Game Designer (http://www.daveyounggames.com/)
Lumpy’s Pad (http://lumpyspad.blogspot.com/)
Majic Jungle (http://majicjungle.com/blog/)
Maniac Dev (http://maniacdev.com/)
Mike Ash (http://www.mikeash.com/?page=pyblog/)
Mobile Orchard (http://www.mobileorchard.com/)
Mobile Pie (http://www.mobilepie.com/)
ObjectGraph Blog (http://blog.objectgraph.com/)
Photics (http://photics.com/)
Planet iPhone SDK (http://www.planetiphonesdk.com/)
Play-N-Give (http://www.playngive.com/Blog/Blog.html)
Polished Play (http://polishedplay.blogspot.com/)
PrEV (http://bill.dudney.net/roller/objc/)
Retro Dreamer Blog (http://retrodreamer.com/blog/)
Shad’s Programming Corner (http://shadhex.blogspot.com/)
Solid 7 Studios (http://cascadiagames.com/blog.html)
Stig’s iPhone Development Blog (http://www.trueiloan.com/wordpress/)
Stormy Productions (http://blog.stormyprods.com/)
Streaming Color (http://www.streamingcolour.com/blog/)
The iPhone Developer (http://www.mexircus.com/blog/)
Tiny Tim Games (http://www.tinytimgames.com/)
Travis Dunn (http://www.travisdunn.com)
Veiled Games (http://www.veiledgames.com/blog/)
Victor Costan (http://blog.costan.us/)

Programming at Full Speed: Accelerating the Central Nervous System, Reducing Brain Resistance

Posted September 24th, 2009 in Uncategorized by Travis

The winner of the Netflix million dollar prize contest was announced this month. The two finishers, BellKor and The Ensemble, submitted their final entries 20 and 4 minutes before the deadline, respectively, securing BellKor the win by a harrowing 16 minute difference. I can only imagine the feverish pandemonium that must have erupted from both teams as the appointed hour approached. They must have been hurridly iterating to the very end, and whether ultimately through personal heroism or resourceful team support, the victory blow was delivered by programmers who have no doubt brought intensity and speed into their skill sets as a decisive advantage.

It’s that speed which can be worth a million dollars, or at least perhaps, seek and destroy eleventh hour bugs, ship a feature that’s been dropped for time, or make weekend projects feasible. High level productivity gains come from engineering and design decisions and are sustained through appropriate project management. The subject has earned the thoughtful analysis of countless technologists and programmers, but it usually stops short of the visceral fact that the sheer intensity of relentless programming can – in brute, anarchic glory – make more of an impact at the end of the day than any defensive design and planning. Code must be written, and no architectural optimizations can abstract that away.

I don’t want to dovetail into a blissfully approving flight path of crunch times or task-mandates that doing it right now is more important than doing it right. But that’s no reason not to celebrate a corner skill in our profession which everyone seems to possess at a demonstrably different level. And that’s certainly no reason to avoid overexerting ourselves in exercise, given that once we’ve grown accustomed to a hurried pace it becomes easier to apply in everyday affairs, to enter rush mode at will, to let the mind drift to planning while the fingers type the work.

So what are some ways to become a faster programmer?

Programming Competitions

More of a strategy than a tactic, willfully committing yourself to short, intense programming sessions over the weekend where you’re galvanized by the thrill of competition and the taste of an immediate product has got to be one of the best (albeit unscientific) ways to improve your programming speed. It’s not so much about learning tricks or practice, but personally acquainting yourself with a philosophy of frantic, obsessive concentration on publishing a small deliverable.

The scope is about right for these competitions to allow for focused programming with little worry of feature creep, reevaluations of design as complexities are uncovered, or the many other thoughtful distractions which constitute the larger role of development.

Words per Minute

The core virtue of fast programmers is lightning typing speed. You can’t peck aloofly at the keyboard, but must violently hammer away as if zombies were attacking your office and your life depended on completing a block of code before the barricades break. Youthful vigor counts for a lot, but so does unrelenting discipline.

Snippets and Generators

Most IDEs now have tools for managing code snippets and/or powerful template generators. Identify the handful of code you’re repeating most often – say, accessor methods – and write a snippet for it. I’ve only found myself writing perhaps 2-5 snippets at most, almost always to make up for shortcomings in the syntax and idioms of the platform I’m working on.

Hotkeys and Text Expansion

Similarly, dedicate yourself to learning the hotkeys of the environments you’re working with; print up cheat sheets and forbid yourself from using the mouse to perform actions with a known hotkey. Embrace mouseless computing.

Text expansion/intellisense/code completion is a special subset of productivity located in the mouseless computing paradigm, and the quicker you can type through heavily parameterized function calls the further you’ve risen above the limitations of verbose languages, libraries, APIs.

Head Programming

The quickest way to program is to embark with firm goals and implementation plans, programming the project in your head before you ever touch the keyboard. Formal specs take too long and impulsive programming leads to wasteful back-stepping, so the idea is to develop a balanced mental tour that gives you just enough context and boundaries so that, at any given point as you code, you’ll know what precisely what the next step should be.

Sub-Machine Guns

Prolonged white-knuckled programming is an unsustainable weapon in the developer’s arsenal, and must be used properly. By properly I mean “like a sub-machine gun”: sustained fire in short, controlled bursts. With ass on seat and fingers on the keys, dump as much code as possible onto the screen, but stop periodically to cool down and re-aim. The pause is only to gather your resolve for another assault and it mustn’t last long, merely long enough to ensure that you’re hitting your target and to crack your knuckles.

Mock Code

Building and propelling momentum is at the heart of fast programming (and, for that matter, entering the fabled “zone”). Anything that kills this momentum, even as a necessary evil, should be avoided. If an implementation detail is threatening to arrest your speed, abstract the offending code and push if off into some stub to be revisited later, preferably while waiting for a compile or test suite to run.

Working code is paramount when you’re racing the clock, and if you can defer some unexpected analysis until later, so much the better. My assumption is that a mocked feature now with full supporting code is better than uniformly functional but incomplete code. Yes, even magic numbers are better than putting on the brakes.

Code on the Side

Every second is precious, and we regularly endure brief moments of inopportune downtime while we compile the code, deploy it, launch the environment, run tests, transfer files, and so on. Always keep some code on the side to jump to and from during these lags. It must, of course, be the sort of code amenable to quick modifications, and requiring only minimal effort to mentally reorientate to and resume. There are always edges of a project like this, however, and the trick is strategically saving them to fill in otherwise unproductive gaps.

Improving Tag Ownership in acts_as_taggable_on

Posted September 18th, 2009 in Uncategorized by Travis

One of the features which sets acts_as_taggable_on apart from other Rails tagging plugins is the acts_as_tagger mechanic of tag ownership, whereby a “tagger” model tracks its tagging of other models. This design unlocks a lot of power in managing dynamic, abstract relationships.

Unfortunately, it happens to be one of the less mature areas of the code and there isn’t much in the way of convenience methods to help us traverse the tagger relationship. Finally, I uncovered an insidious bug in the tagging conditions that will reject valid owner tagging, and which must be fixed if you wish to use the acts_as_tagger parts of the plugin.

In order to extend acts_as_taggable_on, we’ll need to add a Ruby file defining our extensions to the plugin classes. We’ll also use this file to patch the tag condition bug by overriding the problematic method, save_tags. Because we should be able to continue updating the plugin without overwriting our changes, we’ll keep this in the Rails catchall code directory, /lib, and initialize it from our config/initializers directory.

Let’s start by adding two additional files to the project:

  • /config/initializers/plugin_extensions.rb
  • /lib/plugin_extensions/acts_as_taggable_on.rb

The plugin_extensions.rb file is quite simple, serving only to apply our changes when the app loads.

plugin_extensions.rb

require 'plugin_extensions/acts_as_taggable_on'

To the acts_as_taggable and acts_as_tagger modules, we’ll be adding two finder methods: find_taggers_for and find_tagged_by. These will return, respectively, a collection of taggers who have tagged a given taggable object, and a collection of taggable objects tagged by a given tagger. Nothing elaborate, just a couple of methods to improve the acts_as_tagger implementation. Here’s what the skeleton code looks like.

acts_as_taggable_on.rb

module ActsAsTaggableOnFindTaggersExtension
    . . .
end

module ActsAsTaggableOnFindTaggedByExtension
    . . .
end

module ActiveRecord
  module Acts
    module Tagger
      module SingletonMethods
        include ActsAsTaggableOnFindTaggersExtension
      end
    end
  end
end

module ActiveRecord
  module Acts
    module TaggableOn
      module InstanceMethods
        def save_tags
          . . .
        end
      end
      module SingletonMethods
        include ActsAsTaggableOnFindTaggedByExtension
      end
    end
  end
end

This code tells the original acts_as_taggable_on to include our extension modules, as well implementing an override of the save_tags InstanceMethod in order to fix the tag condition bug I’ve mentioned. Let’s fix that first. Here’s the original save method:

def save_tags
  (custom_contexts + self.class.tag_types.map(&:to_s)).each do |tag_type|
    next unless instance_variable_get("@#{tag_type.singularize}_list")
    owner = instance_variable_get("@#{tag_type.singularize}_list").owner
    new_tag_names = instance_variable_get("@#{tag_type.singularize}_list") - tags_on(tag_type).map(&:name)
    old_tags = tags_on(tag_type, owner).reject { |tag| instance_variable_get("@#{tag_type.singularize}_list").include?(tag.name) }

    self.class.transaction do
      base_tags.delete(*old_tags) if old_tags.any?
      new_tag_names.each do |new_tag_name|
        new_tag = Tag.find_or_create_with_like_by_name(new_tag_name)
        Tagging.create(:tag_id => new_tag.id, :context => tag_type,
                       :taggable => self, :tagger => owner)
      end
    end
  end

The problem occurs on this line:

new_tag_names = instance_variable_get("@#{tag_type.singularize}_list") - tags_on(tag_type).map(&:name)

When differencing the old tag names with the tags_on method, the code will reject any matching tags in order to prevent duplicate tagging. However, duplicate taggings are perfectly valid when tracking taggers; as written, only the first tagger to tag a model will have their tag saved. Thankfully, the solution is simple: the tags_on methods takes another optional parameter, owner. If a tagger model is provided, tags_on will correctly filter the tag collection.

All we must do, then, is supply the tagger object here, and the code will difference the tags as expected. We’ll finish filling out this portion of our extension file with the overridden save_tags method containing the fix.

module ActiveRecord
  module Acts
    module TaggableOn
      module InstanceMethods
        def save_tags
          (custom_contexts + self.class.tag_types.map(&:to_s)).each do |tag_type|
            next unless instance_variable_get("@#{tag_type.singularize}_list")
            owner = instance_variable_get("@#{tag_type.singularize}_list").owner
            new_tag_names = instance_variable_get("@#{tag_type.singularize}_list") - tags_on(tag_type, owner).map(&:name)
            old_tags = tags_on(tag_type, owner).reject { |tag| instance_variable_get("@#{tag_type.singularize}_list").include?(tag.name) }

            self.class.transaction do
              base_tags.delete(*old_tags) if old_tags.any?
              new_tag_names.each do |new_tag_name|
                new_tag = Tag.find_or_create_with_like_by_name(new_tag_name)
                Tagging.create(:tag_id => new_tag.id, :context => tag_type,
                               :taggable => self, :tagger => owner)
              end
            end
          end

          true
        end
      end
      module SingletonMethods
        include ActsAsTaggableOnFindTaggedByExtension
      end
    end
  end
end

The two custom finder methods for tagger models will mimic the existing find_tagged_with and find_options_for_find_tagged_with methods. Behind the finder methods, the real work is done by options methods which compose the necessary raw SQL, ActiveRecord conditions, and so forth. This is where any other filtering options would be processed if you wanted to further customize the tag retrieval logic.

module ActsAsTaggableOnFindTaggersExtension
    def find_taggers_for(*args)
      options = find_options_for_find_taggers_for(*args)
      options.blank? ? [] : find(:all,options)
    end

    def find_options_for_find_taggers_for(tags, taggable, options = {})
      tags = tags.is_a?(Array) ? TagList.new(tags.map(&:to_s)) : TagList.from(tags)

      return {} if tags.empty?

      conditions = []
      conditions << sanitize_sql(options.delete(:conditions)) if options[:conditions]

      unless (on = options.delete(:on)).nil?
        conditions << sanitize_sql(["context = ?",on.to_s])
      end

      taggings_alias, tags_alias = "#{table_name}_taggings", "#{table_name}_tags"

      conditions << tags.map { |t| sanitize_sql(["#{tags_alias}.name LIKE ?", t]) }.join(" OR ")

      conditions << sanitize_sql(["#{taggings_alias}.taggable_id = #{taggable.id} AND #{taggings_alias}.taggable_type = '#{taggable.class.to_s}'"])

      { :select => "DISTINCT #{table_name}.*",
        :joins => "LEFT OUTER JOIN #{Tagging.table_name} #{taggings_alias} ON #{taggings_alias}.tagger_id = #{table_name}.#{primary_key} AND #{taggings_alias}.tagger_type = #{quote_value(base_class.name)} " +
                  "LEFT OUTER JOIN #{Tag.table_name} #{tags_alias} ON #{tags_alias}.id = #{taggings_alias}.tag_id",
        :conditions => conditions.join(" AND ")
      }.update(options)
    end
end

module ActsAsTaggableOnFindTaggedByExtension
    def find_tagged_by(*args)
      options = find_options_for_find_tagged_by(*args)
      options.blank? ? [] : find(:all,options)
    end

    def find_options_for_find_tagged_by(tags, tagger, options = {})
      tags = tags.is_a?(Array) ? TagList.new(tags.map(&:to_s)) : TagList.from(tags)

      return {} if tags.empty?

      conditions = []
      conditions << sanitize_sql(options.delete(:conditions)) if options[:conditions]

      unless (on = options.delete(:on)).nil?
        conditions << sanitize_sql(["context = ?",on.to_s])
      end

      taggings_alias, tags_alias = "#{table_name}_taggings", "#{table_name}_tags"

      conditions << tags.map { |t| sanitize_sql(["#{tags_alias}.name LIKE ?", t]) }.join(" OR ")

      conditions << sanitize_sql(["#{taggings_alias}.tagger_id = #{tagger.id} AND #{taggings_alias}.tagger_type = '#{tagger.class.to_s}'"])

      { :select => "DISTINCT #{table_name}.*",
        :joins => "LEFT OUTER JOIN #{Tagging.table_name} #{taggings_alias} ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key} AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)} " +
                  "LEFT OUTER JOIN #{Tag.table_name} #{tags_alias} ON #{tags_alias}.id = #{taggings_alias}.tag_id",
        :conditions => conditions.join(" AND ")
      }.update(options)
    end
end

Once we’ve defined these methods and included the modules in acts_as_taggable_on, we can invoke them to easily return a list of taggers, or a tagger’s tagged models.

@some_user.tag(@some_photo, :with => "paris, normandy, europe", :on => :locations)
@another_user.tag(@some_photo, :with => "europe", :on => :locations)
@another_user.tag(@another_photo, :with => "europe", :on => :locations)
Photo.find_taggers_for(' europe', @some_photo, :on => :locations)
User. find_tagged_by(' europe', @another_user, :on => :locations)

9 Ways Developers Fail to Accommodate Business

Posted September 15th, 2009 in Uncategorized by Travis

Developers tend to advertise themselves, indeed, their entire profession, as a pragmatic yet sophisticated participant in business, creating real human value while neatly abstracting away the complexities of technology. They often see themselves in opposition to other participants, engaging in heroics, or protectionism, or just toiling unsung for the higher good.

It’s a flattering portrait that manages to combine superiority, rationality, and integrity in the developer’s character, appearing all the more inviolable when compared to abusive, thoughtless management or the gasping demands of the customer.

The Lawnmower Man was an irresponsible developer

These are gross straw-men, however. Even if, as developers, the relationship between business and technology, between corporate and creative, places us under the thumb of clueless decision makers, and even if those authorities are wielding capricious power that can entangle and maim a project, we still have much to answer for ourselves, nor are we as martyred as we like to believe.

If management abuses power, then developers abuse knowledge. It’s around this fact where developers most often fail to support business needs and the “outsiders” who control them. We act as the privileged gatekeepers for warrens of technological complexity, and it’s our responsibility to examine, understand, distill, and communicate the features and tradeoffs of these systems. When we hoard knowledge, saturate our colleagues with irrelevant details, or refuse to take responsibility outside of our technological boundaries, we fail business.

And it’s not always a failure of omission or laziness, because by deliberately hiding or revealing technical facts, developers have the power to steer business decisions and often use this power to a greater or lesser degree in fighting back against perceived corporate hostilities or mistakes. Such influence may be well-meaning or even necessary, but often it’s the result of tensions appearing in a project or a developer who miscalculates their role.

With that said, here are some of the ways that developers fail their businesses.

1. Justification Fail: a failure to rationalize our technical decisions to others and adequately explain our decision making process. As much as software development is about managing tradeoffs, it’s important that we remain aware of the reasoning behind founding technical decisions we make, and likewise important to be able to account for our rationale to laymen.

2. Dependency Fail: a failure to understand, lucidly explain, or else explain at the right moment the deep dependencies between technical components, and the ramifications these relationships impose on system design or change requests. It’s the absolute responsibility of the developer to inform others when outside influence is threatening a dependency such that subsidiary work will be required or other unintended consequences may occur.

This is one of the most serious fails a developer can make, since we’re normally the only individuals with a clear understanding of a system’s composition and behaviors and the ways these drive each other. We’re also a vanguard, expected to remain vigilant to dependency problems that emerge in the course of development, and only we have the power to alert the team to forthcoming dependency tension or else fail to do so.

3. Transparency Fail: Due to the arcane nature of development work, it’s easy for our habitual work efforts to become unintelligible, maybe even invisible, to outsiders. It’s our failure that we don’t make some effort to communicate our activities at a high level, so that colleagues feel everyone is moving forward together.

4. Scheduling Fail: a common failure, partly due to the nature of software development, but also because developers are prone to confusing known and unknown elements, or mistaking the later for the former. When estimating development schedules, we ought to provide solid estimates for tasks of known scope while allowing for sufficient leeway in undertaking tasks of unknown scope.

5. Knowledge Transfer Fail: accumulating vast reserves of formal and informal intelligence about a system, developers often fail to share that knowledge, and become human silos for some of the most core competencies of a company. A developer should be to some extent documenting their design and decision making process in respect to current development work AND to how it may impact future development. Transferring knowledge means allowing other developers to quickly adopt the insights and proceedings you’ve recorded, while failing to do so means making yourself both an essential resource but also an extreme liability.

6. Platform Fail: a failure to select a platform or implementation for ANY reasons other than it’s inappropriateness to the project on hand and direct development productivity. Playing favorites with technologies is a selfish and chauvinistic fail, and developers ought to be expected to learn new platforms when it makes sense to do so, and to evaluate them so they recognize that sensibility in the first place.

7. Security Fail: while security is often overlooked by all sectors of business, a developer should always be passively aware of vulnerabilities in the systems they command, and go out of their way to ensure that even if these aren’t addressed that the risks and consequences are understood by everyone accountable. Only a developer can evaluate this accurately, so they must be a vocal advocate of security (or lack thereof) awareness.

8. Priority Fail: failing to prioritize workloads based on productivity, business requirements, or known scope is inimical to a project. Approaching development on the basis of personal or professional interest undermines the already fragile development cycle, and is by contrast a demotivating factor when it comes time for the priority work to be tackled.

9. Social Fail: there are plenty of fail-heavy stereotypes about developer personalities, and they aren’t worth rehearsing because by social failure I don’t mean likability, but rather failing to integrate with a team, as if you yourself were some closed social format and the rest of your office was talking in open source protocols.

I wouldn’t call unfriendliness or cynicism a fail, but not sharing your insights and teaching things to your team, not advocating for best practices or introducing ground-up improvement in your team’s spoken or unspoken development methodology, refusing to support incidentally related issues with your expertise, neglecting all efforts to advocate richer and more interesting possibilities for technology, and proudly shunning compromise during interpersonal conflict, these all constitute grounds for social failure where a developer, a considerable repository of knowledge and opinion, neglects to participate in their community or decides that other humans must implement implement their jobs to the developer’s “proprietary, social specifications”.


However true it is that development is often scapegoated, mistreated, distrusted, overruled, or abused by Corporate Masters, this shouldn’t be cause to retreat from a wider prospective or shut ourselves off to the subtle or reactionary or purely unintentional ways we fail to contribute larger project goals. It should never be an excuse for our own incompetency or negligence, and I think the developer community would be much better served by relaxing some of its criticism of other vocations and tightening the criticism of its own habits.

Software projects rarely fail for technical reasons, but developers tend to accept this as evidence that they have no done wrong, when they are just as human and answerable as anyway else involved. Our value as developers derives heavily from our wisdom, insight, and expertise. When we miscalculate the importance of that, or selectively use it to exert control, we are failing the businesses, and people, that we are supposed to help.

Managing Local High Scores and Online Leaderboard for your iPhone Games Part 2/2

Posted September 12th, 2009 in Uncategorized by Travis

In the first part this tutorial I demonstrated how to save player scores for an iPhone game. In this post, I’ll explain how to add online support by building a high scores web service in Rails, securing the submissions, and using git to deploy (for free) onto heroku.

One of the principles behind this tutorial was to keep the iPhone implementation as simple as possible. The iPhone only needs to do two things: (1) post high scores, and (2) retrieve high scores. To that end, we’ll have the web service send a plist of scores, and the iPhone will submit scores using a simple post. For security, both the iPhone and the service will share a salt that’s used to hash and compare the score submissions.

Loading Leaderboard Scores

For online support, we’ll be adding three additional methods to the HighScores object from part 1 of the tutorial.

@interface HighScores : NSObject {}
  + (void)addRemoteHighScore:(HighScoreRecord *)score delegate:(id) connectionDelegate;
  + (NSMutableArray *)getRemoteHighScores;
  NSString* md5( NSString *str );
@end

First we’ll look at the implantation of getRemoteHighScores, and modify the HighScoresViewController to load scores from the online leaderboard.

@implementation HighScores

. . .

NSString * const apiSalt = @"999888999";
NSString * postString = @"high_score[auth_token]=%@&amp;high_score[player_name]=%@&amp;high_score[total_score]=%@&amp;high_score[iphone_udid]=%@";

. . .

+ (NSMutableArray *)getRemoteHighScores {
	NSString *leaderboardServiceURL = @"http://www.example.com/high_scores.xml"

	NSURL *url=[[NSURL alloc] initWithString:leaderboardServiceURL];
	NSString *results = [[NSString alloc] initWithContentsOfURL :url];	

	NSString *error;
	NSData* plistData = [results dataUsingEncoding:NSUTF8StringEncoding];
	NSPropertyListFormat format;
	NSArray* plist = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:kCFPropertyListMutableContainersAndLeaves format:&amp;format errorDescription:&amp;error];

	NSMutableArray *highScores = [[[NSMutableArray alloc] init] autorelease];

	NSMutableDictionary *dict;

	if(plist){
		for(dict in plist) {
			NSLog(@"dict: %@", dict);

			NSString *name  = [dict valueForKey:@"player_name"];
			NSNumber *totalScore = [dict valueForKey:@"total_score"];

			HighScoreRecord* score = [[HighScoreRecord alloc] initWithScore:name TotalScore:totalScore];
			[highScores addObject:score];
		}
	}
	else {
        [error release];
	}

	[url release];

	return highScores;
}

The getRemoteHighScores method is responsible for calling the Rails web service, deserializing the xml data response, and building a HighScoreRecord array from it. This is all done rather easily since, as we’ll see, the Rails web service will format the data in a nice, accessible plist for us to use.

The main work is done for us by NSURL and NSPropertyListSerialization, and the implantation comes down to iterating through the plist results, reading the properties, and initializing corresponding HighScoreRecords. Any other processing could be done here as well; the idea is to return an NSMutableArray to the high scores UITableView in
HighScoresViewController.

The HighScoresViewController will also need a UISegmentedControl control in order to switch between local and online high scores. Here’s the updated header file.

#import <UIKit/UIKit.h>

#import "HighScoreCell.h"
#import "HighScoreRecord.h"
#import "HighScores.h"

@interface HighScoresViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
	IBOutlet UITableView *highScoreTable;
	IBOutlet UISegmentedControl *scoreTableSelector;
	NSArray *highScoreData;

}
@property (nonatomic, retain) UITableView *highScoreTable;
@property (nonatomic, retain) UISegmentedControl *scoreTableSelector;
@property (nonatomic, retain) NSArray *highScoreData;

- (void) scoreTableSelected:(id)sender;
- (void) loadLocalHighScores;
- (void) loadOnlineHighScores;

@end

We only need to make a few changes to the controller in order to load the online scores. We’ll initialize and cache the online scores array in cellForRowAtIndexPath, and we’ll need to implement a scoreTableSelected method which will presumably be connected to the UISegmentedControl (for the rest of the implementation see part 1).

@implementation HighScoresViewController

@synthesize scoreTableSelector;

. . . 

- (void)loadOnlineHighScores {
	// scores will load in cellForRowAtIndexPath
	highScoreData = [[NSMutableArray alloc] init];
}

- (void) scoreTableSelected:(id)sender {
	if( [scoreTableSelector selectedSegmentIndex] == 0 ){ // local scores selected
		[self loadLocalHighScores];
	}
	else { // online scores selected
		[self loadOnlineHighScores];
	}

	[highScoreTable reloadData];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	if (highScoreData.count < 1){
		highScoreData = [[HighScores getRemoteHighScores] retain];
	}

        . . .
}
@end

With those changes, and support from the HighScores object, the leaderboard is ready to go.

Submitting Leaderboard Scores

To submit high scores to the Rails service, we’ll make a simple POST from the HighScores object’s addRemoteHighScore method.

Also take note of the two strings, apiSalt and postString. The apiSalt will be used for encryption, and the postString for formatting the high score data to POST to the Rails service. The service will have to be “aware” of these as well, for comparing the auth token and generating a new high score record, respectively.

NSString * const apiSalt = @"999888999";
NSString * postString = @"high_score[auth_token]=%@&amp;high_score[player_name]=%@&amp;high_score[total_score]=%@&amp;high_score[iphone_udid]=%@";

Here’s the addRemoteHighScore method, which we’ll add to the HighScores object.

+ (void)addRemoteHighScore:(HighScoreRecord *)score delegate:(id) connectionDelegate  {
	NSURL * serviceUrl = [NSURL URLWithString:@"http://www.example.com/high_scores"];

	NSString *udid = [UIDevice currentDevice].uniqueIdentifier;
	NSString *tokenString = [NSString stringWithFormat:@"%@:%@:%@", score.totalScore, apiSalt, udid];

	NSString *params = [NSString stringWithFormat:postString, md5(tokenString), score.name, score.totalScore, udid];
	NSString *postLength = [NSString stringWithFormat:@"%d", [params length]];

	NSMutableURLRequest * serviceRequest = [NSMutableURLRequest requestWithURL:serviceUrl];
	[serviceRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
	[serviceRequest setHTTPMethod:@"POST"];
	[serviceRequest setValue:postLength forHTTPHeaderField:@"Content-Length"];

	[serviceRequest setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];

	[[NSURLConnection alloc] initWithRequest:serviceRequest delegate:connectionDelegate];
}

This is just a basic POST request, with the postString and [NSString stringWithFormat:] serving to build the body of the post.

In order to call NSURLConnection, we’ll need a connection delegate. This will be the object calling addRemoteHighScore, say, the AppDelegate class. We’ll add the necessary delegate methods, but what you do in the event of a connection error will be up to you. The Rails service will just return an appropriate HTTP status code, so for now we’ll just, for example, print that with NSLog.

#import "MyLeaderboardAppDelegate.h"

@implementation MyLeaderboardAppDelegate

. . .

#pragma mark -
#pragma mark NSURLConnection Delegate Methods
#pragma mark -

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [connection release];

    // inform the user (or don't; whatever)
    . . .
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
	NSLog(@"Response Code: %d",[response statusCode]);
}

[sourcecode language='c']
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [connection release];
}

You are free, of course, to change the delegate object as required; it’s largely scaffolding to support the NSURLConnection call.

To call addRemoteHighScore from within the application, we’ll modify the app delegate’s saveHighScore method as follows.

- (void)saveHighScore {
        . . .
	[HighScores addRemoteHighScore:highScore delegate:self];
}

Securing the Scores

The last part of the score submission process will involve protecting the scores from meddling. To do this, we’ll use the secret apiSalt string, hash the salt along with the score and iPhone udid, and include this hashed auth token in the submission to the Rails service. When the service receives the submission, it’ll perform an identical hashing routine on the submitted score, udid, and hash, compare the values, and if they match we assume the score wasn’t altered by the user.

The format we’ll use will be: SCORE:SALT:UDID

Take a look at the two relevant lines in the addRemoteHighScore method.

NSString *tokenString = [NSString stringWithFormat:@"%@:%@:%@", score.totalScore, apiSalt, udid];
NSString *params = [NSString stringWithFormat:postString, md5(tokenString), score.name, score.totalScore, udid];

Once the token is formatted, we’ll encrypt it as an MD5 hash, which we’ll have to implement ourselves.

Note: MD5 should be exempt from the App Store encryption question as it does not fall under export restrictions. So using this method, you can still answer no to the encryption question.

NSString* md5( NSString *str )
{
	const char *cStr = [str UTF8String];
	unsigned char result[CC_MD5_DIGEST_LENGTH];
	CC_MD5( cStr, strlen(cStr), result );
	return [NSString stringWithFormat:
			@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
			result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],
			result[8], result[9], result[10], result[11], result[12], result[13], result[14], result[15]
	];
}

Caveat: there are actually still a few weakness is this design. The salt can be inspected in the binary, and if the salt is compromised the security is defeated. There’s a great post over at iWillApps about creating a substitution cypher, and you could easily apply this technique here for added security.

The Rails Leaderboard Web Service

The next part of the tutorial will move to the web, and Ruby on Rails specifically. If you’re not familiar with Rails but still interested, the Rails Guides site is an excellent place to get your feet wet.

We’ll also be using git, a distributed version control system. This will let us deploy to heroku, a free Rails host. In fact, they have a quick introductory guide to using git, and links to additional git resources.

Once you have Rails and git set up, you’ll need to install the plist gem to your Rails project.

$ sudo gem install plist

Now we’re prepared to program the service.

Project Configuration

We’ll need to take a few actions to configure the service. First, we need to create a database schema to store our scores.

config/create_score_schema.rb

class CreateCoreSchema < ActiveRecord::Migration
  def self.up
    create_table :high_scores, :force => true do |t|
      t.string :player_name, :limit => 32, :null => false
      t.string :iphone_udid, :limit => 64, :null => false
      t.integer :total_score, :null => false

      t.timestamps
    end
  end

  def self.down
    drop_table :high_scores
  end
end

config/routes.rb

ActionController::Routing::Routes.draw do |map|
  map.resources :high_scores, :only => [:index, :create]
end

config/environment.rb

RAILS_GEM_VERSION = '2.3.3' unless defined? RAILS_GEM_VERSION
require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
  config.gem 'plist'
  config.time_zone = 'UTC'

  module LeaderboardAuth
    SECRET_SALT = '999888999'
  end
end

Standard Rails configuration stuff so far, except note that we’re configing the plist gem here, and declaring a module. The purpose of LeaderboardAuth is just to reference a salt value, the same one used by the iPhone to hash the high score submission. It’s declared here in environment.rb for convenience and we’ll use it throughout the service and tests.

Leaderboard Web Service

Now that the configuration is set, we can start writing the service proper. We’ll only need a controller class, and a model class, since we won’t be serving any HTML, just plist XML lists of high scores.

First, our only model, HighScore.

app/models/high_score.rb

require 'digest'
require 'plist'

class HighScore < ActiveRecord::Base
    attr_accessor :auth_token

    validates_presence_of :player_name, :iphone_udid, :total_score, :auth_token
    validates_length_of :player_name, :within => 1..32, :allow_nil => true

    named_scope :sorted_by_score, :order => 'total_score DESC', :limit => 10

   def validate
      return if auth_token.blank?
      auth = Digest::MD5.hexdigest( [total_score, LeaderboardAuth::SECRET_SALT, iphone_udid].join(':') )

      errors.add(:auth_token, "unauthorized high score submission") if auth_token.downcase != auth
   end

   def to_plist_node
     return {:player_name => self.player_name, :total_score=>self.total_score, :created_at => self.created_at}.to_plist(false)
    end
end

All pretty standard for a Rails model, and I’ll summarize: we need to require digest and plist so we can perform a MD5 hash, and serialize the model into plist format.

There isn’t an “auth_token” field in the database, but the model needs one to store the hashed token when the high score POST is submitted, so we add it as an attr_accessor.

The model will perform some basic validation, with the security test handled in the validate method. An MD5 hash is created from the model’s score, udid, and the application’s secret salt value. The format is the same as used on the iPhone, SCORE:SALT:UDID. Note, if you were running additional substitution routines you’ll have to reproduce them here as well while reconstructing the hash.

auth = Digest::MD5.hexdigest( [total_score, LeaderboardAuth::SECRET_SALT, iphone_udid].join(':') )

If the two hashes don’t match, we can assume that the submission has been altered.

errors.add(:auth_token, "unauthorized high score submission") if auth_token.downcase != auth

To serialize the HighScore model’s properties into plist format, we implement a to_plist_node method that the plist gem will use to format the output.

return {:player_name => self.player_name, :total_score=>self.total_score, :created_at => self.created_at}.to_plist(false)

The controller will only need to respond to GET and POST requests, to retrieve and submit high scores. It follows basic Rails conventions, and the real work is delegated to configuration settings and the high scores model.

app/models/high_scores_controller.rb

class HighScoresController < ApplicationController
    include ApplicationHelper

    skip_before_filter :verify_authenticity_token

    def index
      @scores = HighScore.sorted_by_score.all

      respond_to do |wants|
        wants.xml { render :xml => @scores.to_plist, :status => :ok }
        wants.any { render :nothing => true, :status => :ok }
      end
    end

    def create
      @highscore = HighScore.new(params[:high_score])

      header_status = @highscore.save ? :ok : :unprocessable_entity

      respond_to do |wants|
        wants.xml { render :nothing => true, :status => header_status }
        wants.any { render :nothing => true, :status => header_status }
      end
    end
end

And that’s the Rails online high scores service! To complete the project, we’ll add a few basic tests. I wouldn’t review them, but they should give us confidence to expand on the core leaderboard features without fear of breaking the service.

Testing the Service

We’ll need three files. A unit test, a functional test, and a fixture with a few mock scores.

test/fixtures/high_scores.xml

player1:
    player_name: player one
    total_score: 10
    iphone_udid: 2b6f0cc904d137be2e1730235f5664094b831186

player2:
    player_name: player two
    total_score: 15
    iphone_udid: 2b6f0ce614c43cbb2e0730235f5664094b83b487

player3:
    player_name: player three
    total_score: 20
    iphone_udid: 2bef0b11cff51c9c080730235f566401e80bfe1

test/unit/high_score_test.rb

require File.join(File.dirname(__FILE__), '/../test_helper')
require 'digest'

class HighScoreTest < ActiveRecord::TestCase
  fixtures :all

  def test_should_validate_auth_hash
    salt = LeaderboardAuth::SECRET_SALT
    name = 'player X'
    udid = "2bef0b11cff51cbb2e0730235f5664094b83b487"

    auth_token = Digest::MD5.hexdigest( [name,salt,udid].join(':') )

    score = HighScore.new( :player_name => name,
                          :iphone_udid => udid,
                          :total_score => 50,
                          :auth_token => auth_token)

    assert score.save, "Save failed: #{score.errors.full_messages}"

  end

  def test_should_not_validate_with_bad_auth_hash
    salt = "xxxxxxxxxxxx"
    name = 'player X'
    udid = "2bef0b11cff51cbb2e0730235f5664094b83b487"

    auth_token = Digest::MD5.hexdigest( [name,salt,udid].join(':') )

    score = HighScore.new( :player_name => name,
                          :iphone_udid => udid,
                          :total_score => 50,
                          :auth_token => auth_token)

    assert !score.save
    assert score.errors.on(:auth_token)
  end
end

test/functional/high_scores_controller_test.rb

require File.join(File.dirname(__FILE__), '/../test_helper')

class HighScoresControllerTest < ActionController::TestCase
  def setup
    @plist_expression = /<!DOCTYPE plist PUBLIC "-\/\/Apple Computer\/\/DTD PLIST 1\.0\/\/EN" "http:\/\/www\.apple\.com\/DTDs\/PropertyList-1\.0\.dtd">/
  end

  def test_should_get_index
    get :index, { :format => "xml", :limit => 10 }

    assert_response :success
    assert_not_nil assigns(:scores)
    assert_match @plist_expression, @response.body

    plist = Plist::parse_xml(@response.body)
    assert_equal "Array", plist.class.to_s
    assert_operator plist.size, ">", 0
  end

  def test_should_post_to_create
    salt = LeaderboardAuth::SECRET_SALT
    name = 'player X'
    udid = "2bef0b11cff51cbb2e0730235f5664094b83b487"

    auth_token = Digest::MD5.hexdigest( [name,salt,udid].join(':') )

    assert_difference('HighScore.count') do
      post :create, { :format => "xml", :high_score => {
                                          :player_name => name,
                                          :iphone_udid => udid,
                                          :total_score => 50,
                                          :auth_token => auth_token} }
    end

    assert_response :success
  end
end

Hosting the Service on Heroku

heroku is an excellent Rails host with fancy cloud architecture and a tiered pricing plan that starts at free. The best part about heroku, though, is how easy they’ve made Rails deployments. They did this by creating, essentially, a deployment gem that gives you some slick command line powers. Here’s how you deploy your app for the first time (ripped from the heroku front page) using the heroku gem, and of course, git.

$ sudo gem install heroku
$ heroku create myapp
$ git push heroku
$ heroku domains:add example.com
$ heroku rake db:migrate
$ heroku bundles:capture

You’ll get a basic subdomain at heroku.com which you’ll call from the iPhone. If your game takes off and traffic rises, heroku will scale seamlessly for you, and you’ll generally be well taken care of by a responsive support staff who know a great deal about the environment they host. Head over to their site for more information. (I’m in no way affiliated with them, just a happy user and impressed developer).


With the service built, and the iPhone posting and retrieving high scores, we’ve finally reached the end of the tutorial.

Good luck!

Bug combining acts_as_taggable_on tagged_with scopes

Posted September 8th, 2009 in Uncategorized by Travis

My latest Sogeo work is getting quite a lot of distance out of tagging. With mbleigh’s acts_as_taggable_on I’ve been able to conveniently normalize several different data dimensions down into a tag model.

Modeling this was easy enough, but today I ran into an issue when constructing some of the more sophisticated filtering queries.

acts_as_taggable_on utilizes named scope to provide helpful finder methods, like so:

Location.tagged_with("mytag", :on => :tags)

Easy enough to combine with existing scopes:

Location.include_details.sorted.tagged_with("mytag", :on => :tags)

The problem arises when combining two tagged_with scopes, for example:

Location.tagged_with("mytag", :on => :tags) .tagged_with("mycategory", :on => :categories)

You can read a brief discussion of the problems over on this lighthouse ticket, but the crux of the issue is malformed generated SQL.

Until acts_as_taggable_on is updated, a simple if dreadfully inefficient solution is to call the collection for each tagged_with filter, and then combine the results.

location_scope = Location.include_details.sorted
tag_scope = location_scope. tagged_with("mytag", :on => :tags)
category_scope = location_scope. tagged_with("mycategory", :on => :categories)
category_scope.all(conditions) & tag_scope.all(conditions)

This is a serviceable enough solution for me for now, but if this is still a problem in a few months when my app hits production then I’ll have to start working on a real fix for combining multiple tagged_with helper scopes.

Managing Local High Scores and Online Leaderboard for your iPhone Games Part 1/2

Posted September 6th, 2009 in Uncategorized by Travis

Why high scores? Because the stamp of public achievement rewards players for investing their time and skill in your game; and because the one-upsmanship of ranking promotes a competitive social awareness which can make a title more enduring.

iPhone Leaderboard

With the body of iPhone gaming so often reminiscent of the arcade era, an online leaderboard seems natural to the platform and something that adds a breath of life to an otherwise perishable experience. In this two-part tutorial, I’ll explain how to store and load local and online high scores for a game using only the native iPhone libraries and a Rails web service.

This first part will cover modeling and storing local high scores, and in part two we’ll build the leaderboard web service.

Existing Frameworks

A number of iPhone frameworks for leaderboards, analytics, and social media have been appearing recently and certainly warrant investigation if you’re really looking to dress up your game with community features.

Scoreloop
http://corporate.scoreloop.com

A free service, “adding the Scoreloop SDK to your game gives your users access to features such as challenges, buddies, and high score lists.”

OpenFeint
http://www.openfeint.com

An extremely feature-rich and free service offering social challenges, offline support, in-app friending, high scores, and social media APIs.

iGetScores
http://www.igetscores.com

Free and open source (code.google.com/p/igetscores) “online high scores / ranking service which is convenient for developers and transparent to game players.”

Agon Online
http://developer.agon-online.com
“AGON Online is a complete social platform for iPhone and iPod Touch games. It is a location-aware online high score system, complete with profiles, friends, awards etc. Think Xbox LIVE on-the-go.”

Geocade
http://geocade.com/company.html
Also free, “Geocade is the largest location aware social gaming platform with gaming communities in thousands of cities and towns across the world.”

Requirements

Although each of those services is promising in its own right, we’re going to develop our own leaderboard server here for a few practical reasons.

  1. Avoid dependencies on external libraries
  2. Minimize codebase and build size by staying in the iPhone SDK
  3. Control encryption and avoid App Store export restrictions
  4. Build a system that does precisely what’s needed, no more or less
  5. Keep our user analytics and personal data 2nd party
  6. Learn by doing!!!

Before moving into the code, let’s take stock of the requirements for local high score support.

First, we’ll need a high score class that can be serialized for storage. We’ll need to be able to store and retrieve the records on the iPhone, and we’ll also need to display the records in a UITableView. Finally, we’ll need to process new high scores so that they’re ranked while all low scores are rejected.

Modeling the High Score Classes

HighScoreRecord.h

#import <Foundation/Foundation.h>

@interface HighScoreRecord : NSObject <NSCoding, NSCopying> {
	NSString *name;
	NSNumber *totalScore;

	NSDate *dateRecorded;
}

- (id) initWithScore:(NSString *)name TotalScore:(NSNumber *)totalScore;

@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSNumber *totalScore;

@property (nonatomic, retain) NSDate *dateRecorded;
- (NSComparisonResult) compare:(id)other;
@end

Aside from the obvious properties, the HighScoreRecord class will need to implement NSCoding and NSCopyingfor serialization, and the NSComparisonResult method compare: so that it can be easily ranked against other HighScoreRecord instances.

HighScoreRecord.m

#import "HighScoreRecord.h"

@implementation HighScoreRecord

@synthesize name;
@synthesize totalScore;
@synthesize dateRecorded;

- (id) initWithScore:(NSString *)playerName TotalScore:(NSNumber *)score {
    if (self = [super init])
	{
		name = playerName;
		totalScore = score;

		dateRecorded = [NSDate date];
	}
    return self;
}

- (NSComparisonResult) compare:(id)other {
	return [self.totalScore compare:other];
}

#pragma mark NSCoding
- (void)encodeWithCoder:(NSCoder *)encoder {
	[encoder encodeObject:name forKey:@"Name"];
	[encoder encodeObject:totalScore forKey:@"TotalScore"];
	[encoder encodeObject:dateRecorded forKey:@"DateRecorded"];
}

- (id)initWithCoder:(NSCoder *)decoder {
	if(self = [super init]) {
		self.name = [decoder decodeObjectForKey:@"Name"];
		self.totalScore = [decoder decodeObjectForKey:@"TotalScore"];
		self.dateRecorded = [decoder decodeObjectForKey:@"DateRecorded"];
	}
	return self;
}

#pragma mark -
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
	HighScoreRecord *copy = [[[self class] allocWithZone:zone] init];
	name = [self.name copy];
	totalScore = [self.totalScore copy];
	dateRecorded = [self.dateRecorded copy];

	return copy;
}
#pragma mark -

- (void)dealloc {
	[name release];
	[totalScore release];
	[dateRecorded release];

    [super dealloc];
}

@end

We’ll also need a managing class that is responsible for storing and retrieving the scores, ranking them against each other, and, ultimately, communicating with the leaderboard server.

HighScores.h

#import <Foundation/Foundation.h>
#import "HighScoreRecord.h"

@interface HighScores : NSObject {}

+ (void)addNewHighScore:(HighScoreRecord *)score;
+ (void)saveLocalHighScores:(NSArray *)highScoreArray;

+ (NSString *)highScoresFilePath;
+ (NSMutableArray *)getLocalHighScores;
+ (NSMutableArray *)sortHighScoreDictionaryArray:(NSMutableArray *)highScoreArray;
@end

Look over the following implementation, and then we’ll walk through each of the methods in turn.

HighScores.m

#import "HighScores.h"

@implementation HighScores

const int HIGH_SCORE_COUNT = 10;

+ (void)addNewHighScore:(HighScoreRecord *)score {
	NSMutableArray *locals = [HighScores getLocalHighScores];

	int totalScore = [score.totalScore intValue];
	if (locals.count < HIGH_SCORE_COUNT){
		[locals addObject:score];
		NSMutableArray *sortedLocals = [HighScores sortHighScoreDictionaryArray:locals];
		[HighScores saveLocalHighScores:sortedLocals];
		[sortedLocals release];
	} else {
		NSUInteger lastIdx = HIGH_SCORE_COUNT-1;
		HighScoreRecord *lastRecord = [locals objectAtIndex:lastIdx];
		if (totalScore > [lastRecord.totalScore intValue]){
			[locals addObject:score];
			NSMutableArray *sortedLocals = [HighScores sortHighScoreDictionaryArray:locals];
			[sortedLocals removeLastObject];

			[HighScores saveLocalHighScores:sortedLocals];

			[sortedLocals release];
		}
	}
}

+ (NSString *)highScoresFilePath {
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *documentsDirectory = [paths objectAtIndex:0];
	return [documentsDirectory stringByAppendingPathComponent:@"HighScoresFile"];
}

+ (NSMutableArray *)getLocalHighScores {
	NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[HighScores highScoresFilePath]];
	NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

	NSArray *highScores = [unarchiver decodeObjectForKey:@"HighScores"];

	return [[[NSMutableArray alloc] initWithArray:highScores copyItems:NO] autorelease];
}

+ (void)saveLocalHighScores:(NSArray *)highScoreArray {

	NSMutableData *data = [[NSMutableData alloc] init];
	NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

	[archiver encodeObject:highScoreArray forKey:@"HighScores"];
	[archiver finishEncoding];

	[data writeToFile:[HighScores highScoresFilePath] atomically:YES];
	[archiver release];
	[data release];
}

+ (NSMutableArray *)sortHighScoreDictionaryArray:(NSMutableArray *)highScoreArray {

	NSString *SORT_KEY = @"totalScore";
	NSSortDescriptor *scoreDescriptor = [[[NSSortDescriptor alloc] initWithKey:SORT_KEY ascending:NO selector:@selector(compare:)] autorelease];
	NSArray *sortDescriptors = [NSArray arrayWithObjects:scoreDescriptor, nil];

	NSArray *sortedArray = [highScoreArray sortedArrayUsingDescriptors:sortDescriptors];

	return [[NSMutableArray alloc] initWithArray:sortedArray copyItems:NO];
}

@end

With our classes modeled, we can now examine how the storage and retrieval process actually works.

Storing the Data

In order to locally store our high scores, we’ll use the application’s NSDocumentDirectory, the recommended source for file management on iPhone apps. The records will simply be serialized to an NSArray of HighScoreRecord objects, which we can load and manipulate as needed.

Adding a score is called with addNewHighScore, which takes a HighScoreRecord and saves it if the score is high enough. To save a player’s score, have a method like so:

- (void)saveHighScore {
	int score = [somePlayerObject.score intValue];

	HighScoreRecord *highScore = [[HighScoreRecord alloc] initWithScore:name TotalScore:[NSNumber numberWithInt:score]];
	[HighScores addNewHighScore:highScore];

}

The addNewHighScore method is responsible for the controlling persistence logic, which includes a few simple rules. First, we should only store a maximum of HIGH_SCORE_COUNT records locally. Because of this constraint, only records that exceed the score of the last record in the collection will be saved; otherwise, they’re ignored. Once a new HighScoreRecord has been added to the high scores collection, it needs to be resorted and saved back to the file system.

The sortHighScoreDictionaryArray method is responsible for sorting the scores after a new record has been added to the collection. Using an NSSortDescriptor and the HighScoreRecord’s compare method, the scores are sorted on the totalScore property and a sorted array is returned.

Finally, saveLocalHighScores is called to persist the high scores collection. Its chief responsibility is serializing the HighScoreRecord objects to the NSDocumentDirectory.

Loading the Data

In the most common scenario you’ll be loading the high scores into a UITableView. The process is straightforward, amounting to deserializing the high scores file we’ve been saving.

The code in HighScoresViewController, which implements UITableViewDelegate and UITableViewDataSource, is quite standard. All the high score logic is handled by the other models, and populating a high scores display is simply a matter of making the right calls.

Note, since there won’t always be available local high score, cellForRowAtIndexPath first checks if a row exists and adds placeholder text if there aren’t enough scores to fill the table. In this case, we’ll set the UITableView to always show 10 high score slots with numberOfRowsInSection.

HighScoresViewController.h

#import <UIKit/UIKit.h>

#import "HighScoreCell.h"
#import "HighScoreRecord.h"
#import "HighScores.h"

@interface HighScoresViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
	IBOutlet UITableView *highScoreTable;
	NSArray *highScoreData;

}
@property (nonatomic, retain) UITableView *highScoreTable;
@property (nonatomic, retain) NSArray *highScoreData;

- (void) loadLocalHighScores;

@end

HighScoresViewController.m

#import "HighScoresViewController.h"

@implementation HighScoresViewController

@synthesize scoreTableSelector;
@synthesize highScoreTable;
@synthesize highScoreData;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        [self loadLocalHighScores];
    }
    return self;
}

. . .

- (void)loadLocalHighScores {
	highScoreData = [[HighScores getLocalHighScores] retain];
}

. . . 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return 10;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	HighScoreCell *cell = (HighScoreCell *)[tableView dequeueReusableCellWithIdentifier: @"HighScoreCellIdentifier"];
	if (cell == nil) {
		NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"HighScoreCell" owner:self options:nil];
		cell = (HighScoreCell *)[nib objectAtIndex:0];
	}
	NSUInteger row = [indexPath row];
	cell.rankLabel.text = [NSString stringWithFormat:@"%d", row+1];

	if (row < highScoreData.count){
		HighScoreRecord *record = (HighScoreRecord *)[highScoreData objectAtIndex:row];

		cell.nameLabel.text = record.name;
		cell.scoreLabel.text = [NSString stringWithFormat:@"%@", record.totalScore];

		NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
		[dateFormat setDateFormat:@"yyyy-MM-dd"];

		cell.dateLabel.text = [dateFormat stringFromDate:record.dateRecorded];
	} else {
		cell.nameLabel.text = @"-";
		cell.scoreLabel.text = @"-";
		cell.dateLabel.text = @"-";
	}

	return cell;
}

. . . 

@end

You can note again that the responsibility for loading and deserializing the local high scores file rests with the HighScores class’s getLocalHighScores method. This is called by the HighScoresViewController in loadLocalHighScores.

+ (NSMutableArray *)getLocalHighScores {
	NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[HighScores highScoresFilePath]];
	NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

	NSArray *highScores = [unarchiver decodeObjectForKey:@"HighScores"];

	return [[[NSMutableArray alloc] initWithArray:highScores copyItems:NO] autorelease];
}


Part 2

In the second part of this tutorial, I walk through setting up a simple web service in Rails that will act as an online leaderboard, and we’ll apply some basic authentication to secure the high score submissions. And with git and heroku.com’s free Rails hosting, deploying the service will be about as frictionless as web development gets.

Binary Vocabulary

Posted August 28th, 2009 in Uncategorized by Travis

In my evening prayers, not too long ago, I must have said “Let me, o Lord, forsake the conveniences of the last three decades, permit me to wither and wear out my mind on a microcosm of dreariness, spurn the IDE, remit abstraction and confer to me the simplest of binaries, place me in a tartarean prison to pay my debts like the ghosts of my ancestors, with hex editing and microcode, and so recompense the luxury of modern programming with the tedium of bitwise operations.”

Lord that he is and my piety beyond reproach, here I am, my programming having taken a turn for the elemental and put me to thinking about twos. As an important concept in programming as symmetry a concept in art, other disciplines also keep the medial plane at their core, including language. As I have learned, the prefixes, roots, and combining forms expressing the number two outnumber the others by an impressive sum, and also outflank them in range of expression and conceptual domain.

Michael J. Sheehan, in his “Word Parts Dictionary”, provides the most comprehensive list I’ve found, and I reproduce it here.

Word Parts Expressing “Two”

• ambi- (both)

ambidextrous: using both hands with equal facility
ambisinister: clumsy or unskillful with both hands
ambivert: personality type exhibiting both extroversion and introversion

• ambo- (both)

amboceptor: a substance that, added to another, breaks down red blood cells
ambosexous: of both sexes; hermaphrodite

• amphi- (on both sides)

amphiboly: ambiguity of speech arising from uncertain grammar
amphioxus: sharp at both ends
amphivorous: eating both animal and vegetable food

• ampho- (on both sides)

amphogenic: producing both male and female offspring
amphora: large two-handled storage jar
amphoteric: capable of functioning either as an acid or as a base

• bi- (two)

bicipital: having two heads
bifurcate: to divide or fork into two branches
bimester: a two-month period

• bin- (two)

binary: consisting of, indicating, or involving two
binate: produced or borne in pairs
binaural: having two ears

• bis- (two; twice)

bismarine: between or washed by two seas
bissextile: extra leap year day in the Julian calendar

• deutero- (second)

deuterogamy: a second marriage
Deuteronomy: 5th book of the Pentateuch, with 2nd statement of Mosaic law
deuteropathy: any abnormality secondary to another pathological condition

• deuto- (second)

deutoplasm: reserve nutritive material in the ovarian cytoplasm
deutoscolex: a secondary scolex (headlike segment of a tapeworm)
deutotergite: the second dorsal segment of the abdomen of insects

• di- (two)

dicrotic: double beat of the pulse for each beat of the heart
diplegia: paralysis of the identical part on both sides of the body
dipterous: having two winglike appendages

• dicho- (in two)

dichogamous: having male and female elements at different times
dichotic: affecting the two ears differently
dichotomous: divided or dividing into two parts

• diphy- (double)

diphycercal: having the tail divided into two equal halves
diphyletic: derived from two lines of evolutionary descent
diphyodont: developing both temporary and permanent teeth

• diplo- (two; double)

diploblastic: having two germ layers, as the embryos of sponges
diploneurous: having two nervous systems
diplopia: double vision

• disso- (double)

dissogeny: in ctenophores, two periods of sexual maturity
dissology: repetition

• double- (in combination)

double-barreled: gun having two barrels, side by side or over and under
doubleheader: two events held consecutively on the same program
double-team: to guard an opponent with two players at one time

• du- (two)

duplation: multiplication by two
duplex: having two principal elements or parts
duplicate: existing in two corresponding or identical parts

• duo- (two)

duologue: a dialogue between two persons
duopoly: an oligopoly limited to two sellers
duopsony: market condition with only two buyers

• duplicato- (doubly)

duplicato-dentate: toothed leaves in which the teeth are also dentate
duplicato-serrate: serrated leaves whose notches are themselves serrate
duplicato-ternate: leaves themselves composed of three leaves each

• duplici- (duplex)

duplicidentate: rodents having two pairs of upper incisor teeth
duplicipennate: having two wings folded longitudinally in repose

• dyo- (two)

Dyophysite: person who says that Christ has both divine and human natures
Dyothelite: person who holds that Christ has both divine and human wills

• gemelli- (twin)

gemelled: coupled; paired
gemelliparous: producing twins
gemellous: duplicated

• gemin- (double)

gemination: doubling; duplication; repetition
geminiflorous: having flowers arranged in pairs
geminous: occurring in pairs

• twi- (double)

twibill: an adz/ax combination
twi-headed: two-headed
twi-night: baseball doubleheader in which the second game ends at night

• zyga-/zygo- (pair)

zygapophysis: paired processes of the neural arch of a vertebra
zygodactyl: having the toes arranged two in front and two in back
zygomorphic: having bilateral symmetry

30 Review Sites to Promote Your iPhone Applications and Games

Posted August 24th, 2009 in Uncategorized by Travis

The internet is aflow with tears from developers bemoaning the oversaturation of the App Store. In truth, the App Store is now seeing the same saturation of other software markets. Developers are no longer finding themselves entitled to wild success on the basis of a swift release. Realities like promotion and strategy are taking hold, and if you want a lucrative place beside your competitors you’ll have to work just as hard at promoting your product as you always have.

Fortunately, the iPhone community is perpetually rapt with the buzz of new releases, a fact reflected in the innumerable review sites that have established themselves. I’ve compiled a list of some of these sites, along with statbrain‘s traffic estimates (which seem to error on the low side so far as I can tell) in order to help myself and other pureblood developers organize their promotional tactics.

148Apps (www.148apps.com)
Reviews iPhone and iPod touch applications and games. You can find their submission guidelines here.
Visits/day: 8,328

App Chatter (www.appchatter.com)
Reviews iPhone and iPod touch applications and games, and provides a developer-managed profile for your releases. See their submissions guidelines here, and manage a profile from here.
Visits/day: 322

App Podcast, The (theapppodcast.com)
Reviews iPhone and iPod touch applications and games, but you must pay them for the privilege ($25-$100/review). You can find their submission guidelines here. You will need a video of your app in order to submit it for review and hosting on their video channels.
Visits/day: 3,773

App Safari (www.appsafari.com)
Reviews iPhone and iPod touch applications and games. See their submissions guidelines here.
Visits/day: 17,245

App Store Apps (www.appstoreapps.com)
Reviews iPhone and iPod touch applications and games. No guidelines, but contact them about a review here.
Visits/day: 7,187

AppCraver (www.appcraver.com)
Reviews iPhone and iPod touch applications and games, and also conducts developer interviews. See their submissions guidelines here.
Visits/day: 10,412

AppRater (www.apprater.com)
Lists and “reviews” iPhone applications, but requires link sharing from participating developers. Details here.
Visits/day: 7,269

AppShopper (appshopper.com)
Reviews iPhone and iPod touch applications and games, and doesn’t openly list their submission guidelines. You can try contacting them at arn@appshopper.com
Visits/day: 27,479

Apptism (www.apptism.com)
Reviews iPhone and iPod touch applications and games, including previewing those that haven’t yet been released. See their submissions guidelines here.
Visits/day: 19,121

AppVee (www.appvee.com)
Gives *video* reviews of iPhone and iPod touch applications and games. See their submissions guidelines here.
Visits/day: 7,379

Apple iPhone Apps (appadvice.com/appnn)
Reviews iPhone and iPod touch applications and games. You can find their submission guidelines here.
Visits/day: 9,890

Apple iPhone School (www.appleiphoneschool.com)
Reviews iPhone and iPod touch applications and games from both the App Store and Cydia. No public submission guidelines, but you can contact them here.
Visits/day: 17,342

Art of the iPhone (artoftheiphone.com)
Reviews iPhone and iPod touch applications and games. No public submission guidelines but you can contact them here.
Visits/day: 5,290

iPhone Application List (iphoneapplicationlist.com)
Reviews iPhone and iPod touch applications and games. You can find their submission guidelines here, although they do ask for a reciprocal link in exchange for a review.
Visits/day: 9,867

iPhone App Review (www.iphoneappreview.com)
Reviews iPhone and iPod touch applications and games. You can find their submission guidelines here.
Visits/day: 3,959

iPhone App Reviews (www.iphoneappreviews.net)
Reviews iPhone and iPod touch applications and games. The site allows you to publish promo codes with your review as well. You can find their submission guidelines here.
Visits/day: 5,145

iPhone Game Network (www.iphonegamenetwork.com)
Reviews iPhone games, including Ad-Hoc distributions. You can find their submission guidelines here.
Visits/day: 4,819

iPhone Game Reviews (iphone-game-reviews.com)
Small review blog for iPhone games, you can find their submission guidelines here.
Visits/day: 469

iPhone Games (iphonegames.com/reviews)
Small review blog for iPhone games, you can find their submission guidelines here.
Visits/day: 538

iPhone Footprint (www.iphonefootprint.com)
Reviews iPhone and iPod touch applications and games. You can find their submission guidelines here, and request a review here.
Visits/day: 7,865

iPhone World (www.iphoneworld.ca)
Reviews iPhone and iPod touch applications and games, as well as publishes press releases. You can contact them about app submissions here.
Visits/day: 10,318

NativeiPhoneApps (www.nativeiphoneapps.com)
Reviews iPhone and iPod touch applications and games. You can find their submission guidelines here.
Visits/day: 761

Slide to Play (www.slidetoplay.com)
Reviews iPhone and iPod touch games. You can find their submission guidelines here.
Visits/day: 8,882

App Theater (www.apptheater.com)
Reviews iPhone and iPod touch applications and games. You can find their contact details here.
Visits/day: 970

Touch Arcade (toucharcade.com)
Excellent review site for iPhone and iPod touch games. You can find their submission guidelines and other useful information here.
Visits/day: 44,474

MoDoJo (www.modojo.com)
Promtional site for mobile games allowing you to submit reviews, maintain a profile, and so forth. You can find details here.
Visits/day: 4,823

Touch Reviews (touchreviews.net)
Reviews iPhone and iPod touch applications and games, as well as several additional promotional tools. You can find more details here.
Visits/day: 2,183

Buy me an iPhone (www.buymeaniphone.com)
Reviews iPhone and iPod touch applications and games. You can find their contact details here.
Visits/day: 18

What’s on iPhone (www.whatsoniphone.com)
News and reviews for the iPhone. You can find their contact details and submission information here.
Visits/day: 6,763

Pocket Gamer (www.pocketgamer.co.uk)
Reviews games on various mobile platforms including the iPhone. See their submission guidelines here and check out the iPhone developer forums here.
Visits/day: 3,017

iPwn Games (www.ipwngames.com)
Reviews iPhone and iPod touch games. You can find their submission guidelines here. They also offer personal blogs to game developers here.
Visits/day: 5,344


For some general review tips you’ll find an instructive post over at mobile orchard: http://www.mobileorchard.com/five-tips-for-getting-iphone-reviews/

UPDATE: Additional sites recommended by friendly internet commenters:

APPera, The (www.theappera.com)
Reviews iPhone and iPod games. You can find their submission guidelines here.
Visits/day: 3,719

AppGamer.net (www.appgamer.net)
Reviews iPhone and iPod games. You can find their contact details here.
Visits/day: 979

Touch my Apps (www.touchmyapps.com)
Reviews iPhone and iPod applications and games. They also do interviews with artists, developers, etc. You can contact them here.
Visits/day: 2,262

Portable Gamer, The (www.theportablegamer.com)
Reviews portable games, including iPhone games. You can find their contact details here, and a their iPhone game submission form here.
Visits/day: 2,269

appVersity (www.appversity.com)
Reviews iPhone and iPod applications and games. You can find their submission guidelines here.
Visits/day: 2,605

AppModo (www.appmodo.com)
Reviews mobile applications and games, including the iPhone. They also do interviews with artists, developers, etc. You can find their contact details here.
Visits/day: 6,394