Pinkatio – our latest Rails application
We just had a killer idea to get us thousands of dollars of revenue per month. We will call it pinkatio, and we will put it on Rails.
Creating the Subversion repository
This is the most variable step in the whole procedure. If you know your way around a Unix/Linux system, go ahead and put the repository in
/var. Watch your permissions, though. Else, I suggest sticking with a HOME based repository, like I did below. You can use the
file repository access method. The only caveat is that you will not be able to share your repository with other people using that method.
To create the repository, we simply call svnadmin’s create subcommand:
1 $ mkdir ~/svn 2 $ svnadmin create --fs-type=fsfs ~/svn/pinkatio
To ease our poor fingers, let us create an environment variable to refer tot he repository’s root URL:
1 $ REPOS=file://`pwd`/svn/pinkatio
Subversion recommends creating all repositories with three folders at the root:
branches. This is if you use the one project per repository. This is explained in more details in Choosing a Repository Layout.
This is by no means a requirement to use Subversion, but I suggest sticking to this convention anyway. Most Subversion repositories I have seen adhere to the convention, and if you have only one project in your repository, it makes sense to be able to tag and branch at will.
1 $ svn mkdir --message="Initial project layout" $REPOS/trunk $REPOS/tags $REPOS/branches 2 3 Committed revision 1.
With the repository creation out of the way, let us now turn to creating our Rails application.
Creating the Rails application and importing into the repository
Creating the Rails application is straightforward:
1 $ rails ~/pinkatio 2 create 3 create app/controllers 4 ... 5 $ cd ~/pinkatio
At this point, you could do an svn import and import the whole application into Subversion. I recommend against doing that. If you use the “in-place import” procedure, you can commit only the pieces that you want, not the whole tree (log files being ones we don’t want under version control). See Subversion’s How can I do an in-place ‘import’ FAQ for the full details.
1 $ svn checkout $REPOS/trunk . 2 Checked out revision 1.
Next, let us add the whole tree to the working copy. This is no different than if we had done an
svn import initially, except all changes are local, and we can selectively revert files and folders.
1 $ svn add --force . 2 A app 3 ... 4 A README
The Rails command helpfully creates most of the tree. Since I use migrations in all of my Rails projects, I immediately create the
db/migrate/ folder. Edge Rails and Rails 1.1 also include a
tmp/ folder. For completeness’ sake, I create it at the same time.
1 $ svn mkdir db/migrate tmp 2 A db/migrate 3 A tmp
Removing the log files from version control
Right now, Subversion will helpfully track changes to the log files. This is not really useful for us, as the log files can be pruned at any point.
To ease our burden, the easiest thing is to tell Subversion to ignore the logs.
1 $ svn revert log/* 2 Reverted 'log/development.log' 3 Reverted 'log/production.log' 4 Reverted 'log/server.log' 5 Reverted 'log/test.log' 6 7 $ svn propset svn:ignore "*.log" log 8 property 'svn:ignore' set on 'log'
See svn:ignore in the Subversion book for more details on the property format.
Managing the database configuration
Again the Subversion FAQ comes to the rescue: I have a file in my project that every developer must change, but I don’t want those local mods to ever be committed. How can I make ‘svn commit’ ignore the file?.
The solution is to have a template of the file in the repository, and to force each working copy to copy the template file to the real file. Let us simply revert the add of the config/database.yml file, and add a sample file instead:
1 $ svn revert config/database.yml 2 Reverted 'config/database.yml' 3 4 $ mv config/database.yml config/database.yml.sample 5 $ svn add config/database.yml.sample 6 A config/database.yml.sample 7 8 $ svn propset svn:ignore "database.yml" config 9 property 'svn:ignore' set on 'config' 10 $ cp config/database.yml.sample config/database.yml
The only problem with this procedure is if important changes are made to the config.yml.sample file, the developers might not notice the changes. Most of the time though, the sample file will not change, and leaving it as-is is ok.
Database structure dumps during testing
When you run the tests, Rails dumps the development database’s structure to a file in
db/. Usually, this file should not be under version control. Your migration scripts should be under version control instead, and your migrations should enable you to recreate the development database at will.
Additionally, this step will depend on which configuration setting you use for the config.active_record.schema_format. If you use the
:ruby (the default on Edge Rails and Rails 1.1), you should ignore the
schema.rb file from
db/. If you use
:sql, simply ignore
development_structure.sql instead. Alternatively, you could ignore both files, making this a moot point.
1 $ svn propset svn:ignore "schema.rb" db 2 property 'svn:ignore' set on 'db'
tmp/, documentation, scripts and public
Edge Rails and Rails 1.1 now possess a
tmp/ folder. Since this folder will hold socket and session files, we can safely ignore everything in it.
1 $ svn propset svn:ignore "*" tmp 2 property 'svn:ignore' set on 'tmp'
doc/ folder can hold two subfolders:
apidoc/. If you don’t plan on building the documentation for your project, you can ignore setting svn:ignore on
doc/. Else, you should ignore like this:
1 $ svn propset svn:ignore "*doc" doc 2 property 'svn:ignore' set on 'doc'
Subversion also has a property that tells it which files are executable. We can set the property on files that are intended to be run from the command line:
1 $ svn propset svn:executable "*" `find script -type f | grep -v '.svn'` public/dispatch.* 2 property 'svn:executable' set on 'script/performance/benchmarker' 3 ...
Last but not least, my projects usually have a default home page served by a Rails action. This means building a route and removing
1 $ svn revert public/index.html 2 Reverted 'public/index.html' 3 4 $ rm public/index.html
Saving our work
After all of these changes, it is important to commit our work to the repository.
1 $ svn commit --message="New Rails project" 2 Adding README 3 ... 4 Adding vendor/plugins 5 Transmitting file data ....................................... 6 Committed revision 2.
After this step, it is time to start coding your application, unless you need to go on the Edge…
Using Rails Edge and protecting against overzealous gem upgrades
When you are going to put your application into production, you don’t want an upgrade in your host’s environment to affect your application. To prevent such problems, you should keep a local copy of Rails in your application’s vendor folder.
If you want to live on the Edge (with all the latest features), this step is a necessity. If you are not so comfortable with Edge, replace
tags/rel_1-1-0 when Rails 1.1 is out) in the svn:externals property below.
1 $ svn propset svn:externals "rails http://dev.rubyonrails.org/svn/rails/trunk/" vendor 2 property 'svn:externals' set on 'vendor' 3 4 $ svn update vendor 5 6 Fetching external item into 'vendor/rails' 7 A vendor/rails/cleanlogs.sh 8 ... 9 U vendor/rails 10 Updated external to revision 3830. 11 12 Updated to revision 2.
If you went for Rails Edge, you should really rerun the
1 $ yes | rails . 2 exists 3 exists app/controllers 4 ... 5 identical log/test.log
Don’t forget to commit your changes:
1 $ svn commit --message="Living on the Edge - set svn:externals on vendor/ for Rails" 2 Sending vendor 3 4 Committed revision 3.
Tracking Edge Rails
Next time you
rails:update Rake command:
Gem features a useful subcommand:
unpack. When you run it on a gem, it will unpack that gem’s content into the current folder. We can then move the code to our
vendor/ folder, and again protect ourselves against host upgrades.
As an example, let us unpack the Money gem:
1 $ cd vendor 2 $ gem unpack money 3 Unpacked gem: 'money-1.7.1'
Gems all have a
lib/ folder into which the gem’s source code is stored. Copying the contents of the
lib/ folder into
vendor/ is the important trick here. If you move
lib/ to vendor, it won’t help, as Rails automatic dependency loading mechanism will not know how to find your code.
At the same time, to comply with the library’s license, we copy the library verbatim to our
1 $ cp -Rf money-1.7.1/lib/* . 2 $ cp -Rf money-1.7.1/MIT-LICENSE LICENSE-money 3 $ cp -Rf money-1.7.1/README README-money
Let us tell Subversion what files we now want to version control:
1 $ svn add bank money support money.rb LICENSE-money README-money 2 A bank 3 .. 4 A README-money
To help me remember which version I unpacked, I set a custom property on the main file of the library I just unpacked:
1 $ svn propset version "1.7.1 (Gem)" money.rb 2 property 'version' set on 'money.rb'
Next, we cleanup after ourselves:
1 $ rm -Rf money-1.7.1
Finally, let us commit our changes back to the repository.
1 $ cd .. 2 $ svn commit --message="Unpacked Money 1.7.1 into vendor/" 3 Adding vendor/LICENSE-money 4 ... 5 Transmitting file data ........ 6 Committed revision 4.
When the next version of the Money gem will come around, we simply follow the same procedure as above. Of course, you will use
svn status to know what files changed exactly. You might have to add new files, and remove old ones.
One tool that can help automate this process svn_load_dirs.pl, from the Subversion’s
For plugins, you have to take the same decision as for Rails – Edge or safe. For plugins, I have found that sticking to released version is safer for me. YMMV.
As an example, I will use the FileColumn plugin. Unfortunately, this plugin is not ready to be used by the
script/plugin install -x procedure. So, we have to resort to a manual one.
1 $ svn propset svn:externals "file_column http://opensvn.csie.org/rails_file_column/plugins/file_column/tags/rel_0-3-1/" vendor/plugins 2 property 'svn:externals' set on 'vendor/plugins' 3 4 $ svn update vendor/plugins 5 6 Fetching external item into 'vendor/plugins/file_column' 7 A vendor/plugins/file_column/test 8 ... 9 U vendor/plugins/file_column 10 Updated external to revision 58. 11 12 Updated to revision 4.
Again, we must not forget to commit our changes to the repository:
1 $ svn commit --message="Added FileColumn plugin" 2 Sending vendor/plugins 3 4 Committed revision 5.
Creating migrations, models and controllers
generate has a helpful option:
1 $ script/generate migration --svn InitialSchema 2 exists db/migrate 3 create db/migrate/001_initial_schema.rb 4 A db/migrate/001_initial_schema.rb 5 6 $ svn status 7 A db/migrate/001_initial_schema.rb
destroy generators accept the
--svn option. This makes it easy for the developer to keep his changes under version control.
I follow this script more or less verbatim for all of my Rails projects. After the first two or three times, this becomes automatic. For the adventurous, I have a shell script which does most of the steps above automatically. You can get rails2.sh.
# Distributed in the Public Domain by Francois Beausoleil. # This script might destroy twenty years worth of work, and I cannot be held # responsible. You are your own master. Read this file in detail before # you use it. # # NO IMPLIED WARRANTY.