Sunday 30 June 2013

"Scratches to Scratches" a version of David Bowie lyrics for cat people!

"Scratches to Scratches"

Try listening to Ashes to Ashes after reading these lyrics Ben and I came up with:

Do you remember a cat that's been
In such an early lol
I've heard a rumour from Animal Welfare
Oh no, don't say it's true

They got a message
from the Ceiling Cat
"I'm hungry, hope you're hungry too
I've eaten all I've needed to eat
Nomming details following"

The eating of nothing is boring
Just pictures of grumpy cats in synthesis and I
Ain't got no money and I ain't got no food
But I'm hoping to scratch but the sofa it's unallowed

Scratches to scratches, punk to punky
We know Ginger Tom's a punkie
Strung out in catnip's high
Hitting an all-time low

Time and again I tell myself
I'll not eat tonight
But the little can openers call to me
Oh no, not again
I'm playing with a valuable friend
"I'm hungry, hope you're hungry too"
One flash of led light but no catching it

I never done good things
I never done bad things
I never did anything out of the house
Want an claw to break the skin
Wanna eat right now

My mother said to get things done

You'd better not mess with Ginger Tom

The dreaded 'attack prevented by Rack::Protection::AuthenticityToken' problem

I have a Padrino app with multiple sub-apps.
Each of those sub-apps is not really an app, but rather a set of APIs.
To illustrate here is the super-basic layout:

├── API
│   └── v1
│       ├── airports
│       │   ├── app.rb
│       │   ├── config
│       │   │   └── boot.rb
│       │   ├── controllers
│       │   │   └── airports.rb
│       │   ├── db
│       │   │   └── seed.rake
│       │   └── models
│       │       └── airport.rb
│       └── sessions
│           ├── app.rb
│           ├── config
│           │   └── boot.rb
│           ├── controllers
│           │   └── sessions.rb
│           ├── db
│           │   └── seed.rake
│           └── models
├── Capfile
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
│   ├── app.rb
│   ├── controllers
│   ├── helpers
│   └── views
├── config
│   ├── apps.rb
│   ├── boot.rb
│   ├── database.rb
│   └── deploy.rb
├── config.ru
├── db
├── lib
├── library
├── log
├── models
├── public
│   ├── airports.html
│   ├── favicon.ico
│   ├── home.html
│   ├── images
│   │   └── logo.jpg
│   ├── javascripts
│   │   ├── application.js
│   │   ├── jquery-ujs.js
│   │   └── jquery.js
│   ├── login.html
│   ├── logout.html
│   └── stylesheets
│       ├── application.css
│       ├── bootstrap.css
│       └── reset.css
├── spec
│   ├── spec.rake
│   └── spec_helper.rb
└── tmp

Notice that folder 'API'? That's what holds all the sub-apps.
In this simple case, there are just two.
Each has a minimal set of folders and what not.
The bulk of the work is done in the public html files via jQuery.
So for example, you want a html file to have jQuery make a call to login a user:

The html:

<div id="ProfileLogin">
    <h1>LOGIN</h1>

    <div id="ProfileLoginErrors"></div>
    <form id="ProfileLoginForm" method="post">
        <label for="ProfileLoginUsername">Username:</label>
        <input id="ProfileLoginUsername" name="username" type="text"/>
        <label for="ProfileLoginPassword">Password:</label>
        <input id="ProfileLoginPassword" name="password" type="text"/>
        <input id="ProfileLoginSubmit" type="submit" value="Login"/>
    </form>
</div>

The jQuery:

    <script type="text/javascript">
        $(document).ready(function () {
            $('#ProfileLoginForm').on('submit', function (event) {
                event.preventDefault();
                var postData = $('#ProfileLoginForm').serialize()
                $.ajax({
                    type: 'POST',
                    url: '/api/v1/sessions/login',
                    data: postData,
                    success: function (data, textStatus, jqXHR) {
                        if (data.errors) {
                            $('#ProfileLoginErrors').html(data.errors);
                        } else {
                            window.location.href = '/home.html';
                        }
                    },
                    fail: function (data, textStatus, jqXHR) {
                        $('#ProfileLoginErrors').html(data);
                    }
                })
            });
        });
    </script>

Now when you do a POST, and look at the development log, you see this:

 WARN -  attack prevented by Rack::Protection::AuthenticityToken
DEBUG -      POST (0.0035s) /api/v1/sessions/login - 403 Forbidden

Damn. Rack::Protection::AuthenticityToken is stopping us getting in.
The problem is that Rack::Protection uses a hidden value to ensure you're being called from where it expects.

The code responsible is in /Users/[you]/.rvm/gems/ruby-2.0.0-p0/gems/rack-protection-1.5.0/lib/rack/protection/authenticity_token.rb.
It looks like this:

require 'rack/protection'

module Rack
  module Protection
    ##
    # Prevented attack::   CSRF
    # Supported browsers:: all
    # More infos::         http://en.wikipedia.org/wiki/Cross-site_request_forgery
    #
    # Only accepts unsafe HTTP requests if a given access token matches the token
    # included in the session.
    #
    # Compatible with Rails and rack-csrf.
    class AuthenticityToken < Base
      def accepts?(env)
        return true if safe? env
        session = session env
        token   = session[:csrf] ||= session['_csrf_token'] || random_string
        env['HTTP_X_CSRF_TOKEN'] == token or
          Request.new(env).params['authenticity_token'] == token
      end
    end
  end
end

And the reason is returns false is because 'authenticity_token' hasn't been provided.

Don't bother going into and mucking about with:

set :protection, true
set :protect_from_csrf, true
set :allow_disabled_csrf, true

It won't help. The problem is you're not passing the 'authenticity_token' in the POST request.
The cure:

  // REQUIRES the authenticity token appended!
  var postData = $('#ProfileLoginForm').serialize() + '&authenticity_token=' + CSRF_TOKEN;

Now where did that CSRF_TOKEN come from?
Ah. And here's the trick.
First modify your public/javascripts/applications.js and add this:

var CSRF_TOKEN = '';

function configureCSRF() {
    $.ajax({
        type: 'GET', url: '/api/v1/sessions/csrf_token',
        async: false,
        cache: false,
        success: function (data, textStatus, jqXHR) {
            CSRF_TOKEN = data.csrf;
        },
        fail: function (data, textStatus, jqXHR) {
        }
    })
}

Just see that a call is being made to '/api/v1/sessions/csrf_token' and on success, a global CSRF_TOKEN is being set.
So what is the call '/api/v1/sessions/csrf_token' look like?

get :csrf_token, :map => '/csrf_token', :provides => :json do
  logger.debug 'Retrieving csrf_token'
  result = {
      :csrf => session[:csrf]
  }
  JSON.pretty_generate result
end

Ok. So how do you use it?
You change your javascript to:

<script type="text/javascript">
    configureCSRF();
    $(document).ready(function () {
        $('#ProfileLoginForm').on('submit', function (event) {
            event.preventDefault();
            // REQUIRES the authenticity token appended!
            var postData = $('#ProfileLoginForm').serialize() + '&authenticity_token=' + CSRF_TOKEN;
            $.ajax({
                type: 'POST',
                url: '/api/v1/sessions/login',
                data: postData,
                success: function (data, textStatus, jqXHR) {
                    if (data.errors) {
                        $('#ProfileLoginErrors').html(data.errors);
                    } else {
                        window.location.href = '/home.html';
                    }
                },
                fail: function (data, textStatus, jqXHR) {
                    $('#ProfileLoginErrors').html(data);
                }
            })
        });
    });
</script>

You could also add:

headers: {
    'HTTP_X_CSRF_TOKEN': CSRF_TOKEN
},

To the call, but I found it was not necessary.

YMMV.

Friday 28 June 2013

Nitrous.IO! First impression after public beta release

Ok. I signed on as soon as I heard about this at the end of May 2013 because it was exactly what I was looking for.
I just received the email confirming the public beta, I decided to do some testing for 'real' and see if it really suited my needs in the 'real' world.

First step was to login and confirm my email. No problem.
Got extra N2O because I was part of the first signers and that's very cool.

Oh I forgot to mention; I'm using Chrome Version 27.0.1453.116.

So... I configured a box.
Neat easy and fast.
At the main window, I immediately started digging around.
First let's take a look at the system:

action@kimbo-review-99999:~/workspace$ lsb_release -a
No LSB modules are available.  
Distributor ID: Ubuntu 
Description:  Ubuntu 12.04.2 LTS 
Release:  12.04  
Codename: precise  

(FYI: The boxname is not the real one for obvious reasons...)

Ok. Ubuntu 12.04. There's a later version (which I've upgraded to on one of my home machines due to some gigabit network driver issues) but I wasn't about to try to do a sudo apt-get do-release-upgrade for obvious reasons.

Next networking:

action@kimbo-review-99999:~/workspace$ ifconfig -a  
eth0  Link encap:Ethernet  HWaddr 02:00:c0:a8:fd:f6  
  inet addr:192.168.253.246  Bcast:192.168.253.255  Mask:255.255.255.0 
  inet6 addr: fe80::c0ff:fea8:fdf6/64 Scope:Link 
  ...elided for brevity...

Ok. So know we know that subnets they're using. A quick check on the 'boxes' page (which is very well built I have to say) showed my external hostname. A quick check with nslookup shows it as running off AWS:

bandit:~ kim$ nslookup kimbo-review-99999.apse2.actionbox.io
Server:  192.168.170.1
Address: 192.168.170.1#53

Non-authoritative answer:
kimbo-review-99999.apse2.actionbox.io canonical name = ec2-54-252-89-123.ap-southeast-2.compute.amazonaws.com.
Name: ec2-54-252-89-123.ap-southeast-2.compute.amazonaws.com
Address: 54.252.89.123
Now let's see who I am:

action@kimbo-review-99999:~/workspace$ who  
action pts/1  2013-06-28 02:10 (ip-10-240-57-14.ap-southeast-2.compute.internal)
Interesting. User 'action'. So I had a look at `/etc/passwd` and noticed that `www-data` is included for Apache2 and `puppet` for... well... `puppet`.

Now for giggles I did this:

action@kimbo-review-99999:~$ uptime 
 02:27:42 up 23 days,  9:58,  1 user,  load average: 0.02, 0.06, 0.11  
What? 23 days? Ah. Neat. It appears that boxes are 'pre-provisioned' and assigned a name and extras during the 'provisioning' process.
Clever. Like that. Must remember that.

UPDATE: Whoops. Ben pointed out my mistake. They aren't pre-provisioned. It's likely a clone of a VM that was configured 24 days ago. My bad.

Now let's dig around the home folder:

action@kimbo-review-99999:~$ ls -aF /home/action  
./ .bash_history  .bash_profile  .cache/  .gemrc  .nvm/ .rvm/  .tmux.conf  workspace/  .zshrc  
../  .bash_logout .bashrc  .gem/  .gitconfig  .profile  .ssh/  .VERSION  .zprofile 
Ok. The .gemrc has good stuff in it:

install: --no-rdoc --no-ri 
update: --no-rdoc --no-ri  
gem: --no-rdoc --no-ri 
And the .gitconfig also:

[color]  
  ui = auto  
[alias]  
  st = status  
  di = diff  
  ci = commit  
  br = branch  
  llog = log --date=local  
[rerere] 
  enabled = true 
The .profile is normal and basic. The .tmux.conf is fine:

action@kimbo-review-99999:~$ cat .tmux.conf 
# shell  
set-option -g default-shell /bin/bash  
set -g default-terminal "screen-256color"  
There is a .VERSION file which looks interesting. I'm presuming this is part of the puppet process.
I'm also presuming the .ssh folder keys are used for the same purpose (they are for action@apse2.actionbox.io).

Next a dig around /etc...
Noticed there are no cron jobs configured.
The /etc/hosts is interesting as it has that old 'Ubuntu 127.0.1.1' address thing going on.
The /etc/nsswitch.conf is minimal with the hosts set to files dns.
The /etc/resolv.conf is interesting also. Mine is set to this:

action@kimbo-review-99999:~$ cat /etc/resolv.conf 
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) 
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN  
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) 
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN  
nameserver 172.16.0.23 
search us-west-1.compute.internal  
Which is curious since I configured an 'Oceania' box since I'm in Australia. Oh well.

The /etc/timezone file shows the zone is set to Etc/UTC. If you want to I guess you could change this to your local time zone for your logs.

Now the /etc/init.d folder. Mostly standard stuff and no Apache1 or NGinx.
Ah. That's a question I wanted to ask! How the frack do you configure a 'production' environment.
I'll come back to that.

Next I checked the svn and git commands:

action@kimbo-review-99999:~$ svn --version  
svn, version 1.6.17 (r1128011) 
 compiled Dec 17 2011, 16:12:52  
 
Copyright (C) 2000-2009 CollabNet. 
Subversion is open source software, see http://subversion.apache.org/  
This product includes software developed by CollabNet (http://www.Collab.Net/).  
 
The following repository access (RA) modules are available:  
 
* ra_neon : Module for accessing a repository via WebDAV protocol using Neon.  
  - handles 'http' scheme  
  - handles 'https' scheme 
* ra_svn : Module for accessing a repository using the svn network protocol. 
  - with Cyrus SASL authentication 
  - handles 'svn' scheme 
* ra_local : Module for accessing a repository on local disk.  
  - handles 'file' scheme  
 
action@kimbo-review-99999:~$ git --version  
git version 1.7.9.5  
Ok. The git version is less than 1.7.10 so if you are using capistrano from your own physical box you *may* encounter the old "cap constantly asking for passwords even though they've been defined in the deploy.rb" problem.

Ok. I configured a RoR box, so let's do some digging.
First, of course, is ruby.

action@kimbo-review-99999:~/workspace$ ruby -v  
ruby 2.0.0p195 (2013-05-14 revision 40734) [x86_64-linux]  
Neat. Ruby2 p147 has just been released and hopefully that will be installed on new boxes and be upgradeable on already provisioned ones.
Next, let's see some system stats:

Since I am going to test padrino I did a gem install padrino and it installed very quickly!
The rails gems like activerecord were all 3.2 by the way.
So next I changed directory to the `workspace` and generated a plain app.
Blisteringly quick.
So cd to the workspace app and do a bundle install...
Not so blistering, but fast enough.
It showed that we are using sinatra (1.4.3) and padrino (0.11.2).
So let's play with starting the app:

action@kimbo-review-99999:~/workspace/kimbo$ padrino start  
WARN: Unresolved specs during Gem::Specification.reset:  
  rack (>= 1.0.0, ~> 1.4)  
  activesupport (>= 3.1.0) 
WARN: Clearing out unresolved specs. 
Please report a bug if this causes problems. 
=> Padrino/0.11.2 has taken the stage development at http://127.0.0.1:3000 
[2013-06-28 02:57:14] INFO  WEBrick 1.3.1  
[2013-06-28 02:57:14] INFO  ruby 2.0.0 (2013-05-14) [x86_64-linux] 
[2013-06-28 02:57:14] INFO  WEBrick::HTTPServer#start: pid=969 port=3000 

Ok. I browsed to it from the 'preview' menu (port 3000) as that was what WEBrick starts on.

BLAM!

We couldn't find a server running on this port – are you sure there is a server running?
Make sure to bind your server to host 0.0.0.0 (instead of localhost/127.0.0.1).
Ah. Fracking obvious. So a restart of WEBrick with the host and port set:

action@kimbo-review-99999:~/workspace/kimbo$ padrino start -p 3000 -e development -h 0.0.0.0  
WARN: Unresolved specs during Gem::Specification.reset:  
  rack (>= 1.0.0, ~> 1.4)  
  activesupport (>= 3.1.0) 
WARN: Clearing out unresolved specs. 
Please report a bug if this causes problems. 
=> Padrino/0.11.2 has taken the stage development at http://0.0.0.0:3000 
[2013-06-28 03:02:49] INFO  WEBrick 1.3.1  
[2013-06-28 03:02:49] INFO  ruby 2.0.0 (2013-05-14) [x86_64-linux] 
[2013-06-28 03:02:49] INFO  WEBrick::HTTPServer#start: pid=978 port=3000 
And the 'preview' works. Cool. Don't like the look of those gem messages, but I'll leave that for now.

Let's try thin:

action@kimbo-review-99999:~/workspace/kimbo$ padrino start -p 3000 -e development -h 0.0.0.0 -a thin  
WARN: Unresolved specs during Gem::Specification.reset:  
  rack (>= 1.0.0, ~> 1.4)  
  activesupport (>= 3.1.0) 
WARN: Clearing out unresolved specs. 
Please report a bug if this causes problems. 
=> Padrino/0.11.2 has taken the stage development at http://0.0.0.0:3000 
<= Padrino leaves the gun, takes the cannoli 
/home/action/.rvm/gems/ruby-2.0.0-p195/gems/rack-1.5.2/lib/rack/handler/thin.rb:1:in `require': cannot load such file -- thin (LoadError)  
  from /home/action/.rvm/gems/ruby-2.0.0-p195/gems/rack-1.5.2/lib/rack/handler/thin.rb:1:in `<top (required)>' 
  from /home/action/.rvm/gems/ruby-2.0.0-p195/gems/rack-1.5.2/lib/rack/handler.rb:20:in `const_get'  
...elided for brevity...

BLAM!

Odd. Sure I saw it in the gem list --local... Is it installed?

action@kimbo-review-99999:~/workspace/kimbo$ which thin 
action@kimbo-review-99999:~/workspace/kimbo$ gem list --local | grep thin 
So I was wrong. Not installed.
Install it:

action@kimbo-review-99999:~/workspace/kimbo$ gem install thin 
Fetching: eventmachine-1.0.3.gem (100%)  
Building native extensions.  This could take a while...  
Successfully installed eventmachine-1.0.3  
Fetching: daemons-1.1.9.gem (100%) 
Successfully installed daemons-1.1.9 
Fetching: thin-1.5.1.gem (100%)  
Building native extensions.  This could take a while...  
Successfully installed thin-1.5.1  
3 gems installed 
And try again:

action@kimbo-review-99999:~/workspace/kimbo$ padrino start -p 3000 -e development -h 0.0.0.0 -a thin  
WARN: Unresolved specs during Gem::Specification.reset:  
  rack (>= 1.0.0, ~> 1.4)  
  activesupport (>= 3.1.0) 
WARN: Clearing out unresolved specs. 
Please report a bug if this causes problems. 
=> Padrino/0.11.2 has taken the stage development at http://0.0.0.0:3000 
<= Padrino leaves the gun, takes the cannoli 
/home/action/.rvm/gems/ruby-2.0.0-p195/gems/rack-1.5.2/lib/rack/handler/thin.rb:1:in `require': cannot load such file -- thin (LoadError)  
  from /home/action/.rvm/gems/ruby-2.0.0-p195/gems/rack-1.5.2/lib/rack/handler/thin.rb:1:in `<top (required)>' 
  from /home/action/.rvm/gems/ruby-2.0.0-p195/gems/rack-1.5.2/lib/rack/handler.rb:20:in `const_get'  
...elided for brevity...

BLAM!

Oh my lord I'm a dummy. Look at the Gemfile:

# Server requirements
# gem 'thin' # or mongrel
# gem 'trinidad', :platform => 'jruby'
Ok. (Hmm. JRuby would be interesting to test... :-)
Uncomment thin and bundle install and try again:

action@kimbo-review-99999:~/workspace/kimbo$ padrino start -p 3000 -e development -h 0.0.0.0 -a thin  
WARN: Unresolved specs during Gem::Specification.reset:  
  rack (>= 1.0.0, ~> 1.4)  
  activesupport (>= 3.1.0) 
WARN: Clearing out unresolved specs. 
Please report a bug if this causes problems. 
=> Padrino/0.11.2 has taken the stage development at http://0.0.0.0:3000 
>> Thin web server (v1.5.1 codename Straight Razor)  
>> Maximum connections set to 1024 
>> Listening on 0.0.0.0:3000, CTRL+C to stop 
Check preview again and we see the "Sinatra doesn’t know this ditty." which is fine.

Ok. We have got somewhere.
Just because I'm cantankerous, I always enable development and test logging 'my way'.
If you want to do this, edit your config/boot.rb and add this:

Padrino::Logger::Config[:development] = {
  :log_level => :debug,
  :stream => :stdout,
  :format_datetime => '%Y-%m-%d %H:%M:%S'
}
Padrino::Logger::Config[:test] = {
  :log_level => :debug,
  :stream => :stdout,
  :format_datetime => '%Y-%m-%d %H:%M:%S'
}
I normally adjust the format of the output as well, but I won't bore you.
For the record, I also uncomment the I18n.default_locale = :en line.

One thing I need to mention is the font. Why can't I change it?
I know I'm old and have bad eyes, but I prefer a MUCH smaller font to get a better view.

I found that the in browser editor gets it 'almost' right - curlys don't line up the way I like.
I also noticed that when you click on the language button or soft tabs button you have to re-click the button to get the menu to go away.
Clicking in the editor window doesn't do it.
In fact, ALL buttons have this behaviour which I find personally irritating.
You may feel different and that's fine.
I'm spoiled I guess using IDEs like RubyMine.

No I tried the 'chat' mode with Ben (who is downstairs writing a non-technical review) and up came a 'Collaborators' popup.

"This feature is experimental"

Ah. But heck, I'll try it. So I typed in his email and the first part worked.
To get your collaborator to have editing on a file, you open it in your editor and click the 'Collab Mode' button.
Neat. It's experimental - YMMV.

Ok. One thing I noticed is that there is no config/database stuff.
Now while clicking about on the menus I saw the following options on the 'Connect' menu: 'MongoDB (MongoLab)' and 'PostgreSQL (Heroku)'.
These point to help pages with extensive details on how to get connected to them, so I won't go into it in detail.
You'll have to install the ORMs etc and configure them yourself of course.

One niggle: when on the 'Connect' pages, it's tempting to click the 'Help Center' button at the top.
Unfortunately, this is a href to the page you're on.
Not helpful.
So if you want to go to the actual help center, you'll have to use http://help.nitrous.io
The Help Center is well laid out and easy to navigate but does have some

Conclusion:

Frackin love it. It has quirks but is just a development environment. Ben tried it on the iPad and apart from not listening to attached keyboard commands it worked just fine.

You *could* run up NGinx or Apache2 on your box and have a 'stage/production' environment, but if you are using Heroku or ilk, why bother?
You *could* install MySQL or CouchDB or whatever locally. I didn't try it.
Does it violate some part of the acceptable terms or policy?
Can't see anything on the help center about this.
Need to read a bit more.

Just a point... If you're just using the system as a development environment, why would you need a box with 2Gb memory and 20Tb (Correction 20Gb - been in the big data world too long) of storage just for development? At $89.95 a month this seems over the top. There is an implication (for me) that if you're paying $90 a month you would expect to be able to install extra packages and use the box at least as a staging if not production server.

There are some quirks.

If you use git (you do use it right?) then it's cmdline only.
Maybe they'll add it to the menu to allow issue/branch/merge/commit/push like RubyMine but I'm ok with the cmdline.

Also a website oddness. Once you've gone to the https://www.nitrous.io/app once, you can't seem to get back to the main site at http://nitrous.io - you just keep getting redirected to your app boxes page. Browser issue? In any case it's annoying.
I had to open a new 'incognito' window to get to https://www.nitrous.io/pricing.

There are a few spelling errors and what not, but we're all developers right?

Deployment

Heroku integration
Deplying to Heroku

`Deplying`?

Also I have to say that when I followed the link for connecting to GitHub, I saw the authorize page:
"Authorize Nitrous.IO?
The app Nitrous.IO will be able to:
Read your public information.
Update your user profile.
Update your public and private repositories (Commits, Issues, etc)."
Wait. Update my public information? Make *ANY* change to my public and private repos? How do I authorize that from GitHub to stop someone hacking Nitrous and getting access to my repos? Hmm. Kinda facebookie. Scared to enter my details on that one. Same for LinkedIn. Although I'm obviously trusting GitHub and LinkedIn to not get hacked...

Oh! I forgot: READ THE:

'Acceptible use policy' (http://help.nitrous.io/admin-aup/) especially the 'Quota & Limits' part.

'Terms of use' (http://help.nitrous.io/admin-terms/) especially '8. License from You'

But I love it.
I'll use it.
It won't replace RubyMine, but it's not supposed to.
It's to allow project development anywhere not just on your local desktop.
ChromeBook on the train home anyone?

I give it 5 pings.

FYI: You can get me extra N2O by following this link: https://www.nitrous.io/join/CL9mwvyYmwY

UPDATE: I was discussing my post with Ben and something occurred to me. How would this work with teams? I could easily see that a sole developer (or a duo) would find the system fantastic to work with. But with teams? Here's a simple example:

Ben and Kim are developers. Let's say Ben creates a box to work on. He creates an app. Kim wants in. How does that work? She has access to the box as per http://help.nitrous.io/collab/ but does she start a new instance of the app so she can work on it? How does it fit together when you may have dozens of people working on a project? They each have a Nitrous account, and the IDE open, but do they all have a WEBrick (or Thin - whatever) running? And since it's in development mode, won't changes Ben makes to the code get reloaded by Kims instance of WEBrick? They may be silly or just plain invalid questions, but I need to play with that to answer the question fully. Another post.

Tuesday 18 June 2013

Moose the foster cat likes to Chill-Ax...

Moose the foster cat has some quirks.
One is his habit of sitting in what seems to me to be uncomfortable positions.
Here's one:


Odd no?

FYI while I was typing this, he jumped up next to the keyboard and tried to chew my glasses.
While I was wearing them.
I also now have fur up my nose.

OS X Crayon Color Hex Table

I've been doing dozens of wireframes recently using OmniGraffle.
Which, I might add, is very useful for this.
To help me along, I've been using some stencils:

  • jQmobile
  • iPhone 5 UI
  • Mobile - iPhone
  • Best Practice UX Forms Stencil v2.0
  • Ultimate iPhone

All pretty cool.

But when I came to actually creating some of the layouts to HTML and specifically CSS, I found getting the colors from the Color Picker crayons to hex values was irritating. I did try ColorPicker from RubiCode.com, but in the end I just built a table.

For my reference and for anyone else who needs a visual representation of those crayons I put this together:


Cayenne
#800000

Clover
#008000

Midnight
#000080

Tin
#7F7F7F

Asparagus
#808000

Teal
#008080

Plum
#800080

Nickel
#808080

Mocha
#804000

Moss
#008040

Eggplant
#400080

Steel
#666666

Fern
#408000

Ocean
#004080

Maroon
#800040

Aluminum
#999999

Marascino
#FF0000

Spring
#00FF00

Blueberry
#0000FF

Iron
#4C4C4C

Lemon
#FFFF00

Turquoise
#00FFFF

Magenta
#FF00FF

Magnesium
#B3B3B3

Tangerine
#FF8000

Sea Foam
#00FF80

Grape
#8000FF

Tungsten
#333333

Lime
#80FF00

Aqua
#0080FF

Strawberry
#FF0080

Silver
#CCCCCC

Salmon
#FF6666

Flora
#66FF66

Orchid
#6666FF

Lead
#191919

Banana
#FFFF66

Ice
#66FFFF

Bubblegum
#FF66FF

Mercury
#E6E6E6

Cantaloupe
#FFCC66

Spindrift
#66FFCC

Lavender
#CC66FF

Licorice
#000000

Honeydew
#CCFF66

Sky
#66CCFF

Carnation
#FF6FCF

Snow
#FFFFFF


For reference, here is the CSS used for the above table for your cut-n-paste convenience:

.Cayenne { background-color: #800000; }
.Asparagus { background-color: #808000; }
.Clover  { background-color: #008000; }
.Teal  { background-color: #008080; }
.Midnight { background-color: #000080; }
.Plum  { background-color: #800080; }
.Tin  { background-color: #7F7F7F; }
.Nickel  { background-color: #808080; }
.Mocha  { background-color: #804000; }
.Fern  { background-color: #408000; }
.Moss  { background-color: #008040; }
.Ocean  { background-color: #004080; }
.Eggplant { background-color: #400080; }
.Maroon  { background-color: #800040; }
.Steel  { background-color: #666666; }
.Aluminum { background-color: #999999; }
.Marascino { background-color: #FF0000; }
.Lemon  { background-color: #FFFF00; }
.Spring  { background-color: #00FF00; }
.Turquoise { background-color: #00FFFF; }
.Blueberry { background-color: #0000FF; }
.Magenta { background-color: #FF00FF; }
.Iron  { background-color: #4C4C4C; }
.Magnesium { background-color: #B3B3B3; }
.Tangerine { background-color: #FF8000; }
.Lime  { background-color: #80FF00; }
.SeaFoam { background-color: #00FF80; }
.Aqua  { background-color: #0080FF; }
.Grape  { background-color: #8000FF; }
.Strawberry { background-color: #FF0080; }
.Tungsten { background-color: #333333; }
.Silver  { background-color: #CCCCCC; }
.Salmon  { background-color: #FF6666; }
.Banana  { background-color: #FFFF66; }
.Flora  { background-color: #66FF66; }
.Ice  { background-color: #66FFFF; }
.Orchid  { background-color: #6666FF; }
.Bubblegum { background-color: #FF66FF; }
.Lead  { background-color: #191919; }
.Mercury { background-color: #E6E6E6; }
.Cantaloupe { background-color: #FFCC66; }
.Honeydew { background-color: #CCFF66; }
.Spindrift { background-color: #66FFCC; }
.Sky  { background-color: #66CCFF; }
.Lavender { background-color: #CC66FF; }
.Carnation { background-color: #FF6FCF; }
.Licorice { background-color: #000000; }
.Snow  { background-color: #FFFFFF; }

Thursday 13 June 2013

The latest foster cat "Moose" has arrived

Moose (3yrs old) needs some quiet time to recover form ear mites. And what a surprise! Either by accident or breeding he has half a tail! The curious thing is that I don't have those subtle indicators of mood based on tail language. It's only been two days though so some learning has to be done.

And he SOOOO wants to get outside. I guess he was a outdoor cat in his previous life.

Totally affectionate, chatty and incredibly strong. Just a ball of muscle. When you get head butted, you REALLY feel it.

And he 'understands' sliding doors. My office upstairs has a closet with two sliding doors. I closed them. Later I went looking for him and one door was open. Thinking I had not closed it properly I closed it. "MEEEOOOOWW" Oh. He was in there. So I got him out and closed the doors. Later I went upstairs to check my previous FB status in. And one of the sliding doors was open! And guess who was peeking out? I actually caught him opening a sliding door yesterday, but couldn't video it as I was in the bathroom and didn't have my phone. I'll try to get some video of him opening a door when I can.

Some photos:

 

 

Wednesday 12 June 2013

MySQL: "Truncated incorrect DOUBLE value" issue

Man oh man this had me going for quite a while.
Guess I must be getting old.
Anyway, I have a rails site that uses ActiveRecord to a MySQL db.
One table has two columns that are integers, but have to be unsigned as they hold values > 2147483647.
(See http://dev.mysql.com/doc/refman/5.5/en/integer-types.html for more detail)
Anyway, the insertions went just fine.
An example output of the table structure and last few rows is as follows:

mysql> show columns from ranges;
+-----------+------------------+------+-----+---------+----------------+
| Field     | Type             | Null | Key | Default | Extra          |
+-----------+------------------+------+-----+---------+----------------+
| id        | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| starts_at | int(10) unsigned | NO   | PRI | NULL    |                |
| ends_at   | int(10) unsigned | NO   | PRI | NULL    |                |
| iso3_code | varchar(50)      | YES  |     | NULL    |                |
+-----------+------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

mysql> select * from ranges order by id desc limit 5;
+--------+------------+------------+-----------+
| id     | starts_at  | ends_at    | iso3_code |
+--------+------------+------------+-----------+
| 126602 | 4278190080 | 4294967295 | ZZZ       |
| 126601 | 4261412864 | 4278190079 | ZZZ       |
| 126600 | 4244635648 | 4261412863 | ZZZ       |
| 126599 | 4227858432 | 4244635647 | ZZZ       |
| 126598 | 4211081216 | 4227858431 | ZZZ       |
+--------+------------+------------+-----------+
5 rows in set (0.00 sec)

My code gets a value and does a search for the row that includes that number.
So if for instance I want the iso3_code for value 4261412864:

mysql> select * from ranges where starts_at >= 4261412864 
order by starts_at limit 1;
+--------+------------+------------+-----------+
| id     | starts_at  | ends_at    | iso3_code |
+--------+------------+------------+-----------+
| 126601 | 4261412864 | 4278190079 | ZZZ       |
+--------+------------+------------+-----------+
1 row in set (0.00 sec)

The problem was the generated SQL from ActiveRecord shown below:

SELECT `ranges`.* FROM `ranges` WHERE ("starts_at" >= 4261412864) 
ORDER BY `id` LIMIT 1

In the code I got a nil row set.
My first thought was: WTF?
So I cut and pasted it into the mysql command line and saw this:

mysql> SELECT `ranges`.* FROM `ranges` WHERE ("starts_at" >= 4261412864) 
ORDER BY `id` LIMIT 1;
Empty set, 1 warning (0.00 sec)

Wait. Warnings?

mysql> show warnings;
+---------+------+-----------------------------------------------+
| Level   | Code | Message                                       |
+---------+------+-----------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'starts_at' |
+---------+------+-----------------------------------------------+
1 row in set (0.00 sec)

What? DOUBLE? But the column starts_at is an integer...
So I started changing column types, and generally wasting my time for a couple of hours.

To cut a long story short it's programmer blindness.
It's obvious in retrospect.
The select statement is incorrect and I was interpreting the warning incorrectly.
I assumed the message referred to the column starts_at.
Nope.
What the select where clause is actually saying:
"Oh. You want rows from ranges where the string 'starts_at' has a value that is greater or equal to 4261412864. Er. Ok. I'll try to cast the STRING 'starts_at' to a DOUBLE and then do a comparison. Er... Ok. That didn't produce any rows and by the way I tried to cast that STRING to a double and failed."
Dammit.
So I looked at my code (simplified of course):

range = Range.where('"starts_at" >= ?', val).order(:starts_at).first

Oh how could I have been so dumb?
Quick change to test:

range = Range.where('`starts_at` >= ?', val).order(:starts_at).first

And all is fine.

Sunday 9 June 2013

Capistrano Gotchas: 401 http request failed


In my last post I covered the basics of deploying a simple Sinatra app to a hosted server using Capistrano.
Later that week I had occasion to use Capistrano for real.
A repo on Github was in a private repository owned by someone else.
I was a contributor to that repo and was using Capistrano to deploy it.

During that exercise I encountered a problem.
I kept getting:
error: The requested URL returned error: 401 while accessing 
# https://github.com/GITHUB_USER/GITHUB_REPO.git/info/refs?service=git-receive-pack 
# fatal: HTTP request failed 

What confused me was that because I wasn't using ssh keys, I was being asked to enter the username and password for the repo despite them being defined in the config/deploy.rb.
I struggled with this for some time running debugs, traces and what not.
In the end the answer dawned on me like a large incontinent beast.

The version of git on my local machine (OS-X) was 1.7.7 and on the server (Ubuntu 10.04) was 1.7.0.
Bugger.
So I downloaded the latest version of git source code which at the time I did it was 1.8.3.
Doing:
make prefix=/opt/git all doc info 

Caused a slew of missing components for generating documentation, and since I didn't need it on the server, I just did:
make prefix=/opt/git all 

After that completed, I did:
sudo make prefix=/opt/git install 

Which installed all the components into /opt/git.

I then adjusted the config/deploy.rb thus:
# Specify the EXACT locations of git 
set :scm_command, '/opt/git/bin/git' 
set :local_scm_command, '/usr/bin/git' 

And voila! The cap deploy worked as advertised.

For reference I did read: https://help.github.com/articles/https-cloning-errors

I have to say that Capistrano seems to have the 'growing like topsy' disease.
The options in the config/deploy.rb seem to be organic rather than structured.
What I mean by that is that the scm_username and scm_password are structured and give a clear indication of their purpose, while the remote server variables user and password do not.
I would have perhaps named variables in a hierarchical manner using yml for example:

application:
    name: [APP_NAME]
repositories:
    scm:
      uses: git
      username: [GIT_USER]
      password: [GIT_PASS]
      branch: master
local:
  command: /usr/bin/git
remotes:
  [SERVER_NAME]:
    web: www.somedomain.com
    app: www.somedomain.com
    db: www.somedomain.com
    username: [SERVER_USERNAME]
    password: [SERVER_PASSWORD]
    deploy_to: /path/to/site/folder
    command: /opt/git/bin/git
    keep_releases: 5
    use_sudo: false
    before:
      ...set of tasks...
    after:
      ...set of tasks...
    term_options: { pty: false } # or whatever
  [ANOTHER_SERVER]:
    ...etc...

Just a thought.

Monday 3 June 2013

Let's Deploy! Sinatra-DataMapper-Sqlite-MySQL-Hosted-Capistrano

Ok. This is a long post and mirrors the README.md in https://github.com/ZenGirl/Sinatra-DataMapper-Sqlite-MySQL-Hosted-Capistrano so go there if you want to view the application files.

Local development

Example Sinatra app using Sqlite development, MySQL production deployed to a hosted server via capistrano

To run, do:
bundle install

Then simply type:
rackup

For testing production, simply use:
RACKUP_ENV=production rackup

For reference purposes the output of bundle show is as follows:
  Gems included by the bundle:
    * addressable (2.2.8)
    * bcrypt-ruby (3.0.1)
    * bundler (1.3.5)
    * data_mapper (1.2.0)
    * data_objects (0.10.12)
    * dm-aggregates (1.2.0)
    * dm-constraints (1.2.0)
    * dm-core (1.2.0)
    * dm-do-adapter (1.2.0)
    * dm-migrations (1.2.0)
    * dm-mysql-adapter (1.2.0)
    * dm-serializer (1.2.2)
    * dm-sqlite-adapter (1.2.0)
    * dm-timestamps (1.2.0)
    * dm-transactions (1.2.0)
    * dm-types (1.2.2)
    * dm-validations (1.2.0)
    * do_mysql (0.10.12)
    * do_sqlite3 (0.10.12)
    * fastercsv (1.5.5)
    * json (1.8.0)
    * json_pure (1.8.0)
    * log4r (1.1.10)
    * multi_json (1.7.4)
    * mysql (2.9.1)
    * rack (1.5.2)
    * rack-protection (1.5.0)
    * sinatra (1.4.2)
    * sqlite3 (1.3.7)
      * stringex (1.5.1)
      * tilt (1.4.1)
      * uuidtools (2.1.4)

The system does not install the database rows by default.
You can cause this by making a call to http://whatever.com/idl/SEED_SECRET
Obviously this is FAR from secure, and is shown simply to illustrate the point.
The seed data itself is in the seed_data folder.
Be aware that the IpToCountry.2013-05.27.csv is huge.
It was drawn down from http://software77.net/geo-ip and massaged to create the addresses.csv and countries.csv.
In that folder is an example shell script to create new csv files based on a new original file.

I strongly suggest reading the application.rb file as it is chock full of comments.

Production

Passenger

Installing Passenger is simple assuming you have all the required packages installed.
The Passenger install users guides are here:

http://www.modrails.com/documentation/Users%20guide%20Apache.html
http://www.modrails.com/documentation/Users%20guide%20Nginx.html

The users guides are *long* but the gist is:

1) gem install passenger
2) passenger-install-apache2-module

To ensure you have the Apache headers and linux modules, review: http://www.modrails.com/documentation/Users%20guide%20Apache.html#troubleshooting.
For NGinx users, the install is virtually identical.

In any case, all the installer does is use your ruby install to create some files:
  [/etc/apache2/mods-available/passenger.conf]
PassengerRoot /opt/ruby-2.0.0-p195/lib/ruby/gems/2.0.0/gems/passenger-4.0.2
PassengerRuby /opt/ruby-2.0.0-p195/bin/ruby
PassengerDefaultRuby /opt/ruby-2.0.0-p195/bin/ruby
PassengerMaxPoolSize 6
PassengerPoolIdleTime 0
PassengerMaxRequests 1000

and
  [/etc/apache2/mods-available/passenger.load]
LoadModule passenger_module /opt/ruby-2.0.0-p195/lib/ruby/gems/2.0.0/gems/passenger-4.0.2/libout/apache2/mod_passenger.so

OBVIOUSLY you'll want to change the path to the gems and ruby2!

You'll have to `a2enmod passenger` to create the links from `/etc/apache2/mods-enabled` to `/etc/apache2/mods-available`.
After that, just restart Apache.

DNS

You can't make your site run properly (I'm excluding accessing it from an IP address) without a name.
So access your DNS zone settings (netregistry or whatever) and ensure you have the A or CNAME records configured for that name.
In the case of this application the name is `sdshmc.mydomain.com` and the record in DNS looks like this:
sdshmc 3600 IN A 192.168.170.115

Once the name has propagated, you can check it like this:
my_remote_name@my_remote_host:~$ nslookup sdshmc.mydomain.com
Server:  74.207.242.5
Address: 74.207.242.5#53

Non-authoritative answer:
Name: sdshmc.mydomain.com
Address: 192.168.170.115

Cool.

Database

The site uses MySQL in production, so you will have to ensure that the database defined in config/database.yml exists and is accessible.
Here is a mysql command line example:
mysql> create database sdshmc;
Query OK, 1 row affected (0.01 sec)

mysql> show create database sdshmc;
+----------+-----------------------------------------------------------------+
| Database | Create Database                                                 |
+----------+-----------------------------------------------------------------+
| sdshmc   | CREATE DATABASE `sdshmc` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+----------+-----------------------------------------------------------------+
1 row in set (0.06 sec)

mysql> grant all on sdshmc.* to 'sdshmc'@'localhost' identified by 'sdshmc';
Query OK, 0 rows affected (0.21 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

Don't worry about creating tables as DataMapper will do that.

Web Server

I'm illustrating Apache2 here, but the steps are similar for NGinx.
(I'll cover NGinx setup in a separate post)

First you need to configure the directory where your site will run from.
Before we use capistrano, we'll just test whether your site even functions correctly.
So create your site folder, such as `sdshmc` and use scp to copy your sites files into it.

This is only a test step to make sure your web server configuration is working at all.
Although you could use this method (copying individually changed files) to work on your remote site, it is easy to forget a change.
It's also a PITA.

An example transcript of this looks like this:
my_home_machine:sdshmc my_user_name$ scp -r * my_remote_name@my_remote_host:/home/my_remote_name/sdshmc
The authenticity of host 'my_remote_host (192.168.170.115)' can't be established.
RSA key fingerprint is d6:60:3e:51:07:e0:43:82:cf:5b:49:76:e7:1a:ef:d6.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'my_remote_host' (RSA) to the list of known hosts.
my_remote_name@my_remote_host's password:
Gemfile                      100% 1027     1.0KB/s   00:00
Gemfile.lock                 100% 2000     2.0KB/s   00:00
LICENSE                      100% 1061     1.0KB/s   00:00
README.md                    100% 7036     6.9KB/s   00:00
application.rb               100%   16KB  15.7KB/s   00:00
database.yml                 100%  938     0.9KB/s   00:00
config.ru                    100%  587     0.6KB/s   00:00
...elided for brevity...
IpToCountry.2013-05-27.csv   100% 8827KB  76.1KB/s   01:56
make_seed_data.sh            100%  654     0.6KB/s   00:00

Obviously you'll have different user names, hosts, IP addresses, fingerprints etc.
Once you're done your folder, probably `/home/yourname/sdshmc` will look something like this:
my_remote_name@my_remote_host:~/sdshmc$ tree .
.
├── application.rb
├── config
│   └── database.yml
├── config.ru
├── db
│   └── development.db
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── logs
│   ├── access.log
│   ├── development.log
│   └── production.log
├── models
│   ├── Country.rb
│   ├── IPv4.rb
│   └── Vendor.rb
├── public
│   └── folder_must_exist
├── README.md
└── seed_data
    ├── addresses.csv
    ├── countries.csv
    ├── IpToCountry.2013-05-27.csv
    └── make_seed_data.sh

5 directories, 18 files

BTW: The tree program is very useful to see the structure of a folder.

Now there is an important step.
You need to ensure your gems are installed.
First check your ruby version is valid:
my_remote_name@my_remote_host:~/sdshmc$ export PATH=/opt/ruby-2.0.0-p195/bin:$PATH
my_remote_name@my_remote_host:~/sdshmc$ which ruby
/opt/ruby-2.0.0-p195/bin/ruby
my_remote_name@my_remote_host:~/sdshmc$ ruby -v
ruby 2.0.0p195 (2013-05-14 revision 40734) [x86_64-linux]

In my case I have a global install of ruby2.
You might have an RVM version.
Whatever.
Now we do a bundle install:
my_remote_name@my_remote_host:~/sdshmc$ which bundle
/opt/ruby-2.0.0-p195/bin/bundle
my_remote_name@my_remote_host:~/sdshmc$ bundle install
Fetching gem metadata from https://rubygems.org/.........
Fetching gem metadata from https://rubygems.org/..
Installing addressable (2.2.8)
Installing bcrypt-ruby (3.0.1)
Installing dm-core (1.2.0)
Installing dm-aggregates (1.2.0)
Installing dm-constraints (1.2.0)
Installing dm-migrations (1.2.0)
Installing fastercsv (1.5.5)
Using json (1.8.0)
Installing json_pure (1.8.0)
Installing multi_json (1.7.4)
Installing dm-serializer (1.2.2)
Installing dm-timestamps (1.2.0)
Installing dm-transactions (1.2.0)
Installing stringex (1.5.1)
Installing uuidtools (2.1.4)
Installing dm-types (1.2.2)
Installing dm-validations (1.2.0)
Installing data_mapper (1.2.0)
Installing data_objects (0.10.12)
Installing dm-do-adapter (1.2.0)
Installing do_mysql (0.10.12)
Installing dm-mysql-adapter (1.2.0)
Installing do_sqlite3 (0.10.12)
Installing dm-sqlite-adapter (1.2.0)
Installing log4r (1.1.10)
Installing mysql (2.9.1)
Using rack (1.5.2)
Installing rack-protection (1.5.0)
Using tilt (1.4.1)
Installing sinatra (1.4.2)
Installing sqlite3 (1.3.7)
Using bundler (1.3.5)
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.

So... How do we see if it's working?
Well first we create a virtual host definition.
For Apache2, this is done by creating a file in /etc/apache2/sites-available named after your site.
In this case we're creating a site named sdshmc.mydomain.com, so the file will be /etc/apache2/sites-available/sdshmc.mydomain.com.
I have included an example Apache2 VHost config here:
<VirtualHost *:80>
  ServerAdmin     your.name@some.email.service.com

  ServerName      sdshmc.mydomain.com
  # If you want an alias for your site using CNAME for example, do womthing like this:
  #ServerAlias     sdshmc.your-app-name.com

  ServerSignature Off

  # Points to your site files
  # NOTE: You must have a public folder even if it's empty
  DocumentRoot    /home/some_user/sdshmc/public

  # Only interested in warnings and above
  LogLevel        warn

  # For access and error logging
  # Note that you'll have to ensure this folder is wriatble by www-data
  ErrorLog        /home/some_user/sdshmc/logs/error.log
  CustomLog       /home/some_user/sdshmc/logs/access.log combined

  # If you're using cgi-bin programs
  #ScriptAlias     /cgi-bin/ /usr/lib/cgi-bin/

  # The directory where the site is stored
  # NOTE the trailing slash!
  <Directory /home/some_user/sdshmc/public/>
    Options       Indexes FollowSymLinks MultiViews
    AllowOverride All
    # We allow first, then deny
    Order         Allow,Deny
    # For security during testing, put your home IP address here
    #Allow from    200.200.200.200
    # Otherwise, use this:
    Allow from     All
  </Directory>
</VirtualHost>

Edit your file like this:
sudo vi /etc/apache2/sites-available/sdshmc.mydomain.com

SideStep: The logs folder will need to be writable by Apache, so do this:
sudo chown www-data.www-data logs

Enable the site:
sudo a2ensite sdshmc.mydomain.com

You'll see a message about reloading Apache. So we need to do that:
sudo /etc/init.d/apache2 reload

You should see something like this:
sudo /etc/init.d/apache2 reload
 * Reloading web server config apache2   [ OK ]

Sometimes you make a syntax error like I did doing this. An example might be:
Syntax error on line 30 of /etc/apache2/sites-enabled/sdshmc.mydomain.com:
order takes one argument, 'allow,deny', 'deny,allow', or 'mutual-failure'
   ...fail!

As you can see, I made a mistake with the Order directive.
In my case I had a space between the 'Allow,' and the 'Deny'

Ok. You should now be able to browse to http://sdshmc.mydomain.com and see your first message:
{"errors":["You need to provide an IPv4 address"]}

Now try `http://sdshmc.mydomain.com/60.240.233.28/this_app/df76f1e54f63eae442ebf3b4d6c46531`:
{"errors":["Unknown vendor"]}

What's wrong?
Well the database tables have not been seeded.
To see that, go to your mysql command prompt and look at the created tables:
mysql> show table status;
+-----------+--------+---------+------------+------+-...-+
| Name      | Engine | Version | Row_format | Rows | ... |
+-----------+--------+---------+------------+------+-...-+
| addresses | InnoDB |      10 | Compact    |    0 | ... |
| countries | InnoDB |      10 | Compact    |    0 | ... |
| vendors   | InnoDB |      10 | Compact    |    0 | ... |
+-----------+--------+---------+------------+------+-...-+
3 rows in set (0.00 sec)

The tables are created but are empty.
Now we check the logs to see we now have a production.log:
my_remote_name@my_remote_host:~/sdshmc/logs$ ls -l
total 16
-rw-r--r-- 1 my_remote_name my_remote_name 5446 2013-06-03 00:09 access.log
-rw-r--r-- 1 my_remote_name my_remote_name    0 2013-06-02 23:20 development.log
-rw-r--r-- 1 root           root            999 2013-06-02 23:48 error.log
-rw-r--r-- 1 my_remote_name my_remote_name  576 2013-06-03 00:07 production.log

If you tail it while we use the http://sdshmc.mydomain.com/idl/SEED_SECRET url you'll see the tables filling up with data.
Afterwards you can use the mysql command line to check the results:
mysql> show table status;
+-----------+--------+---------+------------+--------+-...-+
| Name      | Engine | Version | Row_format | Rows   | ... |
+-----------+--------+---------+------------+--------+-...-+
| addresses | InnoDB |      10 | Compact    | 127157 | ... |
| countries | InnoDB |      10 | Compact    |    238 | ... |
| vendors   | InnoDB |      10 | Compact    |      2 | ... |
+-----------+--------+---------+------------+--------+-...-+
3 rows in set (0.00 sec)

Now a call to http://sdshmc.mydomain.com/60.240.233.28/this_app/df76f1e54f63eae442ebf3b4d6c46531 yields:
{"iso3":"AUS","country":"Australia"}

And a call to http://sdshmc.mydomain.com/60.240.233.28/this_app/df76f1e54f63eae442ebf3b4d6c46531.xml yields:
<response>
  <iso3>AUS</iso3>
  <country>Australia</country>
</response>

In browsers you'll see this:
This XML file does not appear to have any style information associated with it. The document tree is shown below.

The error is because we didn't include an <xml .../> header.

We'll be deleting all the code later and pointing to a capistrano structured layout later, but this proves our app works.

Capistrano

Finally. We're close.
First you need to ensure the capistrano gem is installed:
gem install capistrano
Fetching: highline-1.6.19.gem (100%)
Successfully installed highline-1.6.19
Fetching: net-ssh-2.6.7.gem (100%)
Successfully installed net-ssh-2.6.7
Fetching: net-sftp-2.1.2.gem (100%)
Successfully installed net-sftp-2.1.2
Fetching: net-scp-1.1.1.gem (100%)
Successfully installed net-scp-1.1.1
Fetching: net-ssh-gateway-1.2.0.gem (100%)
Successfully installed net-ssh-gateway-1.2.0
Fetching: capistrano-2.15.4.gem (100%)
Successfully installed capistrano-2.15.4
6 gems installed

Next we need to capify our app.
If you're doing this yourself, you'll have to do this:
capify .
[add] writing './Capfile'
[add] writing './config/deploy.rb'
[done] capified!

If you're using this application, I've already done it.
You'll see that two files get created; Capfile and config/deploy.rb.

Capfile is very basic and we won't need to fiddle with it:
load 'deploy'
# Uncomment if you are using Rails' asset pipeline
    # load 'deploy/assets'
load 'config/deploy' # remove this line to skip loading any of the default tasks

The config/deploy.rb is where most of the work is done.
Initially it will be like this:
set :application, "set your application name here"
set :repository,  "set your repository location here"

# set :scm, :git # You can set :scm explicitly or Capistrano will make an intelligent guess based on known version control directory names
# Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`

role :web, "your web-server here"                          # Your HTTP server, Apache/etc
role :app, "your app-server here"                          # This may be the same as your `Web` server
role :db,  "your primary db-server here", :primary => true # This is where Rails migrations will run
role :db,  "your slave db-server here"

# if you want to clean up old releases on each deploy uncomment this:
# after "deploy:restart", "deploy:cleanup"

# if you're still using the script/reaper helper you will need
# these http://github.com/rails/irs_process_scripts

# If you are using Passenger mod_rails uncomment this:
# namespace :deploy do
#   task :start do ; end
#   task :stop do ; end
#   task :restart, :roles => :app, :except => { :no_release => true } do
#     run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
#   end
# end

I've made some changes to the deploy.rb to illustrate what you need to change:
set :application, 'IPv42Country'

# I'm using git. If you use svn, put that here
set :scm, :git
set :repository, 'https://github.com/YOUR_GIT_USERNAME/YOUR_GIT_PROJECT.git'
set :scm_username, 'YOUR_GIT_LOGIN'
set :scm_passphrase, 'YOUR_GIT_PASSWORD'

# Must be set for the password prompt from git to work
default_run_options[:pty] = true

# The server user and password
set :user, 'YOUR_REMOTE_SERVER_USERNAME'

# We always deploy the master branch
set :branch, 'master'

# Where we are going to deploy the code
set :deploy_to, '/home/your_remote_folder/sdshmc'

# Now we set roles
role :web, 'sdshmc.mydomain.com'
role :app, 'sdshmc.mydomain.com'
role :db,  'sdshmc.mydomain.com', :primary => true # This is where Rails migrations will run
# We could have done this:
# server 'sdshmc.mydomain.com', :app, :web, :db, :primary => true


# if you want to clean up old releases on each deploy uncomment this:
# after "deploy:restart", "deploy:cleanup"

# if you're still using the script/reaper helper you will need
# these http://github.com/rails/irs_process_scripts

# If you are using Passenger mod_rails uncomment this:
namespace :deploy do
  task :start do ; end
  task :stop do ; end
  task :restart, :roles => :app, :except => { :no_release => true } do
    run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
  end
end

You'll have to fix all the locations I have used of course.
Now we have to get rid of that test app we used scp to load.
So go to your server and get rid of all the files except logs.
Your tree should look like this:
my_remote_name@my_remote_host:~/sdshmc$ tree .
.
└── logs
    ├── access.log
    ├── development.log
    ├── error.log
    └── production.log

1 directory, 4 files

Now we run our first capistrano command!
On your local machine, in the root folder of your site you do the initial setup:
my_machine:your_root_site_folder your_username$ cap deploy:setup
  * 2013-06-03 11:55:08 executing `deploy:setup'
  * executing "sudo -p 'sudo password: ' mkdir -p /home/remote_user/sdshmc \
    /home/remote_user/sdshmc/releases \
    /home/remote_user/sdshmc/shared \
    /home/remote_user/sdshmc/shared/system \
    /home/remote_user/sdshmc/shared/log \
    /home/remote_user/sdshmc/shared/pids"
    servers: ["sdshmc.mydomain.com"]
Password:
    [sdshmc.mydomain.com] executing command
 ** [out :: sdshmc.mydomain.com]
    command finished in 903ms
  * executing "sudo -p 'sudo password: ' chmod g+w /home/remote_user/sdshmc \
    /home/remote_user/sdshmc/releases \
    /home/remote_user/sdshmc/shared \
    /home/remote_user/sdshmc/shared/system \
    /home/remote_user/sdshmc/shared/log \
    /home/remote_user/sdshmc/shared/pids"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
    command finished in 695ms

I've split the lines up to make it clear what is happening.
Notice that you had to enter your remote servers password.

Your remote server tree should now look like this:
my_remote_name@my_remote_host:~/sdshmc$ tree .
.
├── logs
│   ├── access.log
│   ├── development.log
│   ├── error.log
│   └── production.log
├── releases
└── shared
    ├── log
    ├── pids
    └── system

6 directories, 4 files

See the new folders?

Ok. Now we do a check on your local machine:
my_machine:your_root_site_folder your_username$ cap deploy:check
  * 2013-06-03 12:05:44 executing `deploy:check'
  * executing "test -d /home/remote_user/sdshmc/releases"
    servers: ["sdshmc.mydomain.com"]
Password:
    [sdshmc.mydomain.com] executing command
    command finished in 650ms
  * executing "test -w /home/remote_user/sdshmc"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
    command finished in 649ms
  * executing "test -w /home/remote_user/sdshmc/releases"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
    command finished in 649ms
  * executing "which git"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
    command finished in 639ms
The following dependencies failed. Please check them and try again:
--> You do not have permissions to write to `/home/remote_user/sdshmc/releases'. (sdshmc.mydomain.com)

Ah. Now see what happened? So we check the permissions for our user:
my_remote_name@my_remote_host:~/sdshmc$ ls -l
total 12
drwxr-xr-x 2 www-data www-data 4096 2013-06-02 23:36 logs
drwxrwxr-x 2 root     root     4096 2013-06-03 01:55 releases
drwxrwxr-x 5 root     root     4096 2013-06-03 01:55 shared

Well there's the problem. root owns releases and shared.
So we need to change the permissions:
my_remote_name@my_remote_host:~/sdshmc$ sudo chown -Rv my_remote_name.my_remote_name releases shared
[sudo] password for my_remote_name:
changed ownership of `releases' to my_remote_name:my_remote_name
changed ownership of `shared/pids' to my_remote_name:my_remote_name
changed ownership of `shared/log' to my_remote_name:my_remote_name
changed ownership of `shared/system' to my_remote_name:my_remote_name
changed ownership of `shared' to my_remote_name:my_remote_name
my_remote_name@my_remote_host:~/sdshmc$ ls -l
total 12
drwxr-xr-x 2 www-data        www-data        4096 2013-06-02 23:36 logs
drwxrwxr-x 2 my_remote_name  my_remote_name  4096 2013-06-03 01:55 releases
drwxrwxr-x 5 my_remote_name  my_remote_name  4096 2013-06-03 01:55 shared

And a test of cap deploy:check locally again:
my_machine:your_root_site_folder your_username$ cap deploy:check
  * 2013-06-03 12:05:44 executing `deploy:check'
  * executing "test -d /home/remote_user/sdshmc/releases"
    servers: ["sdshmc.mydomain.com"]
Password:
    [sdshmc.mydomain.com] executing command
    command finished in 650ms
  * executing "test -w /home/remote_user/sdshmc"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
    command finished in 649ms
  * executing "test -w /home/remote_user/sdshmc/releases"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
    command finished in 649ms
  * executing "which git"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
    command finished in 639ms
You appear to have all necessary dependencies installed

Ok. Next step.
Normally at this point you would create the database and add credentials, but we've already done that above.
So we skip onto actually doing a test push to our server.
So on our local machine we do:
my_machine:your_root_site_folder your_username$ cap deploy:update
  * 2013-06-03 12:14:32 executing `deploy:update'
 ** transaction: start
  * 2013-06-03 12:14:32 executing `deploy:update_code'
    executing locally: "git ls-remote https://github.com/YOUR_GIT_USER/YOUR_GIT_PROJECT.git master"
    command finished in 1840ms
  * executing "git clone -q -b master https://github.com/YOUR_GIT_USER/YOUR_GIT_PROJECT.git \
    /home/remote_user/sdshmc/releases/20130603021433 && \
    cd /home/remote_user/sdshmc/releases/20130603021433 && \
    git checkout -q -b deploy f288a7baf7389fd486a777755a3415bec8a90025 && \
    (echo f288a7baf7389fd486a777755a3415bec8a90025 > /home/remote_user/sdshmc/releases/20130603021433/REVISION)"
    servers: ["sdshmc.mydomain.com"]
Password:
    [sdshmc.mydomain.com] executing command
 ** [sdshmc.mydomain.com :: out] Unpacking objects:   1% (1/74)
 ** [sdshmc.mydomain.com :: out] Unpacking objects:   2% (2/74)
 ** [sdshmc.mydomain.com :: out] Unpacking objects:   4% (3/74)
 ** [sdshmc.mydomain.com :: out] Unpacking objects:   5% (4/74)
 ...elided for brevity...
 ** [sdshmc.mydomain.com :: out] Unpacking objects:  97% (72/74)
 ** [sdshmc.mydomain.com :: out] Unpacking objects:  98% (73/74)
Unpacking objects: 100% (74/74), done.] Unpacking objects: 100% (74/74)
    command finished in 4488ms
  * 2013-06-03 12:14:47 executing `deploy:finalize_update'
  * executing "chmod -R -- g+w /home/remote_user/sdshmc/releases/20130603021433 && \
    rm -rf -- /home/remote_user/sdshmc/releases/20130603021433/public/system && \
    mkdir -p -- /home/remote_user/sdshmc/releases/20130603021433/public/ && \
    ln -s -- /home/remote_user/sdshmc/shared/system /home/remote_user/sdshmc/releases/20130603021433/public/system && \
    rm -rf -- /home/remote_user/sdshmc/releases/20130603021433/log && \
    ln -s -- /home/remote_user/sdshmc/shared/log /home/remote_user/sdshmc/releases/20130603021433/log && \
    rm -rf -- /home/remote_user/sdshmc/releases/20130603021433/tmp/pids && \
    mkdir -p -- /home/remote_user/sdshmc/releases/20130603021433/tmp/ && \
    ln -s -- /home/remote_user/sdshmc/shared/pids /home/remote_user/sdshmc/releases/20130603021433/tmp/pids"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
    command finished in 732ms
  * executing "find /home/remote_user/sdshmc/releases/20130603021433/public/images \
    /home/remote_user/sdshmc/releases/20130603021433/public/stylesheets \
    /home/remote_user/sdshmc/releases/20130603021433/public/javascripts -exec touch -t 201306030214.48 -- {} ';'; true"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
 ** [out :: sdshmc.mydomain.com] find: `/home/remote_user/sdshmc/releases/20130603021433/public/images'
 ** [out :: sdshmc.mydomain.com] : No such file or directory
 ** [out :: sdshmc.mydomain.com] find:
 ** [out :: sdshmc.mydomain.com] `/home/remote_user/sdshmc/releases/20130603021433/public/stylesheets': No such file or directory
 ** [out :: sdshmc.mydomain.com]
 ** [out :: sdshmc.mydomain.com] find: `/home/remote_user/sdshmc/releases/20130603021433/public/javascripts'
 ** [out :: sdshmc.mydomain.com] : No such file or directory
    command finished in 669ms
  * 2013-06-03 12:14:48 executing `deploy:create_symlink'
  * executing "sudo -p 'sudo password: ' rm -f /home/remote_user/sdshmc/current && \
    sudo -p 'sudo password: ' ln -s /home/remote_user/sdshmc/releases/20130603021433 /home/remote_user/sdshmc/current"
    servers: ["sdshmc.mydomain.com"]
    [sdshmc.mydomain.com] executing command
 ** [out :: sdshmc.mydomain.com]
    command finished in 897ms
 ** transaction: commit

Whoah! Lot's happened.
I won't go through all the goriness of it.
It's easier to show what happened on our remote server:
my_remote_name@my_remote_host~/sdshmc$ tree .
.
├── current -> /home/remote_user/sdshmc/releases/20130603021433
├── logs
│   ├── access.log
│   ├── development.log
│   ├── error.log
│   └── production.log
├── releases
│   └── 20130603021433
│       ├── application.rb
│       ├── config
│       │   └── database.yml
│       ├── config.ru
│       ├── db
│       │   └── development.db
│       ├── Gemfile
│       ├── LICENSE
│       ├── log -> /home/remote_user/sdshmc/shared/log
│       ├── logs
│       │   ├── access.log
│       │   ├── development.log
│       │   └── production.log
│       ├── models
│       │   ├── Country.rb
│       │   ├── IPv4.rb
│       │   └── Vendor.rb
│       ├── public
│       │   ├── folder_must_exist
│       │   └── system -> /home/remote_user/sdshmc/shared/system
│       ├── README.md
│       ├── REVISION
│       ├── seed_data
│       │   ├── addresses.csv
│       │   ├── countries.csv
│       │   ├── IpToCountry.2013-05-27.csv
│       │   └── make_seed_data.sh
│       └── tmp
│           └── pids -> /home/remote_user/sdshmc/shared/pids
└── shared
    ├── log
    ├── pids
    └── system

18 directories, 23 files

Ah. So now we see that current points to the releases/20130603021433 folder which has all our code.
Each new release will get another timestamp and get a new folder with current pointing to it.
Cool.

Ok. We're not done yet. We have to modify our Apache virtual host config to point to the public folder under current.
So we make these changes:
<VirtualHost *:80>
  ...elided...

  # Points to your site files
  # NOTE: You must have a public folder even if it's empty
  DocumentRoot    /home/some_user/sdshmc/current/public

  ...elided...

  # The directory where the site is stored
  # NOTE the trailing slash!
  <Directory /home/some_user/sdshmc/current/public/>
    ...elided...
  </Directory>
</VirtualHost>

Restart Apache and test it. All should work.

Deploying a new version

Ok. You've made a ton of changes, tested them locally and want to get it live.
Make sure all has been pushed to your repo and then:
cap deploy

You can see what options are available using:
cap -T

Making one-off changes to an existing deployment

Sometimes you have to change just one thing and don't want to do a complete redeployment.
So go ahead, change your file and push it to your repo.
Then on your local machine do:
cap deploy:upload

NOTICE:


The Gemfile has:
group :production do
# Now notice we are using the mysql not mysql2 gem!
gem 'mysql'
gem 'dm-mysql-adapter'
end

Notice that the gem is the mysql gem and not the mysql2 gem.
The gem reference is also in the database.yml file:
production:
  adapter: mysql
  encoding: utf8
  username: sdshmc
  password: sdshmc
  database: sdshmc

Notice that the adapter is mysql and not mysql2.
Supposedly the dm-mysql-adapter does not suffer from the utf8 issue.
Having said that, setting the encoding to utf8, UTF8, utf-8 or UTF-8 causes:
/Users/kim/.rvm/gems/ruby-2.0.0-p0/gems/data_objects-0.10.12/lib/data_objects/connection.rb:79: warning: Encoding utf8 is not a known Ruby encoding for MySQL

This appears to be an issue with the DataMapper DataObject library.
Which of course does not exist.
It's only a warning, but it disturbs me.

If you try to use the mysql2 gem and adapter DataMapper barfs on the DataObject requires.
When run, it attempts to require a mysql2 version of it's code.