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.
- Install required program.
- Properly configure the database.
- Modify environment.rb.
- Run tar2rubyscript.
- Configure Platypus.
- Create your app!
Download and install Platypus: http://www.sveinbjorn.org/platypus
$ 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:
[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