This is a step by step guide for everyone new to Ruby and Ruby on Rails - or for people who want to create a new Rails app on a new computer. And it is also a reminder for myself to check which gems and settings I like in Rails apps.
- Setup Ruby and configure a new Rails app
Install your Ruby versions with:
- ruby-install
- current MRI Ruby version: 2.6.3
- https://github.com/postmodern/ruby-install
- install via homebrew
Change you Ruby version with:
- chruby
- https://github.com/postmodern/chruby
- install via homebrew
It can be setup to change the Ruby version automatically when changing into a directory. Consult the README for the setup steps.
Next install the gems bundler and rails:
gem install bundler
(version 2.x will be installed)gem install rails
(the latest version is 5.2.x and will be installed, you might consider starting on 6.0.0.beta3 though)
Both gems are well documented.
On macOS I use the PostgresApp to install Postgres / PostgreSQL. You can also use homebrew or any other method. When you are ready, try to manually install the pg
gem, before continuing:
gem install pg
Here may be dragons. The installation of Postgres is infamous for causing some configuration trouble on macOS
Rails Guides: https://guides.rubyonrails.org/getting_started.html (don't overlook the 'Guides Index' dropdown in the top right)
Example: an app named "lupus" 🐺
This guide assumes you've already installed a current ruby
(version 2.6.3 by time of writing this) and the gems rails
(version 5.2.x) and bundler
(version 2.0.1 or newer) on your machine. It was written on macOS on might assume you use homebrew. Nevertheless you should be able to follow it on Linux or the Linux DSL in Win10 or even under Windows itself.
When creating a new Rails app, you can pass several parameters on the command line. For this project it could be those:
rails new lupus
--database=postgresql \
--webpack \
--skip-bundle \
--skip-spring \
--skip-test \
--skip-action-cable \
--skip-coffee
- lupus is the name if the app I am creating
- We want to use
PostgreSQL
as a database webpack
is the latest fad in the Rails world to bundle your assets- I'll skip
bundle
because I usually edit the Gemfile before I runbundle install
myself - I'll skip
spring
because it can waste more time than it saves. - I'll skip
test
because I (like most others) userspec
- I'll skip
action
-cable as we don't do anything with websockets (yet) - I'll skip
coffee
(script) as we won't add much Coffee- or Javascript just yet
Change to the newly created lupus folder and run git init
(unless Rails does this automatically for you).
Create your first commit and create commits after every setup step.
Add a .ruby-version file to ensure Ruby version managers use the right version. For heroku you will have to add this information in the Gemfile.
echo '2.6.3' > .ruby-version
Usually I remove some of the gems in the Gemfile and the comments and add a few development and test dependencies. That depends on personal preferences. And I sort the gems alphabetically, to appease my linter.
Those are the gems I like to use in development and test environments:
group :development, :test do
gem "awesome_print", "~> 1.8" # nicer formatted console output
gem "bundler-audit", "~> 0.6" # to ensure no used dependencies have know security warnings
gem "byebug", platforms: [:mri, :mingw, :x64_mingw] # to debug
gem "factory_bot_rails", "~> 4.8" # to create factories for tests
gem "rspec-rails", "~> 3.8" # sets up rspec for tests
gem "rubocop", "0.65.0", require: false # fixed to one version
gem "rubocop-rspec" # sets up the linter
end
Run bundle install
when you are ready.
We bundled the rspec-rails gem, which includes rspec for us, but also has an helper to create a few files and folders. Just run:
rails generate rspec:install
You should add spec/examples.txt
to .gitignore
Rubocop's default settings are horribly strict. So I add a custom configuration file to the project:
.rubocop.yml
require: rubocop-rspec
AllCops:
TargetRubyVersion: 2.6
DisplayCopNames: true
Exclude:
- config.ru
- bin/**
- db/schema.rb
- node_modules/**/*
- vendor/**/*
Capybara/FeatureMethods:
Enabled: false
Layout/AlignHash:
Enabled: false
Lint/AmbiguousBlockAssociation:
Exclude:
- 'spec/**/*.rb'
Layout/EmptyLineAfterMagicComment:
Enabled: true
Layout/IndentHash:
EnforcedStyle: consistent
Layout/LeadingCommentSpace:
Enabled: false
Layout/MultilineMethodCallIndentation:
EnforcedStyle: indented
IndentationWidth: 2
Metrics/AbcSize:
Max: 25
Exclude:
- spec/system/**/*
Metrics/BlockLength:
Exclude:
- config.rb
- config/**/*
- spec/**/*
Metrics/ClassLength:
Max: 120
Metrics/CyclomaticComplexity:
Max: 16
Metrics/LineLength:
Max: 125
Metrics/MethodLength:
Max: 20
Metrics/PerceivedComplexity:
Max: 10
RSpec/AlignLeftLetBrace:
Enabled: true
RSpec/ContextWording:
Enabled: false
RSpec/DescribeClass:
Exclude:
- 'spec/integration/**/*_spec.rb'
- 'spec/system/**/*_spec.rb'
RSpec/EmptyExampleGroup:
Enabled: false
RSpec/ExampleLength:
Max: 25
RSpec/ExpectChange:
EnforcedStyle: block
RSpec/EmptyLineAfterFinalLet:
Enabled: false
RSpec/MultipleExpectations:
Max: 6
RSpec/NamedSubject:
Enabled: true
RSpec/NestedGroups:
Max: 4
RSpec/VerifiedDoubles:
Enabled: false
Style/BracesAroundHashParameters:
Enabled: false
Style/Documentation:
Enabled: false
Style/DoubleNegation:
Enabled: false
Style/EmptyMethod:
Enabled: false
Style/FormatString:
EnforcedStyle: sprintf
Style/ModuleFunction:
Enabled: false
Style/NumericLiterals:
MinDigits: 15
Style/PercentLiteralDelimiters:
PreferredDelimiters:
default: ()
'%': ()
'%i': ()
'%q': ()
'%Q': ()
'%r': '{}'
'%s': ()
'%w': ()
'%W': ()
'%x': ()
Style/PerlBackrefs:
Enabled: false
Style/RegexpLiteral:
Enabled: false
Style/Semicolon:
AllowAsExpressionSeparator: true
Style/StringLiterals:
EnforcedStyle: double_quotes
Style/SymbolArray:
Enabled: false
Style/SymbolProc:
Enabled: false
Style/TrailingCommaInArguments:
EnforcedStyleForMultiline: comma
Enabled: false
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: comma
Style/TrailingCommaInArrayLiteral:
EnforcedStyleForMultiline: comma
Style/WordArray:
EnforcedStyle: percent
Afterwards run bundle exec rubocop -a
to auto-correct all found offenses.
In my projects I add a taks rake ci
to the Rakefile which automatically runs rubocop and the tests and any other task I run to ensure my project is in a good state.
Rakefile
if %w(development test).include? Rails.env
require "awesome_print"
require "bundler/audit/task"
require "rspec/core/rake_task"
require "rubocop"
Bundler::Audit::Task.new
RSpec::Core::RakeTask.new(:rspec) do |t|
# t.exclude_pattern = "**/{system}/*_spec.rb" # skip system specs
end
namespace :factory_bot do
desc "Verify that all FactoryBot factories are valid"
task lint: :environment do
puts "Building all factories and traits to ensure they are valid"
FactoryBot.lint traits: true, strategy: :build, verbose: true
end
end
desc "Run rubocop"
task rubocop: :environment do
sh "bundle exec rubocop -c .rubocop.yml"
end
desc "Run rubocop with autocorrect"
task rubocopa: :environment do
puts "Obey the autocorrection cop!"
sh "bundle exec rubocop -c .rubocop.yml --auto-correct"
end
desc "Run rubocop and the specs"
task ci: %w(rubocop bundle:audit factory_bot:lint rspec)
end
You use the rails generate ...
command to generate files or a scaffold. Usually this creates more files than I need (or want to have automatically created). Therefore I adapt the generator configuration in
config/application.rb
config.generators do |g|
g.orm :active_record, primary_key_type: :uuid
g.helper false
g.javascripts false
g.stylesheets = false
g.controller_specs false
g.factory_bot false
g.helper_specs false
g.view_specs false
g.system_tests = nil
end
I prefer to use HAML over Ruby's default ERB. That's why I install it now:
Gemfile
gem "haml-rails", "~> 2.0"
And convert either only the layout file or all existing files to HAML:
rails generate haml:application_layout convert
HAML_RAILS_DELETE_ERB=true rails haml:erb2haml
One thing I do, is to add my own Settings mechanism to read ENV variables. It also supports default values and local overrides. That would be too much for this guide, but be welcome to keep reading on:
https://github.com/mediafinger/settings
This is an example of a CI configuration for gitlab. Like everything in this setup guide, you have to adapt it to your needs. This example expects the ENV variable HEROKU_PRODUCTION_API_KEY
to be set to push / deploy to production.
.gitlab-ci.yml
image: timbru31/ruby-node # Docker image with Ruby 2.6, Node.js 10, npm 6 and yarn
services:
- postgres:10.1
# Cache the bundled gems to speed up the CI runs by minutes
cache:
key: lupus_ci_cache
paths:
- Gemfile.lock
- vendor/bundle/
untracked: true
variables:
DISABLE_SPRING: 1
RAILS_ENV: test
DB_HOST: postgres
before_script:
- ruby --version
- gem --version
- chmod 755 Gemfile*
- gem install bundler:2.0.1 --no-document
- bundle check || bundle install --without production --jobs $(nproc) --path vendor/bundle --binstubs vendor/bundle/bin
- cp config/database.yml.ci config/database.yml # copy gitlab-ci specific config
- bundle exec rails db:create RAILS_ENV=test
- bundle exec rails db:schema:load RAILS_ENV=test
stages:
- test
- deploy
# enable when JS / CSS or other assets need to be bundled
# - bundle exec webpack
# Optionally add more runners to GitLab-CI
# and extract the tasks to smaller jobs
# which can then be executed in parallel
# (drawback is that the setup will be done for each job)
testing:
stage: test
script:
# run rubocop, bundler:audit, factory_bot:lint and all specs
- bundle exec rake ci
# enable when using selenium_chrome_headless / chromedriver
#
# # install Chrome and chromedriver to run headless system specs
# - chmod +rx ./bin/setup_chrome
# - ./bin/setup_chrome
#
# # run only the system specs
# - bundle exec rspec spec/system
production:
stage: deploy
only:
- master
script:
- curl https://cli-assets.heroku.com/install.sh | sh
- heroku --version
# Use the gem dpl to push code to heroku
- gem install dpl
- dpl --provider=heroku --app=lupus-production --api-key=$HEROKU_PRODUCTION_API_KEY
- heroku run bundle exec rails db:migrate --exit-code --app lupus-production
The database config file that is being copied in the before script could look like this:
config/database.yml.ci
test: &test
adapter: postgresql
encoding: unicode
username: postgres
password: postgres
host: <%= ENV['DB_HOST'] %>
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
database: lupus_test
Whatever your project needs, or you like :-)