Packaging Rails apps for offline use

June 21, 2008 – 12:37 am

Goal

To package an existing Ruby on Rails application into an executable Mac OS X desktop application.  This process (and the result) requires OS X 10.5 (”Leopard”) or later.

Summary

  • Install required program.
  • Properly configure the database.
  • Modify environment.rb.
  • Run tar2rubyscript.
  • Configure Platypus.
  • Create your app!
Note: This tutorial assumes that your Rails app lives at: ~/rails/myapp

Procedure

1. Installation

Download and install Platypus: http://www.sveinbjorn.org/platypus

Install tar2rubyscript:

$ sudo gem install tar2rubyscript

2. Database configuration

To prevent the end-user from having to install and run a database server, this requires your application to use sqlite3 (the current Rails default) for database bindings.  If your existing app uses MySQL or something else, you must modify your program (at least the offline version) to use sqlite3.  In reality, only the environment you’re distributing in needs to be sqlite3 (i.e. Production).

A sample (basic) config/database.yml:

development:
  adapter: sqlite3
  database: db/development.sqlite3
 
test:
  adapter: sqlite3
  database: db/test.sqlite3
 
production:
  adapter: sqlite3
  database: db/production.sqlite3

Your Desktop application will need a .sqlite3 file to use as a default database for the first time a user opens your program.  To do this, get one of your databases into a good default state with no user-specific data and then move it to an external folder called ‘db’.  For example:

cd ~/rails/myapp
$ rm ./db/production.sqlite3
$ rake db:migrate RAILS_ENV=production
[ add any model-instances you need, etc. ]
$ ./script/server -e production # Make sure everything is still kosher, then quit
$ mkdir ~/Desktop/db
$ cp ./db/production.sqlite3 ~/Desktop/db/
$ cp ./db/development.sqlite3 ~/Desktop/db/ # For debugging, if you want it  

Security note: Your end-user will have full access to the database.  Don’t store unnecessary data in the default production database.

3. environment.rb and init.rb

The trick with the environment is to get the (local) app to look somewhere unusual for the database.  I prefer to store the database in ~/Library/Application Support/, but you can put it wherever you want (including in the .app pacakge, if you want your data to move around with the app and not the user account).

Some code must be inserted before the Initializer.  Below is my version of it, but obviously tweak it as necessary.  Mine is an updated and OS X-ified version of code by Erik Veenstra.  Just replace appname on line 6 with your own thing.

module Rails
  class Configuration
    def database_configuration
      conf = YAML::load(ERB.new(IO.read(database_configuration_file)).result)
      if defined?(TAR2RUBYSCRIPT)
        appname = 'My Awesome Application'
	conf.each do |k, v|
          if v["adapter"] =~ /^sqlite/ && v.include?("database")
            system('mkdir /Users/'+ENV['USER']+'/Library/Application\ Support/'+appname.gsub(" ","\\ ")) if !FileTest.exists?("/Users/"+ENV['USER']+"/Library/Application Support/"+appname)
            system('cp '+oldlocation(v["database"]).gsub(" ","\\ ")+' /Users/'+ENV['USER']+'/Library/Application\ Support/'+appname.gsub(" ","\\ ")+'/'+v["database"].from(3)) if !FileTest.exists?('/Users/'+ENV['USER']+'/Library/Application Support/'+appname+'/'+v["database"])
            v["database"] = "/Users/"+ENV['USER']+"/Library/Application Support/"+appname+"/"+v["database"].from(3)
          end
        end
      end
      conf
    end
  end
end

Additionally, at the bottom of your environment.rb, it’s slick to have your user automatically get taken to the rendered web-page once the application is running.  Stick this at the end of your environment.rb file:

system 'open http://127.0.0.1:8000' if ENV['RAILS_ENV'] == 'production'

As always, once you’ve edited your environment.rb file, run the server briefly and make sure that you didn’t break anything:

$ cd ~/rails/myapp
$ ./script/server 

Additionally, you need to make sure that any required gems are unpacked into vendor/gems.  If you’re riding Rails 2.1 or later, you can use gem dependencies to do it automatically.  Otherwise, you can do something like:

$ cd ~/rails/myapp/vendor
$ mkdir gems
$ cd gems
$ gem unpack json_pure # for example

If you’re running anything other than Rails 1.2.6 (and I hope you’ve upgraded to at least 2.0!), you’re going to need to freeze Rails into your app, because OS X has a dated version.  There are various ways to do this, but you can always just:

$ cd ~/rails/myapp/vendor
$ gem unpack rails

4. Compressing your application

rubyscript2exe takes your entire Rails folder and packages it into a single ruby file.  It requires a file called ‘init.rb’ to be present your app’s root folder (ex. ~/rails/myapp/init.rb).  Mine looks like this:

at_exit do
  require "irb"
  require "drb/acl"
  require "sqlite3"
end
 
load "script/server"

Then pack it up:

$ cd ~/rails
$ tar2rubyscript ./myapp # If there are problems, check /usr/bin or /opt/local/bin/
$ mv myapp.rb ~/Desktop/ 

You can test the app to make sure it works by doing:

$ cd ~/Desktop
$ ruby ./myapp.rb -p 8000 -e production

This should create the appropriate folder and database file, as well as start your web server on port 8000.  To make sure everything is running smoothly, navigate to http://127.0.0.1:8000/, load a few pages, quit your server, and then move on to the next step! (Note: any changes you make to your database at this stage will affect the packaged “default” database)

5. Packaging it up

Platypus is a piece of excellent software by Sveinbjorn Thordarson that takes any standard script and packages it into a double-clickable OS X app.  It’s very slick.  I’m going to use it to run a shell script that activates the server.  Why not run the ruby script directly?  With the bash script, you can do things that prepare the environment in any way you want, and pass any parameters you need to into the ruby script, without changing your .rb once it’s been tar2rubyscript’ed.  But the other way works too.

My example shell script (’run_rails.sh’):

#!/bin/sh
cd "$1/Contents/Resources"
/usr/bin/ruby ./myapp.rb -p 8000 -e production

Next, we need to put everything together in platypus.  ’run_rails.sh’ is the main script you’ll be running, and you want to be sure to include your ‘db’ folder and your ‘myapp.rb’ file.  Here’s an example Platypus configuration:

Example Platypus Configuration

[Update: If you're using Platypus 4.0, be sure to check "Set $1 to path to application" in the "Parameters" pane.]

For development (if not production too), I’d suggest using a Text Window and checking “Remain running after completion”, so you can see (potentially) helpful messages from your server.  Click “Create”, and you have yourself a double-clickable OS X web application!  –Jason Crystal

  1. 14 Responses to “Packaging Rails apps for offline use”

  2. There’s also http://www.rosboroughtech.com/products/railsman

    By xcd on Jun 21, 2008

  3. A new Windows convert, I recently spent over an hour trying to find out how to make a ruby script execute, rather than open in an editor, on a double-click in the finder. The only solution I found was through automation of Terminal — not very satisfying.

    Thanks so much for posting this write-up, and then listing it on RubyFlow where I could find it. Platypus was just what I was looking for. Plus, now I’m inspired by all the possibilities for distributing rails desktop apps.

    By Pauli on Jun 23, 2008

  4. I’ve been working in another solution to this:

    http://code.google.com/p/rorgate/

    It is still incomplete, but I’d like you to check it out.

    By Juanger on Jun 26, 2008

  5. VERY cool idea. Excited to see where that leads! Definitely a cleaner way of doing it than mine. I just don’t have the Cocoa background to make that happen. I’m getting your blog feed now, so keep us posted for when you get to more stable releases!

    By Jason on Jun 26, 2008

  6. Very nice Jason. Finally a decent resource for bundling Rails apps up as OS X apps. Great stuff.

    By Alistair Holt on Jun 29, 2008

  7. Hi!

    At my company (http://www.inovare.net - site being changed), we developed a project called RailsOnDesktop (http://rubyforge.org/projects/railsondesktop/). I know it’s been some time since the last modification (we’re really busy with other projects), but it’s a Delphi Windows executable with easy Ruby, Rails, Gems and Mozilla Rendering Engine (no IE hooooray) installation. I know it still doesn’t work on OS X, but it’s a tip for everyone! :D

    Best regards,

    Felipe Giotto.

    By Felipe Giotto on Jun 30, 2008

  8. What I have been experimenting with and would recommend, since it probably works for Mac, Windows and Linux, is to add the bin/ and lib/ directories from the latest JRuby distribution. Then I install the gems I need (without rdoc and ri) using the gem from the JRuby which is now in my rails (well merb app actually) app. Works pretty good.

    I use the h2 database instead of sqlite3, which I install from the jruby-extra gems repo. The only issue I have is that there are some bugs with the h2 jdbc adaptor gem. I had hoped to look into it, but got busy. Perhaps soon. But yeah I think sqlite3 jdbc might also work.

    Another advantage with using JRuby might be that you can also compile ruby code to java bytecode, thus your source code is automatically “protected”. As far as I can tell, the Mingle app from TW, encrypts the source code, and uses a “special” JRuby that decrypts the code before executing it.

    Hope this helps!

    By Kashif on Jul 1, 2008

  9. I’d recommend looking at Joyent’s Slingshot, a cross-plaform solution for offline Rails apps, with up/down data syncronization. I was amazed at how easy it was to get going!

    http://joyent.com/developers/slingshot/

    By Jamie Wilkinson on Jul 6, 2008

  10. Thanks for the post.
    It works great for me up to one point…
    When I run the app. the shell can’t cd into “$1/Contents/Resources”
    I get a ‘No such file or directory’ error.

    If I use platypus to package the my_app.rb it does what it’s supposed to.

    I’m using platypus 4.0

    By David Kern on Jul 6, 2008

  11. This tutorial assumed Platypus 3.4, and Platypus 4.0 came out a couple weeks ago. I just downloaded it and messed around, and here’s the fix:

    When creating your app (in Platypus), click the “Parameters” button and check the box “Set $1 to path to application”. That should have you set.

    I’ll update that step above! Thanks for the catch.

    By Jason on Jul 6, 2008

  12. Thank you

    By FenBleak on Aug 2, 2008

  13. Incredible site!

    By mark on Apr 14, 2009

  1. 2 Trackback(s)

  2. Jul 2, 2008: Packaging Rails Applications for Offline / Third Party Use on OS X
  3. Jul 4, 2008: links for 2008-07-04 at Topper’s Blog

Post a Comment