First, ensure the following tools are available on the local system and reasonably up to date:
- Git
- Git-flow
- RVM
- Bash
- SSH
What follows will assume that your local command prompt is provided by a Bash shell.
Now, come up with a name for your project. Whether or not the project is to be distributed as a gem, at least bear in mind this advice:
- http://guides.rubygems.org/patterns/#consistent-naming
- http://blog.segment7.net/2010/11/15/how-to-name-gems
Next, cd
to the directory above the one in which you intend to place your project code. E.g. if you want to end up with projects/myproject
then go to the projects
directory.
Enter the following at the command prompt, replacing "myproject" with the name of your new project, to create a stub gem:
bundle gem myproject
cd myproject
Enter the following at the command prompt, replacing "myproject" with the name of your new project:
mkdir myproject
cd myproject
We'll assume that Git is to be used for source control; so as a precaution against accidentally checking in temporary files used by Vim or OS X, enter the following at the command prompt:
echo '*.swp' >> .gitignore
echo '.DS_Store' >> .gitignore
Once logged in to the GitHub website, click the button to create a new repository, and then note that new repository's "remote" URL (e.g. https://github.com/sampablokuper/myproject.git
).
Enter the following at the command prompt, replacing GIT_REMOTE_URL with the URL just noted:
if [ ! -d .git ]; then git init; fi # Initialises a new Git repository, if doesn't already exist.
if [ ! -f README.md ]; touch README.md; fi # Creates an empty README.md file, if doesn't already exist.
git add -A # Stages any files/directories present, in preparation to commit them to local Git repo.
git commit -m 'first commit' # Commits the staged files/dirs to the local Git repo.
git remote add origin GIT_REMOTE_URL # Adds the GitHub repo created above as a "Git remote" with the alias "origin".
This assumes a working Gitolite 3.x installation on DreamHost, with a relevant SSH Host called "gitolitespk-git" set in the local SSH config file, and all necessary keys set up.
At the command prompt on the local (i.e. development) computer:
git clone gitolitespk-git:gitolite-admin
cd gitolite-admin
Within the gitolite-admin directory just entered, edit conf/gitolite.conf
to add a new repository, as per the instructions at http://sitaramc.github.com/gitolite/repos.html, and then, replacing "NEWREPOSITORYNAME" with the name of your new repository:
git add conf/gitolite.conf
git commit -m "Create repository NEWREPOSITORYNAME"
git push
cd ..
rm -rf gitolite-admin
if [ ! -d .git ]; then git init; fi # Initialises a new Git repository, if doesn't already exist.
if [ ! -f README.md ]; touch README.md; fi # Creates an empty README.md file, if doesn't already exist.
git add -A # Stages any files/directories present, in preparation to commit them to local Git repo.
git commit -m 'first commit' # Commits the staged files/dirs to the local Git repo.
git remote add origin gitolitespk-git:NEWREPOSITORYNAME # Adds the GitHub repo created above as a "Git remote" with the alias "origin".
Whether dependent upon external gems or not, you should first "Git push" to the remote repo, and then initialise git-flow (which you should install right now if it isn't already installed!), by entering the following at the command prompt:
git push -u origin master # Pushes (i.e. copies, essentially) the local commits to the remote repo.
git flow init -d # Initialises git-flow to manage branching, using the default options.
git push origin develop # Git-flow will have created & switched to this branch. Ensure the remote knows about it.
Don't even bother using RVM. Just code the project, and copy any external libraries (e.g. micro-optparse) into the project directory or even into your source code files (with comments present to indicate any necessary attribution).
From within the project's root directory, create a project .rvmrc file by entering e.g. (if 1.8.6 is the Ruby interpreter desired):
rvm --rvmrc --create 1.8.6
If this works, great. If it fails with an error like "WARN: ruby ruby-1.8.6-p420 is not installed. To install do: 'rvm install ruby-1.8.6-p420'", then enter:
rvm install 1.8.6
Once rvm has installed the relevant Ruby interpreter try again to create the .rvmrc:
rvm --rvmrc --create 1.8.6
Now enter the following:
cd .
in order to "change" into the directory you are currently in. This will cause the .rvmrc just created to kick in (it may give a warning the first time around) and switch the current interpreter to the one requested.
Don't use rvm's "gemsets" feature. Use Bundler instead, as per the instructions below. See this link for reasons why.
First of all, unless you expect to use installed gems' offline documentation or have some other compelling reason not to follow this step, enter the following into the file ~/.gemrc
, to avoid gem installations from utilising unnecessary time (several hours per gem, in some cases!) and disk space:
# As per http://stackoverflow.com/a/7662245/82216
install: --no-rdoc --no-ri
update: --no-rdoc --no-ri
If you used bundle gem myproject
earlier, then list your gem project's dependencies in the gemspec file, via the add_development_dependency
and add_runtime_dependency
attributes; Bundler will be able to install these appropriately.
Alternatively, if you did not use bundle gem myproject
earlier, then you presumably have neither a "gemspec" file nor a "Gemfile" file present in your project so far. Create a file called "Gemfile" (e.g. using vim Gemfile
) and give it a source of gems and a list of gems to install, e.g.:
source :rubygems
gem "trollop"
Either way, once finished specifying the dependencies for your project, save that file and exit the editor.
Depending upon particular gems doesn't necessarily require a project to require/expect to be used with a particular Ruby interpreter. However, in order to simplify development, it's probably best to settle on one. So, make an informed guess about the most appropriate, using, e.g. the following points of information:
- Unless a specific feature of non-MRI interpreter is definitely going to be needed (e.g. the ability to call Java classes directly, as JRuby can do but MRI can't), every new project should use MRI to begin with.
- Performance shootouts should wait until project has already taken enough shape for conclusions to be relevant to project's real-world behaviour, so don't consider Ruby interpreters' generic benchmark results when choosing an interpreter to start a project with.
- Don't live on the bleeding edge until absolutely necessary: use the latest stable version - of whichever interpreter family has been chosen - that is compatible with every gem the project is anticipated to use.
- Remember, interpreters can be upgraded: http://beginrescueend.com/rubies/upgrading/
- If still in doubt, consider:
- http://beginrescueend.com/interpreters/
- http://en.wikipedia.org/wiki/Ruby_(programming_language)#Implementations
- http://rubyonrails.org/download
- http://ftp.ruby-lang.org/pub/ruby/
- http://code.google.com/p/rubyenterpriseedition/downloads/list
Then create a .rvmrc file by cd'ing to the project's root directory and entering e.g. (if 1.8.6 is the Ruby interpreter desired):
rvm --rvmrc --create 1.8.6
If this works, great. If it fails with an error like "WARN: ruby ruby-1.8.6-p420 is not installed. To install do: 'rvm install ruby-1.8.6-p420'", then enter:
rvm install 1.8.6
Once rvm has installed the relevant Ruby interpreter try again to create the .rvmrc:
rvm --rvmrc --create 1.8.6
Now edit the .rvmrc just created, and uncomment the lines that get Bundler to run "bundle" (i.e. "bundle install" - see http://www.sampablokuper.com/2011/09/26/the-bundle-commands-default-behaviour/) each time the project's directory is changed into.
Next, edit the line on which "bundle" is called, which will probably look a bit like this:
bundle | grep -v '^Using ' | grep -v ' is complete' | sed '/^$/d'
or this:
bundle install
so that it instead reads more as follows:
bundle install --path vendor | grep -v '^Using ' | grep -v ' is complete' | sed '/^$/d'
or:
bundle install --path vendor
respectively.
Then enter the following command at the command prompt:
echo 'vendor/ruby' >> .gitignore
Now enter the following:
cd .
in order to "change" into the directory you are currently in. This will cause the .rvmrc just created to kick in (it may give a warning the first time around) and switch the current interpreter to the one requested.
Next, create a file called "main.rb" (e.g. using vim main.rb
), which you'll use as though you were starting to get your application code started, to check that the above has worked. It should simply load the bundled environment and require
the gems specified in the Gemfile, along the following lines:
# Load the bundled environment
require "rubygems"
require "bundler/setup"
# Require gems specified in the Gemfile
require "trollop"
Save this file and exit the editor, then run:
ruby main.rb
All being well, there will be no error messages. That being so, you can now delete the "main.rb" file:
rm main.rb
Package all the gems on which the project depends into the project's vendor/cache directory, as per, by running:
bundle package
Now run the following commands to add the config files and directories to version control:
git add Gemfile Gemfile.lock .rvmrc .gitignore .bundle vendor
git commit -m 'Add remaining config files and packaged bundled gems to version control'
git push origin develop
If your project so far does not have a testing framework activated that you are happy with, then decide on a syntax to use (i.e. "assertion syntax" or "specification syntax") and a suitable test framework (e.g. MiniTest or RSpec). E.g. if you decide to use "specification syntax" tests, via MiniTest, then do something like this:
git flow feature start basictestframework
mkdir spec
cat >> Rakefile << EOF
require 'rake/testtask'
Rake::TestTask.new do |t|
t.pattern = 'spec/*_spec.rb'
end
EOF
cat >> spec/quality_spec.rb << EOF
require 'minitest/autorun'
describe "The project" do
it "must provide the ability to run tests" do
true.must_equal true
end
end
EOF
and then run this sample test using the Rake task just created, by entering the following at the command prompt:
rake test
Assuming that has worked, finish that new feature branch thus:
git add -A
git commit -m 'Create a basic test framework'
git push origin develop
git flow feature end basictestframework
If you used bundle gem myproject
earlier, and your project is intended to be published as a gem to rubygems.org, then fill in the missing details in the generated "gemspec" file (e.g. set the version number to 0.0.0) and any other necessary files. Consider using the files
gemspec attribute to ignore the Gemfile.lock file, as per Yehuda Katz's advice.
Now enter the following set of commands at the command prompt, assuming each of them completes successfully, replacing "myproject" with the name of your gem:
gem build myproject.gemspec
gem push myproject-0.0.0.gem
A few minutes later, your gem should be available via rubygems.org .
Now get coding! And remember:
- use TDD or BDD principles, especially the advice here, here and here;
- "git add" and commit any changes that should be versioned;
- stick to Git commit message conventions;
- use git-flow and semantic versioning conventions (among others) to keep the code manageable;
- if project is a gem, structure the project according to the RubyGems Guides.
- check the .rvmrc into version control as per.
The Gemfile should already include a line saying:
gem "rails"
Copy the .gitignore file and the Gemfile:
cp .gitignore .gitignore-old
cp Gemfile Gemfile-old
In order to create a Rails application in the current folder, enter this at the command prompt (don't forget to include the full stop at the end!):
bundle exec rails new .
Allow this to overwrite .gitignore and Gemfile. This command may end with the following error: Could not find gem 'jquery-rails (>= 0) ruby' in the gems available on this machine. (Bundler::GemNotFound) but if so, don't worry; we'll sort it out shortly.
Next, unless you want to store the database.yml
file in the Git repository, do:
cp config/database.yml config/database.yml.example
echo 'config/database.yml' >> .gitignore
and then edit config/database.yml
as follows, replacing DATABASENAME, MYSQLUSERNAME, MYSQLPASSWORD, and MYSQLDOMAIN with the appropriate values.
# SQLite version 3.x
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem 'sqlite3'
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: sqlite3
database: db/test.sqlite3
pool: 5
timeout: 5000
production:
adapter: mysql2
encoding: utf8
database: DATABASENAME
username: MYSQLUSERNAME
password: MYSQLPASSWORD
host: MYSQLDOMAIN
We'll get this file onto the server later on.
Now do:
vimdiff .gitignore .gitignore-old
and restore any needed lines from .gitignore-old into .gitinore. Then do:
vimdiff Gemfile Gemfile-old
and similarly, restore any needed lines from Gemfile-old to Gemfile.
Next:
rm .gitignore-old Gemfile-old
Finally, cd..
out of and then cd
back into your project directory. This will call bundler install --path vendor which should fix the Bundler::GemNotFound error mentioned above.
(Note: when deploying Rails apps to DreamHost, if the version of Rails used requires a version of Rack newer than the DreamHost system Rack, then you will have to use FastCGI instead of Passenger, because Passenger will always use the system Rack: http://wiki.dreamhost.com/index.php?title=Rails_3&oldid=31201#Rails_3.1.x and http://www.jacoulter.com/2011/12/14/rails-3-1-rack-1-3-5-passenger-and-dreamhost-shared-servers/ and email from DreamHost 13/5/2012.)
Now get coding! And remember:
- "git add" and commit any changes that should be versioned;
- stick to Git commit message conventions;
- use git-flow and semantic versioning conventions (among others) to keep the code manageable;
- check the .rvmrc into version control as per.
Taking a fresh Rails 3.2.3 app from the above state and making it deployable to DreamHost shared with FastCGI
At the command line, enter:
bundle exec rails server
and visit http://localhost:3000 in your browser. You should see the Rails "Welcome aboard" page.
Back at the command line, use Ctrl+C to quit the rails server.
If you wish to use plain JavaScript instead of CoffeeScript, and/or plain CSS instead of SASS, comment out one or both of the following line as appropriate, from Gemfile
:
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
Also in Gemfile
:
-
move the line
gem 'sqlite3'
into thegroup :assets do
code block, to avoid sqlite3 being required in the production environment. -
Uncomment the line,
# gem 'capistrano'
. -
Add the following two lines below the initial
source
line:## Line below added as per https://gist.github.com/1391900 gem 'fcgi'
Save and close Gemfile
.
cd
out of and back into your project directory again, to trigger Bundler to run.
Now, at the command prompt, enter:
bundle exec rails generate controller home index
mv public/index.html public/index-old.html
vim config/routes.rb
Change the line # root :to => 'welcome#index'
to root :to => 'home#index'
, then save the file and exit Vim.
Again, enter:
bundle exec rails server
and refresh the browser tab which is showing http://localhost:3000 . You should see a page which reads something like:
Home#index
Find me in app/views/home/index.html.erb
Back at the command line, use Ctrl+C to quit the rails server again. Then enter:
bundle package
git add -A
git commit -m "Make Rails controller and view operational."
bundle exec capify .
Now SSH into the server you wish to deploy to, and install RVM (if it isn't already installed), and the desired version of Ruby via RVM (if it isn't already installed). Then, being sure that the output from which ruby
is the desired one to deploy to, enter the following at the remote server's command prompt:
gem install bundler
gem env
Make a note of the output, and end the SSH session.
Now, back on the local computer, edit config/deploy.rb
as follows, tweaking as appropriate including paying particular attention to the PATH, GEM_HOME and GEM_PATH variables, which should make sense in the light of the above, and being sure to uncomment either the block for git-less deploys or the one for using a git remote.
## Based on https://gist.github.com/1391900
require "bundler/capistrano"
## If not using a git remote to deploy from, uncomment these lines:
# set :scm, :none
# set :repository, "."
# set :deploy_via, :copy
## If instead using a git remote to deploy from, uncomment these lines and amend the repository access strings as appropriate:
# set :scm, "git"
# set :repository, "[email protected]:ruby193p194test"
# set :local_repository, "gitolitespk-git:ruby193p194test"
# set :deploy_via, :remote_cache
set :domain, "spike.sampablokuper.com"
set :user, "spkspike"
server "#{domain}", :app, :web, :db, :primary => true
set :application, "spike.sampablokuper.com"
set :deploy_to, "/home/#{user}/www/#{application}"
set :use_sudo, false
set :default_environment, {
'PATH' => "/home/spkspike/.rvm/gems/ruby-1.9.2-p290/bin:/home/spkspike/.rvm/bin:$PATH",
'GEM_HOME' => '/home/spkspike/.rvm/gems/ruby-1.9.2-p290',
'GEM_PATH' => '/home/spkspike/.rvm/gems/ruby-1.9.2-p290',
}
# Define tasks
namespace :rvm do
desc "Avoids the need to have to manually 'trust' the .rvmrc file if it has been updated"
task :trust_rvmrc do
run "rvm rvmrc trust #{release_path}"
end
end
## The next few lines based on:
## http://affy.blogspot.co.uk/2008/07/using-scp-with-capistrano.html
## http://webonrails.com/2008/10/25/capistrano-uploading-downloading-directory-to-from-remote-server-via-scp-or-sftp/
## http://www.ruby-forum.com/topic/174627
namespace :deploy do
desc "Uploads the database.yml to shared/config/database.yml"
task :upload_database_yml do
# Need to call `top.upload()` instead of just `upload()` is due to this issue:
# http://www.mail-archive.com/[email protected]/msg04699.html
top.upload("config/database.yml", "#{release_path}/config/database.yml", :via=> :scp)
end
end
# Run tasks defined above
after "deploy:update_code", "deploy:upload_database_yml"
after "deploy", "rvm:trust_rvmrc"
## Lines from running "bundle exec capify ."
# 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
If you would prefer to deploy using Git than by simply copying (Git is faster), then consider pushing relevant git commits to a remote repository that is accessible (e.g. via appropriate SSH certificates and/or Gitolite, etc, if needed) to the user account on the deployment server and further editing the config/deploy.rb
file to use set :deploy_via, :remote_cache
instead of set :deploy_via, :copy
, as described at http://help.github.com/deploy-with-capistrano . If doing this, it may also be necessary to add a line beginning set :local_repository
to config/deploy.rb
, if your deployment server and your development computer do not use identical credentials to access the remote repository.
Create and save the file public/dispatch.fcgi
with the following content, tweaking as necessary and especially remembering to replace RailsInTopDirectory
with the name of your Rails application (which you can find in config/environment.rb
if you're unsure of what it is):
#!/usr/bin/env /home/spkspike/.rvm/bin/ruby-1.9.2-p290
require 'rubygems'
require 'bundler/setup'
require 'fcgi'
ENV['RAILS_ENV'] ||= 'production'
ENV['PATH'] = "/home/spkspike/.rvm/gems/ruby-1.9.2-p290/bin:/home/spkspike/.rvm/bin:$PATH"
ENV['GEM_HOME'] = '/home/spkspike/.rvm/gems/ruby-1.9.2-p290'
ENV['GEM_PATH'] = '/home/spkspike/.rvm/gems/ruby-1.9.2-p290'
require File.join(File.dirname(__FILE__), '../config/environment')
class Rack::PathInfoRewriter
def initialize(app)
@app = app
end
def call(env)
env.delete('SCRIPT_NAME')
parts = env['REQUEST_URI'].split('?')
env['PATH_INFO'] = parts[0]
env['QUERY_STRING'] = parts[1].to_s
@app.call(env)
end
end
Rack::Handler::FastCGI.run Rack::PathInfoRewriter.new(RailsInTopDirectory::Application)
Now enter the following at the command prompt:
chmod +x public/dispatch.fcgi
Create and save the file public/.htaccess
with the following content, which shouldn't need to be tweaked:
<IfModule mod_fastcgi.c>
AddHandler fastcgi-script .fcgi
</IfModule>
<IfModule mod_fcgid.c>
AddHandler fcgid-script .fcgi
</IfModule>
Options +FollowSymLinks +ExecCGI
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.fcgi/$1 [QSA,L]
At the command prompt, enter:
bundle exec rake assets:precompile
bundle exec cap deploy:setup
And if this goes OK, then:
bundle exec cap deploy:check
And if this goes OK, then:
bundle exec cap deploy
Now get coding! And remember:
- "git add" and commit any changes that should be versioned;
- stick to Git commit message conventions;
- use git-flow and semantic versioning conventions (among others) to keep the code manageable;
- check the .rvmrc into version control as per.
- update the
config/database.yml.example
file from theconfig/database.yml
file as necessary to illustrate any desired settings, but of course omitting passwords and other sensitive information.