3 Ways Rails Stays Modern in 2021

My, how time flies! Ten years ago I was the cool kid writing in a framework that was at the peak of its popularity. Javascript was still a young, quirky, and erratic tyke. The ASP.NET and Java communities were abandoning their corporate battleships to hop aboard the cool new Ruby on Rails train. All was right with the world.

But if there’s one timeless lesson any software developer should learn, it’s the old adage: “time and tide wait for no man.” With the rise of mobile browsers and a desire for a unified language to work across the frontend and backend, Javascript and all its many frameworks and variants have skyrocketed in popularity. Javascript, Node.JS, and Typescript are now unquestionably some of the most popular development technologies around.

So, is Rails still relevant? Do we really need it now that we can have a Node.JS backend and tools like React on the frontend?

Actually, we do. I discussed the reasons why last year in my blog post, 4 Reasons You Should Still Choose Ruby on Rails. While all those points are still true, I thought I’d focus in this post on 3 ways Ruby on Rails stays relevant in 2021.

Maybe you are in the Javascript community and are curious about what Rails can offer. Or you used Rails years ago and have since left it for something else. Regardless, hopefully this post will help you consider the framework in your future!

1. You Can Sprinkle in Functional Programming

When I first started coding professionally in the late naughts, I remember arguing with a gray-bearded COBOL developer who had been coding since the 1960s about how object-oriented programming was so superior to procedural programming. While I still believe that in many respects that is true, I had no idea that ten years later I would feel something like an old timer defending the merits of why object-oriented programming is still relevant when functional programming is all the rage.

Don’t get me wrong, I don’t believe debates like “Why X type of programming is dumb and you should use Y instead” are helpful. Instead, we as as software engineers should have the professional maturity to adopt methods of development that are conducive to the problems we are solving. That means it’s not an either/or situation. I believe we can structure our applications using one primary paradigm and then sprinkle in a secondary one where appropriate.

For example, Ruby on Rails primarily relies on classical object-oriented programming. That means good old classes with methods, instances, inheritance, encapsulation, etc. For most web applications, this is a flexible and straightforward approach.

Here’s an example class in Ruby:

class Student < ApplicationRecord
  belongs_to :school
  has_many :classes
  has_many :teachers, through: :classes

  def to_s
    "#{first_name} #{last_name}: #{grade} grade"
  end
end

This is simple enough. The intent is clear: here’s the structure of a student, its relationships to other models, and some methods that are appropriate for it. The beauty of Rails is that it uses these model objects to mimic the real world. As humans, we inherently possess a concept of objects. When we first learn to grasp objects as infants, we discover that they have properties like color, shape, taste, and smell. Objects also have behaviors such as making a noise, flashing a light, bouncing on the floor, or moving in a certain pattern. They also share similarities and relationships to other objects. Since this is how our brain intuitively processes reality, object-oriented programming takes advantage of our internal thinking mechanisms and gives us a way to model the world through software.

But the nice thing about Ruby on Rails is that it doesn’t just rely on object-oriented programming. Yes, the real world consists of objects, but it also has more abstract concepts that can be more concisely and efficiently described using mathematics. That’s one of the reasons functional programming languages are so successful. But you don’t have to abandon Ruby for another language if you want to achieve this goal. Let me show you a simple example to explain what I mean:

Inside of our nicely structured Student class, we can do something like this:

 class Student < ApplicationRecord
    ...
    validates :locker_number, 
        presence: { if: ->(student) { student.grade >= 6 } }

 end

Above we are using an anonymous function to check if the student’s grade is at a minimum level before requiring the locker number attribute. Much like in functional programming languages, we can express intent quickly and concisely through anonymous methods all from within our class-based structure. We get the best of both worlds.

Let’s take another example:

a_grades = student.class_records.select do |class_record|
  class_record.average >= 90
end

Here we are applying a functional ‘filter’ method to a collection and getting back the results that are relevant to us. Looks like Javascript’s filter method, eh? Ruby has a lot of convenient collection methods that you can find in Javascript libraries such as Lodash or Ramda.

Ruby is also suited for other math whiz functional tasks such as pattern matching, tail-call optimized recursion, function currying, infinite ranges, and lazy evaluation. I won’t go into those here, but trust me that you can use some pretty powerful techniques to process data in a more functional style.

2. Types Are a Thing Now

A common complaint I hear about Ruby is that it’s too dynamic, which doesn’t scale well for large projects or for using the functional programming features I discussed above. I’ll save my “all things old are new again” speech about the static vs dynamic types carousel that’s been going on for decades now. The reality is that Ruby gives you the best of both worlds: it’s dynamically typed by default, but as of Ruby 3, it comes packaged with a type signature language called RBS.

The idea is that RBS specifies the rules for the Ruby language. Static type checkers such as Steep or (eventually)Sorbet can take advantage of the type annotations in your program.

Let’s look at a quick example:

# student.rbs
class Student
    attr_reader first_name: String
    attr_reader last_name: String

    def initialize(first_name: String, last_name: String) -> void

    def add_grade(grade: Number | Integer | Grade) -> void

    def a_grades() -> Array[Grade] 

end

If you’re familiar with Typescript or Swift, this won’t look terribly different. However, one major benefit of the way RBS works is that you don’t specify your types in your actual .rb files. Instead, you put them in a companion .rbs file. If you’ve ever worked with C’s header files, this is a similar idea. We can nicely separate the structure of our program from the implementation details. Because of this approach, there’s no compilation that needs to happen to make your program run. Our static analyzer will do the work of reporting any errors that occur. IDEs such as VSCode can take advantage of this and give you type checking with a simple plugin. Best of all, your existing ruby applications can work with only minor alterations.

3. Webpacker is baked into Rails 6

There’s no question that transpiling Javascript and bundling web assets have become an indispensable part of building modern apps. Gone are the days where you just wrote a JS or CSS file and slapped a <script type='javascript'> or <link rel="stylesheet" tag onto your file. Things are more complex now in web development, but with that complexity comes a huge degree of flexibility and efficiency. We can utilize tools like Babel and Webpack to allow our Javascript, stylesheets, and other assets to run efficiently on any browser.

Rails makes it easy to use Webpack using a gem called Webpacker. As of Rails 6, it’s baked right into the framework. Using a simple Rails generator, it can take care of adding all the files needed for writing modern ES6 Javascript: bundle exec rails webpacker:install. This will generate the Webpack configuration files, along with a package.json file where you can add all your favorite NPM packages.

Rails does a nice job of putting some convention around your web assets. For example, all your entry-point asset files are stored like this:

app/packs:
  ├── entrypoints:
  │   # Only Webpack entry files here
  │   └── application.js
  │   └── application.css
  └── src:
  │   └── my_component.js
  └── stylesheets:
  │   └── my_styles.css
  └── images:
      └── logo.svg

These “packs” are basically your bundles of JS, CSS, or other assets. Here’s an example of what one looks like:

// app/packs/entrypoints/application.js

import 'jquery';

import Menu from '../src/menu';

$(() => {
    Menu.initialize();
});

To import your packs, just add this to your html.erb file:

<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>

And voila! Rails’ asset pipeline will automatically compile and reference the application.js and application.css output appropriately. For the most part, I haven’t had to deeply configure Webpacker in any apps where I’ve used it. That said, it does a good job of just working out of the box while still providing you the option to configure at will should you need to.

Conclusion

These are but a few ways at how Rails has stayed relevant in 2021, but there’s more! The Ruby and the Rails team are always hard at work at improving the language and framework. Pretty much any modern programming feature you enjoy can be found when using Ruby 3 + Rails 6. And there’s no sign of them slowing down!

A mature core team and passionate community have kept Ruby on Rails fresh 17 years later, and hopefully will for many years to come. That’s why we at Airship are happily still riding on the Rails with our customers!

References: