It isn’t until I’m pair-programming with someone who is new to Ruby on Rails that I remember the pain. I have so much weird muscle memory for Ruby and Rails and all of the tools that come along with Ruby and Rails. I take it all for granted now; I’m an experienced Rails developer.
You may have read about “the magic” of Rails. You may have even experienced it. But did you experience it à la carte? In order to “learn Rails” you must also learn, in many cases, Factory Bot, RSpec, YARD, Active Job, and Capybara. You may need to learn how Rack middleware works. You may need to write some YAML, or read something called a “VCR cassette” that outputs many large YAML files that are “fun” to “read”. You may even need to learn your coworkers’s preferred, weird ways of writing Ruby (a very expressive language) that you just haven’t encountered yet. This all can be overwhelming.
So, as an experienced Rails developer, I take for granted how easily I can figure out how to do stuff. And how I can figure out how to do stuff if I don’t know how to do. I take for granted that, when you’re new, you haven’t learned where to look yet. None of this, by the way, is unique to the Ruby and Rails ecosystems. Maybe you’ve gone through this “learning how to learn” process a few times already. Maybe you like it now.
Whether you’re a junior developer or an experienced developer who is just new to Ruby, the road from feeling completely lost to feeling able to move forward may be long. My aim in this post is to give you a small jump-start. To point you in the direction of what you need to learn and the resources that can help you learn it. So you, too, can take your past self for granted and leave that completely lost feeling behind.
Because my employer, Super Good, primarily works on Ruby on Rails applications that use the Solidus ecommerce framework, some parts of this article point to stuff I do as a Solidus developer, and stuff I’ve learned because I’m a Solidus developer. But if you don’t care about Solidus, don’t let that sour you on this blog post. At the end of the day, Solidus is just another Rails engine. As a Ruby developer, and a Rails developer, you inevitably will need to locate and understand source code that comes from external dependencies. I hope you find that I’ve written this guide in a way that equips you with the knowledge you need regardless of which gems are listed in your Gemfile.
Before we get deep into things, I want to provide you with my opinionated, high-level checklist. Take it or leave it. It will take you more than a day, maybe more than a month, to confidently check off all these items, depending on your starting point and your end goals. After many years using Ruby and Rails, I’ve identified each of these items as things that really helped me level up and feel more productive at work:
Enumerable
and Array
classes.
🔗Module#prepend
, Module#include
,
Module#extend
, Module#class_eval
and the super
keyword.
🔗These things build on one another, but not in a strictly linear way. You may
have to go back and relearn stuff (Module#prepend
versus Module#include
?) as
you move forward. That said, read on if you want to know what each of these
items really entails.
Skip to the end if you just want my recommended reading list—formatted as a three-week learning plan.
If you’ve made it this far, I will assume that you have previously learned
another programming language or are programming language-curious. If you’re
unsure how to describe the difference between true
and "true"
, this
blog post is not for you. And I’d recommend you go read “How to Pick a
Programming Language to Learn” instead. Ruby would
be a great first programming language to learn. With that said, my following
recommendations will be more useful to someone who already sort of knows how
programming languages work.
If you’re looking to learn Ruby, I’d start at Ruby’s official website, which is the home of two great articles: “Ruby in Twenty Minutes” and “Ruby from Other Languages”. One or both of those articles may be useful in your path to becoming fluent in Ruby.
But the things that I found the most valuable early on in my Ruby journey was the the Edgecase Ruby Koans. Going through each of these tests, from start to finish, is an engaging, interactive way to get familiar with how to read and write Ruby. The Ruby Koans are so helpful, in fact, that I recommend that you do them all twice: once when you’re just starting, and once after six months or a year of writing Ruby somewhat regularly.
I also want to highlight some text on the Ruby Koans homepage:
Testing is not just something we pay lip service to, but something we live. It is essential in your quest to learn and do great things in the language.
I can’t speak for all Rubyists, but in relation to my own Ruby journey: this is true.
“Learning Ruby,” never stops, but it may take you a day or two before you go on to next item in the checklist. It’s okay if you don’t fully get Ruby, or understand everything the Koans have taught you. But in any case: good luck.
Now that you can read and write Ruby, spend some time reading the
Enumerable
and
Array
core library
documentation. As a Rails developer, you will deal with arrays a lot. And an
instance of an Array
not only has all of the standard array methods available
on it, but also all of the Enumerable
methods, since an array is a type of
Enumerable
:
my_array = Array.new
my_array.is_a? Array #=> true
my_array.is_a? Enumberable #=> true
While you read through this documentation, I recommend keeping an open Ruby console nearby so you can try each method out. In my experience, manipulating dead-simple objects is a quick way to test assumptions:
my_array = [
[1, 2],
[3, 4]
]
my_array.flat_map { |item| item * 2 }
#=> [2, 4, 6, 8]
By default, when you install Ruby, you can run irb
to get into an interactive
Ruby console. But I usually recommend installing Pry
to take advantage of its advanced features. (Later
I will recommend Pry as a great debugger for other reasons, too, but let’s
leave that until later.)
As you read and write more Ruby, you will notice that inheritance comes up a lot:
require "date"
class Document
attr_accessor :name, :author, :published_at
def initialize(name:, author:, published_at: ::DateTime.now)
@name = name
@author = author
@published_at = published_at
end
end
class BlogPost < Document
def pretty_date
[published_at.year, published_at.month, published_at.day].join "-"
end
end
my_blog_post = BlogPost.new name: "Good blog post",
author: "benjamin"
published_at: DateTime.new(1990, 01, 01)
my_blog_post.pretty_date
#=> "1990-1-1"
And in many ways:
module DateHelperMethods
def really_old?
published_at < Date.new(2000, 01, 01)
end
def pretty_date
"Published at: #{super}"
end
end
Document.include DateHelperMethods
my_blog_post.really_old? #=> true
my_blog_post.pretty_date #=> "1990-1-1"
BlogPost.prepend DateHelperMethods
my_blog_post.pretty_date #=> "Published at: 1990-1-1"
Even if you don’t know Ruby yet, you probably have some idea of what’s going on in the above code snippets. But as you get deeper into real application and library code, you may need to know what makes including a module on a Ruby class different from prepending or extending a module on a class. There are many great blog posts about this, so I won’t go into any more detail here. Just keep this in mind as you move forward on your Ruby journey.
In the above snippets, we covered Module#include
, and the super
keyword.
Module#prepend
and #extend
will will be in the same wheelhouse, but
Module#class_eval
is a bit different. If you will be working on a legacy
application (and, especially, a legacy Solidus application), you may encounter
Module#class_eval
as well—so be sure to check that out, too, if that’s where
you’re coming from.
Now that you have some Ruby foundations, you can see what Rails offers you as a
developer. If you’re feeling adventurous, you could even jump into Rails’s
source code (maybe start with the activesupport
gem) and make some sense of it.
The Rails Guides can give you a better idea of what Rails is than I can. So I suggest you start with their “Getting Started with Rails” article. It may take you a full day to meaningfully go through this article, but I think it’s worth the time. As a Rails developer, you’ll need to understand what models are, how controllers and routes work together, how views work, and how all of these layers connect to one another. As you go through this Rails guide article:
I recommend following along with a terminal open as you go. Run rails new
for yourself as you’re reading about it. Generate a database migration. Generate
a new model. See what it feels like to create and run a fresh Rails application.
With some Rails basics in hand, you can start work on your real Rails application. You may also just want to source-dive into some open source Rails applications to see what real, in-the-wild feature code can look like. Check out the Mastodon git repository or the Discourse git repository, for example.
While you’re checking out a Rails application codebase, don’t forget to examine its test suite. Every Rails app is different, and when it comes to tests some Rails apps use Ruby’s built-in test framework, Minitest and others use RSpec. There are many other Ruby testing tools that you may or may not encounter. Sometimes when you’re reading RSpec tests (colloquially known as “specs”) with FactoryBot factories sprinkled in, you may not even feel like you’re reading Ruby code anymore.
No matter what testing toolkits you or your colleagues use, you will want tests for each layer of the Rails application: the model layer, the controller layer, the view layer—and probably some other layers, depending on the app in question. Fortunately, Rails comes with testing mechanisms to make this job easier.
I recommend reading the Rails Guides’s “Testing Rails
Application” next. That guide is
tailor-made for Rails applications using Minitest. But in my experience, many
Rails applications use RSpec (and rspec-rails
) instead of Minitest. Put
reading the RSpec documentation in your tentative to-do list.
If you’re new to Ruby, learning Ruby and Rails and RSpec and all of the
other testing tools being used within your RSpec tests, all at the same time,
will be painful. If you look at Mastodon’s rails_helper
file, which defines some functionality that gets
included before many of the application’s test files are run, you will encounter
references to a bunch of stuff that you can’t make sense of without reading a
lot more documentation, and a lot more source code.
As with all toolboxes, you’ll come to recognize the tools that Rails developers reach for most often. You’ll see something like this:
before do
create :blog_post,
title: "Good blog post",
author: create(:author, name: "benjamin")
end
And you’ll just know: “Oh, the person who wrote this is using the
FactoryBot #create
method to create an blog post object. If I need to know
what class that object is, I should go find where the :blog_post
factory is
defined. And blog posts have an association, #author
, which has its own
:author
factory. All of this is happening before the test runs.”
But, yeah, you won’t know any of that at first.
Even worse: maybe the factory isn’t defined in your application, and it’s being included from some third-party library in your Gemfile. How do you locate stuff when you don’t even understand what it is to begin with? We’ll hopefully answer that question in the next section, about getting comfortable with your debugging tools.
In this post I haven’t even begun to cover things like RSpec’s mocking utilities or tools like VCR which can totally change how and what you test. For a while, you can get away with just knowing that these tools exist, and recognizing them when you see them in passing. But eventually, you’ll need to sit down with each of these tools and their READMEs individually.
If you just finished reading the previous section and feel overwhelmed by the amount of things you haven’t learned yet, I bring good news: your debugging tools not only help you resolve bugs in software, they help you find solid ground when you don’t know where to look next. In the end, you need to debug and find your footing in the way that works for your brain (and your text editor). I just hope that you can:
When I work on Rails apps, this boils down to using Pry and bundle open
, for
the most part.
Earlier I mentioned Pry, an alternative interactive shell and REPL (read-evaluate-print loop) to the Ruby’s built-in one, IRB. IRB is really nice, especially on newer Rubies like 3.1 and 3.2. But Pry comes with some tools that IRB doesn’t come with. And, without any configuration, sometimes seems to just format things the way my brain works a little better than IRB does. (But your mileage may vary.)
Pry’s killer feature, in my opinion, is show-source
—or $
, for short:
[3] pry(main)> $ my_order.paid?
From: /Users/benjaminwil/Dev/my_rails_app/vendor/bundle/ruby/3.1.2/gems/solidus_core-3.2.6/app/models/spree/order.rb:483:
Owner: Spree::Order
Visibility: public
Signature: paid?()
Number of lines: 3
def paid?
%w(paid credit_owed).include?(payment_state)
end
In a few seconds, we’ve just located the place in the source code where the method was defined. When you’re working with a complex library like Rails or Solidus, which gives you and your team access to hundreds of thousands of lines of code, this is a godsend. If I were only allowed to use one Pry feature, it’d be this one.
If I want to know more about what goes into making my_order.paid?
return
false, I now have so much more context than I had before. I can now also run
bundle open solidus_core
to open solidus_core
in my text editor and surf the
app/models/spree/order.rb
file for more context if I need it.
Sometimes, the result of show-source
will be less straightforward, and you may
not know what to do with the context you’re given:
[4] pry(main)> $ my_order.total
From: /Users/benjaminwil/Dev/my_rails_app/vendor/bundle/ruby/3.1.2/gems/activemodel-6.1.7.2/lib/active_model/attribute_methods.rb:254:
Owner: Spree::Order::GeneratedAttributeMethods
Visibility: public
Signature: total()
Number of lines: 3
CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) }
end
But you can puzzle out a lot with this: “Oh, this comes from the activemodel
gem, which seems like something I read about in the Rails Guides. The method
definition makes me think that this is actually a generated method. So maybe
that has to do with how #total
is some kind of database-backed, Rails method
defined with special Rails model magic. Interesting.”
It would be negligent of me not to mention that there are other debugging tools
other than Pry, including Ruby’s own debug
gem, which are powerful and good. I offer you
Pry specifically because a) it’s a tool that the Super Good team and I use
frequently and b) you can get up and running without having to learn much at
all. I recommend checking out Pry’s screencasts and the
devhints.io Pry cheatsheet if you want to get a deeper
introduction to this tool.
As you continue your Rails journey, you’ll learn to call #to_sql
after query
methods, read your server logs more efficiently, and find Your Own Best Friends
when it comes to making sense of Too Much information. So I’ll leave you there
to figure that out for yourself.
Depending on what kind of application you’re going to work on, you may or may not be extending functionality provided by external libraries. At Super Good, we often must adjust (or clobber) ecommerce functionality provided by Solidus so that our clients can get the most out of their completely-custom online stores and order management platforms.
Ideally, the frameworks and libraries you’re using provide extension points, or other configuration options that let you add code that does whatever you want without bypassing the library itself. In the real world, though, things don’t always line up like that. The Solidus Guides provide a handful of customization-specific guides: one of them discusses extension points and overrides. So, yeah. It’s canon. Sometimes you just gotta do it. But try not to if you can help it.
I can’t rightly talk much more about extension points. Every library’s extension
points look a little different. The best ones have documentation that help you
use them, or tell you how not to use them. In Solidus, there’s a whole class,
Spree::AppConfiguration
full of
extension points. Many of them provide a default class or function that you can
swap out for code that you keep in your own application:
module Spree
class AppConfiguration < Preferences::Configuration
# ...
# Allows providing your own class for merging two orders.
#
# @!attribute [rw] order_merger_class
# @return [Class] a class with the same public interfaces
# as Spree::OrderMerger.
class_name_attribute :order_merger_class, default: 'Spree::OrderMerger'
# ...
end
end
If you wanted to provide your own order merger functionality, you can set that configuration option in an initializer:
# config/initializers/my_application.rb
Spree::Config.order_merger_class = "MyApplication::OrderMerger"
Then, wherever you see
Spree::Config.order_merger_class
in the
Solidus source code, you’ll be using your class instead of the default one
provided by Solidus.
In a section above, we already discussed how it’s worth understanding how
Module#include
, Module#prepend
, and friends, work so you can understand
where Ruby functionality is coming from when you read and write new features.
If you need to change an existing class or module, or one that you’re inheriting
from a dependency, these tools can help—but be careful, as you really are
overriding the authored behaviour.
Since Rails 6, there are more easy-on-the-eyes, easier-to-skim ways to prepend
additional functionality onto existing classes and modules using
ActiveSupport::Concern
s:
module CustomBlogPostStuff
extend ActiveSupport::Concern
prepended do
has_many :tags
has_many :categories, through: :tags
end
def pretty_date
"Published at: #{super}"
end
end
BlogPost.prepend CustomBlogPostStuff
My advice is to always prefer isolating your application code from code
provided by dependencies. It makes gem upgrades more straightforward. It is less
error prone generally. When you do resort to overrides, fight Rails and your
dependencies as gently as possible. Also don’t forget that, when it comes to
re-defining existing methods: super
is a good friend to have.
If you’re just beginning your Ruby or Rails adventure, I hope this post has been helpful and encouraging. Below I’ve included the links scattered throughout this article into a “three-week plan” of sorts.
Learning Ruby:
Enumerable
classArray
classRecommended tools for week 1:
Learning Rails:
Ruby testing tools to familiarize yourself with:
More fun with Pry:
Real, open source Rails application codebases:
Extra credit:
The debug
gem
Ruby’s own debug
gem, which adds a lot of functionality to IRB.
Getting Started with Engines
Solidus is a Rails engine. Solidus extensions are Rails engines. This is how
they work.
Mastodon’s rails_helper.rb
file
See anything you don’t understand? How would you figure out all of the stuff you don’t understand?
Solidus’s Spree::AppConfiguration
class
Check out how you can swap out entire classes using configuration points.
Here’s an
example
of one such configuration point in Solidus’s Spree::Order
source code.
See lots of stuff you don’t recognize? How do you go about fixing that?