loading...
Cover image for Setting Up A Fresh Ruby Project

Setting Up A Fresh Ruby Project

deciduously profile image Ben Lovy ・6 min read

To start up a Ruby project, you don't need to do anything other than put some Ruby code in a file and invoke it with ruby program.rb.

As projects grow, though, you'll likely add dependencies and tests. You can mitigate this with documentation - tell users what gems they need to pull, and which files contain the test suites - but that becomes unwieldy quick. It's much better to lean on ecosystem tools to specify and automate your build steps.

To follow along, you'll need Ruby installed. The latest at time of writing is version 2.7.0.

EDIT: If you don't want to do this from scratch, try bundler gem:

I find the easiest way to start a new Ruby project is bundler gem, even when not actually writing a gem. It also follows Ruby conventions, i.e. a lib folder instead of src.

This is how you do it:

You can then just delete foo.gemspec πŸ˜€



Make a directory

Don't just spread your ruby source all across your home directory - keep your projects self contained. Create a directory:

$ mkdir ruby_project

Open this directory in your favorite editor. Everything else in this post goes in here.

Add Bundler

To manage dependencies, use Bundler. This tool allows you to specify gems required to run your project and lets users issue a single bundle install command to pull down any required.

First, install the gem in you haven't already:

$ gem install bundler

Then, create a file called Gemfile in your project root. For now, we'll just specify the package repository URL:

source 'https://rubygems.org'

As we need dependencies, we'll add them here below this line.

Add a Test file

The first dependency we need is for testing. This example uses minitest. Add that to your Gemfile:

  source 'https://rubygems.org'
+ gem 'minitest'

Now you can invoke bundle install to pull this down automatically. When you do, a new file called Gemfile.lock will be generated containing the specific package version in use.

This is really up to you, but I like keeping my tests in a separate subdirectory:

$ mkdir test

Create a file in this folder called test/cool_program_test.rb with the following contents:

# frozen_string_literal: true

require 'minitest/autorun'
require_relative '../lib/cool_program'

# Test program coolness
class CoolProgramTest < Minitest::Test
  def test_coolness_off_the_charts
    # skip
    assert_equal CoolProgram.new.coolness, 11
  end
end

The first line, # frozen_string_literal: true, is a magic comment that is kind of like calling Object.freeze on every string literal. This is done for performance reasons, but you can opt out for a given string by prefixing it with a + character: +'my newly mutable string literal'. Now you can do my_str << some_other_str. In Ruby, though, I'm not finding myself manipulating strings like this.

We know we'll be able to use the minitest dependency in the first line because we specified it with Bundler. The actual source code doesn't exist yet, though!

Add an Implementation File

This is also personal preference, but it's a sensible idea to keep your implementations in a separate subdirectory as well:

$ mkdir lib

Create the file we pulled into the test file at lib/cool_program.rb with these contents:

# frozen_string_literal: true

# The coolest program
class CoolProgram
  attr_reader :coolness
  def initialize
    @coolness = 11
  end
end

puts "Coolness: #{CoolProgram.new.coolness}/10"

Groovy! Everything's in place.

Add a Rakefile

Now, we could just direct users to invoke ruby themselves on the proper files - ruby lib/cool_program.rb to run the program, or ruby test/cool_program_test.rb for the tests. It would be better if we could specify those paths and abstract into test or run operations. That's what rake is for! This tool is a lot like GNU Make, but for Ruby.

First, add it to your Gemfile:

  source 'https://rubygems.org'
  gem 'minitest'
+ gem 'rake'

Then, add a new file in your project root called Rakefile:

task default: %w[test]

task :run do
  ruby 'lib/cool_program.rb'
end

task :test do
  ruby 'test/cool_program_test.rb'
end

If you know how make works, this is similar (if you don't, I've got ya covered). The first line defines the default task, or what happens when you invoke rake without specifying a specific task. In this case, we just have it run the test task as a dependency with no block itself. This is a list, you can specify multiple tasks here.

Each task below can be invoked at the command line directly - you'd use rake run to actually execute your program and just rake on its own to run the test suite.

Add a Linter

I've become fairly reliant on the Rubocop linter to keep me in line with the Ruby style guide. It now ships with a ready-to-go Rake task.

First, add it to your Gemfile:

  source 'https://rubygems.org'
  gem 'minitest'
  gem 'rake'
+ gem 'rubocop'

Then, modify your Rakefile:

+ require 'rubocop/rake_task'

- task default: %w[test]
+ task default: %w[lint test]

+ RuboCop::RakeTask.new(:lint) do |task|
+   task.patterns = ['lib/**/*.rb', 'test/**/*.rb']
+   task.fail_on_error = false
+ end

  task :run do
    ruby 'lib/cool_program.rb'
  end

  task :test do
    ruby 'test/cool_program_test.rb'
  end

It's my personal preference to run the linter and the tester by default, but not fail on lint errors. Season to taste.

Optional - Add a GitHub Action

I host almost all my code on GitHub and have become a fan of GitHub Actions. Using YAML, you can have GitHub automatically run your tests whenever you commit or open a PR. Create a new file at <project root>/.github/workflows/ruby.yml:

name: Ruby

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up Ruby 2.7
      uses: actions/setup-ruby@v1
      with:
        ruby-version: 2.7.x
    - name: Build and test with Rake
      run: |
        gem install bundler
        bundle install --jobs 4 --retry 3
        bundle exec rake

You can tweak each step very easily here - the commands specified will get run and if everything completes without errors, you get a green check mark. Sweet! Using rake allows this file to stay concise, and then you'd be able to manage changes to your structure in your Rakefile without having to worry about this file at all.

Add a README and stuff

We're just about done, but you should always include a README:

# ruby_project

The coolest dang program that you ever did see.

## Dependencies

* [Ruby](https://www.ruby-lang.org/en/).  Written with version [2.7.0](https://www.ruby-lang.org/en/news/2019/12/25/ruby-2-7-0-released/) - *[docs](https://docs.ruby-lang.org/en/2.7.0/)*.

## Usage

Install deps: `gem install bundler && bundle install`.  Run `bundle exec rake` to run the tests, or `bundle exec rake run` to run the program.

Add a .gitignore:

*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/

## Environment normalization
/.bundle/
/vendor/bundle
/lib/bundler/man/

# Used by RuboCop. Remote config files pulled in from inherit_from directive.
.rubocop-https?--*

You should probably add a LICENSE file, too - here's the BSD-3-Clause:

Copyright 2020 Coolness McAwesome

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

At the end, your directory should look like this:

$ tree
.
|____Gemfile.lock
|____.gitignore
|____.github
| |____workflows
| | |____ruby.yml
|____Gemfile
|____LICENSE
|____Rakefile
|____test
| |____cool_program_test.rb
|____README.md
|____lib
| |____cool_program.rb

Finally, git init && git add . && git commit -m "Initial commit". Happy hacking!

If you don't feel a pressing need to do this from scratch, you can just use this GitHub template with the code from this post.

Photo by Jukan Tateisi on Unsplash

Discussion

pic
Editor guide
Collapse
citizen428 profile image
Michael Kohl

I find the easiest way to start a new Ruby project is bundler gem, even when not actually writing a gem. It also follows Ruby conventions, i.e. a lib folder instead of src.

This is how you do it:

You can then just delete foo.gemspec πŸ˜€

Collapse
deciduously profile image
Ben Lovy Author

Aha - note to self, read all the docs. Thanks!

Collapse
daviddalbusco profile image
David Dal Busco

Funny that we both published today kind of a "how to setup a project" post πŸ˜‰ (yours being obviously way more detailed and expensive than mine, great job πŸ‘)

Thank you for the share, I should give it try one of these days!

Collapse
deciduously profile image
Ben Lovy Author

It's a child...you know, a beginner at the beginning of something.