Hank Stoever
part time nerd, part time gnar.

My Rails Defaults

posted about 9 years ago - 3 min read

For a while, every time I created a new Ruby on Rails project, I would refer to previous sites I built to remember things like what gems to use, how to create a haml application template, and other defaults like a devise user account. This was a manual process and it was really easy to forget steps along the way.

I've moved all of that logic into a rails template that I store in a public gist on github.

The most important part is my file at ~/.railsrc:

-d postgresql
-m https://gist.github.com/hstove/6058701/raw/template.rb

These configurations mean that anytime I run rails new [app-name], I'll make an app with a Postgresql database and refer to a custom template.rb file with special instructions. I'll walk through what this template file is doing.

The beginning simply adds a ton of gems to my project:

gem 'bootstrap-sass', '~>'
. . .
. . .
gem 'devise'

gem_group :test, :development do
  gem 'rspec', github: 'rspec/rspec'
  . . .
  gem 'quiet_assets'

The only thing different from the syntax of a regular Gemfile is the use of gem_group instead of group to use gems in a specific environment.

The next section adds a few lines to the environment configuration files application.rb, development.rb, and production.rb. This first section requires all files that you add to config/lib, which is helpful for creating general purpose libraries in your project. It uses afterparty for the queue. It tells other generators to use haml and rspec. It tells the a/b testing gem split where to find Redis:

environment do
  config.autoload_paths << "\#{config.root}/lib"
  config.queue = Afterparty::Queue.new

  config.generators do |g|
    g.template_engine :haml
    g.test_framework :rspec

  require 'open-uri'
  uri = URI.parse(ENV["REDISTOGO_URL"] || "redis://localhost:6379")
  redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)

  config.redis = redis
  Split.redis = redis

This next section is for the development environment and adds LiveReload to your assets for automatically reloading your browser on file changes. It uses mailcatcher for handling email testing.

environment nil, env: 'development' do
  config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload)

  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = { :address => "localhost", :port => 1025 }
  config.action_mailer.default_url_options = {
    :host => "localhost",
    :port => 3000

Our production environment uses dalli for our cache store. It gets our Sendgrid credentials from the heroku add-on for email delivery. Exception notification sends me an email whenever an exception occurs.

environment nil, env: 'production' do
  config.cache_store = :dalli_store

  config.logger = Logger.new(STDOUT)

  ActionMailer::Base.smtp_settings = {
    :address        => 'smtp.sendgrid.net',
    :port           => '587',
    :authentication => :plain,
    :user_name      => ENV['SENDGRID_USERNAME'],
    :password       => ENV['SENDGRID_PASSWORD'],
    :domain         => 'heroku.com',
    :enable_starttls_auto => true

  config.middleware.use ExceptionNotification::Rack,
    :ignore_crawlers => %w{Googlebot bingbot googlebot YandexBot bot},
    :email => {
      :exception_recipients => %w{hstove@gmail.com},


The next section removes app/views/layouts/application.html.erb in favor of a more dynamic haml layout:

create_file "app/views/layouts/application.html.haml", <<-eos

       - if content_for? :title
         = yield(:title)
         = " - "
       New Application
     %meta{"http-equiv"=>"Content-Type", :content=>"text/html; charset=utf-8"}
     %meta{name: "viewport", content: "width=device-width, initial-scale=1.0"}
     = stylesheet_link_tag "https://fonts.googleapis.com/css?family=Lato:300,400,700", "https://fonts.googleapis.com/css?family=Roboto:300italic,300,500,500italic,700italic,700", "application", "/assets/bootstrap-wysihtml5/index.css", "/assets/bootstrap-wysihtml5/core.css"
     = csrf_meta_tag
     = favicon_link_tag
     = yield(:head)

     = yield

remove_file "app/views/layouts/application.html.erb"

I then add a file at config/unicorn.rb with instructions for using unicorn to run two web workers and one job worker on a single dyno to stay within Heroku's free tier. Adding a Procfile tells Heroku to use unicorn as the app server.

create_file "config/unicorn.rb", <<-eos
    worker_processes 2
    timeout 30
    preload_app true

    @jobs_pid = nil

    before_fork do |server, worker|
      @jobs_pid ||= spawn("bundle exec rake jobs:work")
      # Replace with MongoDB or whatever
      if defined?(ActiveRecord::Base)
        Rails.logger.info('Disconnected from ActiveRecord')

      # If you are using Redis but not Resque, change this
      if defined?(Split) && !Split.redis.nil?
        Rails.logger.info('Disconnected from Redis')

    after_fork do |server, worker|
      # Replace with MongoDB or whatever
      if defined?(ActiveRecord::Base)
        Rails.logger.info('Connected to ActiveRecord')

      # # If you are using Redis but not Resque, change this
      if defined?(Split)
        require 'open-uri'
        uri = URI.parse(ENV["REDISTOGO_URL"] || "redis://localhost:6379")
        redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
        Split.redis = redis
        Rails.logger.info('Connected to Redis')

create_file "Procfile", "web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb"

We finally run bundle to pull in all of the gems we need and run two generators to setup user accounts with devise.

run "bundle"

generate "devise:install"
generate "devise user"


Setting up this template was a really big productivity booster and I wish I did it earlier. I'm now even more motivated to get started on a new project or MVP because I know that I can get right into application-specific code and not worry about configuration. I encourage you to start with my template as a starting point and change it to fit your needs. The commands available allow you to create a very comprehensive template, so I encourage you to visit the documentation to see what's available.

comments powered by Disqus