Archive – Page 3

What Would It Take for Roda to Win?

Credit: Johannes Groll on Unsplash

I have a confession to make. I have fallen in love with Roda. What is Roda, you may ask? Well I’m glad you asked, because I’m here to tell you all about it. 🤓

Roda is a web toolkit—which is basically another way of saying it’s a web framework. But the reason the author of Roda, Jeremy Evans, likes to call it a toolkit is because it’s really the thing you use to construct the thing you need to build a web application. You start with a toolkit, and end up with a framework: the precise framework your application truly requires.

Roda’s stated goals are simplicity, reliability, extensibility, and performance. And those are the very reasons why I have become a Roda stan.

Let me elaborate.

The Barrier to Entry is Very Low #

In other words, a simple app that you build with Roda is indeed very simple.

In fact, the simplest Roda application can fit into a single file. That’s how easy it is to get started with Roda. And I love that because so much of my mindset these days, so much of my ethos as a web developer, is trying to find or build tools which are fascinatingly simplistic at first glance. How close can we get to a small file with a little bit of code in it? Maybe that’s all you need to do. 😄

I think some of my take on this is born from seeing such momentum in other ecosystems. Over in the world of JavaScript, you’ll quickly discover a shiny new concept called serverless functions which has taken quite a hold in the minds of web developers. If all you need to do is something very straightforward—an API with a couple of routes perhaps—you can write a couple of functions and deploy those files somewhere relevant like Netlify and boom, it just works. You don’t really have to think about it. You don’t have to think about stacks and architectures and all these different considerations upfront.

What I don’t like about serverless functions though is it’s really unclear to me how you move up from there. Sure, the barrier to entry is indeed very low, but how do you grow your architecture into to a much more complicated, fullstack framework approach? And then how portable is that across different platforms and across different kinds of setups? As many of us Rubyists have seen, JavaScript projects tend to mushroom in tooling configuration and build complexity at an alarming rate.

What I like about Roda is that the mental model you need to get started with fits into that “early stage” ease of use. The routing blocks within Roda’s “routing tree” are so immediately accessible, they look a bit like individual serverless functions—only without the serverless, or the functions. 😜 In the end, you’re still just writing Ruby object-oriented code within a traditional Rack-based stack…calling out to plain Ruby objects or other parts of your frameworks like a database ORM or template renderers.

Roda Doesn’t Just Stop After Simple #

Before continuing to describe Roda, it should be noted that there’s another popular, “simple” Ruby web framework called Sinatra with a long and prestigious history. In fact, the Express.js framework for Node was based on concepts from Sinatra. But the main knock against Sinatra is it’s not great at scaling up from a simple app to something more ambitious. Conceptual and performance problems begin to ensue. You’ll typically see Rubyists reaching for a more full-featured framework like Rails, or maybe Hanami. There’s sort of an expectation that Rails and Sinatra don’t occupy the same “space” within the tools available.

Whereas Roda is intended to take you all the way from greenfield starter projects to at least mid-sized, fully-featured web applications. Jeremy Evans has stated as such in the interviews and presentations he’s given. You don’t have to jump ship all of a sudden as your app grows and say, “oh no, I can’t use Roda anymore. My application is too complicated. I need to go reach for something else!” And if you do end up needing to switch frameworks as the project grows, that would likely be considered something to fix in Roda’s feature set and not at all expected.

A Plugin Architecture So Straightforward, You’ll Want to Write Your Own #

Another aspect of Roda I really love is the plugin architecture. It’s very straightforward. You can start writing your own plugins right away, and you probably will. Unlike with Rails where I feel like writing a plugin or a gem, well it’s all a bit messy and complicated. I’ve worked on Rails engines—I’ve looked into how other people have written them, I’ve played around with all this stuff over the years—and sure, I can get it to work in the end but I never feel particularly satisfied with the effort.

Roda’s plugin architecture—while encompassing fewer aspects of your overall stack—to me seems very obvious and intuitive. You can extend the Roda app class itself, extend request objects, extend response objects…every surface area of Roda’s API is extremely customizable. You can even write plugins which themselves depend on other plugins and extend them for additional functionality.

The benefit here is, the more simple it is to get started writing a plugin, the more likely you’re actually going to do it. I have a sense that more people who use Roda will end up writing their own Roda plugins, as compared to people using Rails writing their own Rails plugins. I don’t have any hard proof of that, it’s just my take on the matter having used Rails heavily for well over a decade.

Y U No Like Rails? #

Speaking of Rails…why even look farther afield at a toolkit like Roda? Why not just stick with Rails and the “Rails way”? Why not just continue to be a Rails developer ? I know Rails up, down, and sideways by now. Why not just keep playing in that sandbox?

Right off the bat, I want to make it clear I love many aspects of Rails even now. ActiveRecord is fantastic. (Yes I know…Sequel is a thing. But AR fits my brain like a glove. I’m just so used to it.). ActiveSupport—despite the grumblings of well-meaning Ruby OOP purists out there—I believe is a major selling point of Rails and Ruby in general. And even with everything I said above regarding plugins, it is indeed impressive that there seems to be a Rails plugin gem out there for just about anything and everything you’d ever need to build a sophisticated web application.

But at this point in time, the “VC” of MVC in Rails (you can listen to my recent podcast episode about MVC for background context) feels weak to me. It really feels aging to be quite honest. There has not been any real innovation or change around that part of Rails since the beginning of the framework. The routes file, controllers, actions, ERB views, helpers, partials—all nearly untouched in two decades! (Hotwire/Turbo is the first real update to any of this in some time, and honestly while it’s pretty cool at first glance, it also feels a little bolted on and limitations abound. Hence the need to reach for, say, CableReady. But I digress…)

The vanilla stack of Rails—the “Rails way”—when it comes to the basic request/response cycle and rendering views, well I just have a lot of complaints with it at this point. For simple actions, there’s way too much ceremony for seemingly little gain. There’s what I call an unquestioning adherence to the HTTP-inspired REST concept. As much as I do like REST overall, and as much as I would defend REST against competitors such as GraphQL, I also feel like I’ve seen a lot of what I would call “REST-induced Design Damage” (to take a page out of DHH’s playbook regarding Test-induced Design Damage).

I’ve seen REST-induced Design Damage in projects time and again, where everything you do throughout the entire app somehow has to fit into this concept of:

I feel like all those bits of architecture are increasingly a total mismatch with what I’m actually trying to do. I’ve talked before about the rise of “component-based view architectures”, and more and more I’m finding that I want to orient a lot of my application architecture around what I might label “groupings of component-trees + state” — rather than HTTP resources with controllers and subsequent page templates associated with those controllers and those controller actions. There’s so much ceremony to get to the point where all I really want is to have this tree of components for some specific part of my application, and within that tree of components there are various well-defined components which might have their own individual lifecycles on both the server and the client.

You can think of this as islands architecture if you want, or perhaps “nested layouts”. Some JavaScript frameworks lately have promoted nested layouts as a fundamental building block of the view layer: you have a main layout for your application or a big section of your application, and then it breaks out into sub-layouts, and those sub-layouts have their own sub-layouts. All these different layouts and sub-layouts have their own lifecycles essentially, and as a user, you’re performing interactions within these sub-layouts and those interactions need to update state on the backend and then reflect that state back on the frontend and so forth.

However you want to slice it, I really feel like component-based view architecture has rejiggered how we think of the architecture of a web application, and HTTP resources via REST and the controllers and the views associated with them that you see in the vanilla Rails stack is sort of at odds with those ideas. Even while Turbo Frames and Streams start to break apart the traditional concepts of Rails views, they don’t go nearly far enough. As I’ve stated, they feel sort of tacked-on to Rails, rather than Rails being fully-rearchitected to support Turbo concepts from the bottom to the top of the stack.

Something that’s emblematic of Rails’ “views malaise”, and honestly it continues to trouble me to this day, is the whole ViewComponent fiasco. When GitHub came up with their ViewComponent library, it seemed right at first like it would be folded directly into Rails. In fact it was originally called ActionView::Component. Wow! Rails would gain the idea of creating new isolated, previewable, testable components for views! Progress!

And then, thud. From what I can gather, GitHub backed out of the merger at the behest of Basecamp/DHH. View components did not become part of vanilla Rails. And in my opinion, the impulse to refuse something like components entering the Rails lexicon is very troubling. Instead of seeing innovation, we saw entrenchment. In the web industry today where frontend UI design is quite literally all about components, Rails has become a dinosaur by comparison.

(One thing Rails did do right is it added the ability for any third-party component system to exist by augmenting the render method within views. So by calling render any_ruby_object_here in a view, the render_in method of the Ruby object gets called automatically with the view context as the first argument and an optional block to capture. This is such a powerful concept, we adopted the render_in convention for Bridgetown’s view layer. At this point, I think it should be considered a standard convention across the Ruby ecosystem.)

All right, so that’s my take on Rails, but…what does any of this have to do with Roda?

Well, it’s true that Roda also doesn’t offer any of this componentized view architecture stuff I’m talking about. But the thing about Roda is it’s extremely extensible. Out of the box, it doesn’t even have a view layer! You have to load the render plugin explicitly, by choice. Which means…there’s literally nothing stopping you from building your own view layer for Roda to your exact specifications.

And that’s exactly what we did for Bridgetown’s Roda integration. Bridgetown’s own view layer, including components—even Lit-based web components!—becomes Roda’s view layer. More on that in just a moment.

The Need for Roda Distributions #

To recap, I really love Roda due to its stated goals: simplicity, reliability, extensibility, and performance. I’ve been using Roda (along with Bridgetown) to date for a variety of simple apps, and there’s also a more complicated app in the works that’s not publicly available at the moment—a port of a “newsfeed reader” app I originally wrote in Rails. I brought a lot of the concepts from the original Rails app over to a new Roda application which is part of a Bridgetown installation overall, and so far it’s a pretty sweet setup. Hoping to open source it later this year…

So while I personally haven’t yet written a “large” application in Roda, I can see the path forward. I’ve done enough experimenting at this point to know what I would do, how all the pieces would fit together.

But therein lies the rub.

You can get started using Roda, and simple things are indeed simple, but which Roda will you end up with in the end? Eventually you’ll need what I call a “distribution of Roda”. You’ll need your application’s “fullstack framework” set up in a particular manner. Merely using “pure Roda” by itself, without any configured plugins or extra configuration, is not enough to actually create a fully-featured web application with a lot of complexity. In fact the documentation makes that point abundantly clear. It’s by design: instead of starting with a huge framework with a plethora of various sub-frameworks configured and set up waiting for you to maybe use one day (aka Rails), you can start out with a much simpler architecture and then slowly build things up bit by bit over time.

Nevertheless, you’ll probably want to look at some kind of “distribution” or starter kit or example stack or whatever to get a sense of what’s possible and avoid reinventing the wheel. Jeremy Evans promotes an example stack of a Roda application which features multiple route files and a database configuration via Sequel and live reloading when files change and various view templates set up and all that. But…it’s an opinionated take on a canonical Roda stack, and I don’t necessarily agree with those opinions. I have my own distribution of Roda if you will, and that’s essentially what Bridgetown is turning into.

Roda is so malleable, you can take it in any number of directions in terms of architecture—particularly on the view side which is where my primary interest lies. You can push it in the direction of a Rails-style architecture, sure. Or you can push it in an entirely different direction.

Bridgetown’s Spin on Roda #

What I’m trying to do as I work on the integration of Roda into Bridgetown is that when you create a Bridgetown site, you’re also creating a Roda application. It’s the culmination of what I’ve been calling the DREAMstack (Delightful Ruby Expressing APIs & Markup), where you can start with a very simple website containing just a few static pages with Markdown content or whatever, and then you can start to mix in more advanced component trees—”islands”—using a combination of Ruby components and web components, and then on top of that write corresponding backend code with this “not-serverless-functions-but-they-look-kinda-like-serverless functions” paradigm that Roda affords.

This way you have a clear path for starting simple and then scaling up to more sophisticated, interactive, large applications. It’s an entire spectrum of tooling at your fingertips. We can even pull in “bits” of Rails as needed where it makes total sense. (As I previously mentioned, ActiveRecord remains to this day my ORM of choice!)

While the dynamic file-based routing system we’ve provided in Bridgetown is a pretty great way to get started with server-based routes after working with static resources, something I’ve recently started to experiment with—which is perhaps a bit bonkers but it’s all in the interest of collapsing layers and reducing conceptual complexity—is instead of defining a new Roda route/file and then in that route saying “oh, now I want to render out this particular component” …what if a component itself could have a Roda route associated with it? Within that route you could update state, talk to a database, whatever you need to do, and then just immediately re-render the component via that route and update the frontend instantly. (In case you’re starting to think I’m nuts here, there’s prior art for this very concept.)

Listen, I’m not saying you would always want to do this. I’m not saying it would fit every feature. I’m not saying it’s inherently a good idea compared to all other approaches you might attempt within your architecture. But the point is to strive to innovate and to think of views in terms of islands, and within those islands you have various component trees, and some of those components could be Ruby-based and others JavaScript/web component-based and still others a hybrid of the two, and they all potentially have their own backend/frontend interactive rendering lifecycles.

There’s so much you can do now as part of a truly componentized view architecture. And I don’t want to just bolt this on top of a traditional Ruby framework. I want to have a framework which feels like it was invented in the age of components. My hope is that Bridgetown will endeavor to push the envelope in this area over the next several months and even years.

So What Would It Take for Roda to Win? #

OK, I fully admit the title of this article is tongue-in-cheek. I don’t think Roda has to or should “win”—but neither do I think any other framework should win! That said, I do want to see the Roda community grow and thrive. I want to see more examples out in the wild, more plugins, more “distributions”. Let a hundred flowers bloom.

As Ruby web developers, we need—nay, deserve—a more vibrant set of choices. As much as Rails domination within the Ruby web community has produced a ton of good things and plenty of good side effects. I also think it has sometimes limited thinking, limited innovation—particularly on the frontend side of things. And the problem with that is it’s going to cause certain types of developers to give up on Ruby or not even give it a close look. Someone who’s super into frontend development, who’s super into some of the ways that JavaScript frameworks work today, if you just hand them a traditional Rails architecture and say “hey, here’s Rails, use Rails, learn Ruby, it’s cool”—they’re going to take one look at it and go blegh. Not trying to be overly dramatic here, but I really think Rails can be its own worst enemy sometimes. As a Rubyist, I’d love to see a much broader range of thinking.

What does “modern” web architecture look like right now, today? What could it look like? What might work differently? What might be best left untouched? (If it ain’t broke, don’t fix it!) In other words, start from lofty end goals and blue-sky thinking, and then work backwards to create the sorts of frameworks that we need now and into the future.

That’s essentially what I’m trying to do with the Bridgetown + Roda distribution.

But I’ll be honest. For certain kinds of applications. I’m sure it’s going to be ludicrous. It would be absurd to use Bridgetown + Roda for that particular app. That’s fine. I have no problem with that. Use “vanilla” Roda. Use Rails. Heck, use Hanami! Go for it. That’s totally fine.

I do fervently believe however there are other classes of applications, particularly websites which have public-facing, content-heavy aspects to them, where I feel like using Rails or some other traditional framework or even vanilla Roda would be totally ill suited to that. I think there’s a substantial niche where the Bridgetown + Roda distribution with its very specific feature set might prove to be an ideal solution for those particular projects. Educational sites with paywalls. E-commerce solutions. News/social/publishing apps. Listing-style sites with lots of forms. Live dashboards acting upon disparate datasets. Marketing sites with a tight coupling to advanced headless CMSes. I could go on!

(However, if you’re looking to build a straight-up SaaS app or platform for a well-defined industry…Rails. Always…and maybe forever. For what Rails is best at, it remains the undefeated champion.)

Whichever framework you end up using, at the end of the day I hope it’s built on top of Ruby. That’s always the primary consideration for me. Over time, it’s not that I want to see Roda “supplant” Rails and or even take a larger slice of the same-sized pie. I want to see Roda (and Bridgetown too) help grow the pie entirely. I want to see Ruby-based technologies reach into farther corners of the overall web industry.

I believe it’s possible. Do you? small red gem symbolizing the Ruby language



Episode 4: Design Patterns on the Frontend, History of MVVM, Web Components, and You

Design patterns on the frontend: this is a subject far too little discussed from what I can tell, yet with a fundamental awareness and regular usage of design patterns, you can dramatically uplevel your frontend code. Rubyists in particular will have a major leg up here over devs coming from communities which are more FP (functional programming) in nature, because the view layer of the web is inherently object-oriented.

Ruby developers are well-trained in the ways of object-oriented programming and using design patterns. This is probably why many Rubyists instinctively look askance at certain modern paradigms of frontend programming. It’s overly complicated, poorly architected, and rarely understood from a proper OOP perspective. You view source on many websites and it’s “div tag soup”. It’s a nightmare. You look at how people will write heaps of functional React components, and it’s a buggy spaghetti code mess.

Well guess what? We can change all that.

Web components, and simple libraries like Lit—combined with an understanding of how the DOM works natively plus MVVM (Model-View-ViewModel)—lets us reason about our frontend in similar ways to how we reason about the backend using the OOP paradigm. A web component is simply the “VM” of MVVM, and you easily add in the V part via a declarative template library or just manipulate the DOM (that is, the View) directly in an imperative fashion.

So Rubyists, stop feeling like the frontend is out to get you, or you need to avoid it, or contain it. Embrace it! The frontend can be just as fun and rewarding as the backend—if you know what to do with it.

CORRECTION: in the recording I said Stimulus doesn’t provide view bindings. That’s not actually true — you can use data-action attributes so that the events triggered get handled by the controller. However, you can’t bind reactive data back into the template. You get targets of course, but it’s entirely up to you how you make use of those targets to update the DOM via your Stimulus controller.


Become a part of the Fullstack Ruby community and learn how to put your Ruby skills to work on the backend AND the frontend. Know somebody who’s a JavaScript developer but is interested in learning more about Ruby? Share the site, podcast, or newsletter with them!

The Fullstack Ruby Podcast is a production of Whitefusion, a boutique web studio based in Portland, OR.

Theme music courtesy of Epidemic Sound.


Subscribe to the RSS feed

in your podcast player of choice.



Transform Your Data Object-Oriented Style with Formatters

Credit: Mukund Nair on Unsplash

I admit it. I’m a design pattern nerd. I’ve been a massive fan of object-oriented design patterns ever since I read Patterns of Enterprise Application Architecture in the mid-2000s. One of the aspects which first drew me to Ruby, via Rails, was the in-depth adoption of so many of these patterns and how easy it is to write elegant Ruby code using OOP (Object-Oriented Programming) design patterns.

Today, we’ll talk about a variant of the Template Method design pattern I’ve used in a number of scenarios. I don’t know of a formal name for this pattern, so I like to call it the Formatter pattern. By executing a sequence of formatters, you achieve a process which I think of as a data pipeline. The data pipeline itself can also be represented as an object.

(At first glance, you might think this pattern is better described as a Chain of Responsibility, but that pattern is better suited to cases where each link in the chain should determine whether it can handle all processing and stop, or pass some of the processing directly on to the next link in the chain.)

Perfect for Refactors #

The Formatter pattern is so useful that I recently refactored a huge “spaghetti function” in a TypeScript (yes, that TypeScript) codebase to use this OOP pattern instead. The number of bugs and “gotchas” working with that part of the codebase plummeted to nearly zero—and we were able to write unit tests for each formatter in the pipeline to verify the data transformations therein.

It’s safe to say that you wouldn’t necessarily reach for the Formatter pattern the moment you need to author a sequence of steps to transform some data. Rather, you’d start by writing your code in a simple linear fashion, and then as you slowly begin to realize your process is turning into a big ball of mud, it’s time to refactor. (Remember DOEY!)

NOTE: fans of functional programming, you’ll probably want to leave now. This pattern encompasses everything you don’t like about object-oriented programming: abstract classes, inheritance, encapsulation, mutation, and polymorphism. So don’t at me—you’ve been warned!

The Format of Formatters #

Formatters in a particular data pipeline all inherit from a base class. Let’s create a new ExampleFormatters Ruby module to house our Base class and future subclasses:

module ExampleFormatters
  class Base
    attr_reader :data

    def initialize(data)
      @data = data
    end

    def format
      raise "Method should be implemented in subclass"
    end
  end
end

You can name the format method however you wish—perhaps in your case convert would be more appropriate. At any rate, the idea here is you pass a data object of some type into your initializer, and then by calling the format method, a transformation would occur for that data. Let’s implement a subclass to see this in action (and let’s assume the data object is a Hash):

module ExampleFormatters
  class TitleFormatter < Base
    def format
      # ensure the title string is in Title Case
      data[:title] = data[:title].split.map do |str|
        str.capitalize
      end.join(" ")

      self
    end
  end
end

Now let’s call this formatter with some data:

hsh = { title: "hello   world!" }

p ExampleFormatters::TitleFormatter.new(hsh).format.data

# => {:title=>"Hello World!"}

Note that we’re not returning a copy of the data object. We’re mutating the data in-place. This is by design (generally better performance and lower memory usage). So in the above example, both hsh and data’s return value are equivalent after running the formatter. If you need to preserve the original state of the data, you should ensure you make a duplicate object before passing that along to the formatters—perhaps in the data pipeline (described further below).

This is the most basic example of how to write a formatter. However, you might want to add a bit of smarts, such that you can’t run format more than once on the same dataset. We can change our base class then to this:

module ExampleFormatters
  class Base
    attr_reader :data

    def initialize(data)
      self.data = data
    end

    def data=(new_data)
      @already_formatted = false
      @data = new_data
    end

    def format
      if @already_formatted
        raise "Cannot run formatter a second time on the same dataset."
      end
  
      process

      @already_formatted = true

      self
    end

    protected

    def process
      raise "Method should be implemented in subclass"
    end
  end
end

Now refactor your subclasses to override process rather than format:

module ExampleFormatters
  class TitleFormatter < Base
    protected # keep `process` hidden from the public API:

    def process
      # ensure the title string is in Title Case
      data[:title] = data[:title].split.map do |str|
        str.capitalize
      end.join(" ")

      self
    end
  end
end

This will ensure you can only run format once on the same data. In order to run it again, you’ll need to use formatter.data = some_new_data to reset the process.

One reason it’s so nice to have a base class from which to inherit is you can add to the base API and your formatter subclasses will have full access to those methods. For instance, if this “titleize” formatting is something you’ll need to do several times across different formatters, you can abstract the implementation:

module ExampleFormatters
  class Base
    # …
    
    # convert a "lower   case" string to "Title Case"
    def titleize(str)
     str.to_s.split.map do { |str| str.capitalize }.join(" ")
    end  
  end
end
module ExampleFormatters
  class TitleFormatter < Base
    protected # keep `process` hidden from the public API:

    def process
      data[:title] = titleize(data[:title])

      self
    end
  end
end

Not only does this DRY up your code, but it fixes a subtle bug as well. Before, if data[:title] was nil, your code would error and crash. Now, by using a separate method which is written defensively (i.e., don’t naively assume the incoming object will always be a String), the worst you’ll get is a value of "". If a blank string itself will cause a problem down the road, make sure you write that code defensively as well!

Constructing the Data Pipeline #

Now that we have some formatters working and transforming our data in bite-sized pieces, we need a way to kick off the whole process. Let’s write our data pipeline object. You’ll notice it contains some similar shapes to the formatter base class.

class ExampleDataPipeline
  attr_reader :data

  def initialize(data)
    self.data = data
  end
  
  # Construct the sequence of formatters for the pipeline.
  # Modify or add to this list to update the pipeline:
  def formatters
    [
      TitleFormatter,
      DatesFormatter,
      LineItemsFormatter,
      # future formatters here
    ]
  end

  def data=(new_data)
    @already_formatted = false
    @data = new_data.dup # make a shallow clone
  end

  def run
    if @already_formatted
      raise "Cannot run pipeline a second time on the same dataset."
    end

    formatters.each do |formatter_class|
      formatter_class.new(data).format
    end

    # further processing here?

    @already_formatted = true

    data
  end
end

For the final step, you can run the pipeline with a single statement!

transformed_data = ExampleDataPipeline.new(input_data).run

Boom.

And thanks to the power of object-oriented programming, you can easily experiment with your pipeline—for example by changing the order of formatters or swapping out one formatter for another.

class FasterDataPipeline < ExampleDataPipeline
  def formatters
    [
      TitleFormatter,
      DatesFormatter,
      FasterLineItemsFormatter
    ]
  end
end

# Try benchmarking these two pipelines…

ExampleDataPipeline.new(input_data).run
# vs.
FasterDataPipeline.new(input_data).run

It goes without saying that unit testing formatters in isolation will ensure your formatters don’t become too dependent on each other (aka it would be bad if a formatter only worked if executed after another particular formatter).

Audit Trails? External APIs? Dynamic Converters? #

You can build upon this pattern in increasingly sophisticated ways as your business logic needs grow. One idea might be to log the before/after of each formatter as it runs in the pipeline in order to performing an audit of data integrity after the fact. Another idea might be to encapsulate expensive or complex external API calls within formatters so you can work with your overall pipeline steps at a higher level without messy API calls getting in your way. (And then swapping those formatter(s) out with stubs during unit tests becomes quite straightforward.)

In Bridgetown, the subsystem of template converter plugins (which itself is based on Jekyll’s converter subsystem) is architected in a similar fashion to this Formatter pattern. In the Bridgetown case, the order of converters (aka formatters) is determined by a priority designation. High priority converters run first, then medium priorities, then lower priorities. In Bridgetown, I actually did implement an “audit trail” for the conversions, so if you’re curious how a particular resource changed throughout all the conversions, you can inspect that trail and see the state of the template before/after a particular conversion. The precise nature of the converter pipeline is constructed based on extension matches or other metadata (aka .liquid will be converted differently from .erb). Your data pipeline might similarly need to dynamically construct its list of formatters based on the nature of the incoming data.

In summary, I truly adore this design pattern. Once you know it, you start to see its usefulness across a wide variety of scenarios, codebases, and even programming languages! small red gem symbolizing the Ruby language



Episode 3: String-Based Templates vs. DSLs: the Pros & Cons of Each

Typically when you mention “Ruby” and “template” in the same breath, people will think of ERB. Perhaps even Haml. But did you know that the Ruby ecosystem offers a wide variety of template engines, and quite a few are built upon pure Ruby? In this episode, I break down the main conceptual difference between “string-based templates” such as ERB and “DSLs” such as Papercraft, the various options within each category, and some of the reasons you might want to choose one approach or another depending on your use case. Enjoy!


Become a part of the Fullstack Ruby community and learn how to put your Ruby skills to work on the backend AND the frontend. Know somebody who’s a JavaScript developer but is interested in learning more about Ruby? Share the site, podcast, or newsletter with them!

The Fullstack Ruby Podcast is a production of Whitefusion, a boutique web studio based in Portland, OR.

Theme music courtesy of Epidemic Sound.


Subscribe to the RSS feed

in your podcast player of choice.



What's Better Than DRY? DOEY! (Don't Over-Exert Yourself)

Credit: Benjamin Massello on Unsplash

It never ceases to amaze me how much work programmers create for themselves. Time and again I hop onto DuckDuckGo to search for a particular answer to a problem, or a tool to help me accomplish a task, only to find article after article and readme after readme riddled with overly-complicated, brute force, verbose solutions to what are ostensibly simple problems.

Question: how do I convert JSON to YAML?

Verbose Answer: you install these packages and then you download this script and then you modify these three variables and then you pray to the open source gods and then you sign up for this service and then you connect to that API and then you…

Real Answer: you don’t need to do anything…JSON is valid YAML. 😅

I’m Actually Quite Lazy #

So here’s the deal: I hate work. 😜 I’d much rather go outside and take a walk. So whenever I’m trying to solve a big, thorny programming problem, what I’m not going to do is try a linear A, then B, then C, then D, then E approach to getting it to work. Because I know from experience that thinking in that manner actually creates far more work down the road. And I’m lazy, remember? So I want to get less work done in the long run. Way less work.

According to Wiktionary, “brute force” is a method of computation wherein the computer is let to try all permutations of a problem until one is found that provides a solution, in contrast to the implementation of a more intelligent algorithm. We can also apply that concept to our own “human computation” as we’re programming: just taking wild stabs in the dark to guess a solution to a problem, and upon the first working demonstration, well there you go! Problem solved.

That’s really not the ideal way to go about things at all.

Eliminating Redundancies #

When I’m in my “flow state” as a programmer, what I’m constantly doing is finding ways to eliminate redundancies. This goes far beyond DRY (Don’t Repeat Yourself), which is a generally useful concept but typically only thought of as applying to small code blocks. “Hey, these few lines here are basically the same as these few lines over there. Let’s extract them out to a single function! Cool, cool.”

I find that application of DRY to be far less compelling than one where you can recognize that entire subsystems of your application as a whole can be made entirely redundant if you simply took the time to search for higher-level abstractions.

Sometimes these higher-level abstractions are missed on codebases because multiple people are working in silos. Programmer A works on a feature over here. Programmer B works on a feature over there. At first glance neither feature seems related. But to a well-trained eye looking at the sales pitch for both features, it becomes apparent that most of the code could be conceptually shared between the two features. They’re really not two features at all. They’re one feature, expressed in slightly different ways depending on the context.

And that’s what I mean by eliminating redundancies in code. What if your application with 50 features could actually be built with only 25 features, each slightly more malleable at run-time to provide the illusion of 50 features? What if you could whittle that down even further? What if you could extract some of the features’ lower layers to a shared library? What if you could use someone else’s battle-hardened library instead?

Measuring your application codebase’s health by LoC is never wise. But I believe looking at the number of new lines of code added in each PR is very important. If PR after PR comes up for code review and nearly everything is “new code”—unless this is literally a brand-new application, something has gone terribly wrong. The majority of your PRs should be attempting to refactor, to streamline, to eliminate redundancies. Programmer works on Feature B, realizes it’s not that different from Feature A, so her PR for Feature B actually redoes Feature A so they both share as much in common—a higher-level abstraction.

Remember, the best code is the code nobody writes. “No code” is bug-free, infinitely fast, and incredibly easy to maintain. “No code” doesn’t need to be tested or understood because it doesn’t exist. The next best thing to “no code” is “worthy code”. The more verified impact each single line of code can have within your overall application architecture, the better.

But I Don’t Know What Other Code Has Already Been Written! #

One possible objection to this way of thinking might be that on sufficiently-large codebases, multiple programmers won’t have any idea what other people have already written or how those subsystems work exactly. So someone can be forgiven for pushing up a PR that’s basically just new code they’ve written to get something done.

I don’t subscribe to that philosophy.

Either (a) that programmer needs to spend more time simply reading code and learning how all of the different components and subsystems and configurations and layers of the application function and why they’re there, or (b) the code review process needs to incorporate an “architectural review” step to ensure PRs have taken possible refactoring into account, rather than just offering yet additional “append-only” code blocks.

That Sounds Like a Lot of Work! I Thought You Were Lazy?! #

A cursory examination of the concepts above might lead you to believe all this reading and refactoring and high-level architectural review of codebase concepts is a ton of extra work. Much easier to simply sit down at your code editor with a blank file, write some stuff, write some tests, verify the damn thing works, and call it a day.

Wrong!

That approach only works when you have a simple, greenfield application. As your codebase grows larger and more sophisticated, writing code in that manner eventually leads to “the big ball of mud” architecture, also known as “spaghetti code”. Unfortunately, I can’t begin to count how many tutorials I’ve seen (DEV is sadly riddled with them) which present code which is obviously spaghetti in nature, yet provide a sort of “copy-and-paste these 20 files into your project and you’re done!” appeal to newbies.

Listen, I understand that appeal. You don’t want to have to spend five hours crafting the perfect software architecture for your specific problem domain. You just want to download the “Gatsby-React-Tailwind-Firebase-Stripe-Netlify” starter kit, stuff a wad of JSON here and JSX there, copy-n-paste some random components off of StackOverflow, and boom you have a website.

Unfortunately the quality of that website’s code will be hot garbage. 😬 Might not matter to you now. But down the road, it’s going to bite you in the ass.

Embrace DOEY #

By taking the time to carefully, deliberately, intentionally write high-quality code from the start, understanding your problem domain while avoiding redundancies, expressing features in higher-level abstractions, utilizing battle-tested and well-crafted libraries/frameworks based on “first principles”, staying away from “new hotness” tools which make for cool tutorials but are a disaster to maintain over the long term—you begin to reap the benefits of DOEY. Soon, while other people are spending hours, days, even weeks wrestling with brittle codebases which are hard to intuitively understand and nearly impossible to refactor, you are enjoying your outside walk in the sun because you already shipped three new features yesterday.

All of that upfront work paid off. Now you get to be lazy. Stop to smell the roses. Life is good.



How Ruby and Web Components Can Work Together

Credit: Pawel Czerwinski on Unsplash

As a follow up to my recent podcast all about componentized view architecture, I thought it would be worthwhile to share some real-world code examples from various projects I’ve worked on so you can get a sense of what I’m talking about.

As you’ll soon discover, many of the Ruby view components I write tend to wrap around web components—either ones I’ve written or from third-party libraries. A web component is technically a custom HTML element, paired with some combination of JavaScript and optionally CSS which affects the styling and behavior of the element. For example, instead of writing HTML for a “badge” like this (example from Bootstrap):

<span class="badge bg-warning text-dark">Warning</span>

You could write it like this (example from Shoelace):

<sl-badge variant="warning">Warning</sl-badge>

The DX of web components tends to be much higher than CSS frameworks & utility-class-based libraries because the web component can provide an explicit API at both the HTML markup level and within JavaScript. For example, if you wanted to change the above badge’s variant from “warning” to “danger”, it’s as simple as:

document.querySelector("sl-badge").variant = "danger"

Now let’s look at some real-world examples. On the Ruby side, some components use Bridgetown’s native component class, others use ViewComponent within a Rails app. For templates I generally use Serbea, but I’ll also provide some ERB translations. On the frontend side, you’ll see much use of Ruby2JS paired with Lit & Crystallized.

Documentation Note Component #

We’ll start out with something simple. At the time of this writing I’m designing a new website for Bridgetown, and I need to add notes here and there on various documentation pages. I decided to use Shoelace’s sl-alert element since that gets me pretty close to how I want the notes to look visually:

note component screenshot

The Ruby component code is nice and concise. It accepts a type keyword argument which defaults to :primary, and an optional icon identifier. Otherwise the icon will be determined based on the note type.

# src/_components/note.rb
class Note < Bridgetown::Component
  def initialize(type: :primary, icon: nil)
    @type, @icon = type.to_sym, icon
  end

  def icon
    return @icon if @icon

    case @type
    when :primary
      "system/information"
    when :warning
      "system/alert"
    end
  end
end

And here is the template file (Serbea and ERB examples provided):

<!-- src/_components/note.serb -->
<sl-alert type="{{ @type }}" open>
  <sl-icon
    slot="icon"
    library="remixicon"
    name="{{ icon }}"
    style="font-size:1.25em"
  ></sl-icon>
  {{ content | markdownify }}
</sl-alert>
<!-- src/_components/note.erb -->
<sl-alert type="<%= @type %>" open>
  <sl-icon
    slot="icon"
    library="remixicon"
    name="<%= icon %>"
    style="font-size:1.25em"
  ></sl-icon>
  <%= markdownify content %>
</sl-alert>

As you can see, this sets up the sl-alert markup as well as sl-icon for displaying an icon in the note. The content variable is automatically provided by the component class which is the output of the block passed to the note, and we use Bridgetown’s markdownify helper to render Markdown content to HTML. Using the note component on a page couldn’t be easier:

<!-- Serbea -->
{%@ Note do %}
  #### Front matter variables are optional
  If you want to use [Liquid tags and variables](/docs/variables/)
  …etc.
{% end %}
<!-- ERB -->
<%= render Note.new do %>
  #### Front matter variables are optional
  If you want to use [Liquid tags and variables](/docs/variables/)
  …etc.
<% end %>

And passing keyword arguments is just how you might expect:

<!-- Serbea -->
{%@ Note type: :danger, icon: "development/bug-fill" do %}{% end %}
<!-- ERB -->
<%= render Note.new(type: :danger, icon: "development/bug-fill") do %><% end %>

Star Rating Component #

I’m working on a Rails app where a “ratable” object needs to display a component where people can rate it from 1 to 5 stars. Since Shoelace offers a very nice stars component, we can wrap that in our own component with both Ruby and frontend aspects. The component actually serves two purposes: it can display a read-only average of all the ratings for the object, or it can display the current user’s own rating of the object (if any).

First, here’s how the component gets used within a Rails template (all examples in Serbea):

{%@ RatingStarsComponent ratable: @bank, value: @bank.ratings.find_by(user: current_user)&.rating %}

Next, let’s look at the Ruby component:

# app/components/rating_stars_component.rb

class RatingStarsComponent < ApplicationComponent
  attr_reader :readonly

  def initialize(ratable:, readonly: false, value: nil)
    @ratable, @readonly, @value = ratable, readonly, value
  end

  def value
    return @value if @value

    @ratable.has_been_rated? ? @ratable.average_rating : nil
  end

  def ratable_url
    "/banks/#{@ratable.short_id}/ratings"
  end
end

The value method returns the value which many have been passed to the component, otherwise it returns the average rating (if possible). Also, since currently the system only has one type of ratable object, the ratable_url is hardcoded, but that could easily be made more flexible later on.

Now let’s look at the template:

<!-- app/components/rating_stars_component.html.serb -->
<rating-stars href="{{ ratable_url }}">
  <sl-rating
    {%= "readonly" if readonly %}
    {%= %(value="#{value}") if value %}
  ></sl-rating>
</rating-stars>

Pretty straightforward—but what’s the deal with that rating-stars tag? That is the custom element which has also been written alongside the Ruby component/template. Let’s take a look at that now.

# app/components/rating_stars_element.js.rb

import [ signed_in, initiate_sign_up ], from: "../javascript/lib/utils.js.rb"

class RatingStarsElement < LitElement
  custom_element "rating-stars"

  def connected_callback()
    set_timeout 100 do
      self.add_event_listener "sl-change" do |event|
        value = event.target.value
        rate(value)
      end
    end
  end

  async def rate(value)
    unless signed_in?()
      # grab the context and id out of the url for rating
      context, context_id = @href[1..].split("/")
      return initiate_sign_up(context.delete_suffix("s"), context_id)
    end

    response = await Daniel.post(@href, rating: value)
    return Toaster.raise("check2-circle", "Thanks for your rating!") if response.ok?

    alert "I'm sorry, there was a problem saving your rating. Please contact our support team."
    data = await response.text()
    console.error response, data
  end

  def render = "<slot></slot>"
end

There’s a lot going on here so I’ll break it down for you. Also, in case you’re still scratching your head wondering how a web component has been written using Ruby (that is, something very much like it), you can thank Ruby2JS. We can even use the latest Ruby 3 syntax! Awesome, isn’t it?

So here’s the rundown:

(FYI: if you’re wondering what Daniel is, it’s a simple wrapper around fetch I wrote, and it’s called Daniel because there’s a popular Ruby gem for making web requests called Faraday, and there’s a character in the TV show Lost named Daniel Faraday. Daniel. Faraday. Get it? 😋)

Now you may be wondering why I would even use a web component in this context, when it seems like Stimulus could do the job quite nicely. And many people working on a Rails app would probably assume you should use Stimulus for this sort of thing.

If that’s the flavor of ice cream you prefer, go for it! You can still employ patterns very similar to the one above. However, I personally have chosen to migrate away from Stimulus and only write web components. The reason for this is that I want to limit architectural complexity. After writing a wide variety of Stimulus controllers in the past, there were a number of cases where Stimulus just wasn’t cutting it, and I was able to write better and less buggy code by switching to Lit/web components technology. And at that point, if I’m writing both Stimulus controllers and Lit components in the same project, the question becomes: why? Why can’t I just use Lit alone?

So that’s the direction I’ve headed in. I find the conceptual 1:1 mapping between a Ruby component and a web component to be very easy to reason about. Plus, thanks to Crystallized—a small Lit add-on I wrote which provides a solid Stimulus-like actions/targets mechanism for “light DOM” markup—I really don’t miss Stimulus in the least. Let’s take a look at that next.

Soundclip Play Button #

Here’s a component which provides a play/pause button for an audio clip. It also connects up with a site-wide, persistent audio player not covered herein. (I’ve simplified the example down a little from the shipping component for clarity.)

The Ruby component itself does very little:

# app/components/soundclip_component.rb

class SoundclipComponent < ApplicationComponent
  def initialize(soundclip:, hidden: false, order: nil)
    @soundclip, @hidden, @order = soundclip, hidden, order
  end
end

And here’s the Ruby template, making great use of multi-line Serbea filters:

<soundclip-button
  {%= %(style="display:none") if @hidden %}
  {%= %(order="#{@order}") if @order %}
>
  {{
    content |>
    strip |>
    safe |>
    link_to:
      rails_blob_path(@soundclip.audio_file),
      class: "button is-primary",
      "soundclip-button-action": "play"
  }}
</soundclip-button>

This button wraps around an icon/title pair passed to it within other templates. An example being:

{%@ SoundclipComponent soundclip: soundclip do %}
  <i class="icon icon-music-play-button" soundclip-button-target="icon"></i>
  <ui-label soundclip-button-target="title">{{ soundclip.name }}</ui-label>
{% end %}

So how does soundclip-button work and what’s with all those soundclip-button-action and soundclip-button-target attributes? Let’s find out!

class SoundclipElement < LitElement
  self.properties = {
    playing: { type: Boolean, reflect: true },
    # additional properties are auto-defined by Ruby2JS
  }

  self.targets = {
    button: ".button",
    title_text: "@title",
    icon: "@",
  }

  custom_element "soundclip-button"

  def initialize
    DeclarativeActionsController.new self
    TargetsController.new self

    @playing = false
    @order = 0
  end

  # no shadow dom
  def create_render_root() = self

  def play(event = nil)
    event&.prevent_default()
    player = document.query_selector("audio-player")
    if @playing
      player.stop() # player will then call the stop method
    else
      resume()
      player.play self
    end
  end

  # Plays the next soundclip within the current box
  def play_next()
    return unless @order.present?

    self
      .closest(".box")
      .query_selector("soundclip-button[order='#{@order + 1}']")
      &.play()
  end

  def stop()
    @playing = false
    self.icon.class_list.replace "icon-music-pause-button", "icon-music-play-button"
  end

  def resume()
    @playing = true
    self.icon.class_list.replace "icon-music-play-button", "icon-music-pause-button"
  end
end

I won’t go through every single line of code in detail here, but I want to highlight a few of the special aspects:

Collapsing Mental Models #

As DHH is often fond of saying, conceptual compression is a hallmark of Rails, and it’s a philosophy I very much subscribe to as well. I also like to collapse mental models. The fewer layers of “different stuff” living in parallel universes you have to boot up in your mind in order to accomplish simple tasks, the better.

What I love so much about the patterns above is that once you’ve wrapped your mind around what’s a Ruby component and what’s a web component, the two can operate as one conceptually-speaking across a wide variety of use cases…and by using Ruby2JS, you don’t even need to leave your beloved Ruby syntax behind. I find it fatiguing to have to context-switch constantly between Ruby and JavaScript when working on a singular feature. Now I don’t have to. Amazing! While knowledge of DOM APIs and some JavaScript methods is still required, the mental models are mostly collapsed. In a broad sense, you’re just writing Ruby objects and templates to build up discrete building blocks of user interface, and merely a small amount of effort is required to determine which is the code that executes server-side vs. client-side.

Boom. 🤯



Episode 2: Componentized View Architecture FTW!

There are no full stack engineers?! Let’s talk about that. Also, just what is a componentized view architecture anyway? What are components? For that matter, what are templates? What are partials? I break it all down and explain why I’m gung-ho about view components. Plus I answer questions regarding Stimulus, nice_partials, and other Rails tooling from listeners like YOU! Enjoy, and keep on Ruby-ing!

Become a part of the Fullstack Ruby community and learn how to put your Ruby skills to work on the backend AND the frontend. Know somebody who’s a JavaScript developer but is interested in learning more about Ruby? Share the site, podcast, or newsletter with them!

The Fullstack Ruby Podcast is a production of Whitefusion, a boutique web studio based in Portland, OR.

Theme music courtesy of Epidemic Sound.


Subscribe to the RSS feed

in your podcast player of choice.



Episode 1: Why Ruby2JS is a Game Changer

Hey everybody, I’m so glad you could tune in for the debut episode of Fullstack Ruby. I’ve been on a few Ruby-themed podcasts over the past 18 months, but this is the first time I’m running a show about Ruby myself!

To kick things off, I’d like to introduce you to Ruby2JS and explain why I think this technology is a game changer.

Ruby2JS isn’t simply about an attempt to write what appears to be Ruby code for your website frontend. It’s really about writing JavaScript—AS IF JavaScript had Ruby’s syntax and was inspired by Ruby’s stdlib, ActiveSupport, and the like. A “RubyScript” if you will.

Three examples I cover on today’s episode:

Visit the Ruby2JS website for live compilation demos, documentation on the various transformations and approaches available, and a whole lot more.

Become a part of the Fullstack Ruby community and learn how to put your Ruby skills to work on the backend AND the frontend. Know somebody who’s a JavaScript developer but is interested in learning more about Ruby? Share the site, podcast, or newsletter with them!

The Fullstack Ruby Podcast is a production of Whitefusion, a boutique web studio based in Portland, OR.

Theme music courtesy of Epidemic Sound.


Subscribe to the RSS feed

in your podcast player of choice.

Newer Posts Older Posts
Skip to content