Our First Chef Cookbook

We recently deployed an Amazon EC2 instance and I was tasked with installing and configuring Red5 and Openfire on the system. As I often do, I took careful notes of what I did on a wiki, which is useful to have in case another instance needs to be brought up later. Manual installation using notes, however, can be an arduous and error prone process. This can be alleviated by adding automation, typically through shell scripts. The problem with writing shell scripts is that you can only run those scripts on a specific system. And often, running the same script twice can leave your system in an inconsistent state.

Enter Chef , an open source systems integration framework written in Ruby. You can easily manage and configure your servers using what Chef calls cookbooks. A cookbook contains all the things you need to automate the configuration of your server and basically consists of one or more recipes. The easiest way to familiarize yourself with Chef is by running it with Chef Solo, which is what I ended up doing. Running it this way allows you to run cookbooks without running a Chef server and client. You can read more about deploying Chef in this configuration by reading the Chef Architecture documentation.

Before writing my cookbook, I had to have all the dependencies for Chef Solo installed on my Ubuntu 10.04 (Lucid Lynx) EC2 instance. Lucid Lynx already ships with Ruby so I didn’t need to install that. All I needed to do to get myself going was run these commands to install Chef Solo:

% sudo apt-get update
% sudo apt-get -y install ohai chef rubygems

You should read the Chef Installation documentation if you’re on a different system.

The first thing I did was to create a directory to contain my cookbooks and configuration files:

% mkdir -p ~/chef/cookbooks
% mkdir -p ~/chef/config

The chef-solo command comes with two main options:

  • -c <configuration_file>
  • -j <json_attributes>

The -c option tells chef-solo which cookbooks to pick up. If not specified, chef-solo will read the configuration file at /etc/chef/solo.rb. The -j option specifies which recipes chef-solo should run, as well as set or override any recipe attributes. The format of the file that the -j option reads in is JSON.

I wanted chef-solo to pick up my recipes, so I wrote my own configuration file:

% cd ~/chef/config
% vim solo.rb
home_dir = File.expand_path(File.dirname(__FILE__) + "/..")
file_cache_path  home_dir
cookbook_path [ "#{home_dir}/cookbooks" ]

The cookbook_path specifies where my cookbooks are going to exist, which is an array of paths. The file_cache_path is the directory where remote cookbooks will be downloaded and installed into. Chef solo has a -r option to download remote recipes into your system. Both cookbook_path and file_cache_path need to be full paths.

I needed to install Openfire and Red5 on my system, which meant there was a Java dependency. I knew there was a Java Chef recipe, so I downloaded it and installed it in my cookbooks directory:

% cd ~/chef/cookbooks
% wget https://s3.amazonaws.com/opscode-community/cookbook_versions/tarballs/194/original/java.tar.gz
% tar zxf java.tar.gz

One of the great things about Chef is that a whole slew of recipes are available to be used. You can find them at http://cookbooks.opscode.com.

I was now be able to install Java, but before doing that, I needed to create an attributes file telling chef-solo I wanted to run java as a recipe:

% cd ~/chef/config
% vim node.json
{
    "recipes": [ "java" ]
}

My first chef-solo run:

% cd ~/chef
% chef-solo -j config/node.json -c config/solo.rb

Voila! That installed Java on my system.

I was finally ready to write my first cookbook:

% cd ~/chef/cookbooks
% mkdir -p openfire/recipes
% cd openfire/recipes
% vi default.rb
tarball = "openfire_3_6_4.tar.gz"
execute "wget" do
 tarball_url = "http://www.igniterealtime.org/downloadServlet?filename=openfire/#{tarball}"
 cwd "/tmp"
 command "wget #{tarball_url}"
 creates "/tmp/#{tarball}"
 action :run
end
remote_file "/tmp/#{tarball}" do
 source "http://www.igniterealtime.org/downloadServlet?filename=openfire/#{tarball}"
 mode "0644"
 checksum "bc43e96d23dc865b624fd3ed2531b2a8a8ef3f0483e829dd087b08688d92ede2"
end
execute "tar" do
 user "ubuntu"
 group "ubuntu"
 installation_dir = "/home/ubuntu"
 cwd installation_dir
 command "tar zxf /tmp/#{tarball}"
 creates installation_dir + "/openfire"
 action :run
end

The openfire/recipes/default.rb file is the main recipe file that’s executed when the Openfire cookbook is referenced.  If you look in the Java cookbook, you’ll find java/recipes/default.rb. Each cookbook has a default.rb recipe.

A recipe is essentially a list of commands or, in Chef speak, resources. A resource is a command abstraction that can be run on any platform. For example, there’s a file resource that allows you to create or delete a file, a package resource that let’s you to install, remove or upgrade a package in your system. In the Openfire cookbook, I’m using the execute resource which runs a specific shell command. I found the terminology in Chef often confusing and turned to a blog post called The Chef Way Episode 2: Chef Speak to explain it all. I highly recommend using it as reference while reading the Chef documentation.

What the wget execute resource does is:

  • Changes the working directory to /tmp (cwd “/tmp”)
  • Runs wget to fetch the tarball (command “wget #{tarball_url}”)
  • Says that this action creates the tarball in the /tmp directory and, if it exists, not execute the resource (creates “/tmp/#{tarball}”
  • Lastly, says that the action for this command is to be run (action :run)

The other valid action for execute is :nothing which basically tells the resource not to run the command. Each resource has its own set of actions and valid attributes. For more details, check out the resources wiki.

The only difference with the tar execute resource are the user and group attributes which says the resource should change to a specific user and group id before running the command.

What the remote_file resource does is fetch the tarball from the remote server into the /tmp directory and run a checksum. The tar execute resource:

  • Sets a specific user  id to run the command (user “ubuntu”)
  • Sets a specific group id to run the command (group “ubuntu”)
  • Changes the working directory (cwd installation_dir)
  • Runs tar to untar the tarball (command “tar zxf /tmp/#{tarball}”)
  • Says that this action creates the openfire directory in the installation directory. If the directory already exists, the resource will not execute. (creates installation_dir + “/openfire”)
  • Lastly, says that the action for this command is to be run (action :run)

Each resource has its own set of actions and valid attributes. For more details, check out the resources wiki.

There were a lot of hard-coded values in the cookbook. So I decided to extract them out as much as possible into attributes. You can do that by creating a file in the attributes directory:

% cd ~/chef/cookbooks/openfire
% mkdir attributes
% vim attributes/openfire.rb
tarball = "openfire_3_6_4.tar.gz"
set_unless[:openfire][:installation_dir] = "/home/ubuntu"
set_unless[:openfire][:tarball_url] = "http://www.igniterealtime.org/downloadServlet?filename=openfire/#{tarball}"
set_unless[:openfire][:tarball_checksum] = "bc43e96d23dc865b624fd3ed2531b2a8a8ef3f0483e829dd087b08688d92ede2"
set_unless[:openfire][:tarball] = tarball
set_unless[:openfire][:dirname] = "openfire" # directory name of extracted tarball
set_unless[:openfire][:user] = "ubuntu"
set_unless[:openfire][:group] = "ubuntu"
% vim recipes/default.rb
tarball = node[:openfire][:tarball]
execute "wget" do
 cwd "/tmp"
 command "wget #{node[:openfire][:tarball_url]}"
 creates "/tmp/#{tarball}"
 action :run
end
remote_file "/tmp/#{tarball}" do
 source "#{node[:openfire][:tarball_url]}"
 mode "0644"
 checksum "#{node[:openfire][:tarball_checksum]}"
end
execute "tar" do
 user node[:openfire][:user]
 group node[:openfire][:group]
 installation_dir = node[:openfire][:installation_dir]
 cwd installation_dir
 command "tar zxf /tmp/#{tarball}"
 creates installation_dir + "/" + node[:openfire][:dirname]
 action :run
end

An attribute is a property that can be accessed via a hash called node in any recipe. When Chef runs, every single file in the attributes directory is read in alphabetical order. The attribute values set in those files are set in node. You can set attributes using the set or set_unless methods: set assigns the value even if the attribute has already been set, set_unless only assigns the value if it hasn’t been set already. When running chef-solo, you can override attributes using the JSON attributes file. Check out the attributes wiki for more information.

The Red5 cookbook is very similar to the Openfire cookbook, so I won’t include it here for brevity’s sake. To install Java, Openfire and Red5 using chef-solo:

% cd ~/chef/config
% vim node.json
{
    "recipes": [ "java", "openfire", "red5" ]
}
% cd ~/chef
% chef-solo -j config/node.json -c config/solo.rb

In this case, only the Openfire and Red5 cookbook will run because we installed Java before. Chef is smart enough not to rerun installations that have already run.

These cookbooks are all checked into github at http://github.com/blazingcloud/chef if you want to check it out. I also recommend reading Cooking with Chef 101 for a great Chef Solo tutorial.

I know the cookbooks here could be factored out better and be less platform dependent on Ubuntu, but for the purposes of instruction, I hope this was useful. I’ll leave it at that for my first Blazing Cloud blog post. Thanks for reading!

Objective-C Gets Blocks!

New to Objective-C is the block syntax. A block is a section of code that is grouped together and designed to be treated like one statement. Blocks also package up the lexical scope of variables and functions within its environment. Blocks are found in many languages including, but not limited to, Ruby, Javascript and Lisp. Blocks are objects and should be treated like such!

Programming Language Code Example Colloquial
Ruby
[1, 2, 3, 4].each do |i|
    puts i
end
Block
Lisp
((lambda (a b) (+ b a)) 1 2)
Lambda function
Javascript
var foo = function () {
   var j = 42;
}
Anonymous Function, Closure
Objective-C
^BOOL(num1,num2){
   return [num1 isEqualToNumber:num2];
}
Block

Now there are other block structured languages out there but the few listed here are used to provide a basis for understanding if you’re coming from another language where you may have experienced this concept.

Objective-C inherited blocks from the C language, which it is a superset of. Apple has created an extension of the C language implementing blocks. Syntactically, blocks start with the carrot symbol, followed by its return type followed by arguments (enclosed by parenthesis) and finishing off with the code body defined between curly braces.

^Return Type (Arguments) {Code Body}

The blocks return type and arguments are both optional parameters, leaving them off will not effect the block.

^{Code Body}

You can pass blocks to a function:

Repeat(12, ^{
   [str appendFormat:@"rand: %d", rand()];
});

Scoped variables (by default) are immutable in blocks. To make these mutable you prefix the keyword __block.

For example

__block int count = 0; //mutable thanks to the __block keyword
int specialValue = 50;  //immutable inside the block
^{
  // do some calculation
   int newValue = someCalculatedValue + specialValue;
   count++; //increment the count every time we do this calculation
}

A good example of when you might want to use a block may be a callback. Since callbacks are generally a grouping of code that will run after an operation.

Strategies on Improving Pair Programming

For last week’s retrospective, we decided to focus on strategies to improve our performance as pair programmers. We chose the “Force Field Analysis” activity from the Agile Retrospectives book, an activity geared toward gaining insight. I was pleased to hear at the end of the retrospective, one of the newer engineers remark:

“In my fifteen years of software industry experience, this is the first bullshit team exercise where I have actually learned something”

There were six of us at Blazing Cloud on Friday, with Liah and Jen-Mei on vacation. We split into two groups, making sure that the two engineers who had been here the longest were in different groups and the two newest engineers were also in different groups. We then had 2 sessions, each 7 minutes long, where each group brainstormed things that would 1) drive and support better pairing and 2) inhibited us from becoming better at pairing.

Then we got back together and reported “round robin” from each team and I listed on the whiteboard (skipping over duplicates) with “drivers” on the left and “inhibitors” on the right. After that we talked about what we think would help or inhibit us the most and drew fat and skinny arrows.

“Even if you can’t be the one on the keyboard, you need to feel responsible for those lines of code.”

Drive / Support Better Pair Programming

  • More Pairing
    • Pair should finish at least one feature (schedule for features, instead of allocating time in days)
    • Think about pairing partners when planning
    • Enforce/Plan Pair Programming
    • Pair overlap when shifting resources for on-going projects
  • Get Advice
    • Ask Wolf to present his knowledge on the subject
    • Ask folks at Pivotal about their experience teaching pairing to new hires and best practices
  • Talk more while pairing
    • Talk first about the problem/give overview of project, before starting to code or look at Feature/Issue Tracker
    • The person typing should vocalize what they are doing
    • Ask “is this a requirement? Is this the thing we should be doing?”
  • Experienced person types as an intro, then less experienced person types when familiar with the project
  • When brainstorming what approach to take, consider taking on different roles, such as one person looking up code and another documenting the plan that both are coming up with.
  • Switch up Pairs
  • Read books about PP — any suggestions here?
  • Pay attention to the other peson
  • Physical presence: pairs need to coordinate when they will be at the office
  • Project Consistency
  • Workstation availability
  • Parallel research at the same time by mutual agreement, but consider reading the found document together, benefits and drawback of reading difeerent things
  • Share experiences and talk to each other
  • One person responsible for drawing outlines, and taking notes while the other is writing the code (driving)
  • Write down strategy on paper before writing code
  • Participants pay attention to the other’s state of mind/focus area
  • If the partner is moving fast ask to slow down

Inhibits Pair Programming

  • Lack of context
  • Pairs working on different tasks
  • Lack of attention
  • Insufficient solo learning -> more experienced person is responsible for guidance:
    • More experienced partner should suggest books, blog posts, articles for less experienced partner for solo learning.
    • Note that less experienced programmer may still have specific relevant experience/reading to educate the more experienced partner and should feel obliged to recommend reading as well
  • People using different tools
  • Interruptions
  • Noise

I closed the retrospective with a “three H” exercise. I handed out sticky notes and asked people to write down what they thought helped or hindered the exercise or if they has a hypothesis (idea) of what we could do better next time.

Help

  • format of the exercise was productive
  • Like that we broke into smaller groups and later brainstormed with the whole group
  • Everyone seemed engaged
  • Structure was great, very clear instructions
  • Focused the team
  • Made everyone think and work together
  • Team have the same concerns that I do
  • I like the visual effects of the arrows to demonstrate significance

Hinder

  • Need larger whiteboard
  • Need larger whiteboard, difficult to read
  • Whiteboard difficult to read
  • Could have had more time
  • We see what is important on the board now, but unsure of how this will be enforced in the future
  • Can’t read whiteboard
  • Need a bigger whiteboard

Lacking flip charts or more whiteboard space, we used the back of the office door to post stickies, which worked quite well:

Deploying a Redmine Wiki to Heroku

One of the recommendations that came out of our recent retrospective was to create a Blazing Cloud wiki where we would store all of our project information, best practices, how to’s, etc.  We wanted a wiki that was free, configurable/customizable and could be easily deployed to either Heroku or another hosting provider we are familiar with.  Additionally, it had to require users to log in to see content since some of the information may be confidential.

We decided to use the Redmine wiki since we have team experience using it at RailsBridge and we like that it is a Rails app.  We have a wealth of Rails knowledge here at Blazing Cloud, so we can address any issues we run into ourselves.  Also, Redmine has an large development community, is hosted on github, good documentation and an active online forum.

Redmine is many things, including a wiki. This post will focus on only on using RedMine for its wiki, specifically

Deploying Redmine on a development box was a snap.  However, moving it to Heroku wasn’t as straightforward.  Hopefully this post will help anyone out there looking to get a Redmine instance running on Heroku quickly.

Installation

Here is the easy part, getting Redmine running on a development box. Note that I’m running a Mac OSX 10.6, Ruby EE 1.8.7. You also need to have the Rails 2.3.5 gem installed on your system. Obviously you will also need the heroku gem to interact with Heroku. Finally you’ll need the taps gem to push your local database to Heroku.

Get a copy of Redmine to you local development machine:

cd <some directory>
git clone http://github.com/edavis10/redmine.git
cd redmine
cp config/database.yml.example config/database.yml
vim config/database.yml   # Change it to reflect the DBMS you want to use.  It is configured to use mysql by default.
rake config/initializers/session_store.rb
rake db:migrate
script/server

Configuration

Now that the Redmine server is running locally we need to configure the Wiki and change the security settings to meet our needs.

  1. In a browser navigate to http://localhost:3000
  2. Redmine comes with a built in administrator account (admin/admin). Login as admin.

Make Redmine password protected:

  1. Click on Settings -> Authentication
  2. Check the Authentication required checkbox and save.

Now everyone must log in to access the wiki!

Create the Redmine project and wiki

  1. Click on the Administration link then New project
  2. Name it whatever makes sense for your installation, I called mine Blazing Cloud Wiki, and the identifier is blazingcloudwiki.  I left all other options in their default state.
  3. Now click on the “wiki” tab on the secondary navigation, not the “wiki” tab on the top navigation!
  4. Notice the start page defaults to “Wiki”.  Name it whatever you like.  I kept the default name for this wiki.
  5. Click save
  6. Next click on the wiki tab on the top navigation
  7. The “wiki” page editor should open by default.  Now add whatever content you want to the wiki home page.
  8. Click save

Change the routes.rb file

  1. Next you need to edit the routes.rb file
  2. Replace the current “map.home” route with…
map.home '', :controller => 'wiki', :id => '1'

Note that the :id parameter is the database project.id assigned to the project you created above.  If you run into issues later you may want to verify that the record in the “projects” table does have ID = 1.

Restart the application server

ctrl+c # on the console that is running the Redmine server to stop it
script/server # to restart it

Verify that the wiki is the home page

  1. In your browser log out and back in to Redmine
  2. You should see the Wiki home page you created above.  Congrats, Redmine is running locally!

Deploying to Heroku

Now that we have the Redmine server running the way we want it to locally we need to make some changes before we can deploy it to Heroku.
In the “redmine” directory…

  1. Edit the .gitignore file
  2. Remove the line “public/plugin_assets”

Add public/plugin_assets to git

  1. If the public/plugin_assets folder does not exist, create this folder and add an empty file named “README” to it. Why? Because git tracks files not folders. So to add the public/plugin_assets folder to git there needs to be a file in the folder for it to track.
  2. If you need to create the README file just put some placeholder text in it. The file just needs to exist, the content does not matter.

Configure Redmine Session Handling

Redmine uses a “key” to encode cookie session data.  To deploy Redmine to Heroku you must modify the config/environment.rb file.

  1. Open the config/environment.rb file and add this line within the Rails::Initializer.run do |config| block. Note that the :key should be _myapp_session and the :secret should have no spaces.
config.action_controller.session = { :key => "_myapp_session", :secret => "somesecretphrase" }

IMPORTANT: your secret phrase should be > 30 characters

Update your git repository

Next, add the new and updated files to your local git repopsitory:

git add .
git commit -m "added the plugin_assets folder to git, updated the routes.rb for the new home page"

Push to Heroku

heroku create
git push heroku master

Create and update the database on Heroku

Run this command to migrate the remote database

heroku rake db:migrate

Next push your local development database to Heroku so the project and wiki you created locally will be available to your users:

heroku db:push #Note that this step requires you have installed the "taps" gem.

Test on Heroku

  1. Finally go to the URL that the “heroku create” command reported.
  2. Log in with admin/admin and verify that the wiki is the home page.

</done!>

WWDC Day One

Apples annual conference kicked off last week and Blazing Cloud wasn’t about to miss it’s chance to attend. Many exciting things happened like a preview of the 4th generation iPhone, a preview of developer tools and a look into safari improvements. The keynote also offered up some interesting statistics and new device capabilities of iPhone 4 that we wanted to share with you.

iBooks

In the first 65 days users have download over 5 million books

Enhancement to iBooks

Highlight sections
Make notes
Bookmark the page in the corner
View and read PDFs

AppStore

Apple supports two platforms, HTML5 and AppStore (225,000 apps)
15,000 apps submitted every week supporting up to 30 languages
95% of apps are approved in 7 days
5% of apps get rejected

Reason why apps are rejected from the AppStore

Use of private Apple APIs
App doesn’t work like developer says it works
The app crashes

Downloads from the AppStore total 5 billion
$1 billion dollars have been paid out to developers thus far

iPhone stats

Us smartphone market share Q1 of 2010

RIM 35%
iPhone 28%
Winmo 19%
Android 9%
Other 9%

US mobile browsers share Q1 of 2010

iPhone 58.2%
Android 22.7%
Rim 12.7%
Other 6.4%

iPhone 4

1st new feature

All new design
9.3 mm thick
“Thinnest smartphone on the planet”
Camera and LED flash
2nd mic for noise cancellation
Integrated Antennas in the structure of the phone
Stainless steel body
Glass on front and back

2nd new feature

Retina display
4 times as many pixels in the same area
326 pixels per inch
300 pixels per inch is the limit of the human retina to differentiate pixels
3.5 inches 960×640
800:1 contrast ratio
IPS technology
78% of the pixels on the iPad on the iPhone

3rd new featured

Powered by the A4 processor
3G talk time equals 7 hours
3G browsing time equals 6 hours
WIFI browsing time equals 10hours
32G and 16G storage options

4th new feature

3 Axis Gyroscope
Pitch, roll and yaw
Rotation about gravity
Gyro plus accelerometer provides 6 axis

5th new feature

New camera system
5megapixel sensor
Backside illuminated sensor
5x digital zoom
LED flash built in
HD video recording 720p at 30 frames per sec
Built in video editing (iMovie application)
LED flash will stay on for video

6th new feature

Renaming iPhone OS to iOS 4
1500 new developer APIs
100 new user features
Multitasking
Folders
Added bing as a search option (with google and yahoo)

Golden master candidate released June 7th

100th million iOS device (iPhone + ipads + iPods) sold

7th new feature

iBooks coming to iPhone
Same features as the iPad
Wireless sync between iPhone, iPad and iPod touch
Same books for all devices at no extra charge

150 million accounts with credit cards (iBook store, AppStore and iTunes Store)
Over 16 billion downloads from all three stores

8th new feature

iADs
Ads keep you in your app which gives a better user experience
Built right into iOS4
Brands so far to sign up (within the last 8 weeks): Nissan, Citi, unilever, AT&T, Chenel, GE, liberty mutual, State Farm, Gieco, Cambell, Sears, JCPennys, Target, Best Buy, DirecTV, tbs, and Disney

These companies committed 60 million dollars towards iAds

9th new feature

FaceTime video calling
Uses wifi, no setup required
Use front or rear camera
Wifi only in 2010 have to work with the cellular companies to get their networks ready for video calling

Price and availability

2 colors white or black
$299 for 32 gig $199 for 16 gig
On sale June 24th
Pre-sale June 15th

iOS 4 upgrades are free for everyone (iPhone + iPod Touch)!!!

Retrospectives: The Fish Bone

Can a fish bone help make a good team great?  That depends on what kind of fish bone we are talking about…

Blazing Cloud recently purchased the book Agile Retrospectives: Making Good Teams Great.  I skimmed through the book’s activities last week in preparation for leading a retrospective.  I picked the Fish Bone activity because it sounded light hearted and fun.  The activity is intended to help a team “Look past symptoms to identify root causes related to an issue…and find reasons behind problems and breakdowns”.  We brainstormed for a few minutes and decided on a topic to discuss:  Restarting Projects. We separated the potential issues surrounding restarting a project into three categories which we wrote down on each of the “bones”. Then we brainstormed things we could change or improve in each category. We ended up with a fish bone chart like this:

I’d say the fish bone exercise helped us dig deeply into one important issue for Blazing Cloud. Some members of the team didn’t like this exercise as much as other retrospective activities, mainly because they felt like there wasn’t enough of a positive ending or feeling of catharsis. For me the most important role played by weekly retrospectives is building team moral and creating an atmosphere of good communication and openness. Any time we get the team together and communicate openly, I think it is helpful.

Cucumber Debugging Tip

Tracking down the root of an unmet expectation is even trickier when you can’t see what the expectation is being compared to… one great resource I’ve been using as-of-late is cucumber_rails_debug. Here’s the readme, in all of its complexity:

Usage:

Add:

require ‘ruby-debug’
require ‘cucumber_rails_debug/steps’

To features/support/env.rb

Then use:

Then debug # opens the debugger
or
Then what #prints out params, url and html

Simply being able get a hold of the resulting html by saying Then what has proven immensely helpful.

ReadWriteWeb Mobile Summit

I was excited to participate in the ReadWriteWeb Mobile Summit last Friday. Elesterama has a nice write-up of the keynote, which was followed by an unconference led by the fabulous Kaliha Hamlin. The unconference format puts the hallway track front and center, allowing for lots of interaction and great conversations. The hot topics of the day were geolocation and the “internet of things.” I attended sessions on mobile video, cross platform application design and development, mobile analytics, identity and privacy — all very engaging and relevant.

Memorable quotes from the day:

“the user experience doesn’t begin when the user starts clicking, it starts much earlier than that… in their minds when they think about using the app” — Nick Bicanic, Echo Echo

Q: Do you do have formal partnerships with the news organizations?
A: They are more like conversations.
— Jim Spencer, Newsy

Cross-Platform Roundup

With a specific interest in cross-platform applications, I attended a compelling session led by TJ Thinakaran of CallFire. It seems everyone creates an iPhone app first — the market is there and it makes for a high-fidelity demo, but what next? BlackBerry still has more marketshare than Android, but many (most?) are business users whose phones are locked down and prevented from downloading apps. I wonder if there are stats on business v. consumer BlackBerry. We also talked about Symbian’s strong international presence, but many of us noted that Symbian isn’t a single platform like iPhone, BB, and Android. The wide variety of Symbian phones fragments the market. Also, someone remarked that for phones that are not iPhone, BB and Android, 95% do not have data plans. At this session and throughout the day, I collected stories of cross-platform development and design:

Micello Maps runs on iPhone and Android with near identical interfaces. Prakash Narayan told the story of implementing first using PhoneGap, but finding that the UI was not responsive. He had more success implementing the applications natively on each platform.

WorldMate, a travel applications, runs on iPhone, BlackBerry, Android, Nokia and Samsung s60, and WinMo. These folks pride themselves on having the best consumer experience and have been at it for a while, so all of their apps are native.

Yapper (Your APP makER) demonstrated their web-based tool for creating RSS-readers for iPhone/iPad and Android.

Echo Echo is a cross-platform library for finding where friends are located (5 platforms: iPhone, Android, BlackBerry, Nokia S60, and WinMo). Nick explained how the design of user interface elements varies cross-platform. The UI implements platform conventions, yet also borrows good ideas from other platforms. The new BlackBerry version (full screenshots here) borrows a badge notification from iPhone UI as well as implementing the standard BlackBerry notifiers. On Android (see full screen shots), the menu bar notifier comes with a slide open list of notifications, as is standard on Android.

I enjoyed the series of demos after lunch, called “speed geeking,” where 12 tables were set up with demos and we switched every 5 minutes. I liked Cinch, an iPhone app and micro podcasting service and MogoTix, a very cool mobile ticketing service that uses QR codes for your ticket and delivers both ticket and ticket scanner on mobile phones. At the end of the day, I was delighted to meet Richard McManus who I’ve long followed as @rww on twitter, and also his colleague Marshall Kirkpatrick (@marshallk).

Rails3, RSpec2 and WebMock

Yesterday we were deep in the middle of coding using RSpec 2 and Rails 3 and needed to verify that a method made a specific http request. I happily discovered WebMock, which appears to be a next generation FakeWeb. Originally developed by Bartosz Blimke, I was happy to see that Sam Phillips had it working with Rspec 2 and Rails 3.

I found the following posts helpful:
RSpec 2 on Rails 3
using bundler with Rails 3

Here’s what I did to set it up (using rvm, which I highly recommend):

rvm gemset create rails3
rvm use 1.8.7@rails3
gem install rails --pre
gem install rspec-rails --pre
gem install sqlite3-ruby
rails rails3_webmock
cd rails3_webmock/
vi Gemfile
group :test do
  gem "rspec", "2.0.0.beta.8"
  gem "rspec-rails", "2.0.0.beta.8"
  gem "webmock", :git => "git://github.com/samdanavia/webmock.git"
end
rails g rspec:install
       exist  lib
      create  lib/tasks/rspec.rake
       exist  config/initializers
      create  config/initializers/rspec_generator.rb
      create  spec
      create  spec/spec_helper.rb
      create  autotest
      create  autotest/discover.rb
bundle install
add these lines to spec/spec_helper:
    require 'webmock/rspec'
    include WebMock

We wrote the following little test to test how to write a spec that verifies that an http request happens:

require 'spec_helper'
class Thing
  def self.call(uri_string)
    uri = URI.parse("http://www.google.com")
    result = Net::HTTP.get(uri)
  end
end
describe Thing do
  it "should call a net API" do
    uri_string = "http://www.google.com"
    stub_request(:get, uri_string).to_return(:body => "something")
    Thing.call(uri_string).should == "something"
    WebMock.should have_requested(:get, uri_string)
  end
end

I put it in spec/lib and can run it with:

spec spec/lib/thing_spec.rb

Note: I don’t need to call “bundle exec” with rspec to ensure that I use the versions specified in the bundle. [Update from@wycats spec_helper loads config/environment.rb which loads application.rb which calls Bundler.setup -- nice.]

Special thanks to @zakkap for the opportunity to dive into Rails 3 on a real project.

startup internships

Blazing Cloud is seeking companies to join the next session of its innovative internship program. The program targets companies which are developing with Ruby on Rails and related tech. Arguably, this shouldn’t even be called an internship — it is really an on ramp for talented engineers hungry to break into Rails development. The company gets to work with an engineer who has received some training, but perhaps no prior experience with Ruby or Rails. They work together for a few months and at the end of the internship, the company has the option to offer the engineer a job. Blazing Cloud seeks to identify candidates who will be a good fit for the target company. While any company is welcome to apply, this is an ideal program for startups or small companies that don’t have the resources to create their own internship program.

I am excited to announce that Blazing Cloud is again working with Captain Recruiter to work with companies to understand their specific needs and identify great candidates for the program. All candidates will be interviewed by Sarah Allen and receive one week of training with experts in Ruby, Rails, CSS, HTML and Javascript.

Read more details or contact Michael Pope if you have questions or are interested in having an intern at your company.