Monthly Archives: August 2011

Forget Chef or Puppet – Automate with Sprinkle

Miso has a relatively standard server architecture for a medium-level traffic Rails application. We use Linode to host our application and we provision a number of VPS instances that make up our infrastructure. We have a load balancer equipped with Nginx and Varnish, we have an application server that runs our Rails application to serve dynamic requests, we have a master-slave database setup, and a cache server that has memcached and redis.

Early on, we manually setup these servers by hand by installing packages, compiling libraries, tweaking configurations and installing gems. We then manually documented these steps to a company wiki which would allow us to mechanically follow the steps and setup a new instance of any of our servers.

Automating our Setup

We decided recently that we wanted a more robust way of provisioning and configuring our different types of servers. Mechanically following steps from a wiki is inefficient, error-prone and likely to become inaccurate. What we needed was a system that would allow us to provision a new application or database server with just a single command. Here were additional requirements for our desired setup:

  • Simple to setup and configure
  • Lightweight system using familiar syntax
  • Preferably utilizing ssh and commands in the vein of Capistrano
  • Modular components that can be assembled to setup each server
  • Locally executable such that I can provision a server from my local machine
  • Doesn’t require the target machine to have anything installed prior

The set-up and provisioning tools which are most popular for open-source seem to be Puppet and Chef. Both are great tools that have a lot of features and flexibility, and are well-suited when you have a large infrastructure with tens or hundreds of servers for a project. However, Chef and Puppet recipes are not trivial to create or manage and by default they are intended to be managed from a remote server. We felt that there was unnecessary complexity and overhead for our simple provisioning purposes. Thanks for the corrections in the comments regarding the remote server requirement for Puppet/Chef.

For smaller infrastructures like ours, we felt a better tool would be easier to manage and setup. Ideally, something that is akin to deploying our code with Capistrano. A tool that can be managed and run locally and that can be maintained easily. After some exploration, we stumbled upon a ruby tool called Sprinkle, probably best known by one example automated script called Passenger Stack.

There are several aspects of Sprinkle that made this our tool of choice. For one, it is locally managed and the setup is done leveraging the rock-solid Capistrano deployment system. Also, even though Sprinkle is written in Ruby, the tool does not require Ruby or anything else to be installed on the target servers since the automated setup is executed on your development machine and communicates with the remote server using only SSH. The best part is that there are only a few concepts required to understand and use this system.

Understanding Sprinkle

The rest of this article is intended to be a long and comprehensive overview of Sprinkle. While I have read many blog posts about Sprinkle written across several different years, I hadn’t seen a post that covered each aspect of Sprinkle in full detail. The goal is that by the end of this post, you should be able to understand and write Sprinkle scripts as well as execute them. Let’s start off by exploring the four major concepts that make up Sprinkle and that will be used to build out your server recipes: Packages, Policies, Installers, and Verifiers.

Packages

A package defines one or more things to provision onto the server. There is a lot of flexibility in a way a package is defined but fundamentally this represents a “component” can be installed. Packages are sets of installations, options, and verifications, grouped under a meaningful name. The basic structure of a package is like this:

# packages/some_name.rb
package :some_name do
  description 'Some text'
  version '1.2.3'
  requires :another_package

  # ...installers...

  verify { ...verifiers... }
end

Note that defining a package does not install the package by default. A package is only installed when explicitly mentioned in a policy. You can also specify recommendations and/or optional package dependencies in a package as well:

# packages/foo_name.rb
package :foo_name do
  requires :another_package
  recommends :some_package
  optional :other_package

  # ...installers...
  verify { ...verifiers... }
end

You can also create virtual aliased packages that are various alternatives for the same component type. For instance, if I wanted to give people a choice over the database to use when provisioning:

# packages/database.rb
package :sqlite3, :provides => :database do
  # ...installers and verifiers...
end

package :postgresql, :provides => :database do
  # ...installers and verifiers...
end

package :mysql, :provides => :database do
  # ...installers and verifiers...
end

You can now reference that you want to install a :database and the script will ask you which provision you want to install. For more information on packages, the best place is looking at the source file itself.

Policies

A policy defines a set of particular “packages” that are required for a certain server (app, database, etc). All policies defined will be run and all packages required by the policy will be installed. So whereas defining a “package” merely defines it, defining a “policy” actually causes those packages to be installed. A policy is very simple to define:

# Define target machine used for the "app" role
role :app, "208.28.38.44"
# Installs the packages specified with a 'requires' when the script executes
policy :myapp, :roles => :app do
  requires :some_package
  requires :nginx
  requires :postgresql
  requires :rails
end

A role merely defines what server the commands are run on. This way, a single Sprinkle script can provision an entire group of servers specifying different roles similar to using Capistrano directly. You may specify as many policies as you’d like. If the packages you’re requiring are properly defined with verification blocks, then no software will be installed twice, so you may require a webserver on multiple packages within the same role without having to wait for that package to install repeatedly.

For more information on policies, the best place is looking at the source file itself.

Installers

Installers are mechanisms for getting software onto the target machine. There are different scripts that allow you to download/compile software from a given source, use packaging systems (such as apt-get), copy files, install a gem, or even just inject content into existing files. Common examples of installers are detailed below.

To run an arbitrary command on the server:

package :foo do
  # Can be any line executed in the shell
  runner "run_some_command --now"
end

To install using the aptitude package manager in Ubuntu:

package :foo do
  apt 'foo-package'
  # Supports only installing dependencies
  apt('bar-package') { dependencies_only true }
end

That would install the ‘foo-package’ when you run the ‘foo’ package. To install a ruby gem:

package :foo do
  gem 'foo-gem'
  # Supports specifying version, source, repository, build_docs and build_flags
  gem 'bar-gem' do
    version "1.2.3"
    source 'http://gems.github.com'
    build_docs false
  end
end

To upload arbitrary text into a file on the target:

package :foo do
  push_text 'some random text', '/etc/foo/bar.conf'
  # Supports sudo access for a file
  push_text 'some random text', '/etc/foo/bar.conf', :sudo => true
end

You can also replace text in a target file:

# packages/foo.rb
package :foo do
  replace_text 'original foo', 'replacement bar', '/etc/foo/bar.conf'
  # Supports sudo access for a file
  replace_text 'original foo', 'replacement bar', '/etc/foo/bar.conf', :sudo => true
end

To transfer a file to a remote target file:

package :foo do
  # Transfers are recursive by default so whole directories can be moved
  transfer 'file/some_folder', '/etc/some_folder'
  # Supports sudo access for a file
  transfer 'file/foo.file', '/etc/foo.file', :sudo => true
  # Also a file can have "render" passed which runs the template through erb 
  # You can access variables to output the file dynamically, or pass explicit locals
  foo_port = 8080
  transfer 'file/foo.file', '/etc/foo.file', :render => true, 
            :locals => { :bar_port => 80 }
end

To run a rake task as part of a package on target:

package :foo, :rakefile => "/path/to/Rakefile" do
  rake 'foo-task'
end

To install a library from a given source path:

package :foo do
  source 'http://foo.com/latest-1.2.3.tar.gz'
  # Supports prefix, builds, archives, enable, with, and more
  source 'http://magicbeansland.com/latest-1.1.1.tar.gz' do
    prefix    '/usr/local'
    archives  '/tmp'
    builds    '/tmp/builds'
    with      'pgsql'
  end
end

Installers also support installation hooks at various points during the install process which vary depending on the installer used. An example of how to use hooks is as follows:

package :foo do
  apt 'foo-package' do
    pre :install, 'echo "Beginning install..."'
    post :install, 'echo "Completing install!"'
  end
end

Multiple hooks can be specified for any given step and each installer has multiple steps for which hooks can be configured. For more information on installers, the best place is looking at the source folder itself.

Verifiers

Verifiers are convenient helpers which you can use to check if something was installed correctly. You can use this helper within a verify block for a package. Sprinkle runs the verify block to find out whether or not something is already installed on the target machine. This way things never get done twice and if a package is already installed, then the task will be skipped.

Adding a verify block for every package is extremely important, be diligent to have an appropriate verifier for every installer used in a package. This will make the automated scripts much more robust and reusable on any number of servers. This also ensures that an installer works as expected and tests the server after installation as well.

There are many different types of verifications, for each one there are installers for which they are particularly useful. For instance, if I wanted to see if an aptitude package was installed correctly:

package :foo do
  apt 'foo-package'
  apt 'bar-package'
  verify do
    has_apt 'foo-package'
    has_apt 'bar-package'
  end
end

This will only install the package on a target if not already installed and verifies the installation after the package runs. If we wanted to check that a gem exists:

package :foo do
  gem 'foo-gem', :version => "1.2.3"
  gem 'bar-gem'
  verify do
    has_gem 'foo-gem', '1.2.3'
    # or verify that ruby can require a gem
    ruby_can_load 'bar-gem'
  end
end

If you want to check if a directory, file or executable exists:

package :foo do
  mkdir '/var/some/dir'
  touch 'var/some/file'
  runner 'touch /usr/bin/abinary' do
    post :install, "chmod +x /usr/bin/abinary"
  end

  verify do
    has_directory '/var/some/dir'
    has_file      '/etc/apache2/apache2.conf'
    has_executable 'abinary'
  end
end

You can also check if a process is running:

package :foo do
  apt 'memcached'

  verify do
    has_process 'memcached'
  end
end

For more information on verifiers, the best place is looking at the source folder itself.

Putting Everything Together

Once you understand the aforementioned concepts, building automated recipes for provisioning becomes quite straightforward. Simply define packages (with installers and verifiers) and then group them into ‘policies’ that run on target machines. Generally, you can have a deploy.rb file and an install.rb file that are defined as follows:

# deploy.rb
# SSH in as 'root'. Probably not the best idea.
set :user, 'root'
set :password, 'secret'

# Just run the commands since we are 'root'.
set :run_method, :run

# Be sure to fill in your server host name or IP.
role :app, '83.434.34.234'

default_run_options[:pty] = true

The install file tends to define the various policies for this sprinkle script:

# install.rb
require 'packages/essential'
require 'packages/git'
require 'packages/nginx'
require 'packages/rails'
require 'packages/mongodb'

policy :myapp, :roles => :app do
  requires :essential
  requires :git
  requires :nginx
  requires :rails
  requires :mongodb
end

deployment do
  delivery :capistrano

  source do
    prefix   '/usr/local'
    archives '/usr/local/sources'
    builds   '/usr/local/build'
  end
end

Then you should store your packages in a subfolder aptly named ‘packages’:

# packages/git.rb
package :git, :provides => :scm do
  description 'Git version control client'
  apt 'git-core'

  verify do
    has_executable 'git'
  end
end

You can store your assets (configuration files, etc) in “assets” folder and access them from your packages to upload. Once all the packages and policies have been defined appropriately you can execute the sprinkle script on the command line with:

sprinkle -c -s install.rb

And sprinkle is off to the races, setting up all the policies on the target machines.

Further Reading

There are several other good posts about Sprinkle:

In addition, there are a lot of good examples of sprinkle recipes:

Let me know what you think of all this in the comments and if you have any related comments or questions. What do you use to automate and manage your servers?

Distributed Persistence for YAMLRecord

Last month we released YAMLRecord, a lightweight way to persist a small dataset into a simple YAML file which is fine if you’ve only one app server and you keep the YAML file stored locally.

As pointed out by Nelson Hernandez, YAMLRecord presents a problem when you have multiple application servers because the file can no longer be stored locally on a single application server. The problem is that if a change occurs in one of theses servers, the YAML file would have to be updated on all the others or stored in a new location accessible by all the servers.

To address this issue, the goal was to find a way to move the YAML file away from the app server and store it somewhere else that can handle access from multiple instances. With Nathan, we brainstormed and listed several solutions we could implement to allow us to scale out with YAML Record:

  1. Store YAML file on S3 and cache the data locally
  2. Use NFS and mount a shared volume and store the YAML on that volume
  3. Serialize YAML content in an existing persistence store such as PostgreSQL or Redis

Why Redis?

We chose to augment YAMLRecord with a pluggable “adapter” system that supports storing the YAML content on Redis. We would then move the YAML data from the file system to a redis-backed store. This may seem to be an odd choice but we have a number of simple lightweight YAMLRecord resources setup and we wanted to keep this system in tact for the time being.

The first solution with S3 and memcached seemed relatively sound in which we store the YAML files on S3 and then update them there each time a change is made. This solution seemed to be an issue because we would be forced to leave our LAN and access S3 each and every time we wanted to update the YAML data. Also, to retrieve the YAML data from a remote location each time seemed a bit overkill.

Using NFS shared volumes is the more “traditional” solution. Simply setup a shared volume and mount the YAML files to each application server. We opted not to use this approach because this would be yet another “moving part” in our system that we would have to manage as we deploy servers. We wanted to use an existing system if possible so that additional complexity wouldn’t be introduced.

We ultimately felt that the last solution where we would allow YAMLRecord to support different storage adapters would be a decent approach. We felt this made sense for our infrastructure which was already set up for redundancy and backups on PostgreSQL and Redis. When we built YAML Record, it was to avoid the heavy-ness of a SQL Database, it didn’t really make sense to pursue a storage adapter in that direction yet. We felt Redis would be an easy to use storage adapter with the key being the YAML file name and the value being the serialized YAML data.


What’s new on YAML Record?

With Redis as a solution to scale out YAML Record data, we also wanted to keep the option for a local store with a simple file. We came up with this idea of swappable adapters which allows YAML Record to be modular.

In order to use the Redis adapter it’s quite simple, you just need to pick which adapter you want to use in a model:

class Team < YamlRecord::Base
  # Declare which adapter you want to use
  adapter :redis, $redis # $redis is your redis client

  # Declare your properties
  properties :name, :role

  source "team," # Will store data in a key "yaml_record:team"
end

What’s next?

Here’s some ideas we want to achieve soon:

  • Be able to specify type for properties
  • Add validations
  • Add timestamps magic fields like on activerecord
  • New adapters build by you? :)

Objective-C Conventions

Writing code without conforming to some form of convention lends itself to a lot of confusion; Both for the code writer, and for someone else trying to understand your code. The pain worsens as the code base starts to increase in size, especially for a non-GC programming languages like Objective-C where memory management has to be cared for meticulously. Following a set of naming conventions for your instance variables, class names…etc. will help maintain a certain level of sanity. Here at Miso, we conform to a set of conventions that are partly derived from Apple standards, and partly from common best practices we’ve seen from other developers.

Class and Variable names

For variable names, Apple recommends starting with a lowercase letter (eg. UILabel *titleLabel). For class names, starting with a uppercase letter (eg. MyClass). Notice in the examples we also like to camel case the rest of the name.

iVars and local variables

One of the most common issues with a large class or viewController containing more than several instance variables is distinguishing them from a local variable within a function. What I like to do is prefix iVars with an underscore to distinguish them. (eg. _titleLabel)

- (void)createViews {
    // some bunch of code above
    UIImageView *imageView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"somepic.png"]] autorelease];

    [_containerView addSubview:imageView];
    [_containerView addSubview:titleLabel];
    // some more code below
}

In this example you can see that I don’t have to look at the header file to immediately tell that imageView and titleLabel are a locally defined variables, and that _containerView is an iVar simply looking at the name of the variables.

Starting with the Header File

When I approach designing a new class I like to start with the header file first. This is where you wireframe your code design without actually writing any implementation. Reason for this is because I like to start with what someone using this class would need. This means defining public class/instance methods, properties (public accessors), and instance variables. Let’s try this approach with an example. I love cats, so let’s go with that:

//
//  VirtualCat.h
//  Miso
//
//  Created by Joshua Wu on 8/12/11.
//  Copyright 2011 Miso. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface VirtualCat : NSObject {
    BOOL _hungry;
    NSMutableArray *_foodHistory;
    NSString *_furColor;
    float _weight;
}

@property (nonatomic, readonly) float weight;
@property (nonatomic, retain) NSString *furColor;

- (id)initWithColor:(NSString *)furColor weight:(float)weight;
- (void)feed:(NSString *)food;

@end

Ok great! Here you can see that certain properties of this class is can be modified after instantiating. (furColor and weight) We also see that certain properties can’t be modified because public accessors were not defined. (hungry and foodHistory). I’ve also defined a constructor, and a instance method “feed”. For someone using this class, what they can do with this class is all nicely defined in the header file.

Private Interfaces

Using properties is great because you can use the accessors you get from it to assign new values to them without worrying about memory management. This is great for public properties, but what if I want to define private accessors to leverage the same convenience in my implementation file? This is what we can do:

//
// VirtualCat.m
// Miso
//
// Created by Joshua Wu on 8/12/11.
// Copyright 2011 Miso. All rights reserved.
//

#import "VirtualCat.h"

@interface Cat()

@property (nonatomic, retain) NSMutableArray *foodHistory;

- (void)askForFood;
- (void)sleepInBathTub;
- (void)meow;

@end

@implementation VirtualCat
@synthesize weight=_weight;
@synthesize furColor=_furColor;
@synthesize foodHistory=_foodHistory;

@end

By declaring an interface within the implementation file, you can assign private properties. I’ve also sneaked in some additional code in the private interface to demonstrate defining private instance methods. So far, we’ve done enough to define the blueprint of this class quite sufficiently without having written any implementation code! Yet, it is already clear what someone instantiating this class would be able to do with it, and what I need to implement. This gives me a good picture of what functions and variables I’ve defined when I come back to this code in the future. If you were in XCode looking at this class, it’d also conveniently give you a compiler warning saying that your implementation file is incomplete (Incomplete implementation of class). This is a good guideline for me to make sure I’ve implemented all the functions I intended to as I work through the implementation.

When it all comes together…

Alright, great! Let’s have some fun and finish off the implementation using the conventions I’ve defined earlier. Enjoy!

//
//  VirtualCat.m
//  Miso
//
//  Created by Joshua Wu on 8/12/11.
//  Copyright 2011 Miso. All rights reserved.
//

#import "VirtualCat.h"

@interface VirtualCat()

@property (nonatomic, retain) NSMutableArray *foodHistory;

- (void)askForFood;
- (void)sleepInBathTub;
- (void)meow;

@end

@implementation VirtualCat
@synthesize weight=_weight;
@synthesize furColor=_furColor;
@synthesize foodHistory=_foodHistory;

- (id)initWithColor:(NSString *)furColor weight:(float)weight {
    if((self = [super init])) {
        // Primitive iVars so I don't bother defining properties for them
        _weight = weight;
        _hungry = YES;
        
        // Using self. accessors to maintain memory santiy.
        // Less sane alternative would be eg. _foodHistory = [[NSMutableArray array] retain]
        self.furColor = furColor;
        self.foodHistory = [NSMutableArray array];
    }
    
    return self;
}

- (void)dealloc {
    [_foodHistory release];
    [_furColor release];
    [super dealloc];
}

#pragma mark - public methods

- (void)feed:(NSString *)food {
    if ([food isEqualToString:@"Can Food"]) {
        NSLog(@"Om nom nom nom");
    } else if ([food isEqualToString:@"Dry Food"]) {
        NSLog(@"Om nom");
    } else {
        NSLog(@"Not eating this");
    }
    
    [_foodHistory addObject:food];
    _hungry = NO;
    _weight += 1;
    
    [self sleepInBathTub];
}

#pragma mark - private methods

- (void)askForFood {
    _hungry = YES;
    [self meow];
}
          
- (void)meow {
    _weight -= 0.1;
    
    if (_hungry && _weight > 0) {
        NSLog(@"MEOW!");
        [self performSelector:@selector(meow) withObject:nil afterDelay:2];
    } else if (!_hungry && weight > 0) {
        NSLog(@"Purrr~");
    }
        NSLog(@"You allowed me to die damn it! (╯‵Д′)╯彡┻━┻");
    }
}

- (void)sleepInBathTub {
    NSLog(@"zzzzzz");
    [self performSelector:@selector(askForFood) withObject:nil afterDelay:1000];
}

@end

Pragma Marking

One more thing I’d like to suggest is use pragma marks to section off method types in your code. (eg. #pragma mark – private methods) This allows xcode to nicely section off methods in their quick access menus.

Suggestions!

By now, it should be evident how following conventions when coding can be very beneficial. This is by no means THE standard to follow, but one that I feel has helped me stay sane as I develop. If you have your own conventions that you feel would add value, I’d love to hear from you and exchange ideas on this topic.

Vendor – Bringing Bundler to iOS

Using and sharing iOS libraries is tragically difficult given the maturity of the framework, the number of active developers, and the size of the open source community.  Compare with Rails which has RubyGems and Bundler, it’s no surprise that Rails/Ruby has a resource like The Ruby Toolbox, while iOS development has…nothing?

Are iOS developers fundamentally less collaborative than Rails developers?  When developing on Rails, if I ever find myself developing anything remotely reusable, I can almost always be certain that there is a gem for it (probably with an obnoxiously clever name).

I don’t think the spirit of collaboration is lacking in the iOS developer community; rather, there are a few fundamental challenges with iOS library development:

  • Libraries are hard to integrate into a project.  Granted, it’s not *that* hard to follow a brief set of instructions, but why can’t this process be more streamlined?
  • No standardized versioning standard.  Once a library is integrated into a project, there is no standard way of capturing which version of the library was used.
  • No dependency specification standard (this is a big problem).  Why does facebook-ios-sdk embed it’s own JSON library when there are better ones available?  So many libraries come embedded with common libraries that we all use – and worse than that, who knows what version they’re using!  Not only can this lead to duplicate symbols, but library developers essentially have to start from scratch instead of leveraging other existing libraries.

Of course, coming up with a naming, versioning, and dependency standard for iOS libraries and convincing everyone to adopt it is a daunting task.  One possible approach is follow the example of Homebrew, a popular package manager for OS X.  Homebrew turned installing and updating packages on OS X into a simple process.  Instead of convincing everyone to comply to some standard, Homebrew maintains a set of formulas that helps describe commonly used packages.  These formulas allow Homebrew to automate the installation process as well as enforce dependencies.  This works well for Homebrew, although it puts the burden of maintaining package specifications in one place, rather then distributed as it is with Ruby gems.

There seems to be a need for some type of solution here.  When we write Rails libraries, we write the readme first to help us understand what problem we’re trying to solve (Readme Driven Development).  Below is the readme for an iOS packaging system called Vendor.

Vendor – an iOS library management system

Vendor makes the process of using and managing libraries in iOS easy.  Vendor leverages the XCode Workspaces feature introduced with XCode 4 and is modeled after Bundler. Vendor streamlines the installation and update process for dependent libraries.  It also tracks versions and manages dependencies between libraries.

Step 1) Specify dependencies

Specify your dependencies in a Vendors file in your project’s root.

source "https://github.com/bazaarlabs/vendor"
lib "facebook-ios-sdk"  # Formula specified at source above
lib "three20"
lib "asi-http-request", :git => "https://github.com/pokeb/asi-http-request.git"
lib "JSONKit", :git => "https://github.com/johnezang/JSONKit.git"

Step 2) Install dependencies

vendor install
git add Vendors.lock

Installing a vendor library gets the latest version of the code, and adds the XCode project to the workspace.  As part of the installation process, the library is set up as a dependency of the main project, header search paths are modified, and required frameworks are added.  The installed version of the library is captured in the Vendors.lock file.

After a fresh check out of a project from source control, the XCode workspace may contain links to projects that don’t exist in the file system because vendor projects are not checked into source control. Run `vendor install` to restore the vendor projects.

Other commands

# Updating all dependencies will update all libraries to their latest versions.
vendor update
# Specifying the dependency will cause only the single library to be updated.
vendor update facebook-ios-sdk

Adding a library formula

If a library has no framework dependencies, has no required additional compiler/linker flags, and has an XCode project, it doesn’t require a Vendor formula. An example is JSONKit, which may be specified as below. However, if another Vendor library requires JSONKit, JSONKit must have a Vendor formula.

lib "JSONKit", :git => "https://github.com/johnezang/JSONKit.git"

However, if the library requires frameworks or has dependencies on other Vendor libraries, it must have a Vendor formula.  As with Brew, a Vendor formula is some declarative Ruby code that is open source and centrally managed.

An example Vendor formula might look like:

require 'formula'

class Three20 < Formula
  url "https://github.com/facebook/three20"
  libraries libThree20.a
  frameworks "CoreAnimation"
  header_path "three20/Build/Products/three20"
  linker_flags "ObjC", "all_load"
  vendors "JSONKit"
end

Conclusion

Using iOS libraries is way harder than it should be, which has negatively impacted the growth of the open source community for iOS.  Even if I was only developing libraries for myself, I would still want some kind of packaging system to help me manage the code.  Vendor essentially streamlines the flow described by Jonas Williams here.  Unfortunately, programmatically managing XCode projects isn’t supported natively, but people have implemented various solutions, such as Three20Victor Costan, and XCS.

Open Questions

  • Is there an existing solution for this?
  • Would this be a useful gem for your iOS development?
  • Why hasn’t anyone built something like this already? Impossible to build?