twocentstudios

Chris Trott and iOS, Ruby, Rails

How I Wrote Vinylogue for iOS With ReactiveCocoa

Vinylogue is a simple app I designed and developed for iOS that shows your last.fm weekly album charts from previous years. This post will detail the process of creating V1.0 of the app from start to almost finish (is an app ever really finished?).

The full source is available on GitHub.

Warning: this post is super long (10000+ words). I spend a lot of time discussing ReactiveCocoa techniques, a little bit of time on pre-production, and a little bit of time on design.

Contents

Idea

I recently came across an awesome app called TimeHop that compiles your past posts from Facebook, Twitter, etc. and shows you what you posted on that day one year ago. I love reminiscing through my old things, so this concept was right up my alley.

I also love music too though, and have been an avid Last.fm user since 2006 or 2007, scrobbling tracks religiously. I thus have quite a history of my past album listens in their database, and with those listens, a lot of memories connected to those albums. I can remember exactly what I was doing, where I was, and mental snapshots when I see an album or group of albums together.

And so the idea of combining Last.fm and TimeHop was born.

Planning

Getting a feel for the data

The first step was seeing what data was available from the Last.fm API. I could indeed get a weekly album chart (a list of albums, ranked by number of plays) for a specified user and week with user.getWeeklyAlbumChart. It’s also possible to get the same chart grouped by artist or track, but at the current time it seemed like albums would appeal to me most.

One of the great things about this particular API call is that you don’t need a password for the username you’re requesting. One of the bad things was that you can’t just send a particular timestamp and let Last.fm figure out what week that date falls into.

The latter problem is solved by making another API request to user.getWeeklyChartList. This call provides the specific weekly date ranges in Epoch timestamps you can then use as an input to the user.getWeeklyAlbumChart call. The data looks something like this:

user.getWeeklyChartList
1
2
3
4
5
6
7
8
9
10
11
   "chart": [
      {
          "from": "1108296002",
          "to": "1108900802"
      },
      {
          "from": "1108900801",
          "to": "1109505601"
      },
      
  ]

Two API calls so far to get most of our data. Not bad. So to document our information flow:

  • take the current date
  • subtract n years (1 year ago to start)
  • figure out where that date is within the bounds of the Last.fm weeks
  • request the charts for the username and date range
  • display the data

Pretty simple. Time to dig in a little more.

Features

It’s best to draw a line in the sand about what the constraints of the app will be, at least in the first version. Below are my first set of constraints. They (inevitably) changed later in the project, and we’ll discuss those changes as we make them.

  • We’ll support only one last.fm user.
  • The user can view charts from the week range exactly n years ago (with the lower bound provided by Last.fm).
  • The user cannot view any charts newer than one year ago.
  • The app has a chart view and a settings view (keeping it very simple).

Again, I’ll discuss changes as they happened in the process.

Schema

Next I had to decide how the data would be structured and stored. The initial options were:

Keep everything transient

We should request everything from the network each time. Don’t store anything in Core Data. We’re only looking at old data and we don’t have to worry about realtime feeds so we can use the regular old NSURLCache and set our cache times basically forever (in practice, it’s a little more complicated than this).

(As an aside, I ended up using SDURLCache as a replacement for NSURLCache. From pure observation, NSURLCache was faster from memory, but I could not get it pull from disk between app terminations.)

The weekly charts technically only need to be pulled from the server once a year (I’ll leave that as an exercise to the reader as to why). The chart data for a particular week is only relevant once per year the way the app is set up.

At the end of the day, the URL cache ends up being a dumb key-value store keyed on URLs and returning raw json data.

  • Pros
    • Less complexity keeping data in sync.
  • Cons
    • More data transferred.
    • Longer waiting for previously requested data.

Incrementally build our own database of the data we request

We should set up a full schema of related objects. Data should be requested locally, and if it doesn’t exist, it should be requested from the server and added to the local database.

  • Pros
    • Speed of subsequent requests.
    • Less API requests.
    • May enable future features.
  • Cons
    • Much greater complexity keeping data in sync and knowing when the cache is invalid.
    • More local storage required.

Decision

I was much more inclined to build a local database, but a big goal of this project was a quick ship. I decided to take a few hours building something out with AFIncrementalStore. It didn’t take long to realize doing things this way would slow down development substantially. I decided to keep the local database method as a goal, but leave it until the app idea itself was validated with users. At the current time, it felt like premature optimization.

I went ahead with the two table Core Data schema since I already had it set up and my classes generated with Mogenerator. I added fields for the data I wanted from the user.getWeeklyChartList API call.

Later, I would completely rid Core Data from the project and turn these model objects back into standard NSObject subclasses.

Schema V1
Schema V1

Wireframing

Since I now had an idea of what data I had to work with, it was time to put together wireframes of what the main views would look like.

The aim was simplicity. One main view that shows a single week’s charts. Another modal view for settings and about.

I wanted to go with a more classic look with a standard UINavigationBar. A UITableView as a given based on the nature of the data (unknown number of albums, 0 - ???).

Essential elements of the weekly chart view / Proposed inter-year navigation mechanism
Essential elements of the weekly chart view / Proposed inter-year navigation mechanism

I also needed to show additional metadata including what week and year were being shown, and also what user was being shown. Maybe some sort of table header view?

The next decision was how the user should navigate to past and future years. The first thought was a pull-up-to-refresh-type control to go to the future, and a pull-down to go back. iOS users are pretty used to that control type being for refreshing data, and it probably wouldn’t work with the metadata on a table header view already. Not to mention the amount of scrolling it might take to get to the bottom of the table view.

My solution was to combine the metadata header view and selection into one view/control. I like sliding things, so sliding left and right would request future and past years, respectively. While creating the control later, I also decided that classic arrow buttons would be a good idea so the functionality was immediately discoverable.

SlideSelectView
SlideSelectView

And finally, there was the table view cell itself. I wanted to keep this classic. Album image on the left, artist above album name, artist reemphasized and album name emphasized, and play count on the accessory side. I also left a placeholder for the numbered rank above the album image, but decided it was unnecessary later in the process.

Cell detail
Cell detail

I wanted to get a feel for the settings view too, so I sketched that out. When I was getting my first prototype results back, I realized that if you listen to a lot of mixes, you’ll have row after row of albums with 1 play. I personally wanted the option to only see “full” album listens, and decided to add an option to filter rows by play count.

Settings view wireframe
Settings view wireframe

Development

Finally time to dig into some coding (for real)! Well, almost time…

Setup

Up to this project, I still was git cloning all my external libraries into a lib folder and doing the target settings dance with static libraries. It seemed like CocoaPods was nearly complete on all the popular and semi-popular open source libraries out there. I decided to give it a shot, and it turned out to be mostly a boon to development. I ran into a few snags when I was doing my first archive builds, but I’m of the persuasion now that every iOS project should budget at least a full day to dealing with Xcode quirks.

ReactiveCocoa

I first learned about ReactiveCocoa (RAC) almost a year ago (Maybe March of 2012?) and was immediately interested and immediately confused. ReactiveCocoa is an Objective-C framework for Functional Reactive Programming. I spent a lot of time trying to understand the example projects and following development for the next few months. On a (yet to be released as of this writing) project before this one, I was comfortable enough with a few design patterns to use RAC in selected places throughout that app. Another one of my goals for this app was to try to use RAC wherever I could (and it made sense).

Luckily, RAC hit 1.0 right before I started the project and a substantial amount of documentation was added. For a good, brief overview of the library, I also recommend the NSHipster writeup.

I’ll be explaining my RAC code in some detail later in this post. I want to preface it by saying that I am still very much an RAC newbie and still getting a feel for its patterns, limitations, quirks, etc. Keep that in mind when reading through the code.

Other Libraries

I tried to keep the dependencies lower than normal on this project. I did, however, use a few staples.

  • AFNetworking - the defacto iOS/OS X networking library.
  • SDURLCache - didn’t add this until very close to shipping. I wrestled with NSURLCache during several non-adjacent coding sessions, and eventually decided to replace it with SDURLCache.
  • ViewUtils - there’s a bunch of UIView categories out there. I do a lot of manual view layout, so this library was invaluable.
  • DrawRectBlock - I don’t like cluttering my file list with single-use view classes, so having access to drawRect when creating a new UIView is often helpful. (Especially with different colored top & bottom borders).
  • TestFlightSDK - getting feedback from beta testers has improved my apps and workflow a lot. TestFlight is definitely doing some great work for the iOS community.
  • Crashlytics & Flurry - I spent an entire day before App Store submission trying to figure out the best way to do analytics and crash reports (and how all the libraries fit together). The jury is still out on this, but I have to note that the people at Crashlytics were super helpful and responsive in getting me set up.

TCSLastFMAPIClient

We’re going to start development from the back end (data) and move towards the front (views).

There are some older Last.fm Objective-C clients on GitHub, but it made sense to write my own since I was only using a few of the API endpoints and I wanted to use RAC as much as possible.

Interface

I followed the GitHub client RAC example as a template for my client. The initial interface looked like this:

(TCSLastFMAPIClient.h)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    #import "AFHTTPClient.h"
  
  @class RACSignal;
  @class WeeklyChart;
  @class WeeklyAlbumChart;
  
  @interface TCSLastFMAPIClient : AFHTTPClient
  
  @property (nonatomic, readonly, copy) NSString *userName;
  
  + (TCSLastFMAPIClient *)clientForUserName:(NSString *)userName;
  
  // returns a single NSArray of WeeklyChart objects
  - (RACSignal *)fetchWeeklyChartList;
  
  // returns a single NSArray of WeeklyAlbumChart objects
  - (RACSignal *)fetchWeeklyAlbumChartForChart:(WeeklyChart *)chart;
  
  @end

A few things to notice:

  • We’re subclassing AFHTTPClient.
  • A client instance is specific to a last.fm user.
  • We can request data from two API endpoints as discussed in the planning section.
  • The fetchWeeklyChartList method only requires a username.
  • the fetchWeeklyAlbumChartForChart: method requires a username and a WeeklyChart object returned by the former method. A WeeklyChart object simply holds an NSDate for from and to.

I want to focus on the RACSignal return types. I say in the comments that the methods “return an NSArray…” when what I mean is that they immediately return an RACSignal. Subscribing to that signal will (usually) result in an NSArray being sent to the subscriber in the sendNext method.

It’s probably not immediately clear why I wouldn’t just return the NSArray, but bear with me.

enqueueRequest

Feel free to read the full implementation, but I’m going to focus on a few methods in particular to explain the RAC parts. I’ll explain this one inline with comments.

(TCSLastFMAPIClient.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 // This is our request assembler/responder. 
  // It sits closest to the network stack in the code we'll write.
  - (RACSignal *)enqueueRequestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
  
      // An RACSubject is an RACSignal subclass that can 
      // be manually controlled. It's used to bridge the 
      // block callback structure of the AFHTTPRequestOperation to RAC.
      RACReplaySubject *subject = [RACReplaySubject subject];
      
      NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:parameters];
      // Network caching is such a fickle thing in iOS that I don't really rely on this to do anything
      request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
  
      // We assemble our operation callbacks here
      AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
      
        // Last.fm returns error codes in the JSON data,
        // so it makes sense to handle that on this level.
        // Our endpoint methods should only care about good responses.
        NSNumber *errorCode = [responseObject objectForKey:@"error"];
        if (errorCode){
          // Make a new error object with the message we get
          // from the JSON data.
          NSError *error = [NSError errorWithDomain:@"com.twocentstudios.vinylogue" code:[errorCode integerValue] userInfo:@{ NSLocalizedDescriptionKey: [responseObject objectForKey:@"message"] }];
      
          // Subscribers will "subscribeError:" to this signal
          // to receive and handle the error. It also completes the request
          // (subscribeComplete: won't be called).
          [subject sendError:error];
        }else{
          // If last.fm doesn't give us an error, go ahead and send
          // the response object along to the subscriber.
          [subject sendNext:responseObject];
      
          // There's not going to be any more data, so this
          // RACSignal is complete.
          [subject sendCompleted];
        }
      } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
          // A network error is handled similarly to a Last.fm error.
          [subject sendError:error];
      }];
  
      [self enqueueHTTPRequestOperation:operation];
      
      // RAC can be used for threading.
      // In this case, we want our "sendNext:" calls to be
      // processed by the API endpoint functions on a 
      // background thread. That way, the UI doesn't hang
      // while we're moving data from JSON -> new objects.
      return [subject deliverOn:[RACScheduler scheduler]];
  }

So that’s our first taste of RAC in action. RACSubjects are a little different than vanilla RACSignals, but again, they’re very useful in bridging standard Foundation to RAC.

fetchWeeklyChartList

We’re now at the point where an endpoint function can specify a URL and HTTP method and get a response object to process. I’m only going to explain one of the API endpoint functions (the simplest one), but the other ones should be very similar.

(TCSLastFMAPIClient.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 // A public API endpoint function exposed in our interface
  - (RACSignal *)fetchWeeklyChartList{
      
    // First, assemble a parameters dictionary to give to our enqueue method.
    NSDictionary *params = @{@"method": @"user.getweeklychartlist",
                             @"user": self.userName,
                             @"api_key": kTCSLastFMAPIKeyString,
                             @"format": @"json"};
  
    // This is one big method chain we're returning.
    // The first step is to get the proper request signal
    // from the enqueue method.
    return [[[self enqueueRequestWithMethod:@"GET" path:@"" parameters:params]
              
              // Then we hijack the signal's response and run it 
              // through some processing first.
              //
              // The "map:" blocks below are called with any
              // objects sent through the signal's "sendNext" call.
              // In this example, that object happens to be the
              // responseObject, which is an NSDictionary created
              // from a JSON file received from Last.fm.
              // 
              // The first processing we'll do is 
              // pull the object array from its shell.
              // I'm using an NSDictionary category to define a 
              // "arrayForKey" method that always returns an array,
              // even if there's only one object present in the
              // original dictionary.
             map:^id(NSDictionary *responseObject) {
               return [[responseObject objectForKey:@"weeklychartlist"] arrayForKey:@"chart"];
  
             // Next, we're going to iterate through the array
             // and replace dictionaries with WeeklyChart objects.
             // We use the "rac_sequence" method to turn an array
             // into an RACSequence, then use the map function to
             // do the replacement. Finally, we request a standard 
             // array object from the RACSequence object.
             }] map:^id(NSArray *chartList) {
               return [[chartList.rac_sequence map:^id(NSDictionary *chartDictionary) {
                 WeeklyChart *chart = [[WeeklyChart alloc] init];
                 chart.from = [NSDate dateWithTimeIntervalSince1970:[[chartDictionary objectForKey:@"from"] doubleValue]];
                 chart.to = [NSDate dateWithTimeIntervalSince1970:[[chartDictionary objectForKey:@"to"] doubleValue]];
                 return chart;
               }] array];
             }];
  
  }

So it might look a little hairy, but it’s essentially just a few processing steps chained together and all in once place.

We introduced the RACSequence at the end there to iterate through the array. There are more standard NSArray ways to do this, but RACSequences are a subclass of RACStreams, which have a bunch of cool operators like map, flatten, filter, etc.

The main point to get out of this is that our API endpoint method defines several processing steps for a stream, then hands the stream off to its assumed subscriber. At the point the API endpoint method is called, none of this work will actually be done. It’s not until the subscriber has called subscribeNext on the returned signal that the network request and subsequent processing be done. The subscriber doesn’t even have to know that the original signal’s next values are being modified.

That about does it for the API client. To summarize, the data flow is as follows:

  • The client object’s owner (we’ll assume it’s a controller) requests a signal from a public API endpoint method like fetchWeeklyChartList.
  • The API endpoint method asks the enqueue method for a new signal for a specific network request.
  • The enqueue method creates a new manually controlled signal, sets up the signal so that it will send responses when the network call completes, and then passes the signal to the API endpoint method.
  • The API endpoint method sets up a list of processing steps that must be done with responses that are known to be good.
  • The API endpoint passes the signal back to the controller.

At this point the signal is completely set up. It knows exactly where and how to get its data, and how to process the data once it has been received. But it is lazy and will only do so once the controller subscribes to it.

We haven’t shown the act of subscribing yet, and we’ll do that in the next section.

TCSWeeklyAlbumChartViewController

Our primary view controller is called TCSWeeklyAlbumChartViewController. Its function is to request the weekly album charts for a particular user and display them in a table view. It also facilitates moving forward and backward in time to view charts from other years.

It was originally imagined that this would be, for lack of a better term, the root controller of the app. In its original implementation, it had many more responsibilities and had to be more mutable. During the coding process, the app architecture changed, allowing me to assume that the controller would have an immutable user, and simplifying things a bit.

Public Interface

The interface for this controller is pretty simple. Just one designated initializer.

(TCSWeeklyAlbumChartViewController.h)
1
2
3
4
5
 @interface TCSWeeklyAlbumChartViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
  
  - (id)initWithUserName:(NSString *)userName playCountFilter:(NSUInteger)playCountFilter;
  
  @end

Our controller needs to know which user it should display data for. It also needs to know which albums will be filtered based on play count. This controller will also handle all delegate and datasource duties from within. As a side note, I sometimes separate table datasources and delegates out to be their own classes. For this particular controller, things haven’t gotten so complex that I’ve needed to refactor them out.

Private Interface

It’s good OO practice to keep your class variables private by default, and that’s what we’ll do by defining our private interface in the source file. I’ll go through all the class variables we’ve defined.

Views

Let’s start with the views.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
 @interface TCSWeeklyAlbumChartViewController ()
  
  // Views
  @property (nonatomic, strong) TCSSlideSelectView *slideSelectView;
  @property (nonatomic, strong) UITableView *tableView;
  @property (nonatomic, strong) UIView *emptyView;
  @property (nonatomic, strong) UIView *errorView;
  @property (nonatomic, strong) UIImageView *loadingImageView;
  
  ...
  
  @end

The slideSelectView is the special view we use to move to past and future years. It sits on above the tableView and does not scroll.

The tableView displays an album and play count in each row.

The emptyView, errorView, and loadingImageView are used to show state to the user. The empty and error views are added and removed as subviews of the main view when necessary. The loadingImageView is a animated spinning record that is added as a custom view of the right bar button item.

Data
(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 @interface TCSWeeklyAlbumChartViewController ()

  ...
  
  // Datasources
  @property (atomic, copy) NSString *userName;
  @property (nonatomic) NSUInteger playCountFilter;
  @property (atomic, strong) TCSLastFMAPIClient *lastFMClient;
  
  @property (atomic, strong) NSArray *weeklyCharts; // list of to:from: dates we can request charts for
  @property (atomic, strong) NSArray *rawAlbumChartsForWeek; // unfiltered charts
  @property (atomic, strong) NSArray *albumChartsForWeek; // filtered charts to display
  
  @property (atomic, strong) NSCalendar *calendar; // convenience reference
  @property (atomic, strong) NSDate *now;
  @property (atomic, strong) NSDate *displayingDate;
  @property (atomic) NSUInteger displayingYearsAgo;
  @property (atomic, strong) WeeklyChart *displayingWeeklyChart;
  @property (atomic, strong) NSDate *earliestScrobbleDate;
  @property (atomic, strong) NSDate *latestScrobbleDate;
  
  ...
  
  @end

From a general overview, we’re storing all the data we need to display, including some intermediate states. Why keep the intermediate state? We’ll respond to changes in those intermediates and make the chain of events more malleable. Some variables will be observed by views. Some variables will be observed by RAC processing code to produce new values for other variables. As you’ll see in a moment, we can completely separate our view and data code by using intermediate state variables instead of relying on linear processes.

I’ll reprint our data flow from the planning section above adding some detail and variable names. Variables related to the step are in [brackets].

  • take the current date [now]
  • subtract n years (1 year ago to start) [displayingDate & displayingYearsAgo along with a convenience reference to the Gregorian calendar]
  • figure out where that date is within the bounds of the Last.fm weeks [weeklyCharts & displayingWeeklyChart]
  • request the charts for the username and date range [rawAlbumChartsForWeek]
  • filter the charts based on play count and display the data [albumChartsForWeek]
  • We also need to calculate the date bounds we can show charts for [earliestScrobbleDate & latestScrobbleDate]

We store a lastFMClient instance to call on as our source of external data.

Controller state
(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
 @interface TCSWeeklyAlbumChartViewController ()

  ...
  
  // Controller state
  @property (atomic) BOOL canMoveForwardOneYear;
  @property (atomic) BOOL canMoveBackOneYear;
  @property (atomic) BOOL showingError;
  @property (atomic) NSString *showingErrorMessage;
  @property (atomic) BOOL showingEmpty;
  @property (atomic) BOOL showingLoading;
  
  @end

We have some additional controller state variables set up. I have to admit that I’m not sure my implementation of empty/error views is the best. There was plenty of experimentation, and I ran into some trouble with threading. It works, but will eventually require a refactor.

canMoveForward/BackOneYear depend on which year the user is currently viewing as well as the earliest/latestScrobbleDate. The slideSelectView knows what it should allow based on these bools. Any of our data processes can decide they want to show an error, empty, or loading state. Other RAC processes observe these variables and show the appropriate views. (Again, this took some tweaking and is a little fragile.)

loadView

I’ll annotate loadView inline:

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 - (void)loadView{
      // Create a blank root view
      self.view = [[UIView alloc] init];
      self.view.autoresizesSubviews = YES;
      
      // Begin creating the view hierarchy
      [self.view addSubview:self.slideSelectView];
      self.tableView.delegate = self;
      self.tableView.dataSource = self;
      [self.view addSubview:self.tableView];
      
      // loading view is shown as bar button item
      UIBarButtonItem *loadingItem = [[UIBarButtonItem alloc] initWithCustomView:self.loadingImageView];
      self.loadingImageView.hidden = YES;
      self.navigationItem.rightBarButtonItem = loadingItem;
      
      // double tap on the slide view to hide the nav bar and status bar
      UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doDoubleTap:)];
      doubleTap.numberOfTapsRequired = 2;
      [self.slideSelectView.frontView addGestureRecognizer:doubleTap];
  }

All view attributes are defined in the view getters section. I’ve taken up this habit to keep my controllers a bit more tidy. The views are created at the first self.[viewname] call in loadView.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 // This custom view does most of its own configuration
  - (TCSSlideSelectView *)slideSelectView{
    if (!_slideSelectView){
      _slideSelectView = [[TCSSlideSelectView alloc] init];
    }
    return _slideSelectView;
  }
  
  // The tableview is also pretty vanilla. I'm using a custom
  // inner shadow view (although it's still not quite perfect).
  - (UITableView *)tableView{
    if (!_tableView){
      _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
      _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
      _tableView.backgroundView = [[TCSInnerShadowView alloc] initWithColor:WHITE_SUBTLE shadowColor:GRAYCOLOR(210) shadowRadius:3.0f];
    }
    return _tableView;
  }
  
  // Spinning record animation. It uses 12 images in a standard UIImageView.
  - (UIImageView *)loadingImageView{
    if (!_loadingImageView){
      _loadingImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
      NSMutableArray *animationImages = [NSMutableArray arrayWithCapacity:12];
      for (int i = 1; i < 13; i++){
        [animationImages addObject:[UIImage imageNamed:[NSString stringWithFormat:@"loading%02i", i]]];
      }
      [_loadingImageView setAnimationImages:animationImages];
      _loadingImageView.animationDuration = 0.5f; // trial and error
      _loadingImageView.animationRepeatCount = 0; // repeat forever
    }
    return _loadingImageView;
  }

viewDidLoad & RAC setup

Now for the fun stuff. The majority of the controller’s functionality is set up in viewDidLoad within two helper methods, setUpViewSignals and setUpDataSignals.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
 - (void)viewDidLoad{
    [super viewDidLoad];
  
    [self setUpViewSignals];
    [self setUpDataSignals];
  
    // these assignments trigger the controller to begin its actions
    self.now = [NSDate date];
    self.displayingYearsAgo = 1;
  }

I’m going to start with the view signals to stress that as long as we know the exact meaning of our state variables, we can set up our views to react to them without knowing when or where they will be changed.

But before we can read through the code, we’ll need a quick primer on some more bread-and-butter RAC methods.

@weakify & @strongify

Because RAC is heavily block-based, we can use the EXTScope preprocessor definitions within the companion libextobjc library to save our sanity when passing variables into blocks. Simply throw a @weakify(my_variable) before your RAC blocks to avoid the retain cycles, and then @strongify(my_variable) within each block to ensure the variable is not released during the block’s execution. See the definitions here.

RACAble(…) & RACAbleWithStart(…)

RACAble is simply magic Key Value Observing. From the documentation:

[RACAble] returns a signal which sends a value every time the value at the given path changes, and sends completed if self is deallocated.

This will be the backbone of our RAC code and is probably the easiest place to get started with RAC. It’s easy to pepper these signals into existing code bases without having to rewrite from scratch. Or just use a few in a project to get started.

setUpViewSignals

Let’s dive right into our first view signal to get a feel for it. I’ve separated it out into several expressions in order to simplify the explanation. In the actual source, it’s a single expression.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 // Subscribing to all the signals that deal with views and UI
  - (void)setUpViewSignals{
    @weakify(self);
  
    // SlideSelectView: Top Label
    // Depends on: userName
    RACSignal *userNameSignal = RACAbleWithStart(self.userName);
  
    // View changes must happen on the main thread
    [userNameSignal deliverOn:[RACScheduler mainThreadScheduler]];
  
    // Change views based on the value of userName
    [userNameSignal subscribeNext:^(NSString *userName) {
      @strongify(self);
      if (userName){
        self.slideSelectView.topLabel.text = [NSString stringWithFormat:@"%@", userName];
        self.showingError = NO;
      }else{
        self.slideSelectView.topLabel.text = @"No last.fm user";
        self.showingErrorMessage = @"No last.fm user!";
        self.showingError = YES;
      }
      [self.slideSelectView setNeedsLayout];
    }];
  
    
  
  }

Let’s deconstruct this. RACAbleWithStart(self.userName) creates an RACSignal. Again, an RACSignal can send next (with a value) and error or complete messages to its subscribers.

The WithStart part sends the current value of self.userName when it subscribed to. Without this, the block in subscribeNext would not be executed until self.userName changes for the first time after this subscription is created. In our case, because self.userName is only set once in the controller’s init method (before the signal is created), it would never be called with a normal RACAble.

(At this point you may be wondering, “Why even observe the userName property if it’s guaranteed to never change within the controller?” That’s a good question. It’s partly vestigial from when the value could change. It would very much be possible to transplant the code from within the subscribeNext block to viewDidLoad, but as you’ll see it fits pretty well with the rest of the more dynamic view code.)

The next statement, deliverOn:, modifies transforms the signal into a new RACSignal on the specified thread to ensure next values are delivered on the main thread. This is a necessary because UIKit requires user interface changes to be done on the main thread. Like all other signal transformations, it only takes effect if the result is used.

subscribeNext is where our reaction code goes. Basically saying, “When the value of self.userName changes, execute the following block with the new value.” In this example, we’re going to change a label to show the userName. If it’s nil, we’ll throw up the error view.

I could have also used another variation of the subscribeNext method like subscribeNext:complete: to also add a block to execute when the signal completes. We don’t really need to do anything on completion, so we’ll just add a block for next:.

Instead of the if/else, we could have used two separate subscriptions that first filter for nil or empty userName. To keep it simple though, we’ll just mix in the iterative style for now.

Alright, so one signal down. Let’s look at another common pattern: combining multiple signals.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 - (void)setUpViewSignals{
      @weakify(self);
  
      
  
      // SlideSelectView: Bottom Label, Left Label, Right Label
      // Depend on: displayingDate, earliestScrobbleDate, latestScrobbleDate
      RACSignal *combinedSignal = [RACSignal combineLatest:@[ RACAbleWithStart(self.displayingDate), RACAbleWithStart(self.earliestScrobbleDate), RACAbleWithStart(self.latestScrobbleDate)] ];
      
      // Do it on the main thread
      [combinedSignal deliverOn:[RACScheduler mainThreadScheduler]];
      
      // Actions to perform when changing any of the three signals
      [combinedSignal subscribeNext:^(RACTuple *dates) {
           NSDate *displayingDate = dates.first;
           NSDate *earliestScrobbleDate = dates.second;
           NSDate *latestScrobbleDate = dates.third;
          @strongify(self);
          if (displayingDate){
            // Set the displaying date label
      
            // Calculate and set canMoveBackOneYear and canMoveForwardOneYear
      
            // Only show the left and right labels/arrows 
            // if there's data there to jump to.
      
          }else{
            // There's no displaying date, so set all labels to nil
          }
      
       }];
      
      
      
  }

I’ve separated out the expressions again, and I’ve replaced a bunch of tedious date calculation code and view updating code with comments in order to focus on what’s happening with RAC. You can see everything in the source.

The slideSelectView has a couple components that depend on displayingDate, earliestScrobbleDate, and latestScrobbleDate. I want to update this view when any of these values change. Luckily, there’s an RACSignal constructor for this.

[RACSignal combineLatest:] allows you to combine several signals into one signal. The new signal sends a next: message any time one of its component signals sends next:. There are a few variations of combineLatest: but the one we’ll use in this example will combine all the latest next: values of our three component signals into a single RACTuple object. If you haven’t heard of a tuple before, you can think of it like an array for now.

combineLatest takes an array of signals, which we’ll generate on-the-fly with RACAbleWithStart.

When we subscribe, we expect a single RACTuple object to be delivered with the latest values of our component signals in the order we placed them in the original array. We can use the RACTuple helper methods .first, .second, etc. to break out the values we need.

Within the block, we calculate some booleans and set some labels. This could be broken up into multiple methods, but in this case, it made the most sense to do these calculations and assignments in the same place because they depend on the same set of signals.

Let’s do one more view-related signal to show how primitive values are handled.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 - (void)setUpViewSignals{
      @weakify(self);
  
      
  
      // Show or hide the loading view
      [[[RACAble(self.showingLoading) distinctUntilChanged]
       deliverOn:[RACScheduler mainThreadScheduler]]
       subscribeNext:^(NSNumber *showingLoading) {
        @strongify(self);
        BOOL isShowingLoading = [showingLoading boolValue];
        if (isShowingLoading){
          [self.loadingImageView startAnimating];
          self.loadingImageView.hidden = NO;
        }else{
          [self.loadingImageView stopAnimating];
          self.loadingImageView.hidden = YES;
        }
      }];
      
      
      
  }

Here we’re observing the showingLoading state variable. This variable will presumably be set by the data subscribers when they’re about to do something with the network or process data.

This time I left the signal creation, modification, and subscription all in one call.

RACAble(self.showingLoading) creates the signal. distinctUntilChanged modifies the signal to only send a next: value to subscribers when that value is different from the last one. For example, let’s assume showingLoading is YES. If somewhere in our controller, a method sets showingLoading to NO, then it is also set to NO later, the subscribeNext: block will only be executed on the first change from YES to NO.

We’ve seen deliverOn: a few times now. No surprises there.

Now for the subscription. You can see that the next: value is delivered as an NSNumber object even though self.showingLoading is a primitive BOOL. RAC will wrap primitives and structs in objects for us. So before we compare against the value, I’ll use [showingLoading boolValue] to get the primitive back.

You can check out the rest of the view signals and subscriptions in the source.

setUpDataSignals

We’ll introduce a couple new RAC concepts in this method. But first, here’s an ugly ascii variable dependency graph. We’ll use this to set up our signals.

    userName            now         displayingYearsAgo
        |                \              /
    lastFMClient          displayingDate
        |                       /
    weeklyCharts              /
        \                   /
        displayingWeeklyChart
                |
        rawAlbumChartsForWeek
                |
        albumChartsForWeek
                |
        [tableView reloadData]

When any of these variables change, they trigger a change upstream (downstream?) to the variables that depend on them.

For example, if the user changes displayingDate (moving to a past year), a change will be triggered in displayingWeeklyChart, which will trigger a change in rawAlbumChartsForWeek and so on. If our data was more time sensitive (by-the-minute instead of by-the-week), we could set up a signal for now that would trigger a change down the line every minute. Or if we decided to reuse our controller with different users, a userName change would trigger a change down the line.

We’ll start with the userName observer.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 // All the signals that deal with acquiring and reacting to data changes
  - (void)setUpDataSignals{
  
    @weakify(self);
  
    // Setting the username triggers loading of the lastFMClient
    [[RACAbleWithStart(self.userName) filter:^BOOL(id x) {
      return (x != nil);
    }] subscribeNext:^(NSString *userName) {
      NSLog(@"Loading client for %@...", userName);
      @strongify(self);
      self.lastFMClient = [TCSLastFMAPIClient clientForUserName:userName];
    }];
  
    
  
  }

We’ve seen the RACAbleWithStart pattern. We’re going to use filter: to act only on non-nil values of userName. Filter takes a block with an argument of the same type as is sent in next:. In this case, we don’t need to cast it directly, we just know it shouldn’t be nil. filter’s block returns a BOOL that indicates whether it should pass the next value to subscribers. Returning YES passes the value. Returning NO blocks it and the subscribeNext block will never see it. filter is a operation defined by RACStream like map which we saw earlier.

Next is another RAC pattern. You can automatically assign a property to the latest next value sent by a signal by using RAC(my_property) = my_signal. There are actually a couple other ways to accomplish this too. Here’s an example from Vinylogue.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 - (void)setUpDataSignals{
  
    @weakify(self);
  
    
  
    // Update the date being displayed based on the current date/time and how many years ago we want to go back
    RAC(self.displayingDate) = [[[[RACSignal combineLatest:@[ RACAble(self.now), RACAble(self.displayingYearsAgo) ]]
      deliverOn:[RACScheduler scheduler]]
      map:^(RACTuple *t){
        NSDate *now = t.first;
        NSNumber *displayingYearsAgo = t.second;
        NSLog(@"Calculating time range for %@ year(s) ago...", displayingYearsAgo);
        NSDateComponents *components = [[NSDateComponents alloc] init];
        components.year = -1*[displayingYearsAgo integerValue];
        return [self.calendar dateByAddingComponents:components toDate:now options:0];
      }] filter:^BOOL(id x) {
        NSLog(@"Time range calculated");
        return (x != nil);
      }];
  
    
  
  }

This one is a little tricky so let’s step through it. First thing we’re doing is setting up the RAC(property) assignment. self.displayingDate will be assigned to whatever next value comes out of our complicated signal on the other side of the equals sign.

We’re creating a signal that combines our self.now and self.displayingYearsAgo signalified properties. Remember, they’re not just regular properties. We’ve made them into signals by wrapping them with RACAble, and they send their values each time they’re changed.

We’re injecting a deliverOn with the default background scheduler [RACScheduler scheduler] before doing any work on the next values to make sure the work will be done off the main thread (ensuring a snappy UI).

Edit: Doing this on a background thread is probably overkill and most likely not the best idea since we’re now changing controller properties off the main thread.

Remember that combineLatest creates an RACTuple with the next values of each signal it wraps. We break that tuple out into an NSDate and an NSNumber and use those to calculate a date in the past. Remember that map just modifies next values. It has to return a value to pass to subscribers. It doesn’t have to be the same type; our input is an RACTuple and our output is an NSDate in this case.

Our last operation is a filter for nil. It’s useless to assign nil to our displayingDate. If this returns YES, then our next date returned from map will automatically be assigned to self.displayingDate.

We’ll do one expression in order to show how we use the Last.fm client functions we wrote earlier.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 - (void)setUpDataSignals{
  
    @weakify(self);
  
    
  
   // When the weeklychart changes (being loaded the first time, 
   // or the display date changed), fetch the list of albums for that time period.
   [[[RACAble(self.displayingWeeklyChart) filter:^BOOL(id x) {
     return (x != nil);
   }] deliverOn:[RACScheduler scheduler]]
    subscribeNext:^(WeeklyChart *displayingWeeklyChart) {
      NSLog(@"Loading album charts for the selected week...");
      @strongify(self);
      [[[self.lastFMClient fetchWeeklyAlbumChartForChart:displayingWeeklyChart]
        deliverOn:[RACScheduler scheduler]]
       subscribeNext:^(NSArray *albumChartsForWeek) {
         NSLog(@"Copying raw weekly charts...");
         @strongify(self);
         self.rawAlbumChartsForWeek = albumChartsForWeek;
       } error:^(NSError *error) {
         @strongify(self);
         self.albumChartsForWeek = nil;
         NSLog(@"There was an error fetching the weekly album charts!");
         self.showingErrorMessage = error.localizedDescription;
         self.showingError = YES;
       }];
    }];
  
    

  }

Once we have a specific time period to request charts for, we’ll get a signal from the client by supplying the displayingWeeklyChart. We have a signal within a signal in this block. As soon as we subscribeNext to the signal that was returned from the client, it will request data from the network and do the processing.

We also subscribed with an error block this time so we can pass the error along to the user. By setting showingError and showingErrorMessage, the view signal subscriptions we created earlier are triggered. Remember that in this subscription, we’re still on a background thread. Changing these properties on a background thread will still trigger the view updates on the main thread. Pretty cool.

The rest of our setUpDataSignals method uses similar tricks with RAC, observing properties as outlined in our simple ascii chart. Check out the source to decipher the rest.

You should be at the point now where you can start picking through the signal operations in RACSignal+Operations.h. RACStream.h also has some operations you should be aware of.

In the next section we’ll also briefly cover RACCommands.

TCSSlideSelectView

The slideSelectView is the extra special control we dreamed up earlier in the wireframing section.

Reminder of slideSelectView
Reminder of slideSelectView

We should break it out so we know how we should code the view hierarchy.

slideSelectView broken out
slideSelectView broken out

Let’s implement it!

Interface

From our sketch, we can deconstruct the views we need.

(TCSSlideSelectView.h)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 @interface TCSSlideSelectView : UIView <UIScrollViewDelegate>
  
  @property (nonatomic, readonly) UIView *backView;
  @property (nonatomic, readonly) UIButton *backLeftButton;
  @property (nonatomic, readonly) UIButton *backRightButton;
  @property (nonatomic, readonly) UILabel *backLeftLabel;
  @property (nonatomic, readonly) UILabel *backRightLabel;
  
  @property (nonatomic, readonly) UIScrollView *scrollView;
  
  @property (nonatomic, readonly) UIView *frontView;
  @property (nonatomic, readonly) UILabel *topLabel;
  @property (nonatomic, readonly) UILabel *bottomLabel;
  
  // Signals will be fired when the scroll view is dragged past the offset
  @property (nonatomic) CGFloat pullLeftOffset;
  @property (nonatomic) CGFloat pullRightOffset;
  
  @property (nonatomic, strong) RACCommand *pullLeftCommand;
  @property (nonatomic, strong) RACCommand *pullRightCommand;
  
  @end

We’ll decide up front that this view will be somewhere between a generic and concrete view. A good future exercise would be figuring out how to make this view generic enough to be used by other controllers and apps. We’ve made it sort of generic by exposing all the subviews as readonly, therefore allowing other objects to change view colors and other properties, but not allowing them to replace views with their own.

We’ll actually redefine these view properties in the private interface in the implementation file.

Although our view will have defaults for the pull offsets and commands, we’ll allow outside classes to change or replace them at will.

The pull offsets are values that answer the question, “How far do I have to pull the scroll view to the right or left before an action is triggered?” The commands are RACSignal subclasses that are designed to send next values triggered off of an action, usually from the UI. We’ll use these constructs instead of the delegate pattern to pass messages from our custom view to the controller that owns it.

Implementation

Here’s the private interface:

(TCSSlideSelectView.m)
1
2
3
4
5
6
7
 @interface TCSSlideSelectView ()
  
  @property (nonatomic, strong) UIView *backView;
  // redefine the rest of the views as strong instead of readonly
  // ...
  
  @end

We’ll define the view hierarchy and create defaults in init.

(TCSSlideSelectView.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 - (id)init{
    self = [super initWithFrame:CGRectZero];
    if (self) {
      [self addSubview:self.backView];
  
      [self.backView addSubview:self.backLeftLabel];
      [self.backView addSubview:self.backRightLabel];
  
      [self.backView addSubview:self.scrollView];
  
      // Buttons technically sit above (but not on) 
      // the scrollview in order to intercept touches
      [self.backView addSubview:self.backLeftButton];
      [self.backView addSubview:self.backRightButton];
  
      [self.scrollView addSubview:self.frontView];
      [self.frontView addSubview:self.topLabel];
      [self.frontView addSubview:self.bottomLabel];
  
      // Set up commands
      self.pullLeftOffset = 40;
      self.pullRightOffset = 40;
      self.pullLeftCommand = [RACCommand command];
      self.pullRightCommand = [RACCommand command];
    }
    return self;
  }

Logically, the buttons would be behind the scrollView, but I was having trouble getting taps to get forwarded from the invisible scrollview to the button below it (maybe I should have just made the scrollView narrower?). Instead, the buttons sit above the scrollview and disappear when scrolling begins.

We also define defaults for the pull offsets and set up generic RACCommands.

I use layoutSubviews to resize the views and lay them out. This is mostly self explanatory and tedious to explain. Feel free to read the source to see how I do that.

We’ll move on to the more interesting part: using RACCommands to pass messages.

(TCSSlideSelectView.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    # pragma mark - UIScrollViewDelegate
  
  - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    [self showBackButtons:NO];
  }
  
  - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    CGFloat offset = scrollView.contentOffset.x;
    if (offset <= -self.pullLeftOffset){
      [self.pullLeftCommand execute:nil];
    }else if(offset >= self.pullRightOffset){
      [self.pullRightCommand execute:nil];
    }
    [self showBackButtons:YES];
  }

Like I just mentioned before, we’ll hide the buttons when scrolling starts and show them again when scrolling ends.

When scrolling ends, we check the x offset of the scrollview. If it’s past the offsets that were set earlier, we use the execute method of the RACCommand. An RACCommand is just a subclass of an RACSignal with a few behavior modifications. execute: sends its argument in a next message to subscribers. In our example, we don’t need to send any particular object, just the message is enough. We could have alternatively only had one command object and sent the direction as the message object. That’s a little confusing though.

This design pattern works for a few reasons. If the command has no subscribers, the message will be ignored, no harm done. So far though, it isn’t really that much different than creating a protocol and holding a reference to a delegate. I’ll show you the interesting part in a second.

Before we move back to the controller to show how we handle these messages, here’s how we handle the buttons:

(TCSSlideSelectView.m)
1
2
3
4
5
6
7
8
9
    # pragma mark - private
  
  - (void)doLeftButton:(UIButton *)button{
    [self.pullLeftCommand execute:nil];
  }
  
  - (void)doRightButton:(UIButton *)button{
    [self.pullRightCommand execute:nil];
  }

The buttons trigger the same action as the scrollView.

Another way to interface UIControls with RAC is to use the RACSignalSupport category:

(UIControl+RACSignalSupport.h)
1
2
3
4
5
6
7
 @interface UIControl (RACSignalSupport)
  
  // Creates and returns a signal that sends the sender of the control event
  // whenever one of the control events is triggered.
  - (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents;
  
  @end

Let’s quickly jump back to the TCSWeeklyAlbumChartViewController to show how we interface with this custom control.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 - (void)setUpDataSignals{

    
  
    // Change displayed year by sliding the slideSelectView left or right
    self.slideSelectView.pullLeftCommand = [RACCommand commandWithCanExecuteSignal:RACAble(self.canMoveBackOneYear)];
    [self.slideSelectView.pullLeftCommand subscribeNext:^(id x) {
      @strongify(self);
      self.displayingYearsAgo += 1;
    }];
    self.slideSelectView.pullRightCommand = [RACCommand commandWithCanExecuteSignal:RACAble(self.canMoveForwardOneYear)];
    [self.slideSelectView.pullRightCommand subscribeNext:^(id x) {
      @strongify(self);
      self.displayingYearsAgo -= 1;
    }];
  
    
  
  }

We’re creating signals (commands) and assigning them to the slideSelectView. The slideSelectView will own these signals, but before the controller hands them over, it will add a special “canExecuteSignal” and then subscription instructions for each.

The command will check the latest value of its canExecute signal (which should be a BOOL) to decide whether it should fire before executing its next block. In the example, we don’t want to let the user move to the past unless there are weeks to show there. We create a signal from our BOOL property canMoveBackOneYear and assign it to the command. Our canMove properties will now govern the actions of the slideSelectView for us.

When these commands are allowed to fire, they’ll update our displayingYearsAgo property, and the changes will propagate through our ascii dependency chart.

That’s about it for the slideSelectView. Now that we have the skeleton of our app created, time to start thinking about the design.

Design

Alright, so we’ve now done a fair amount of development. There’s at least enough for a prototype using built in controls. Now it’s time to get a feel for how everything is going to look.

This is approximately what our app looks like at this point:

Ugly working prototype
Ugly working prototype

I am not a designer. I usually work with my friend CJ from Oltman Design, but since this was a low-stakes, unpaid project with a tight-timeline, I decided to give it a shot myself.

Many shops will complete the wireframes, UX, and then do the Photoshop mockups before even involving the developers. Since I’m doing everything, I sometimes pivot back and forth between the two, using new ideas while doing each to iterate in both directions.

You can also see that from my ugly prototype screen shot that I like to multicolor my views initially to make sure everything is being laid out as expected.

Photoshop

I took my ugly mockup and threw it into photoshop, then styled on top of it. I wanted to start with a more colorful mockup and used this color picker to pick out a bunch of colors I liked (again, not a designer!).

Here is my first mock up:

First mock up
First mock up

I chose iOS6’s new Avenir Next font in a few weights because it’s pretty, a little more unique, but still very readable and not too opinionated.

As a non-designer, I simply asked myself what the relative order of importance for each element was, and adjusted its relative color and size accordingly. The artist name is not as important as the album name, therefore the artist name is smaller and has less contrast. The album cover is important, so it is nice and big and the first thing you see on the left side (also a standard iOS design pattern). I added padding to the album images so they wouldn’t bump up against each other on the top and bottom, which sometimes looks weird. The number of plays is big and stands out, while the word “plays” can be inferred after seeing it the first time, and can therefore be downplayed heavily. I gave the cells a classic one-line top highlight and bottom shadow.

For my slide control, I wanted to give it depth to imply it being stacked. A darker color and inner shadow accomplished this (although I’ve found Photoshop-style inner shadows much harder to implement with Core Animation and Core Graphics). The user name is mostly understood and is only there as a reminder, so it can be downplayed. The week is important so it should stand out.

I was originally planning on this being the only controller, so I put the logo top and center. The logo is just our normal Avenir Next font with a little extra tracking (not a designer). It looks just hipstery enough I think.

Code the cell design

Now that there’s a map for our cell layout, let’s implement it.

Interface

(TCSAlbumArtistPlayCountCell.h)
1
2
3
4
5
6
7
8
9
 @interface TCSAlbumArtistPlayCountCell : UITableViewCell
  
  @property (nonatomic, strong) WeeklyAlbumChart *object;
  
  - (void)refreshImage;
  
  + (CGFloat)heightForObject:(id)object atIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableView;
  
  @end

We’ll keep a reference to the model object we’re displaying. There’s some debate about the best way to implement the model/view relationship. Sometimes I borrow the concept of a “decorator” object from Rails that owns the model object and transforms its values into something displayable. I decided to keep this app simple this time and have the cell assign its own view attributes directly from the model object. If we didn’t have a one-to-one relationship between views and model objects, I would definitely reconsider this.

Ignore refreshImage for now. That tackles a problem we’ll run into later.

heightForObject is our class object that does a height calculation for an object before an instance of the cell is actually allocated. Part of the UITableView protocols is knowing the height of a cell before it is actually laid out. I have yet to figure out a good way to do this without doubling up on some layout calculation code, but alas I’ve gotten used to writing it.

Implementation

Our private instance variables:

(TCSAlbumArtistPlayCountCell.m)
1
2
3
4
5
6
7
8
9
10
 @interface TCSAlbumArtistPlayCountCell ()
  
  @property (nonatomic, strong) UILabel *playCountLabel;
  @property (nonatomic, strong) UILabel *playCountTitleLabel;
  @property (nonatomic, strong) UILabel *rankLabel;
  @property (nonatomic, strong) UIView *backView;
  
  @property (nonatomic, strong) NSString *imageURLCache;
  
  @end

I’m reusing UITableViewCell’s textLabel and detailTextLabel for my artist and album labels, and reusing the imageView for the album image. Our cells aren’t currently selectable, so the cell only has a normal background view and not a selected one.

I’ll come back to that imageURLCache string in a moment.

(TCSAlbumArtistPlayCountCell.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 @implementation TCSAlbumArtistPlayCountCell
  
  - (id)init{
    self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:NSStringFromClass([self class])];
    if (self) {
      self.selectionStyle = UITableViewCellSelectionStyleNone;
  
      self.backgroundView = self.backView;
  
      [self configureTextLabel];
      [self configureDetailTextLabel];
      [self configureImageView];
      [self.contentView addSubview:self.playCountLabel];
      [self.contentView addSubview:self.playCountTitleLabel];
      [self.contentView addSubview:self.rankLabel];
  
    }
    return self;
  }
  
  
  
  @end

I create and/or configure my views in custom getters at the bottom of my implementation file. Nothing too interesting here. Moving on…

(TCSAlbumArtistPlayCountCell.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 - (void)setObject:(WeeklyAlbumChart *)object {
    if (_object == object)
      return;
  
    _object = object;
    self.textLabel.text = [object.artistName uppercaseString];
    self.detailTextLabel.text = object.albumName;
    self.playCountLabel.text = [object.playcount stringValue];
    self.rankLabel.text = [object.rank stringValue];
  
    [self refreshImage];
  
    if (object.playcountValue == 1){
      self.playCountTitleLabel.text = NSLocalizedString(@"play", nil);
    }else{
      self.playCountTitleLabel.text = NSLocalizedString(@"plays", nil);
    }
  }

Our custom object setter is in charge of properly assigning model object data to our views. The interesting problem we come across is the albumImage. Let’s look at the refreshImage instance method:

(TCSAlbumArtistPlayCountCell.m)
1
2
3
4
5
6
7
8
9
10
11
12
 - (void)refreshImage{
    UIImage *placeHolderImage = [UIImage imageNamed:placeholderImage];
    if (self.imageView.image == nil){
      self.imageView.image = placeHolderImage;
    }
  
    if(self.object.albumImageURL && ![self.object.albumImageURL isEqualToString:self.imageURLCache]){
      // prevent setting imageView unnecessarily
      [self.imageView setImageWithURL:[NSURL URLWithString:self.object.albumImageURL] placeholderImage:placeHolderImage];
      self.imageURLCache = self.object.albumImageURL;
    }
  }

What do we know about the image at this point? When our WeeklyAlbumChart object is originally created, it does not have an album image URLl The Last.fm API does not return that data with the call we’re using. If we want that image URL, we have to request it using a separate album.getInfo API call. And it may not even exist for a particular album.

But getting that URL isn’t the cell’s responsibility. We don’t want to create or pass in a copy of the lastFMAPIClient to each cell. That seems like the controller/datasource’s responsibility.

Why don’t we just request all these image URLs when we originally receive the album chart list from the API client? We could, but if that list has 50+ albums in it, that’s 51+ API calls we just made. And if the user only scrolls through a couple, it’s a lot of wasted data. We should strive to do it more lazily. Only request the album image URL if the cell is actually being displayed. Luckily, we have a nice table view delegate method for that.

(TCSWeeklyAlbumChartViewController.m)
1
2
3
4
5
6
7
8
9
10
 - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    // If the object doesn't have an album URL yet, request it from the server then refresh the cell
    TCSAlbumArtistPlayCountCell *albumCell = (TCSAlbumArtistPlayCountCell *)cell;
    WeeklyAlbumChart *albumChart = [self.albumChartsForWeek objectAtIndex:indexPath.row];
    if (albumChart.albumImageURL == nil) {
      [[[self.lastFMClient fetchImageURLForWeeklyAlbumChart:albumChart] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSString *albumImageURL) {
        [albumCell refreshImage];
      }];
    }
  }

In the controller, if the model object does not have an albumImageURL we request it using a new API client method (that returns an RACSignal of course). We subscribe, so when it’s done we can refresh the cell and load the new image using AFNetworking’s UIImageView category that loads images asynchronously from a URL.

While we’re waiting for our response, the user could possibly have scrolled past the cell, and the cell could be reassigned a new object. No problems though, because refreshImage will just fall through without doing anything and the URL will be saved and loaded the next time the object is assigned to the cell.

The last thing we should do is clear out our imageURLCache in prepareForReuse, although technically everything will still work without doing so.

All of this work seems like exactly the thing that RAC would excel at. Unfortunately, I wrestled with several such implementations but could not get things to work out as I wanted. On my to-do list is to try again and see if something unrelated was the problem.

I was considering digging into the view layout calculations, but I think as was with the slideSelectView, explaining the layout code would be tedious and superfluous. Check it out in the source if you’re interested.

Photoshop part 2

We coded up our cell layout and did the same for the slideSelectView and took a step back to see how it looks on the iPhone.

Coded layouts
Coded layouts

Everything seems to be in the right place. Let’s go ahead and adjust fonts and colors, then tweak the layout to get things as close to our mock up as possible.

Set fonts and colors
Set fonts and colors

Cool. Looking pretty close to what we had in photoshop. We haven’t gotten the inner shadows of the slideSelectView yet though. And the sharp rectangle of the slideSelectView’s top view doesn’t look particularly draggable. Let’s round off the corners. We’ll also add a button for opening up the settings (user name and play count, along with about info).

Some tweaks
Some tweaks

Settings

Next we need to create a settings controller and a controller for setting the user name.

I’m not going to go in depth for this one. I used a generic static table view datasource class I threw together for another project. You basically give it a specially coded dictionary with titles and optional selectors, then tell it what cell classes to use for headers and cells.

I didn’t do this one in photoshop first. Since it’s only one line of text per line, I simply coded it up and experimented a little with the fonts and colors I already had chosen.

First settings controller
First settings controller

I kept this theme going with the username input controller. I heavily borrowed the styling of the awesome and beautiful app EyeDrop.me.

User name edit controller
User name edit controller

Iteration

We now have a functional app! But now we can take a step back and really look at what’s going on.

  1. The color scheme of the root controller is too much. The colors of the album art should draw the most focus not the background of the table cells nor the play count.
  2. The flow of the settings page kind of works, but it feels a little odd.
  3. My original thought was that this would be a one-user app, but during testing, I realized that:
    • We don’t need to input a user’s password to view their charts.
    • During testing I was viewing my friends’ charts and enjoying perusing them.
    • Why not just having a landing page controller where we can easily select charts for different users!?

It seems obvious in hindsight, but at the time it was anything but.

So let’s address each of these points.

  1. I’m kind of liking the settings page. It was sort of an accident, but it’s minimalist, which is easier to pull off for someone with no sense of design. It’s also the way design trends have been going lately. Let’s aim for that on the root controller.
  2. We can probably address this along with #3.
  3. The root controller should be a list of users.
    • Selecting a user pushes their chart on the nav controller.
    • Settings can be viewed and changed from the root controller.
Re-imagined wireframed controller flow
Re-imagined wireframed controller flow

Awesome! A little more complicated, but overall a much more functional app.

Photoshop part 3

Luckily all the layout is still winning our favor. All we have to do is tweak fonts and colors.

Photoshop comp 2
Photoshop comp 2

That looks a lot cleaner. The slideSelectView looks a little weird, but we’ll play around with those colors on the device/simulator.

Design implementation part 2

Redesign on the simulator
Redesign on the simulator

Yeah, much better.

Let’s check out the new users controller.

Users controller
Users controller

Looks good. UIKit automatically adds those table header view bottom borders and I can’t reliably get rid of them. Weird.

Because we’ve moved the primary user name out of settings, here’s the settings controller one more time.

Settings controller with no user name
Settings controller with no user name

Back to development

Alright, we totally skipped the implementation of the users controller. Let’s touch on that and see if RAC has anything to do with it.

Data store

We have a new decision to make: how will we set/store users? We have a few options:

  1. The user adds their friends’ user names manually.
    • Pros: simplest, may be what most users want.
    • Cons: may be difficult for users to find/enter their friends’ usernames.
  2. Automatically sync friends with last.fm.
    • Pros: users also might find this easiest.
    • Cons: may be difficult to keep the user’s personal ordering.
  3. Manually sync friends with last.fm on user request.
    • Pros: probably a good compromise as a “first run” option or default.
    • Cons: users may get upset if they sync again and everything is overwritten.

We should probably start with option 1. Later we can add option 3 if the interest is there. Keep an ear open for option 2.

(Update: V1.1 has friend list importing, which solves these problems by only importing friends not already in the list).

The easiest way to implement option 1 is NSUserDefaults. We should probably wrap it in its own datastore class in order to abstract out the implementation details and make option 3 easier to add in the future.

Our new app start up sequence is the following pseudo code in our AppDelegate:

  1. Pull the play count filter setting from NSUserDefaults.
  2. Create a user data store class instance that will pull user data from NSUserDefaults.
  3. Create a UsersController and pass in our userStore and playCountFilter.

Why not have each controller interact with NSUserDefaults as needed? I consider NSUserDefaults a store for global variables. Doing all global IO in one place (the AppDelegate) is better separation of concerns.

At the end of the day it’s a judgement call, and with an app this size there’s only so much anti-pattern spagettification you can even create. With larger apps, it’s a better idea to keep your controllers as functional (in the functional programming sense) as possible.

Moving data around

TCSFavoriteUsersController has a by-the-book table view delegate and datasource implementation. It includes adding, removing, and rearranging elements in one section only.

An instance of TCSFavoriteUsersController is a spring board for 3 other controllers.

  • TCSSettingsViewController - users can change the value of playCountFilter.
  • TCSUserNameViewController - used to add a new friend or edit an existing friend.
  • TCSWeeklyAlbumChartViewController - used to display charts.

We need to keep a connection to the Settings & UserName controllers we create and display because we need to know when they change values. Normally this is accomplished through the delegate pattern. Our parent controller will get a message with the new data and dismiss its child. We’re going to try a different pattern using RAC.

This example is for editing the userName of a friend by tapping the cell during edit mode (this used to be done by tapping the accessory button, but changed before release).

(TCSFavoriteUsersViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
  
     // Get the user name for the row using a helper function.
     // We could get the whole User object, but in this case 
     // we only need the name.
    NSString *userName = [self userNameForIndexPath:indexPath];
  
    // Selecting the cell has different behavior depending on 
    // whether or not the controller is in edit mode.
    if (!self.editing){
  
      // In non-editing mode, just present a new chart controller.
      TCSWeeklyAlbumChartViewController *albumChartController = [[TCSWeeklyAlbumChartViewController alloc] initWithUserName:userName playCountFilter:self.playCountFilter];
      [self.navigationController pushViewController:albumChartController animated:YES];
    }else{
  
      TCSUserNameViewController *userNameController = [[TCSUserNameViewController alloc] initWithUserName:userName headerShowing:NO];
      @weakify(self);
  
      // The userNameController has a signal (analogous to a protocol) that
      // sends the User object if it has changed.
      // We subscribe to it in advance, and write our instructions for
      // processing changes.
      [[userNameController userSignal] subscribeNext:^(User *user){
        @strongify(self);
  
        // Section 0 is for the primary user.
        // Section 1 is for friends.
        // Our userStore class takes care of the actual replacement 
        // and persistence.
        if (indexPath.section == 0){
          [self.userStore setUser:user];
        }else{
          [self.userStore replaceFriendAtIndex:indexPath.row withFriend:user];
        }
      }completed:^{
        // The signal completing tells us that we can remove the user name controller.
        [self.navigationController popViewControllerAnimated:YES];
      }];
  
      [self.navigationController pushViewController:userNameController animated:YES];
    }
  }

The child controller has a signal instead of a protocol method. We place what would be the protocol method’s contents in the signal subscription block.

What do I gain by doing it with RAC? Not much in this particular implementation. I can keep the response behavior localized in the controller definition, and expand it to its own method if it happens in multiple places. I also could have done some filtering on the sending or receiving side or sent an error signal.

To make this work on the TCSUserNameController.m side, I first created a public property for the userSignal. Then, I created a new RACSubject in the designated init method of the controller. Remember, an RACSubject is capable of sending next, complete, or error values whenever we want.

We’ll use a done button or textField’s return button as our confirm button.

(TCSUserNameViewController.m)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    #pragma mark - private
  
  - (void)doDone:(id)sender{
    self.loading = YES;
    [[[self.lastFMClient fetchUserForUserName:self.userNameField.text]
       deliverOn:[RACScheduler mainThreadScheduler]]
     subscribeNext:^(User *user) {
      [self.userSignal sendNext:user];
      [self.userSignal sendCompleted];
    } error:^(NSError *error) {
      [[[UIAlertView alloc] initWithTitle:@"Vinylogue" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
      self.loading = NO;
    } completed:^{
      self.loading = NO;
    }];
  }

I initially implemented this by simply returning whatever userName the user entered in the box without any checking. As an additional feature, I implemented a network call into this step that checks the userName with Last.fm before returning it. The side effect is that the name gets formatted as it was intended.

Release

We’re skipping a few steps at the end here (testing, analyzing, prepping for the App Store), but this post is long enough as it is.

Improvements

There are a few things I’d like to take a look at in future versions.

More stable threading

I’m doing some strange things with threading, including setting controller properties on background threads. Most of the time it seems to work out, but I need to take a step back and map out the flow of property setting and events.

Better cell image handling

I covered how I handle cell images above, but my method has some code smell and could probably use another look.

Testing

At this point in time, I haven’t explored unit testing or UI automation testing at all, and would like to give those a shot with this project.

V1.1

While the app was processing in the App Store, I worked on several improvement to V1.1. I added friend importing to the Scrobblers controller. I also created an album detail controller which changes background color based on the album image.

I’ll be waiting to hear back from users before implementing any other new features.

Final thoughts

Congrats if you made it this far. Although I didn’t exactly hit all my goals for this post, I didn’t think it’d be nearly this long either. I hope this post will add to the discussion about ReactiveCocoa. Ping me on twitter @twocentstudios if you have comments or questions. Or check out the Hacker News thread.

Simple AJAX Comments With Rails

I’ve been working on and off with Rails for a few years now, but when I started I had little HTML/CSS/JS knowledge from which to build. Most of my web experience I learned along the way in the context of Rails.

HTML and CSS were much easier to build familiarity with than JavaScript. I always found more time-tested best practices concerning HTML and CSS than I did with JS/AJAX. AJAX with Rails techniques seemed to have changed significantly between releases of Rails major (and even minor) versions.

I am by no means an expert, but my goal with this post is to walk beginners through a working technique for vanilla AJAX comments on resources based on Rails 3.2.x.

What we’re making

Our goal is to make a comment form that we can attach to any resource in our Rails app. It will look something like this:

Our goal
Our goal

The layout is pretty standard. A create form sits on top of a list of comments (newest first).

Our example resource throughout this post is an “Event”. We’ll only discuss it in terms of being an example generic resource with comments that belong to it.

Create

When a logged in user enters a comment and clicks “Create Comment”, the browser sends a message back to the server with the comment body, the resource name, and resource id. Once the server processes the message, it will send the comment body rendered in HTML in the same partial as the other comments were rendered with.

In the meantime, on the client side, we’ll be doing some basic jQuery effects to let the user know their comment is being processed. We’ll disable the textarea and submit button so they don’t accidentally submit the same comment twice.

Once the server returns our new HTML, we’ll reenable the form controls, clear the text from the textarea, and then add the new HTML to the top of the comment list.

To keep it simple for now, we won’t be handling the error cases in significant detail.

Processing order

  • route: GET /event/1
  • controller: events#show
  • view: events/show.html.haml
  • partial: comments/_form.html.haml
  • partial: comments/_comment.html.haml
  • user: add comment body and click create
  • js: comments.js.coffee -> ajax:beforeSend
  • route: POST /comments
  • controller: comments#create
  • partial: comments/_comment.html.haml
  • js: comments.js.coffee -> ajax:success

We’ll touch on each of these steps, but not necessarily in that order.

Delete

We’ll also allow users to delete comments (eventually only comments they’ve created!). When they click the ‘x’ next to the comment, we’ll prompt them with a standard confirmation. If they answer yes, we’ll then send the comment id to the server.

On the browser side, we’ll immediately dim the comment to half opacity to let the user know we’re trying to delete the comment. Once we receive a response indicating the comment has been removed from the database, we’ll then hide the comment in their browser the rest of the way.

There are a few error conditions we should handle here as well, but we won’t look at those in this post.

Processing order

  • route: GET /event/1
  • controller: events#show
  • view: events/show.html.haml
  • partial: comments/_form.html.haml
  • partial: comments/_comment.html.haml
  • user: click “x” next to comment
  • user: click “yes” to confirm
  • js: comments.js.coffee -> ajax:beforeSend
  • route: DELETE /comments/1
  • controller: comments#destroy
  • partial: comment.json
  • js: comments.js.coffee -> ajax:success

The first half is the same as we’ll see for comment creation, so we’ll focus on the last half mostly in that order.

Where to start?

First place to start is getting our backend comment system in place. We’ll be using the acts_as_commentable_with_threading gem (although we won’t be using the threading right away).

The instructions for setting this up are pretty simple. I’m just using ActiveRecord and SQLite right now.

  • Put the gem in your bundle gem acts_as_commentable_with_threading.
  • Run bundle install.
  • Run the migrations rails g acts_as_commentable_with_threading_migration.
  • Run rake db:migrate.
  • Add acts_as_commentable to the Event model class (and any other model you want to have comments).

      # event.rb
      class Event < ActiveRecord::Base
          acts_as_commentable
      end     
    

This post is supposed to be more about AJAX than Rails associations, but it’s worth mentioning that acts_as_commentable uses a polymorphic association. This means that any of your models can reference the same kind of comment model object, and we don’t have to have a separate table in our database for an EventComment or a VideoComment for example. Each comment record keeps track of what type of object its parent is, which will be important later since we need to know information about the parent in order to create a comment.

Routes

Next we’ll set up our routes just to get that out of the way. We’re going to let the CommentsController handle creation and deletion of comments, so the routes should point there.

    # routes.rb
    resources :comments, :only => [:create, :destroy]

This will give us two methods from the following urls (from rake routes).

    $ rake routes
    comments    POST        /comments(.:format)     comments#create
    comment     DELETE  /comments/:id(.:format)     comments#destroy

This is going to give us a commments_path helper and comment_path(:id) helper to complete our POST and DELETE requests, respectively. It will forward requests to those URLs to the CommentsController’s create and destroy methods. The create method has no parameters in the URL string. The destroy method takes the comment’s id as the single parameter of the URL string. Like we mentioned earlier, in order to create the comment, we’ll need a few more parameters. We’ll talk more about that when we get to the form.

Alternate implementation

Aside: An alternate implementation worth mentioning is to include comments as a nested resource beneath each resource that has them. It would look something like this:

    # routes.rb - alternate
    resources :events
        resources :comments, :only => [:create, :destroy]
    end

    resources :videos
        resources :comments, :only => [:create, :destroy]
    end

This works fine if your resources are all siblings. In my case, I have Video nested within Event already. It gets pretty hairy pretty quickly and gives you (unnecessarily) complicated routes and URLs. In this case, we’ll go with the other implementation that includes the necessary data about the comment’s parent in the HTTP POST data rather than the URL string.

Again, it works either way so always tailor your implementation based on your particular situation.

show.html.haml

Now that we’ve got the bridge between the view and the controller built (the route), we’ll tackle the show view template.

Our goal is to be able put the comment “block” (the add new form and the list of previously created comments) anywhere. In this example, we’ll stick it in the show view of the EventsController.

(Sidebar: I use Haml and simple form, sorry in advance to users of other templates. Hopefully you can still follow along.)

    / show.html.haml
    .event
        %h1= @event.name
    .comments
        %h2 Comments
        = render :partial => 'comments/form', :locals => { comment => @new_comment }
        = render :partial => 'comments/comment', :collection => @comments, :as => :comment

As you can see, our show template expects 3 different instance variables from the EventsController.

  • @event: the unique Event object we’re showing.
  • @new_comment: a new Comment object that acts as our framework for building out the comment form. It exists only in Rails for now and has not been created in the database yet.
  • @comments: an array of all or just a subset of the Comment objects that exist as children of the @event object (in reverse chronological order of course).

In our views/comments folder, we have the two partials _form.html.haml and _comment.html.haml. _form expects a local variable named comment as an input to help build the new comment form. comment.html.haml is our partial for displaying a single comment. It takes a collection of comments and tells the renderer to treat each object in the collection as a comment.

events#show

Before we dig into writing each partial, let’s step backwards in the chain of events and go back to our EventsController to set up those instances variables that the show template will be looking for.

    # events_controller.rb
    class EventsController < ApplicationController
      def show
        @event = Event.find(params[:id])
        @comments = @event.comment_threads.order('created_at desc')
        @new_comment = Comment.build_from(@event, current_user, "")
      end
    end

The first line of the show method should be par for the course. We’re pulling the event in question from the database based on the id provided in the URL. Rails automatically inserts a render 'show' for us at the end of the method.

The second line looks a little fishy. We’re using a helper method included in acts_as_commentable_with_threading to get the comments associated with the @event and order them by date. You might also want to do pagination at this step too, but with our nested event->comment architecture, it might also warrant an AJAX solution to load more (that’s a topic for another post).

The third line creates a placeholder comment object that acts as sort of a carrier for our parent object info. This new blank comment object will carry with it a reference to the parent @event and therefore its object type and id, and the current user. The build_from method is another helper created by acts_as_commentable_with_threading.

comments/_form.html.haml

Now we can continue on to our new comment form partial.

    # _form.html.haml
    .comment-form
      = simple_form_for comment, :remote => true do |f|
        = f.input :body, :input_html => { :rows => "2" }, :label => false
        = f.input :commentable_id, :as => :hidden, :value => comment.commentable_id
        = f.input :commentable_type, :as => :hidden, :value => comment.commentable_type
        = f.button :submit, :class => "btn btn-primary", :disable_with => "Submitting…"

Let’s step through line by line.

First, we’ll wrap the form with the comment-form class.

Next, we’re going to use simple form to create a form block for our comment. Adding :remote => true will provide the Rails magic to turn our standard form into an AJAX one. The form_for helper is smart enough in this case to pick the correct URL and HTTP method. We could specify it directly as:

    = simple_form_for comment, :url => comment_path, :method => 'post', :remote => true do |f|

The first input is the textarea for our comment body. Nothing special here, just limiting the rows to 2 and turning the label off.

The next two inputs are hidden from the user and will be included with the form submission to the server. We’re including the commentable_type or class name of the parent object and its id so that our CommentsController will know what object to link the new comment to.

Aside: I want to mention that since these hidden inputs are technically open to alteration, they must be properly sanitized by the server before being acted upon. By altering these values, the user could potentially create a new comment on a different object type and/or an object they aren’t allowed to see.

Our last form element is a submit button with Twitter Bootstrap classes for styling. Clicking this will trigger the AJAX action and submit our form data to the CommentsController for handling. The disable_with takes care of some of the JS we’d have to write by disabling the submit button.

I’m going to skip the JS for now and move onto the CommentsController implementation. We’ll get back to the JS in a moment.

CommentsController

If you recall earlier, we set up routes to our CommentsController for two methods: create and destroy. Let’s take a look at create.

    # comments_controller.rb
    class CommentsController < ApplicationController
      def create
        @comment_hash = params[:comment]
        @obj = @comment_hash[:commentable_type].constantize.find(@comment_hash[:commentable_id])
        # Not implemented: check to see whether the user has permission to create a comment on this object
        @comment = Comment.build_from(@obj, current_user, @comment_hash[:body])
        if @comment.save
          render :partial => "comments/comment", :locals => { :comment => @comment }, :layout => false, :status => :created
        else
          render :js => "alert('error saving comment');"
        end
      end
    end

The first thing we do is grab a reference to the form data. Our form data is in the params hash under the :comment symbol. We’ll store it as @comment_hash for use below.

Next we need to derive the parent object where the comment was created. Luckily, we included the commentable_type and commentable_id in our form data. @comment_hash[:commentable_type] will return the string "Event". We can’t call find on a string, so we have to turn it into a symbol that Ruby recognizes. We can use constantize to do this conversion (it would be a good idea at this point to check to make sure the commentable_type is legitimate). With a fully qualified Event class we can call the class method find and pass it the :commentable_id. Out pops our event object.

The next step is to determine whether the current_user has permission to create the comment on the object. This depends on your authentication system, but should definitely be included.

We now have references to all the objects we need in order to create the comment. We’ll use the build_from helper method again and give it the object, current_user, and the body of the comment.

We need to save the comment back to the database. If the save is successful, we’re going to do a few things.

  • Render the single comment partial with our new comment as the local variable. This will give the comment all the markup it needs to be inserted directly into the existing page.
  • :layout => false will tell the renderer not to include all the extra header and footer markup.
  • :status => :created returns the HTTP status code 201 as is proper.

If the save is not successful, we need to tell the user that there was a problem. I’m leaving this outside the scope of the post simply because there are several different ways of doing this depending on how you set up your layout. Above, all we’re doing is popping up an alert box to the user. You should consider this an incomplete implementation.

Aside: using Rails to render HTML is a technique opposite that of returning raw JSON and using client-side JS libraries to handle all things view related. You may want to look into something like Ember.js.

JavaScript for create

We’re finally back to the JavaScript, or more specifically, CoffeeScript. I’m not an expert in either, but for this stuff you don’t need to be one. I’m using CoffeeScript because it makes the code slightly cleaner.

The only CoffeeScript we’re going to write can sit comfortably in the asset pipeline in the comments.js.coffee file (more specifically, app/assets/javascripts).

    # comments.js.coffee
    jQuery ->
      # Create a comment
      $(".comment-form")
        .on "ajax:beforeSend", (evt, xhr, settings) ->
          $(this).find('textarea')
            .addClass('uneditable-input')
            .attr('disabled', 'disabled');
        .on "ajax:success", (evt, data, status, xhr) ->
          $(this).find('textarea')
            .removeClass('uneditable-input')
            .removeAttr('disabled', 'disabled')
            .val('');
          $(xhr.responseText).hide().insertAfter($(this)).show('slow')

What is code actually doing? We’re simply registering for callbacks on the AJAX requests that will originate from our comment form. When those events occur, we’re going to run functions.

$(.comment-form) targets the comment-form class we applied to the div that wraps our comment form partial. This allows us to actually use multiple comment forms on a single page if we want to.

.on is the jQuery function that binds an event to a function. It replaces the older jQuery functions .bind, .delegate, and .live. You can read about it here.

The first event we’re binding to is "ajax:beforeSend". When the user clicks the submit button, Rails will trigger this event, and our function will be called. The arguments passed to the function (and all the available callbacks) can be found on the jquery-ujs wiki.

The function that runs on this event is embedded as anonymous. We could call a function that exists elsewhere just as easily.

$(this) is the jQuery object version of the .comment-form div that was involved in the click. Alternatively, we could grab a reference to the form from $(evt.currentTarget). We’ll use $(this) to extract the textarea element in the next line. .find('textarea') will select textarea elements within the form. In our case, we only have one. We then chain two functions together to perform two operations on the textareas.

    $(this).find('textarea')
      .addClass('uneditable-input')
      .attr('disabled', 'disabled');

is equivalent to:

    $(this).find('textarea').addClass('uneditable-input');
    $(this).find('textarea').attr('disabled', 'disabled');

addClass adds the uneditable-input class to our textarea, which will perform some Bootstrap styling to our textarea, but not actually make it uneditable.

attr adds the disabled='disabled' element to our textarea actually disabling the user input.

We’re then chaining another .on for the ajax:success event that gets called if the AJAX call returns successfully. Our first move is to find the textarea and undo the temporary disabling (you may want to consider doing this at the ajax:complete event, because it should be done regardless of whether the AJAX was successful). You’ll notice we chained one additional function .val('') at the end. This will clear the textarea in anticipation of the user adding another comment. You wouldn’t want to do that in the error case, because the user should have an opportunity to resubmit the comment without having to retype it.

We’re finally ready to add the nicely formatted comment to the top of our comment feed.

  • $(xhr.responseText) gets a jQuery object version of the response HTML returned by the server.
  • .hide() disappears our new div so it can be animated in.
  • .insertAfter($(this)) places our new div after the comment form. If you want to put it somewhere more specific, you can replace the $(this) selector with a more specific selector.
  • .show('slow') animates our new div sliding down from the form.

_comment.html.haml / deletion

I skipped our single comment template, so I’ll add it here for completeness. This will lead us into the comment deletion section.

    # _comment.html.haml
    %div.comment{ :id => "comment-#{comment.id}" }
      %hr
      = link_to "×", comment_path(comment), :method => :delete, :remote => true, :confirm => "Are you sure you want to remove this comment?", :disable_with => "×", :class => 'close'
      %h4
        = comment.user.username
        %small= comment.updated_at
      %p= comment.body

Our wrapper div has a comment class, and a CSS id unique to each comment. We’re not actually going to use that id, but it could be useful in the future.

link_to should look familiar. Our display text is an x. The link will go to the delete path we created earlier in the Routes section. To refresh your memory, it will go to /comments/:id. :method => :delete tells Rails to use the DELETE HTML method.

:remote => true performs the Rails AJAX magic like we saw earlier with the creation form. :confirm pops up a JS alert to confirm the user wants to do remove the comment. :disable_with makes sure the user can’t try to delete the comment while the server is processing the first request. And the close class is Bootstrap styling.

Another reminder: you’ll probably want to conditionally display the delete link to the comment creator and admins. Draper is a good option for doing this cleanly.

The rest of the markup should be pretty straightforward.

Back to CommentsController

Time to add the destroy method to your CommentsController.

    # comments_controller.rb
    def destroy
      @comment = Comment.find(params[:id])
      if @comment.destroy
        render :json => @comment, :status => :ok
      else
        render :js => "alert('error deleting comment');"
      end
    end

@comment will track down the comment-to-be deleted from the database (check that user is allowed to delete it!).

Then try to destroy the comment. This time, when the call completes successfully, I’m sending raw json back to the client with an ok status. There are a myriad of options here. Use what’s best for your app.

And on error I’m copping out again and sending back JS.

Aside: if you want to do some informal testing, I recommend throwing a sleep 5 call before the if statement so you have more time to observe your AJAX.

JavaScript for destroy

Back to our comments.js.coffee file.

    jQuery ->
      # Create a comment
      # ...

      # Delete a comment
      $(document)
        .on "ajax:beforeSend", ".comment", ->
          $(this).fadeTo('fast', 0.5)
        .on "ajax:success", ".comment", ->
          $(this).hide('fast')
        .on "ajax:error", ".comment", ->
          $(this).fadeTo('fast', 1)

We’re going to use the other incarnation of .on for the reason I’ll explain in a moment. This time we’re calling .on on the whole DOM. We specify our event first as we did before, but now we’ll add the ".comment" selector as the second argument. Again, this applies to all of our comment divs with the comment class.

We’re not going to bother including the arguments to the ajax event callbacks (for example (evt, xhr, settings)); we don’t need them.

$(this) refers to the comment div that generated the event. We’re going fade the entire comment to half opacity before sending the request to the server by calling .fadeTo('fast', 0.5). On success, we’ll animate the comment fading the rest of the way out and disappearing to show the user the request was completed succesfully. On error, we’ll fade the comment back to full opacity to show that the comment still exists.

The reason we used $(document) this time instead of calling .on on the selector directly is because it will apply the callback to newly created DOM elements as well. For example, I can add a comment and then immediately delete it without refreshing the page.

Wrap up

This turned out to be quite the mega-post. I may have gone into too much detail, but I’m hoping this has enlightened any new Rails devs out there.

We didn’t actually write that much JavaScript, and most of it was simply for decoration. But this should give you the building blocks you need to add more interesting functionality on AJAX triggers. I highly recommend this jQuery reference/overview.

Discuss this on Hacker News.

Core Data + iCloud Is Kind of Ridiculous

I’ve spent most of the evening getting reacquainted with Core Data. My goal is to get up to speed enough to use it and iCloud for a simple app idea I want to prototype out. It’s turning out to be a little more daunting than I had hoped.

Past Core Data experience

On a previous app, I used Core Data to store values pulled from a server in a single table on the user’s device as a makeshift cache so that it could be used as a fallback if the network was not available. This was almost two years ago. I could cobble together enough code from intros and blog posts to set up my one-table schema, set up my persistent store, pull a managed object context, do some simple storing, and some very simple fetching based on a unique object field.

I was using it as more of a nice-to-have than a core feature of my app, so I never had to dig into it that much.

In my new app, I’m also planning on storing a single table of data. But this time, that data is the heart of my app. It’s also going to need to be sliced and diced in a number of different search contexts. And the biggest X-factor is that I want to be able to access and modify that data from different iOS devices. This is where iCloud comes in.

I almost used iCloud on my BrightBus application, but pulled out of that decision last minute because I needed to support older devices and didn’t want to have two different document models. In the case of BrightBus, I wanted to store a collection of favorite stops. This is a simple plist, so it would be using the simpler UIDocument APIs.

Commence learning

So now that I’ve decided that it makes sense to use Core Data and iCloud for this app, it’s time to get started.

First thing, I need to set up a new app ID specific to this app that has iCloud enabled. Okay great, but I have no idea what to name my app yet. Well I can just create a throwaway one and change it after I’ve got most of the app ready to go. Okay, but the ID will hang around forever in my account because you can’t delete or rename them. Okay whatever.

Now a new provisioning profile, okay that’s not that bad.

Let’s make a new project. Again, not sure what the name is, but whatever, no one will see it anyway.

Now I’ve got to set the entitlements. Alright I’ll use my app ID and reverse URL string. But oh wait, I used a dash in the app ID, but a space when I created the Xcode project. Ugh, let me go back and try again. Delete folder, create new project.

I have to decide whether I want people with iCloud disabled to be able to use my app. Uh, yeah probably, but oh wait that makes things much more complicated on my side? Uh… I’ll punt on that decision while I do some more research.

I have to decide what data isn’t important and can be regenerated and make a separate persistent store for that that won’t go to iCloud. Or I can use CoreData configurations to handle that in a single store. I’m thinking that in my case it’s all important so I don’t have to worry about that.

I don’t have to worry about seeding either, so that’s good.

But if I start digging into the actual details of setting this stuff up, I get constants like NSPersistantStoreUbiquitousContentNameKey and NSPersistentStoreDidImportUbiquitousContentChangesNotification that look scary, but maybe if I watch this WWDC video enough, the framework designers’ explanations will give me insight into what those words represent.

And then I stop for a second.

At this point I have to tally my pros and cons for using iCloud versus something like Parse, which I’ve done a little research into in the past but haven’t actually used it on a production app.

iCloud

  • pros
    • I don’t have to worry about managing users accounts
    • I don’t have to worry about storing user data on my servers
    • My app really is only storing individual data, but I can still post to Twitter and Facebook if need be
    • I can sync between iOS devices and Macs
  • cons
    • If people don’t have iCloud accounts, switch accounts, or delete my app’s data from their accounts it gets hairy
    • I have to set up all the weird provisioning and entitlements and app keys right
    • I have to deal with conflicts
    • I can’t ever run a normal web service or let users use this data from anything other than an iOS or OS X device
    • Have to worry about schema migrations if I decide to change stuff in the future = more/more difficult maintenance
    • Wow is this complicated (CoreDataController from WWDC sample is ~750 LOC and takes lots of shortcuts)

Parse

  • pros
    • I (kind of) don’t have to worry about managing user accounts
    • I don’t have to worry about storing user data on my servers
    • I have access to all data in case there’s a need
    • Users can get the data from a normal web service or any device I want
    • I can sync data
    • Schemaless = more flexible
    • Users can still use the service anonymously
  • cons
    • I still have to deal with merge conflicts
    • Fetch performance is probably much worse because I have to hit the network (although there’s a lot of caching so that could be ambiguous)

Neither (punt)

  • pros
    • I can focus more on user experience within the app
    • No worrying about syncing edge cases
    • Less boilerplate
    • I can validate the main app idea faster
    • I don’t have to deal with conflicts
  • cons
    • Users may immediately request app access from multiple devices
    • I may want to add out-of-app features that need the infrastructure
    • It will be more difficult to migrate over to a network model later (the foundation of my app might need to be rearchitected)
    • I’m missing out on an opportunity to become skilled in one of the above technologies

This is a tough call. I’m going to finish out these CoreData + iCloud videos and sleep on it.

All I know is that with Rails and ActiveRecord this is a completely different ballgame. Yes, at first it was just as opaque, but it just seems so much more flexible up against the Core Data APIs. Apple exposes so much complexity in their APIs sometimes, and you think that in the vast majority of cases it just doesn’t need to be that way.

Now I’m just ranting, so I’ll have to sleep on it like I said and circle back in the morning.

Deconstructing a chirp.io

UPDATE (10/13/12): The kind folks at chirp.io pointed me to their tech page. Read more at the end of the post.

TL;DR: I tried to figure out the chirp.io sound->URL protocol but failed.

I came across an interesting app today called chirp.io. From the chirp.io website:

Chirp is an incredible new way to share your stuff – using sound. Chirp sings information from one iPhone to another.

Just reading about it, I was very impressed. It’s not easy to encode a few hundred kilobytes of data (small jpeg) into a sound. But in the App Store blurb, it says:

Sharing requires a network connection.

Oh, so it’s actually just transmitting a link. Still pretty cool.

I downloaded the app and played a few of the example chirps. I noticed that they were relatively high pitched and seemed to be the same length. I also noticed they were monophonic - only one frequency was played at a time.

By tapping on a chirp, it shows what is basically a short URL for that resource. An example is chirp.io/gsm2h88c7u which links back to chirp.io/blog. You can also share images and text.

I did some similar DSP and frequency detection projects in college, so I decided to see if I could reverse engineer the protocol that chirp.io uses. I’m definitely no codebreaker or cryptographer, but we’ll see how far we can get.

Busting out the DAW

I usually use Magix Samplitude as my Digital Audio Workstation of choice, but since I was booted up on my OS X side, I decided to use cross-platform Reaper instead.

The first thing I needed to do was record the waveform. I could have direct connected into my sound card using a 3.5mm to 3.5mm jack, but I didn’t have one of those handy. I did have my Shure KSM27 set up, so I decided to record it through the air instead.

The first chirp I analyzed was chirp.io/gsm2h88c7u. If you notice, the short URL is only 10 characters long. We may be able assume that it uses lowercase characters a-z and 0-9.

The full waveform of a single chirp
The full waveform of a single chirp
One monophonic segment of the chirp
One monophonic segment of the chirp

If you count, there are 20 monophonic segments in the chirp. Each segment is around 88ms long.

Reaper has a pitch detector plug-in, so I looped each segment and estimated the frequencies. The pitch detector plug-in sometimes got confused though, so I had to double check with a normal FFT.

The fourth segment of the chirp was about 8981Hz
The fourth segment of the chirp was about 8981Hz

Looking at the data

I recorded the data for this first chirp:

chirp1 = [4717, 5300, 4453, 8981, 6324, 
            1976, 4717, 2797, 2797, 3522, 
            2640, 10000, 3737, 9400, 6660, 
            3965, 4189, 2220, 7131, 5613]

Those are the 20 frequencies in Hz in the order they’re played.

With only one data point so far, I decided to make an initial hypothesis:

  • Points 8 and 9 are the same (2979Hz) so maybe that divides the chirp into a metadata section and a URL section.
  • The unique URL part is 10 characters so maybe that’s sections 10-19 and 20 is the stop bit.

I can’t do much with only one data point, so I analyzed a second chirp. This one is a short text block with the URL chirp.io/mnac2dvevb.

chirp2 = [4717, 5300, 6324, 6660, 3143, 
            3522, 1976, 3737, 10844, 3965, 
            10844, 3329, 5000, 4717, 2797, 
            6660, 4189, 2098, 3965, 2220]

Hmm… not as much correlation as I expected. Let’s look at them side by side.

1   4717    4717
2   5300    5300
3   4453    6324
4   8981    6660
5   6324    3143
6   1976    3522
7   4717    1976
8   2797    3737
9   2797    10844
10  3522    3965
11  2640    10844
12  10000   3329
13  3737    5000
14  9400    4717
15  6660    2797
16  3965    6660
17  4189    4189
18  2220    2098
19  7131    3965
20  5613    2220

The only thing that stands out at first glance is segments 1 and 2 are the same. That would make sense as our start code.

Let’s combine these two sets, sort them, then discard the duplicates.

uniq_freqs = [1976, 2098, 2220, 2640, 2797, 
                3143, 3329, 3522, 3737, 3965, 
                4189, 4453, 4717, 5000, 5300, 
                5613, 6324, 6660, 7131, 8981, 
                9400, 10000, 10844]

Between the two chirps, there are 23 unique frequencies. So frequencies are shared quite a bit.

Now let’s subtract the neighbors to try to figure out how many we’re missing.

diff_freqs = [122, 122, 420, 157, 346, 
                186, 193, 215, 228, 224, 
                264, 264, 283, 300, 313, 
                711, 336, 471, 1850, 419, 
                600, 844]

I’d guess that we’re missing a couple from the low range, but a few more in the higher range. I expect the differences to increase as the we get higher up the scale, but that really depends on the frequency detection algorithm being used by the app.

It seems like we’re kind of stuck. My initial hypothesis is mostly wrong. It doesn’t look like frequencies map directly to letters. Let’s do one more chirp before we give up.

Flower Picture: chirp.io/9gf6q9ltu3

chirp3 = [4717, 5300, 2963, 4453, 4189, 
            2490, 7922, 2963, 5945, 9400, 
            10000, 2098, 7922, 5945, 7521, 
            3965, 8981, 5000, 4717, 2098] 

1   4717    4717    4717
2   5300    5300    5300
3   4453    6324    2963
4   8981    6660    4453
5   6324    3143    4189
6   1976    3522    2490
7   4717    1976    7922
8   2797    3737    2963
9   2797    10844   5945
10  3522    3965    9400
11  2640    10844   10000
12  10000   3329    2098 
13  3737    5000    7922
14  9400    4717    5945
15  6660    2797    7521
16  3965    6660    3965
17  4189    4189    8981
18  2220    2098    5000
19  7131    3965    4717
20  5613    2220    2098

uniq_freqs = [1976, 2098, 2220, 2490, 2640, 
                2797, 2963, 3143, 3329, 3522, 
                3737, 3965, 4189, 4453, 4717, 
                5000, 5300, 5613, 5945, 6324, 
                6660, 7131, 7521, 7922, 8981, 
                9400, 10000, 10844]

diff_freqs = [122, 122, 270, 150, 157, 
                166, 180, 186, 193, 215, 
                228, 224, 264, 264, 283, 
                300, 313, 332, 379, 336, 
                471, 390, 401, 1059, 419, 
                600, 844]

We’re now up to 28 unique frequencies. I’m not sure if there’s enough frequency space left to suggest they’re using a 36 character alphabet.

Unfortunately, it doesn’t look like we can deduce anything new from our third set of data. The two segment start code is the same. But other than that, there doesn’t seem to be any correlations I can tease out.

Analysis

My assumption that the unique URL component was related one to one with the frequencies was wrong. It’s looking more and more like there’s some sort of hashing combined with error detection/correction.

It looks like I’ve failed to deduce the protocol, but it’s interesting to see how chirp.io uses the frequency space.

I can’t find the specs of the iPhone internal speaker and mic, so I don’t know what the hard limits are for frequency response. But small speakers are bad at reproducing low frequencies so it makes sense that they’re not going lower than 1000Hz.

The upper limit is a little more difficult to determine. It still has to do with the limit of the speaker and mic, but at a certain point, those higher frequencies may start to get a little annoying, even if the duration is short. At a certain point, due to the limitations of human hearing, the higher tones wouldn’t be audible enough even if they were annoying. Chirps are supposed to sound like a continuous stream of notes, and therefore even if the mic could deduce the correct frequency, it would lose some of the value.

Another one of my initial assumptions was that the amplitude of each frequency segment was not relevant. From the waveform, it looked like all segments were not of equal amplitude, but that may have been due to micing the iPhone speaker, which basically puts another 3 filters on the signal (speaker response, air, and microphone response).

The App Store description also mentions that chirping works in noisy environments, so I’m going to stick with my assumption that even relative amplitudes aren’t used.

Looking at pitch detection algorithms, there are three choices: Time domain, frequency domain, or both.

A simple time domain algorithm like period detection through zero-crossing would not work in a noisy environment, especially for higher frequencies. Autocorrelation is possible especially since we are only looking for a single frequency. Frequency domain methods are also likely because the spacing between frequencies can be chosen and there are no harmonics to worry about.

The iPhone CPUs are powerful enough now to use almost any of the popular pitch tracking algorithms and libraries, so performance shouldn’t be a limiting factor.

Conclusion

I’m looking forward to trying out the chirp.io app with some friends to see how well it performs. It’s definitely a cool idea, and I’m interested to see if it picks up steam.

If you happened to have some insights about my data than I didn’t, it’d be great to hear about it: @twocentstudios.

Update

If I would have read the FAQ on chirp.io more carefully, I would have seen their post about the technology behind chirp.io.

I was almost there…

Let’s see where we went wrong.

  • 20 pure tones - got that one.
  • 87.2ms each - I estimated 88ms.
  • 2 tone start code - got that.
  • 32 character alphabet - I first guessed 36, but then revised to saying it probably wasn’t more than 30.
  • [0-9, a-v] characters - I assumed they’d use up through ‘z’, and that it would start with letters and end with numbers.
  • [startcode][shortcode][error-correction] - I’m not sure why I didn’t think the shortcode would be at the front.
  • Pitch detection algorithm - nothing specific is mentioned yet, although the site says they’ll be publishing more on the topic soon.
  • Error-correction with Reed-Solomon - I don’t have enough experience with error correction algorithms that I could have made a prediction on this one. But my lack of understanding did cause me to overestimate how good the pitch detection algorithm needs to be to recover the signal.

    Error correction means that Chirp transmissions are resilient to noise. A code can be reconstituted when over 25% of it is missing or misheard.

Overall, it was a fun exercise and taught (or re-taught) me a little bit about DSP, coding & protocols, and I even got to play around with some Ruby.

I highly recommend downloading chirp.io if you’ve got an iOS device.

TDD, RSpec & Conway’s Game of Life

On my continuing quest to do more micro-projects, I decided to begin focusing on TDD today. After doing a little RSpec Googling, I came across this live coding video for doing TDD with RSpec on Conway’s Game of Life, something I hadn’t heard of before.

After watching about half the video, I realized I was far enough along that I could try it myself. So I took a stab at it doing TDD the whole way. It was actually pretty fun doing a simple OO Ruby project and getting my feet wet with RSpec. I’ve always had this ugly feeling of repeating myself with tests, which is one reason I never could bring myself to write tests upfront for my little Rails projects.

It turns out (as everyone knows) that testing actually saves the repetition that comes with hand-testing everything in irb. If I had a nickel for every time I sat there banging away at the keyboard constructing objects and relationships in the Rails console just to do a reload and wipe everything out, I’d be rich (and a cliche).

In any case, it was apparent very quickly that I was really saving myself a lot of headaches by writing tests. The feedback loop was much quicker writing tests first. Fixing bugs and not having to re-establish all of your test setup by hand each time makes it much easier to focus on the bug at hand. And this was just for a three-class micro-project. I can only imagine how much headaches testing saves in large scale Rails projects.

RSpec was actually a lot more intuitive than I expected. It helped that I found an old cheat sheet to kind of guide my thinking of how my should statements should be structured. I tried to keep all the best practices in mind while writing my tests, but I’m sure I missed quite a bit on my first run. Looking back, I probably should have had a few more edge case tests especially on the 4 big rules. I’m still getting used to describe, context, let, before, and all the other structural stuff. It doesn’t help that, from what I’ve seen, there seems to be a personal style to writing tests that varies. It varies in both test structure and amount of testing.

I also forced myself to get a primer on Ruby Debug (using the debugger gem). It’s a little different than I’m used to with the full IDE and the goodies that Xcode offers, but I’m sure it’ll be another useful tool in the toolbox.

I wrote a Cell class and a World class in what I think was good OO. Then I wrote a Display class that handles printing the world to the terminal (and even a little animation). I didn’t write tests for that though, it was kind of a just-for-kicks deal.

Sidebar: this actually reminds me a lot of the big project we did in my C++ class Sophomore year of high school. We spent what seemed like several months writing all these classes to simulate fish. I can’t remember exactly what the rules were, but I remember it being a bit tough to wrap my mind around. I don’t think I really understood the difference between UI code and game logic back then. I didn’t get that STDOUT was all we really needed for the concepts we were learning. I kept wanting to understand the GUI classes when all that was in the curriculum were the comp sci basics.

I’m not sure if my code is worth posting to github. I’ll keep it off for now and try to leave room for the more important/interesting projects I’ve got planned.

Summer 2012 Project Wrap Up

A lot has happened since my last quarterly project wrap-up. I’ve done a lot of little projects here and there, abandoned a few, and dreamed up a bunch more that I can’t wait to start on. Here’s a summary of the smaller things that might not have gotten their own posts over the last couple months.

To-be-titled Music App

Early in the summer, I decided to start seriously working on the music app I’ve been talking about since last Winter. It went through several iterations of what format it should be and what the scope of it should be. I coded up a quick Rails app to see the basic concept in a couple days, but left it untouched for a while because at the time it didn’t seem like the browser was the right primary format for it.

After spending a few more weeks rethinking the purpose of the app, it seemed possible to strip the app down to its core and do an iPad/iPhone only release. Initially everything was going to be local to the user’s device. Then, of course, the scope started to expand, and eventually I found myself and what I considered to be a happy medium of still having a core iOS UI, hosting backend data with Parse, and leveraging existing social media sharing.

In prep for the iOS app, I circled back to a new iOS library from Github called Reactive Cocoa. I spent a number of days just wrapping my head around the concept and example code, and finally felt confident enough to start diving in myself.

I spent a few consecutive weekends with my Graphic Designer friend CJ doing wire framing, prioritizing features, learning some more about the Parse framework, and starting on some prototypes of the backend. We even spent a full day going through vocabulary related to our concept to come up with a good name and good terminology within the app to best describe what the user would be doing.

At a certain point though, a few things converged and the project sort of sputtered out. CJ and I seemed to have more divergent ideas about what the core purpose of the app was, which was sort of a wakeup call that maybe it would be best to do more of that up-front work of surveying users and seeing if we actually have any interest in what the concept is.

And so the project is currently on hold. I don’t plan on giving up on it. The plan now is to build a few smaller projects before starting up again in order to be able to leverage that experience. Rails has proven to be a much quicker platform to prototype on, and thus my first steps will be to build out a shell that I can start soliciting user feedback from.

TestPlanIt

One of the first big Rails undertakings I started almost a year ago was an app targeted at a specific data management task I wanted to solve for a friend of mine at my day job. He is a Test Engineer, which basically means receiving physical samples and a test plan, performing the test, acquiring data, writing a test report, and distributing it.

My initial cobbled together solution to this problem involved a SharePoint list with a few basic bells and whistles. I always imagined a much better system with full database representation of all the elements he works with, and thus started wire framing a Rails app that could accomplish this on my nights and weekends.

It turned out that my vision was a little bit too complex to accomplish with my early Rails knowledge. A lot of it had to do with my Javascript deficiencies. And after a ton of work, eventually the project sort of died under the weight of its own complexity.

Fast-forward now to early summer. My Test Engineer friend brought up his data organization and workflow problems, and I started to think about the problem again. I revised my initial data model to reduce the complexity in a way that didn’t decrease functionality much, and basically started over again. I had dabbled in a few other projects since then and my familiarity with Rails-related technologies had given me enough skill to be able to power through the roadblocks that had hindered me the last time around. I reused some of my haml and some model code which saved time with the tedious aspects.

In what I think was less than a few weeks, I had already gotten the core of the app working well beyond what I used to have. I was already adding embellishments (PDF export support, amongst others), but realized that there was still a great deal of work left to do making the app enterprise-ready (roles & permissions, deployment). It was at this point that we started working on implementing our new Product Lifecycle/Data Management application (see this post). I realized that my app would probably never be used in production, and therefore stopped actively working on it. I took it as a learning experience and used that knowledge to build more apps like Flexible Parts.

myGengo API Wrapper

I wrote about this in detail, but the overall summary is that I missed Objective-C and wanted to do a little weekend project.

Looking back, I don’t think I did myself as much of a service as I should have with this project. I took shortcuts that I shouldn’t have because deep down I knew that I probably wouldn’t be using what I had written at all. Kind of another learning experience in choosing personal/open-source projects.

EngSurvey

My Test Engineer friend from work came to me with another request. He also needed an automatic survey system for his test reports. For ISO certification, you need to have some sort of feedback system from your clients (the Engineers that need their samples tested) about your reports.

I found an almost framework-scale gem called Surveyor which got me almost 80% of the way there. The last 20% is always the hardest. My friend wasn’t great at giving me requirements, I saw a lot of headaches ahead in deploying it internally, and the project wasn’t high on either of our priority lists, so this project died out as well.

Codecademy

I had read a lot about Codecademy since their New Years push. A non-programming friend of mine had been doing the full course for a few months, so I decided to check out the jQuery course. I finished about half the lessons which helped ready me for the AJAX part of my next few projects.

HaveRead

I got a request from another co-worker to find a solution to a problem she had. Her one requirement was that she needed a way to send off a document to a dozen or so people and simply collect responses of when they had read the document. I explained the voting feature of Outlook mail, but she didn’t seem to think that would be simple enough.

She needed it urgently of course. I threw together a Rails project to do this in about two half-days of work (naming them is always the hardest part). In the process I learned a little more simple AJAX and using the CarrierWave gem, which would help me in an adjacent project I was working on.

I showed her what I had, and although she was impressed, she said the limitation I have of running it off my laptop wouldn’t work out in the short term. But that she would consider using it in the future.

I wasn’t too bothered because it was a quick project and I learned a lot doing it. It was getting a little tiresome though that deployment was becoming a problem. Heroku or other cloud services weren’t an option because they were outside the company firewall. And getting my own VM in the company was blocked by too much red tape since I wasn’t part of the IT department. For the time being, the only way to deploy at work was to run the dev server on my machine and send links to my (non-static) IP, which is the technique I used in my next project.

SolidWorks Model Challenge Friday

Part of my (new) day job was administrating SolidWorks for our couple dozen Mechanical Engineers. We have a bunch of younger Engineers that still love a good modeling challenge, and from a seed of an idea sprang “Model Challenge Friday”. We’d spend half an hour on Friday morning designing/modeling various objects. The goal was speed and creativity, and to give the Engineers a sandbox to experiment with new techniques that they might not be comfortable with doing on real mission-critical projects.

In a lot of ways, it was inspired by the kind of projects I mentioned above. Stuff I never intended to launch and support, but instead used to experiment with new gems or techniques without having to get bogged down in details irrelevant to that goal.

I tried to participate in the first two weeks, but realized quickly that I could barely model a cylinder in an hour. I spent the next two weeks giving myself a crash course in 3D modeling with some help from my fellow Engineers. Before long, I was modeling up my own chair designs and actually participating in the contests. After a few more Fridays, I was to the point where other judges couldn’t tell which design was mine (because they used to be so simplistic and bad).

The judging was initially done by passing some unmarked printouts around the office area for an informal poll of the fan favorite design. This process was just begging to be web-appified though.

The next few weeks I spent several nights and weekend days working on my next Rails app to facilitate this process.

My design goals were the following. * Engineers would have user accounts. * Registered users could submit screenshots of their designs each week to the contest. * Voters would be able to vote without logging in, but they should only be able to vote once per contest. * Entries would not show the creator’s name until the contest was over. * When the contest was declared over, the votes would be automatically tallied and a winner declared.

This required a good combination of normal CRUD layouts, AJAX for voting, CarrierWave for image uploads, Bootstrap for a basic design, cookies to store voting status, and of course running my ad-hoc “production server” on my laptop.

After informally launching one Friday as sort of test, things went over really well. The system worked almost flawlessly (except for a few unscrupulous Engineers voting for themselves by not logging in). I realized quickly that I had compromised my dev server by using it to store real production data and thus had a bit of a problem on my hands working on improvements without breaking stuff.

We used my new creation for a few weeks. It worked well. I made some improvements to the entry browsing using the Bootstrap js components and digging around for other good js plugins. Eventually though, the contest itself died out when suddenly we all seemed to get too busy for that half hour.

AppleCart

I’ve talked about AppleCart in detail here, but the long and short of it is that the app has been a big success so far in raising money for the American Cancer Society and keeping things organized in the drive.

What’s Next?

I’ve started transitioning out of my day job role and into a more freelance/consulting lifestyle. I plan on doing this for a little while to increase my skill level and be able to tackle more theoretical and practical programming problems faster than I would by doing so only on nights and weekends. Time and energy have always been an issue, and especially with a lengthy commute, it just makes more sense to go all-in and laser focus on what I really want to do for the rest of my life.

Some of the programming related things I’m planning on doing with my time this Fall: * Tackle a bunch of small projects I’ve wanted to explore on iOS and Rails. * Really dig into RSpec and TDD. * Finish some programming books I’ve started, and few more I haven’t opened yet. * Start contributing to an open source project. * Immerse myself in the code bases of a few well-known projects to learn more about architecture and best-practices. * Begin learning a functional programming language. * Keep exploring the elements of traditional graphic design. * Do a project with a NoSQL database. * Go to a start-up event in Chicago. * Watch at least one conference talk a week on YouTube. * Organize my browser bookmarks.

I don’t know where the priorities will end up being for each of those, but here’s to a healthy and productive Fall.

AppleCart: My First Production Rails App

I pushed my first production Rails app to Heroku this week. Feels exciting to have a real project launch to solve a real problem after having started working with Rails such a long time ago.

What is AppleCart?

A friend of mine, Jen, has been very involved in the Making Strides Against Breast Cancer organization. There is a yearly walk that involves raising money for the cause. The last few years, she has resold gourmet candy apples from another friend who is a chocolatier as one of her fundraising efforts.

Jen came to me a few weeks before fundraising was to begin asking if I had a way to keep track of her apple orders. Orders involve someone looking over a list of available apples, choosing the apples they want and quantities, paying in cash, and then waiting while Jen orders the apples and distributes them a week or so later.

I initially recommended a free online store such as Shopify or Storenvy, but she said she did not want to deal with credit cards or any of the fees involved. It would also make it more difficult for people that just wanted to pay in cash.

I thought about the requirements a little more and realized it was probably perfect for a simple two screen Rails app. (Plus, you’re not a real developer until you’ve written a shopping cart app…) I promised her I’d get started on it right away and have it done in two weeks.

Screenshots

Here are some screenshots of the final product.

Homepage
Homepage
Apple selection page
Apple selection page
Cart page
Cart page
Admin dashboard
Admin dashboard
Admin all orders page for tracking order and payment status
Admin all orders page for tracking order and payment status

Planning

The first step was diagramming out the workflow. The workflow for a customer is pretty simple for this app.

  • Customer creates an account.
  • Customer chooses apples from a single browsing page.
  • Customer edits quantities in cart.
  • Customer submits order, confirming they will be purchasing those apples.
  • Jen collects money from all customers and marks them off as paid.
  • Jen orders apples.
  • Jen receives apples.
  • Jen delivers apples to each customer, marking them off as they are delivered.

The object diagram is actually pretty simple too. The main objects are:

  • Users (of course)
  • Orders (collections of apples chosen by the customers)
  • Items (in our case, they’re all apples)
  • OrderItems (a line item in an specific order)

I won’t go into all the details (you can look at my schema.rb), but some relevant things:

  • A user always has one order (it is auto generated by the user model if does not exist when requested).
  • An item (apple) has a price and cost, so that apple specific totals can be shown.
  • An order and item are joined by an OrderItem table which includes a quantity.
  • An order has three possible states: open, processing, or delivered. An open order is one that has not been “confirmed” by the customer, and therefore is not counted in most totals shown to the admin. An order in “processing” state is submitted, but hasn’t been delivered yet. An delivered order has been received by the customer.
  • An order also has a boolean “paid?” flag that the admin will set to true when they have received payment for that order. This could have arguably been rolled into the state machine after “processing”, but to handle any edge cases I decided to make it separate. It also makes it easier to convey to the customer what each state means. (Now that I’m thinking about it even further, it might have been good to change the state machine to open->confirmed->paid->processing->delivered.)
  • The app should stop accepting new orders when the admin wants, so there needs to be a global flag for whether sale is enabled.
  • A user should only be able to see their own order, but an admin can see/edit anyone’s order.
  • A user should be able to check the status of their order even after sale is closed.
  • An admin needs to see aggregate data including how many of each apple needs to be bought from the supplier, how many orders are in each state, and how much money is being processed.

There are other rules, but that’s a good sampling. Those are starting to look like some combination of testing and user stories, but unfortunately, I did neither formally for this project. One of my next big goals is TDD, and this would have been a great project for it, but the time constraint excuse got me again.

Interface

I decided on each interface problem one at a time.

The first decision was whether I wanted anonymous users (not logged in visitors) to be able to view the apples. In retrospect, this would have probably been better for the UX because it would have given potential customers the opportunity to view the selection before committing the time to create a user account. I went with a forced account creation though because I was worried it would have taken me too long to figure out how the additional logic would work.

Going with that decision for now, I had to decide whether the store section (viewing pictures of apples) and the shopping cart would be one view or two views. One of my requirements was that we have decent sized pictures of the apples, which doesn’t lend itself well to a nice tabular view like most people are used to seeing with shopping carts. The only compromise would have been to embed the cart view in the sidebar of the store view. This would have been nice, but I think it would have been necessary to have a full page cart view anyway, so it would just be a nice-to-have.

Once I decided on two views, it was time to decide how customers would decide on which apples they wanted (lots of decisions). I could make a view for each individual apple, but that seemed unnecessary for the small amount of information I had on each apple. It would also slow down the process of selecting apples.

I decided to put an “add to cart” button next to each apple. I was briefly thinking about having a quantity box next to each as well, but it seemed simpler to have the button be just an AJAX callback to increment the amount of that apple by one. That way, there wouldn’t have to be multiple form submission buttons, or a confusing combination of cart aesthetics. I’m not quite sure how this is going to work out, as I haven’t gotten to do any user testing yet, but I’m looking forward to see how (un)intuitive it is for regular users.

The AJAX response tells the user how many of that apple they now have in their cart, and updates on each successive click. Once they’ve scrolled through the seven apples, they’re met with a big question at the bottom of the page “Done shopping? View your cart” which guides customers to the next step.

In the cart view, there is a familiar tabular list (with no thumbnails) with quantities, prices, and line prices. Because the store page wasn’t very flexible at specifying quantities, the customer can edit quantities at this step. I sketched out a few ways to implement editing, including inline editing, transforming the show into edit view on a button press, or just using a separate view for it. I chose the last option both for simplicity, and because inline editing may have been unintuitive and required more explanation than a simple form. The middle option may have been the best in retrospect.

The final user action is confirming their order and moving it to the next step. Once this is done, they can track their order by logging in and viewing their cart at any time. I get the luxury of not keeping multiple carts because of the nature of this project, so I took advantage of the simplicity.

Building

So I started building. I started things off with drone.bz again which helped knock off the basic gems I needed.

I pulled a few gems from other recent projects I had, including using Thin for my development server and Quiet Assets. Haml is a staple now. Nested_form and simple_form for the little bit of form work I needed to do. Easy Roles simply because I needed that Admin identifier and wanted something a little more robust than a boolean just in case.

I decided to try out migrant again, which in some ways saved time, but probably ended up hindering more than helping. Devise and high_voltage. And bootstrap has been a huge timesaver and taught me a bunch more about SASS and CSS. I thought I’d use Dragonfly for image uploading, but since I didn’t need dynamically added images (and didn’t want to set up S3 later in the project), I decided to go with just simple asset pipeline links.

The final bit was stateflow as my state machine helper. I’d like to try out something different next time, although it did do the job.

With most of that in place, I did my models and migrations (a little backwards because of migrant), then controllers and views, controllers and views. Dropped back to models to add methods. Tweaked things here and there. Nothing to see here folks.

But seriously, I learned some great new stuff this go around. I dug a little deeper into rails view helpers, which I had never used before. They helped a little to clean up some of the tricky view logic I had. Some views I wrote toward the end I must admit I started slipping and using direct model accessing in the view (bad!). But overall, my code was a lot cleaner than in past projects where I was focusing more on getting it working than cracking down on technical debt.

The other big victory for me was doing a lot more AJAX. I feel like I’m finally getting a handle for how to make javascript do what I want. Naming methods is still difficult. My biggest weakness is understanding how to structure these calls, where the code should be placed, and where the response text should originate from. I was consistent in my inconsistency of trying a few different options on this project to see what flowed the best. One of my other big goals is to find and read more good code so that I can learn to create good structure and organization in my projects.

I wrestled with Twitter Bootstrap bugs at times, and I still am awful at design, but the upside was that I did my first bootstrap skinning and some more customization than usual. I dug a little more into SASS, although I’m still a bit confused about import order and had to throw in some ugly hacks in order to get the customization I needed.

I had the core built on my first Saturday working on the project, and then started the skinning and admin page stuff the next day. The next weekend I worked on more admin functionality, cleaned up the design a bit and added some final copy.

Deployment

I was a little nervous that I hadn’t done any deployment before and really needed everything to go smoothly with his project. It took a little digging and a little poking around, but I did indeed get my first heroku instance up and running. I made myself a little cheat sheet of all the new command line stuff I needed to remember and worked with Postgres for the first time.

I delt with my first 500 server errors and tailed my first heroku logs. It was exciting to see something I built up and available for all to see. I pushed several times over the course of building, and then for the last time (hopefully) last night.

Wrap up

I’m looking forward to seeing the reception to the site. I’m hoping people will find it relatively usable and get some constructive feedback from real users as to where my assumptions were held true and where they fell flat.

Objective-C API Wrapper for myGengo

Last weekend, I decided to take on what I thought would be a small task and turned into be a moderately small task. I wrote an API wrapper for the myGengo translation web service.

MyGengo uses a worldwide base of qualified translators to provide quick turnaround times for translation jobs. One of their selling points is a well documented API that developers can use to create automated upload flows for their companies.

They have official API wrappers for C#, Java, Perl, PHP, Python, and Ruby. After I was completely finished writing my Objective-C wrapper, I realized they had a lot more unofficial APIs in even more various languages (including Objective-C, but I’ll get to that in a bit).

Use Cases

I decided to take on an Objective-C wrapper for a couple reasons. Although the authentication is a little tricky, I still think it would be cool to have an iOS interface for their platform. One of the upsides to this would be being able to check translation statuses on the go and respond with comments. I’m not sure if anyone would be directly submitting text to translate from an iPhone (slightly more likely with an iPad). But a native OS X app might be useful, especially in situations like I found at my day job.

Instruction Manuals

I used to write a lot of instruction manuals for electronics at my day job. The standard way our marketing department would handle these would be:

  • The technical writer would write the copy in a Word document.
  • The designer would copy all that into an Adobe InDesign document.
  • The designer would create the layout.
  • The designer would then copy the text back out into another Word document with a table row for each sentence and a column for each language.
  • The Word doc would be emailed to a translator.
  • Two to four days would pass.
  • The translator would send back the Word document with the other languages filled in.
  • The designer would copy the English pages of the InDesign document and copy and paste the text from the Word document into InDesign.
  • The InDesign document would be routed for proofing and checked against the translation Word document.

It was a very arduous process as you can see, and resulted in a lot of errors. Being the impromptu technical writer with a little design in my blood, I decided to take on the task of learning InDesign well enough that I could write the copy for the manuals and do the basic layout directly in InDesign. This saved one step and a lot of back and forth with the designers trying to explain what needed to be done with some of the layout.

However, it did not solve the problem of having to copy text in and out of the final design document. This is where something like myGengo would have been cool. You could implement a way to read the raw text out of the InDesign document, then somehow provide the translation back into the template, all in an automated workflow. The designer could then focus on cleaning up the design and doing what they do best instead of all the manual copying and pasting that inevitably leads to errors.

I’m not sure if I’m ready to take on the task of automating this process with something like myGengo. Our company doesn’t do enough manuals to justify the time, first of all. It would take a lot of effort to learn the InDesign file format well enough to parse it and insert text back into it. And finally, I’m not sure the old guard would get behind using someone other than their trusted translator to do the actual translation work.

Implementation

That was a bit of a tangent, so lets talk more about the implementation.

Because I know Ruby fairly well, I decided to model my wrapper closely off of the Ruby wrapper. I followed the structure closely (maybe a little too closely), but this helped get me started quickly, and I learned the layout of the API along the way.

Overall Structure

I refactored the API into two parts about half way into my coding. The first class holds the authentication information in a singleton. The second class provides instances of the actual API handler that can be used by multiple view controllers.

The logic is that you can initialize the credentials singleton class once in your app delegate based on keys stored in the iOS keychain. Then, each view controller can have an instance of the handler itself that uses those shared credentials.

Handler Structure

The handler class has a couple helper functions that deal with the private key hashing and UNIX timestamps. I had to do a little research, but luckily the CommonCrypto library and Stack Overflow provided what I needed. Most of my previous work was with read-only APIs, so I never had to deal with authentication before. I got a good primer in how secure hashing of parameters works.

The handler has two functions whose job is to assemble the parameters and send out the request to the server. One is for GET and DELETE requests. The other is for POST and PUT requests.

Over time, these two functions became more and more similar, but I never did end up combining them like I should have. They share quite a bit of code, but they’re just different enough that I’m a little hesitant to refactor without running the full gamut of manual testing (which I don’t quite have the time for right now). This is the same way the Ruby library was laid out.

The handler then has a function for each API endpoint. I made another decision to make all the functions similar in that almost all of them ask for a single NSDictionary of parameters as their input. I was very back and forth on this, because it’s very un-cocoa. But after looking over all the functions, there are enough outlier functions that would require a primary NSDictionary anyway that there were two reasons for keeping this structure: consistency and future operability.

If the API user has to look up the exact parameters from the API for function X, they might as well just have the docs open for function Y even if it just asks for an :id parameter. Future operability comes into play since I don’t work with the API enough to keep track of any changes that might occur in future versions. Since I’m less rigid in the function structure, I can handle extra parameters without having to change any of the code. All the user has to do is change the API version DEFINE and assemble the params dictionary differently. Still, it was a tough decision, and it was mostly a time and attention decision.

Other Library

I mentioned earlier that I found out there was another Objective-C API wrapper already on github. I wrote my whole implementation without knowing about this, even though it should have been my first instinct to check github. In retrospect, I’m glad I did mine without having any outside influence. The author of the other library did things a little bit differently than I did, and I think both of our implementations have their places.

Wrap Up

Overall, this was a good exercise for me. This is my first real authored open source project. Going in with a goal of open sourcing this kept me honest in my documentation and helped me cut less corners than I would have if it was just a means to an end of getting another app done. Plus, it forced me to learn some more about git and github, and authentication, and the ASIHTTPRequest library, and JSONKit, and API design in itself. I tried to follow some of Matt Gemmell’s advice in (API Design)[http://mattgemmell.com/2012/05/24/api-design/], and did my best to think about the design from an outside-in perspective.

I’d still like to tie up those loose ends with formatting, refactoring, and even stuff like ARC and OS X support. I was also planning on doing an example project. Hopefully my README and test fixtures are clear enough for end users to follow.

I’m not sure where I’m going to go with the library in the future. But I hope someone finds it useful.

You can view this project on github here.

Flexible Parts: A Part Attributing Prototype Project

The company I work for recently started our implementation of Oracle’s Agile PDM/PLM system.

A PLM (product lifecycle management) system is a (usually) software application that guides an organization through the lifecycle of a product. This includes all aspects of design, production, and continuous management of a product. PDM (product data management) concerns the aspects of storing and managing product data, especially changes over time.

Finding Limitations

Over the past few weeks of implementation, we’ve already run into several limitations of Agile. One of them is very low granularity of item attributes.

Agile has two object classes: part and document. Each of these has its own set of attributes applied to all its instances. For example, everything that is a part has a weight attribute and individual dimension attributes. Any custom attributes you add at the “part” level will be applied to anything under the umbrella of “part”.

Parts also can have subclasses. A subclass could normally just be a single attribute, one that contains multiple “tags” or a single list value. Agile uses this special subclass attribute to provide another level of granularity in spawning more specific attributes to a part instance.

An example for subclasses would be a resistor subclass. A resistor instance would not only have the weight and dimension attributes inherited from the “part” level, but also subclass specific attributes inherited from the subclass, like a resistance value (10 Ohms, 100 kOhms) and type (wire-wound, chip, etc.).

This abstraction seems to hold well for a few large groups of well defined objects. Unfortunately, if you have two groups that are similar to each other, but not similar to the rest of the groups, you either end up duplicating attributes across subgroups, or polluting the global attribute list with attributes that don’t apply to the majority of parts.

Designing My Own System

I was unhappy with not having infinite granularity in parts. So the other weekend, I woke up on Saturday morning and designed the schema for a simple proof-of-concept Rails app that had the flexibility I expected Agile to have.

I called it Flexible Parts, and I worked on it furiously for two days. The design requirements were as follows:

  • A part has a number and a name.
  • A part can have any number of attributes.
  • A part has its own unique values for its attributes.
  • Attributes can be shared across parts or unique to a part.
  • Attributes can be organized into “groups” in order to facilitate setup of a new part.
  • When selected, attribute groups attach their attributes to a part, but individual attributes can be removed.

The overall goals I was shooting for were:

  • There should be no compromises made in being able to capture all data relevant to a part in a way that is structured and searchable. Nothing should be left out because it doesn’t “fit” the way the system is laid out (flexibility is the #1 priority).
  • Parts only have the attributes that are relevant to them. There is no need to display irrelevant attributes or leave blank values during part creation.
  • Attributes are not free form, but shared across parts. They must be descriptive enough so that a user could be sure any existing attribute they added to a part was actually the same usage as it was created for.
  • The workflow for creating a new part that is similar to an existing part should be dead simple, either through attribute groups or copying relevant attributes from the similar part.
  • The workflow for creating a new part that is NOT similar to an existing part should seamlessly guide the user through creating the proper attribute groups so that future parts will be even simpler.
  • Searching across attributes should be intuitive.
  • Comparing shared attributes across parts should also be intuitive.
Schema generated by Rails ERD
Schema generated by Rails ERD

Implementation

I made a list of the conceivable features for a first release, then assigned a priority for each based on wanting to give a demo on Monday to the members of my implemenation group.

I compromised on the following:

  • Attributes are only allowable as string value and not boolean, numerical, list, or any other storage type.
  • Only certain aspects of the models are directly editable through the UI.
  • New parts can’t be added through the UI.
  • New attributes can’t be added through the UI.
  • Attribute groups can’t be modified through the UI.
  • There is no search interface.

Regarding new attributes being added to parts, I consider this interface to be critical to the success of the app. Attributes should always be reused across parts (with the condition that the attribute really is describing the same characteristic of both parts). Although new attributes can be created, searching should occur in the list of already existing attributes before a new attribute is created to avoid duplication.

I didn’t get a chance to get this feature the way I wanted. I had to settle for a simple list box of all attributes not already applied to the part. I did experiment with different UX concepts, including modal dialogs, but nothing stuck and the JavaScript pinned me down once more.

Screenshots

Parts index page
Parts index page
Choose trait groups using a chosen plugin type-ahead text box
Choose trait groups using a chosen plugin type-ahead text box
Fill in trait values, and add more from another chosen type-ahead text box
Fill in trait values, and add more from another chosen type-ahead text box
Viewing a single part. I was planning on having the "global|plier" radio buttons filter their traits and values, but didn
Viewing a single part. I was planning on having the “global|plier” radio buttons filter their traits and values, but didn’t get around to implementing it.
All trait groups. Trait groups can keep track of which parts they
All trait groups. Trait groups can keep track of which parts they’re attached to.
A single trait group. It shows its linked traits and associated parts.
A single trait group. It shows its linked traits and associated parts.

The Outcome

Sunday night ran up on me pretty fast, but I did get most of the critical features up and running. What I was really shooting for was to show the paradigm of flexibility of attributes. I showed it to my group members during a break, and I’m not sure they had their heads wrapped around the problem I originally presented to appreciate the solution I proposed. I got some “that’s cool”s and “you must not be married or have kids” comments when I mentioned I did it over the weekend. Besides that, we moved on with the implementation and made the compromises we needed to.

I’m pleased with the work I did, though. I learned about a bunch of new gems through drone.bz, including migrant, chosen, best-in-place, and even rails-erd (entity relationship diagram – see the figure above). I got much more versed in writing HAML, using Twitter Bootstrap, and getting my first taste of SCSS and SASS (and even Coffeescript). Unfortunately, I’m still very much untrained in writing tests or anything remotely BDD. I also didn’t get to tackle any client-side magic until the last minute and didn’t make the progress I’d hoped I would.

I like these little projects because I’m getting more comfortable with making decisions on where business logic should go, Rails conventions, getting set up, finding gems to build off of, and a host of other things I had to stop and Google every two minutes during development.

One of the most difficult parts of this project in particular was naming. Throughout this post, I talked about things in terms of attributes, but I couldn’t name my model “attribute” or anything remotely similar because Rails has a monopoly on generic terms like that. I actually had to go to a thesaurus and ended up using “traits” as a drop-in replacement for “attributes” throughout my project. I kept a list of Rails reserved words open in a Chrome tab throughout developement and had to check it several times to prevent the cryptic error messages that are bound to follow that type of mistake.

I’ve posted the code on github (more or less my first project of consequence, so that’s exciting…).

“I Wonder if I Could Make Something Like That”

For as long as I can remember, I’ve been a jack-of-all-trades sort of person.

Whenever I see someone create something that I enjoy or respect, my immediate response is “I wonder if I could make something like that too”. It’s led to me acquiring all sorts of weird skills and hobbies. Here are some somewhat self-indulgent auto-biographical examples to get things rolling.

Calculator Programming

Since this is a programming blog, I’ll start with an example about my first experiences with writing code. I’ve been playing around with computers for as long as my family had one (Dell, Intel Pentium, Windows 3.1). But it was freshmen year of high school that the graphing calculator fad took off and I really fell for programming.

One of the best ways to waste time in math class was playing calculator games. I’m still surprised how quickly these games spread from TI-83 to TI-83 (I wonder if kids do it the same way today?). There were the classic snake-type games, and then there were the formula pack apps where you could plug in variables and get your answer without having to punch out the whole equation.

Getting my first taste of these, I immediately thought “I wonder if I could make something like this too?”. I was in my first real programming class that year (Visual Basic 6), so I was getting a taste of common programming syntax and elements, but learning the specific TI-83 APIs was trial and error. Over the next year or two, I made my fair share of little apps and games. Even to the point where I made an entire playable board game app for my Homer’s Odyssey English class project.

Music

This behavior had already started for me back in middle school, where I started getting into popular rock music. Before long, I had started/joined my first band and started writing songs as soon I learned my first two guitar chords.

Then we needed recordings, so I listened to my favorite records and said “how can I make recordings that sound like this?”. I started buying gear and cobbling together our first records. I’ve recorded in the order of 30 to 40 records for my bands and others’ bands since then.

By college, my musical tastes had expanded. I started a second band with one of my friends who sided more towards classic punk/acoustic music. I listed to the records he and I shared a taste for and thought “I bet I could make music like this.” And so we did. Five records with that band in eight years. Everyone has to have a solo project, and so I started one of those too. At one point I was writing and recording one record for each of my three bands per year.

More Programming

I learned VB6 and C++ in high school mostly by curriculum. In college I learned C and a couple different flavors of assembly, mostly by curriculum too. It wasn’t until the summer after my graduation that I looked at smartphone apps and said “I wonder if I could write something like that?”. I dove in and learned Objective-C and Cocoa Touch from scratch. That was the only thing I did for an entire two months until I got my first job. Once I got my job, however, it was back to the part-time shelf with all the music stuff.

Looking at the App Store and coming up with new ideas for apps over the next year, I realized all my best ideas needed a back-end. So I put my iOS coding on hold while I started learning Ruby and Rails. That of course spawned the need for JavaScript, HTML, CSS, and all the other related technologies.

Photos & Video

During college, A friend of mine was delving into photography and always posting his DSLR photos of our group of friends to Facebook and blowing all of us away. I asked myself “could I learn how to take photos like that?”. I borrowed his old camera when I went to Japan for two months, and returned with a new back-burner hobby.

It wasn’t until a few years later that I got my own camera and started shooting regularly again. My camera has video, so it wasn’t long before I had focused most of my energies to shooting video at gatherings and editing together clips with my own music arrangements to show everyone a few days later, an idea I appropriated from the same friend that loaned me his camera.

Master of None

At a certain point, I realized that having this many semi-active interests means that I will never be at the top 1% of any of them. I’m still of the persuasion that no one can escape the 10,000 hours rule.

This wouldn’t be a problem if I was still a high school or college kid, but as an adult I realize that some pursuits have more of a payback than others. In the past, the only priorities I assigned to all these activities was based on my own random day-to-day decisions and the promises I’d made to those I worked on these projects with.

And because most of these pursuits require multi-month projects, I am constantly backlogged with a dozen simultaneous commitments. Every time I finish one, two are already there to replace it.

With my realization that programming is going to be my breadwinning function for the indefinite future, should my programming pursuits be my default number one priority? Should music and photography and the others be placed further back in the list than they already are if I am to keep up with the programming industry and stay employable?

Or is this a trait that a lot of successful people have? I know that my various pursuits always end up giving me some lightbulb-over-head insight into other seemingly unrelated pursuits.

I don’t think I’ll ever be able to completely during off my wandering ambition towards acquiring new skills. I know it’s part of who I am. The question that’s on the table is if I should artificially adjust my priorities. I’m sure if I were 40 instead of 25, I’d be able to answer these questions succinctly and insightfully, and it would help a lot of young people like myself.

Going Forward

From here, I’m guessing I’ll just have to keep a better handle on my current projects and commitments. Opportunities for new projects should be evaluated more carefully for time and enjoyment, because at the end of the day, a lot of these pursuits are things I should be doing because I really really want to do them.

That’s how I’ll have to reason things next time the thought pops into my head: “I wonder if I could make something like that?”.