Hank Stoever
part time nerd, part time gnar.

My Rails Defaults

posted over 4 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', '~> 2.2.2.0'
. . .
. . .
gem 'devise'

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

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
  <<-eos
  config.autoload_paths << "\#{config.root}/lib"
  config.queue = Afterparty::Queue.new

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

  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
  eos
end

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
  <<-eos
  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
  }
  eos
end

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
  <<-eos
  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},
    }

  eos

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
 !!!
 %html

   %head
     %title
       - 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)

   %body
     = yield
 eos

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)
        ActiveRecord::Base.connection.disconnect!
        Rails.logger.info('Disconnected from ActiveRecord')
      end

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

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

      # # 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')
      end
    end
    eos

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"

Conclusion

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