The ecosystem of Ruby gems is rich with libraries to enable all sorts of useful functionality you’ll need as you write your web applications. However, at times it can be a challenge when you’re working within a broader Ruby context (aka not using Rails) to find gems which integrate well into all sorts of Ruby applications.
Occasionally you’ll come across a gem which doesn’t clearly label itself as Rails-only. In other cases, the manner in which you can use the gem outside of Rails isn’t clearly documented or there are odd limitations.
But thankfully, there are plenty of gems which are quite solid to use no matter what architecture you choose, and a few you might come across may even themselves be dependencies used by Rails because they’re reliable and battle-tested.
In this article, I’ll share with you some of my favorite gems you can use in your Ruby web apps. I have personal experience with all of them, and a few I’ve used extensively in the gems and frameworks I work on. (Note: order is mostly random.)
It’s certainly true that the Ruby console, IRB, has seen a lot of improvements over the past few years (gotta love that syntax highlighting!). But there’s always more that can be done to make it easier to visualize complex objects and datasets, and that’s where the AmazingPrint gem comes in.
It’s easy to install and integrate into your IRB sessions, and once loaded you can gain a more comprehensive idea of what’s actually inside arrays, hashes, and other types of objects as you inspect variables and method output.
AmazingPrint is loaded into the Bridgetown framework’s console automatically, and I can definitely recommend giving it a try in your projects.
Anyone who’s written a Rails application and used Action Mailer to send email, congratulations! You’ve used the Mail gem. Mail is indeed what powers Action Mailer under the hood—and good news is, it actually provides a very nice API all on its own.
As the readme demonstrates, you can send simple emails with a simple DSL:
mail=Mail.newdofrom'me@test.lindsaar.net'to'you@test.lindsaar.net'subject'Here is the image you wanted'bodyFile.read('body.txt')add_file:filename=>'somefile.png',:content=>File.read('/somefile.png')endmail.deliver!
Setting up a configuration to transport using SMTP is straightforward, and it’s also possible to send both text and HTML-formatted email parts in just a few lines of code.
Apparently Mail can also read email via POP3 or IMAP protocols, but I’ve never tried that personally. I can certainly vouch for sending email though, having done so in several Bridgetown + Roda projects. Thanks Mail!
Y’know, you might want store that sensitive SMTP username & password in environments variables your application can read in…the perfect segue to our next gem, Dotenv.
Dotenv does exactly what it sounds like. It reads in .env files and provides those values as environment variables. While you typically wouldn’t need this functionality in production, in development or local testing environments having a .env file in your project root makes a lot of sense. For example:
# in .env fileMAIL_SMTP_USERNAME=emailgobrrr
MAIL_SMTP_PASSWORD=asdfzxcv987
Then after loading Dotenv, you’ll have access to ENV["MAIL_SMTP_USERNAME"] and so on.
At one point in the past, I’d used a gem called Figaro which could read in YAML files and populate env vars accordingly, but development on that gem stalled. Meanwhile, Dotenv is simple and proven. And I’ve integrated this gem into the Bridgetown framework so repos can make use of it right out of the box.
Many Ruby frameworks—and Rails of course is among them—offer automatic code loading (and reloading!). It’s an expectation that once you’ve added your Ruby files in the appropriate folder structures with a naming convention that matches filenames to class names, it all Just Works™. No need to manually require various files in designated places and keep track of the changes needed if a file gets moved, renamed, or deleted.
Zeitwerk (pronounced zight-verk) was originally developed for Rails to replace the “classic” Rails autoloader, but as a standalone library it can be used by any number of frameworks and gems (and increasingly is!). I wrote about Zeitwerk before on Fullstack Ruby as well as the broader philosophy of why Ruby doesn’t have “imports” like so many other language ecosystems.
And at the risk of sounding like a broken record, the Bridgetown framework uses Zeitwerk both internally and as a code loader for application developers. It’s a fantastic library and a genuine workhorse for this very important Ruby functionality.
The ice_cube gem is for those cases—and you’d be surprised how often this can come up in application development—when you need to generate a series of dates. Every day. Every Tuesday and Friday at 1pm. The next several weekends. Just recently for example, I wanted to pull some data out of the database and display values on a month-based chart…which means I needed to generate a monthly series starting from now working backwards by n months. Perfect job for ice_cube!
There are a few additional features you gain if you’ve loaded in Active Support’s time extensions, but that’s optional. And the very Rubyesque API of ice_cube is quite enjoyable to work with. If you need to do anything at all with calendaring logic, this is the gem for you!
In the grand tradition of software projects coming up with funny-sounding names simply because they’re bits of other names smooshed together, the Nokolexbor gem is named such because it’s a portmanteau of Nokogiri (the popular XML/HTML Ruby parser) and Lexbor (an HTML engine written in C). Nokolexbor, like its underlying engine, has a goal to be very, very fast, as well as offer a high degree of HTML5 conformance.
We’ve found good use for Nokolexbor in Bridgetown to transform output HTML in various ways after the initial markup generation (a common example is to add # symbols on hover to headings so you can copy the URL with its extra fragment for deep linking). And I’d probably reach for this over Nokogiri going forward, although if you already have Nokogiri in your project I don’t know that I’d say there’s a compelling reason to switch.
Still I like the fact that Ruby has this new(ish) option available, especially as I believe DOM-like transformation of HTML server-side will become more and more common in Ruby web frameworks as traditionally client-side view techniques transition back to the server.
The Concurrent Ruby gem offers a huge collection of features and data structures which provide for writing Ruby code that is, well, concurrent. What does that mean? It could mean creating a thread-safe data structure for sharing between multiple threads executing in tandem. It could mean creating “promises” — aka code blocks which run in threads and return values to the main thread. These and many more use cases are difficult to write in “vanilla Ruby” all on your own (at least in a bug-free manner!), so it’s helpful to use a library like this instead.
See also: the Async gem which lets you schedule asynchronous tasks using fibers instead of threads.
Just like ice_cube lets you work easily with date series, the Money gem lets you work easily with currency values. You can parse strings into currency values (also requires monetize), perform math between values, exchange one currency for another (as long as an exchange rate is configured), and much more.
Values are stored internally as integers in cents, avoiding errors which can arise with floating-point arithmetic. And when you need to print out the money value, the format method makes this straightforward.
Dates, cash, now phone numbers! The Phonelib gem is specifically designed to let you validate phone numbers. The variety of phone number formats across countries and regions makes phone numbers uniquely difficult to work with, especially when you need to ensure you have a correct number and know how to use it in order to send text messages or automated callbacks.
Phonelib makes use of Google libphonenumber under the hood to ensure robust validation and introspection features. Trust me, this is the sort of logic you do not want to attempt to cobble together on your own!
The IP Anonymizer solves a problem you may not even realize you have. One of my own pet peeves is the default manner in which many authentication frameworks and code examples just log or store IP addresses verbatim. This is information I never actually want to capture. Knowing roughly what address a user is coming from vs. any other address for debugging purposes can be helpful, but I never need to know the exact address.
IP Anonymizer to the rescue! It can mask both IPv4 and IPv6 addresses, and as long as you’re able to configure your authentication & logging subsystems to pass IP addresses through this gem, you’ll keep that PII (Personally Identifiable Information) out of your records—at least in part.
I just couldn’t keep myself to ten! So here’s an eleventh option for you, and it’s one I’ve written. The HashWithDotAccess gem is used extensively by Bridgetown, and it started out life as an enhanced version of Active Support’s HashWithIndifferentAccess before a recent rewrite to remove that dependency. Now you can use HashWithDotAccess::Hash anywhere you need a hash which provides read/write access via dot notation (aka user.first_name instead of user[:first_name]).
There have been other solutions like this out there for quite a long time, most notably Hashie, and there are also times when you’d simply want to use a Struct value (or more recently a Data value) instead. But I think having a flavor of Hash which allows for interchangeable string, symbol, and dot access is hugely valuable, and this gem tries to provide that in as performant a way possible. (I did a lot of benchmarking as I worked on the most recent refactor, so I’m pretty confident it’s reasonably speedy.)
So there you have it folks: my top 10 11 favorite gems which are useful across many variations of Ruby web applications. Which ones have you used? What are your favorites? Do you have additional suggestions of gems to cover in a follow-up? Head on over to Mastodon and let me know your thoughts!
When you’re designing an abstract class for the purpose of subclassing—very common when looking at the framework/app divide—it’s tempting to want to throw a whole bunch of loosely-related functionality into that one parent class. But as we all know, that’s rarely the right approach to designing the models of your system.
So we start to reach for other tools…mixins perhaps. But while I love mixins on the app side of the divide, I’m not always a huge fan of them on the framework side. I’m not saying I won’t do it—I certainly have before—but I more often tend to consider the possibility that in fact I’m working with a cluster of related classes, where one “main” class needs to talk to a few other “support” classes which are most likely nested within the main class’ namespace.
The question then becomes: once a subclass of this abstract class gets authored, what do you do about the support classes? The naïve way would be to reference the support class constant directly. Here’s an example:
classWorkingClassdefperform_workconfig=ConfigClass.new(self)do_stuff(strategy: config.strategy)enddefdo_stuff(strategy:)="it worked! #{strategy}"classConfigClassdefinitialize(working)@working=workingenddefstrategyraiseNoMethodError,"you must implement 'strategy' in concrete subclass"endendend
Now this code would work perfectly fine…if all you need is WorkingClass alone. But since that’s simply an abstract class, and the nested ConfigClass is also an abstract class, then Houston, we have a problem.
For you see, once you’ve subclassed both, you may find to your great surprise the wrong class has been instantiated!
classWorkingHarderClass<WorkingClassclassConfigClass<WorkingClass::ConfigClassdefstrategy# a new purpose emerges"easy as pie!"endendendWorkingHarderClass.new.perform_work# ‼️ you must implement 'strategy' in concrete subclass (NoMethodError)
Oops! 😬
Thankfully, there’s a simple way to fix this problem. All you have to do is change that one line in perform_work:
Courtesy of the reference to self.class, now when you run WorkingHarderClass.new.perform_work, it will instantiate the correct supporting class, call that object, and return the phrase “it worked! easy as pie!”
Note: in an earlier version of this article, I used self.class.const_get(:ConfigClass), but I received feedback (thanks Ryan Davis!) the above is an even cleaner approach. 🧹
What’s also nice about this pattern is you can easily swap out supporting classes on a whim, perhaps as part of testing (automated suite, A/B tests, etc.)
# Save a reference to the original class:_SavedClass=WorkingHarderClass::ConfigClass# Try a new approach:WorkingHarderClass::ConfigClass=Class.new(WorkingClass::ConfigClass)dodefstrategy="another strategy!"endWorkingHarderClass.new.perform_work# => "it worked! another strategy!"# Restore back to the original:WorkingHarderClass::ConfigClass=_SavedClassWorkingHarderClass.new.perform_work# => "it worked! easy as pie!"
This almost feels like monkey-patching, but it’s really not. You’re merely tweaking a straightforward class hierarchy and the nested constants thereof. Which, when you think about it, is actually rather cool.
Note: the code examples above are written in a simplistic fashion. In production code, I’d move the setup of config into its own method and utilize the memoization pattern. Read all about it in this Fullstack Ruby article.
The rumors are true: I became a Ruby developer because of Rails. It’s probably common that folks find a tech stack or framework which offers the features and the community they desire and so learn the language undergirding that stack. Truth is, Ruby did not appeal to me at first and I resisted it! I wanted to remain a PHP developer, damnit! But the industry draw of Rails required me to learn Ruby, and I eventually fell head-over-heels in love with this language. Now I find myself at a crossroads. Do I leave Ruby behind because I’m no longer a “Rails developer”? Or do I embrace the essence of what makes Ruby Ruby and forge a different path? Gather around, my fellow devs, and listen to my story…
Ruby blocks are simply amazing, and I’ve written about some of the cool things you can do with them.
But something which confused me early on as I learned Ruby and started using gems and frameworks was when to write a block vs. when to write an explicit proc or lambda.
conn=Faraday.new(url: 'http://httpbingo.org')do|builder|builder.request:authorization,'Bearer',->{MyAuthStorage.get_auth_token}# more code here...end
As you can see, there are two different use of blocks/procs here. The first one is the one passed to Faraday.new — it yields builder so you can configure the request. But the second one is a “stabby lambda” (-> {}) which is passed to the authorization configuration. Why do it this way? I mean, in theory you could write an API which swaps the two:
weird_conn=Appleaday.new("keeps the doctor away!",->(builder){builder.make_me_a(:treehouse,'Painted'){MyFavoriteColors.default}})
Now I don’t have a particularly valid technical reason why the Faraday syntax is “correct” and my silly example is not. The simplistic answer would be “idiomatic Ruby”. We know it when we see it, and otherwise it looks off.
But I think that’s a cop-out. There must be some greater heuristic we can keep top of mind as we evaluate the best shapes for an API.
Here are some thoughts I have as I study these sorts of APIs and design new ones in my own apps and gems.
Let’s start with blocks. In the original Faraday example, using a block to provide configuration via the builder local variable is a very common Ruby pattern and lies at the heart of many DSLs. Some APIs provide a block without even yielding a variable, in which case you’re calling methods on the DSL object directly—like in Rodauth:
plugin:rodauthdoenable:login,:logoutend
In this example, enable is a method of the DSL object that is the execution context of the block. If this were written like Faraday’s API instead, it might instead look like this:
You with me? Cool. So blocks are great for configuration DSLs and for setting up various sorts of “declarative” data structures. Heck, what is a .gemspec file if not a DSL using a block?
OK, but what about lambdas? When is it appropriate to use a lambda instead of just a typical block? (FWIW I just love stabby lambdas…aka the -> (variables) { ... } syntax as opposed to lambda { |variables| ... }.)
I think it makes a lot of sense to use lambdas when you need to provide an expression which later gets resolved to a value—perhaps over and over again. We might call this deferred evaluation. Some APIs for example will accept either a data value or a lambda…the data expression would be evaluated right at the call site, but the lambda would be evaluated at runtime when it’s needed.
In this example, the hypothetical configuration option has been provided two settings. The first setting receives the current time. That setting can never change—whatever the current time was when the statement was evaluated has been “baked” into the configuration. However, the second setting receives a lambda. Executing this lambda returns the current time at the time the setting is later accessed. Each occurrence of runtime logic accessing that setting would get the time at that precise moment, not a previous time when the configuration was created.
Here’s a real-world example. In the Bridgetown web framework I work on, this is part and parcel of our “Ruby front matter DSL”. Front matter is resolved immediately when a page template is read, which occurs at an earlier time than when a page template is transformed (aka rendered) to its final format (typically HTML). In an example provided by the Bridgetown documentation:
~~~ruby
# some_page.mdfront_matterdolayout:pageurl_segments=["custom"]url_segments<<"permalink"segmentsurl_segmentstitle"About Us"permalink->{"#{data.segments.join("/")}/#{Bridgetown::Utils.slugify(data.title)}"}end~~~
This will now show up for the path: **/custom/permalink/about-us**.
Here we see code providing front matter variables such as layout, segments, and title. But it’s also providing a lambda for permalink. Because the value of permalink is a lambda, Bridgetown knows that at the time it’s about to transform the page template, it should then resolve that expression to the permalink variable.
We don’t use standard blocks for this because we also allow nested data structures via standard blocks. So you’d mix and match depending on the use case. For example:
front_matterdoimagedourl"/path/to/image.jpg"alt->{"My alternative text for #{data.title}"}endtitle"My Wonderful Page"end
Here we can see there’s an image variable which will resolve to a hash: { image: "/path/to/image.jpg", alt: -> { ... } }. Because the value of alt is a lambda, it will resolve when the page renders to become "My alternative text for My Wonderful Page. We can then use those variables in the template:
Another interesting use of lambdas I’ve experimented with at various times is utilizing them in control flow scenarios. For instance, did you know you could write your ownif and unless statements?
defdo_if(condition,&block)condition.().then{_1?block.(_1):nil}enddefdo_unless(condition,&block)condition.().then{_1?nil:block.(_1)}enda=123do_if->{a==123}do|result|puts"it's #{result}"# => "it's true!"endunless_if->{a==123}do|result|puts"it's #{result}"# => oops, this never gets run!end
Now I don’t exactly know why you’d write anything like this, other than as a cool experiment. But maybe you need to write a method which takes two lambdas, one for the “success” case and one for the “failure” case:
defperform_work(value:,success:,failure:)go_do_other_stuff(value)=>resultresult.completed?success.(result):failure.(result)endperform_workvalue: [:abc,123],success: ->(result)doputs"Yay, it was a success! #{result.output}"end,failure: ->(result)doraise"Darn, we blew it. :( #{result.problem}"end
Sure you could write this another way, like if perform_work yielded a config object and you could pass blocks to its success and failure methods. But the nice thing about this pattern is you could use other object methods instead by converting them into lambda-like callables. Oh yes!
deftime_for_work(value)perform_workvalue:,success: method(:success),failure: method(:failure)enddefsuccess(result)puts"Yay, it was a success! #{result.output}"enddeffailure(result)puts"Darn, we blew it. :( #{result.problem}"end
Because you can call lambdas and Method objects the same way, the API doesn’t need to care which is which.
# calling a lambdamy_lambda.call(:abc)# or my preferred syntax:my_lambda.(:abc)# calling a method objectmy_method.(:abc)# did you know any object can be callable# if it responds to a `call` method?classMyCallabledefcall(value)"Value is: #{value}"endendMyCallable.new.(123)# => 123
So actually with that last example, we see that you can pass any callable object to an API which accepts lambdas (more or less). How cool is that?!
Wait, could we try that with our earlier do_if example?
classValueCallabledefinitialize(value)@value=valueenddefcall@valueendenddo_ifValueCallable.new(123)do|result|puts"it's as easy as #{result}"# => "it's as easy as 123!"enddo_ifValueCallable.new(nil)do|result|puts"nope"# this never executesend
And if we wanted to make that ever slicker, we could make the class itself callable!
classValueCallabledefself.call(...)# forward all argumentsnew(...).callend# ...endMyCallable.(123)# => new instance of MyCallable with @value: 123
I hope you’re starting to get a sense for how understanding the difference between blocks and lambdas—or perhaps we just think of them as “callables”—can help you build more expressive APIs utilizing modular building-blocks which offer deferred evaluation, control flow, and entirely new patterns.
This is what I love so much about Ruby. We think of Ruby as a programming language, and it certainly is—and a “high level” one at that with an impressive heaping of abstractions. When “all cylinders are firing”, “programmer happiness” ensues.
But Ruby goes even a step beyond. Because of the expressiveness of the syntax Ruby provides us, we can in a sense write our own languages. Our APIs can have an advanced shape, with many affordances people typically assume must be baked into the language itself.
I’ve been hard at work on Bridgetown 2.0, and one of the areas of improvements I hope to focus on is our use of blocks vs. callables and the distinction between the two. Blocks are so easily used (and abused!), but I think the callable pattern may be one of Ruby’s best unsung heroes. The fact you can not only pass a lambda but any object method or really any object of any kind as a “callable” is, well, pretty mind-blowing.
I’ll close with one more wild factoid I always forget until I remember…you can convert methods directly to procs! What’s the point of doing that? Because then you can pass your callables into any standard methods which expect blocks. Like, for real!
This is a comeback story of sorts for Fullstack Ruby, but it’s more than that.
I’ll spare you the intimate details of my serious Ruby-flavored burnout in the back half of 2023—if you really care to you can read up on it here as part of the Bridgetown 2.0 kickoff announcement. (Did I mention I’m hard on work on the next version of Bridgetown? 😁)
TL;DR: I got thoroughly bummed about the state of the Rails & Turbo ecosystems due to a long series of epic fails (in this author’s opinion) on the part of DHH and the cult of personality surrounding him which should have resulted in his ouster but instead seemed to cause Rails/Turbo to slide into yet more sorta-mostly-but-not-really-open-source insularity.
I mean, if I’m going to choose to continue working in a language community that is already niche (let’s face it, no matter how much you love Ruby in 2024, it’s a niche language at this point in its history), do I really want to bolster behavior I find extremely offensive? It makes no sense. I’m no real fan of TypeScript and the state of fullstack JS frameworks, but gosh darnnit I might as well just go full dark side if I’m unable to identify with a community I truly enjoy and respect.
Time, essentially. By “emotionally walking away” from the fray of Ruby development and focusing on projects out in other corners of the industry (The Spicy Web, That HTML Blog, The Internet Review…uh, apparently I solve burnout in one area by burning out in some other area. Do I need therapy? DON’T ANSWER THAT! 😅), I was able to get some much needed perspective. And that period of rumination led me to this perhaps unsurprising conclusion:
I love Ruby! 😍
And more to the self-serving point, I love Bridgetown + Roda (+ Sequel apparently as the next logical link in this chain). Nothing makes me happier than using these tools to craft delightful online experiences, and while I can name tools in other language ecosystems I definitely appreciate—namely Eleventy by the inimitable Zach Leatherman, Fastify for rapid API development, and of course the One Frontend Bundler to Rule Them All: esbuild—my happy place still remains in the Ruby corner.
So it was time to step up to the plate once again. Time to go all in on Ruby, and specifically “alt Ruby” outside of the confines of a certain train-themed framework which dominates the space. It’s a niche within a niche, certainly, but at least it’s a niche I can be proud of. I am now a man “unconstrained”…able and willing to jump down any rabbit hole I might come across. Are you ready for some real “Keep Ruby Weird” vibes? I sure am!
So What’s Next? Relaunch of the Podcast? More Articles About Code? #
Yes! Next question…
But seriously (not too seriously), I do intend to get everything up and running again. The full redesign of this website is the first step. I completely started over with a fresh Bridgetown install, using a variant of a theme I developed for That HTML Blog, which itself is a riff off of an older theme I developed for JaredWhite.com. (Hey, it’s a layout I really like!) I also rewrote the About page and my bio with a bit of a, hmm shall we say, salty take on the state of Rubyist frontends. 🤓
Now that this has all gone live, I can finally start mapping out future content. I have a ton of material in the form of work I’ve put into various projects over the past year utilizing both Bridgetown and Roda, so henceforth I can start “plumbing the depths” and bringing this material to the surface. It’ll not only benefit readers & listeners of Fullstack Ruby like yourself, but provide educational resources to users of Bridgetown & Roda. Best of all possible worlds? Precisely!
“OK, but Jared. All I do every day as a Ruby programmer is write Rails controllers and models and views and models and controllers and service objects [don’t!] and background jobs. How does any of this help me??”
Well, there’s nothing like exploring other patterns and techniques and ways of looking at things in other frameworks and languages and whatnot to level up your skills in your primary stack of choice. My love of Ruby has definitely made me a better JavaScript developer, and guess what? The reverse is also true, believe it or not! Researching other stacks can help solidify what it is you love about the tools you already use, or better yet it can give you brand new ideas to serve as a launching pad for writing better software.
I guess all I am saying is give alternative Ruby a chance. ✌️
What are signals? What is find-grained reactivity? Why is everyone talking about them on the frontend these days? And what, if anything, can we apply from our newfound knowledge of signals to backend programming? Is it possible to use signals in Ruby? (Yes!) Learn all about signals, the Preact Signals library, and the new Signalize gem right here in the latest episode of Fullstack Ruby.
Ayush is on the core team of Bridgetown, a specialist in Ruby on Rails and Hotwire app development, and a personal friend. I’m very excited to have him on the show today to talk about all things fullstack web dev, his new book The Rails & Hotwire Codex, and why “vanilla” is awesome!
Ruby can now run on Wasm! WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications. So…we can now run “real” Ruby in the browser, right? Yes! …and no. Caveat emptor, but nevertheless this is a very welcome development and promising technology for the future. Let’s dive in.