Codectionary / Developer documentation / Ruby

Learn how a small ordinary Ruby project can organise scripts, reusable code, tests and optional dependencies without assuming Rails.

Start small, then add structure

A one-file Ruby script is already a valid project. As it grows, separate code you want to reuse from the script that starts the program, then add tests and dependency files when needed. Ruby itself does not require one universal folder layout. The structure below is a simple example for a command-line application, not a rule every project must follow.

Rails applications have their own conventions, such as app, config and db directories. Do not copy a Rails tree into a small standalone Ruby script just because both use the Ruby language.

An illustrative small Ruby application

ruby-notes/
  bin/notes
  lib/notes.rb
  test/notes_test.rb
  Gemfile
  Gemfile.lock
  .ruby-version
  .gitignore
  README.md

Source files and the entry script

Files ending in .rb hold Ruby source. In this example, lib/notes.rb defines a small reusable module. The script bin/notes is the entry point: it loads that module, calls it and prints the result. A bin or exe directory often holds commands people run, but the name is a convention. You can execute this script explicitly with ruby bin/notes, including on Windows, whether or not it has a file extension.

The directory names describe purpose rather than changing Ruby syntax. You might instead put a tiny application entirely in hello.rb. Split a file only when a second file makes the program easier to understand or reuse.

lib/notes.rb — reusable behaviour

module Notes
  def self.title(name)
    "Note: #{name}"
  end
end

bin/notes — program entry point

require_relative "../lib/notes"

puts Notes.title("Ruby")

Terminal — from ruby-notes

ruby bin/notes

require and require_relative

require loads a feature from Ruby’s load path, usually a standard library or an installed gem: require "json" or, after adding and installing a dependency, require "rainbow". Ruby normally loads a required feature only once per process. Writing require does not install a missing gem.

require_relative loads a file relative to the file containing that statement. From bin/notes, ../lib/notes means go up from bin and then into lib. You can omit the .rb extension. This is a clear choice for another source file in your own project. A bare require "notes" is not automatically the same thing; it needs an appropriate load path or project packaging setup.

Gemfile, Gemfile.lock and a gemspec

If the application uses external gems, place a Gemfile at its root. It lists direct dependencies and their sources. Run bundle install there; Bundler resolves and installs them and writes Gemfile.lock with exact versions. For an application, commit both files so another developer can reproduce the tested dependency set. If the project needs no external gems, it can start without either file.

A published reusable gem usually also has a .gemspec file declaring package metadata and runtime dependencies. That is a different job from a small application’s Gemfile. An application that uses a gem resolves dependency versions in its own bundle; a library cannot force consumers to use its development lockfile.

Configuration and tests

A .ruby-version file can record the Ruby release a version manager should select for this project; it is useful when teammates or tools need a consistent interpreter. A .gitignore can list generated or machine-specific files not worth committing. A README.md can explain what the project does and the command needed to run it. None of these is compulsory for a one-line script.

A test directory holds checks that exercise code in lib. A full project may use a testing gem, but you can start with the simple check below. It loads the same module as the executable, compares actual and expected text, raises an error if they differ, and prints a success message otherwise. This is a learning example, not a replacement for a test suite with useful coverage as the project grows.

test/notes_test.rb — a small executable check

require_relative "../lib/notes"

raise "wrong title" unless Notes.title("Ruby") == "Note: Ruby"
puts "Check passed"

Terminal — run the check from the project root

ruby test/notes_test.rb

Choose the files your project actually needs

For a first program, keep hello.rb and run it with ruby hello.rb. Move reusable behaviour into lib when the script grows; add a bin entry point if the program becomes a command, tests when behaviour needs checking, and a Gemfile when you introduce external gems. Keep paths and project-specific Ruby versions documented so the next person can run the same code.

RubyMine or VS Code can show this folder as a project, but the folder structure belongs to your program and works from a terminal too. A Rails project may organise files differently because the framework adds its own roles and tools.

Syntax

require_relative "../lib/notes"

Examples

Load a local module from a command

Save the two files in the shown folders, then run ruby bin/notes from the project root. Expected output: Note: Ruby.

require_relative "../lib/notes"
puts Notes.title("Ruby")
  1. Save the shown lib/notes.rb module separately, then save this snippet as bin/notes.
  2. require_relative resolves ../lib/notes from the bin/notes file, so the local module is available.
  3. Notes.title("Ruby") returns Note: Ruby, and puts prints that result in the terminal.

Best practices

  • Start with the smallest layout that makes the code clear.
  • Use require_relative for local source files and require for features on the load path.
  • Commit an application’s Gemfile and Gemfile.lock together when it uses Bundler.

Runtime & versions

Ruby interpreter. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

Ruby documentation

Specifications & further reading