Things I Used to Use (But Don't Anymore)

I spend a lot of time on the Ruby On Rails Link Slack, and a recent resurgence of active admin questions and another thread Hidden Gem Challenge here, which I’m very excited about (though cautiously), inspire me to share my transition from magpie to boring vanilla rails proponent.

Over the years I’ve watched myself adopt, struggle with, and eventually discard a category of gems that promised to improve on vanilla Rails. SimpleForm, ActiveAdmin, Ruby Facets. Each one came with strong opinions and narrow use cases. The moment my requirements diverged from their happy path, I was fighting the abstraction rather than solving the problem.

Facets is the most interesting relic of this era. It emerged when monkey-patching core classes felt like Ruby’s killer feature rather than a warning sign. Rails itself still does a bunch of this: 5.days.ago is a patch on Numeric. Some of that early ActiveSupport magic has since been absorbed into Ruby proper, and the community has cooled on ambient class extension. For good reason.

The worst offenders were gems that patched Rails internals directly. Early versions of both ViewComponent and WickedPDF patched the render method in incompatible ways, making them impossible to use together. Rails has since amended render to accept an object, which addresses those needs more cleanly and generally. But for a while, adopting either gem meant accepting a quiet time bomb.

The lesson I keep relearning:

  1. Dependencies are liabilities.
  2. Monkey patching is dangerous.
  3. UI Generation Libraries inevitably mismatch with changing needs.
  4. Boring Rails beats clever gems.
4 Likes

Hi @andynu ,

This is a very interesting topic. I agree that things like monkey patching are very powerful but at the same time can become problematic, specially when it clashes with a different implementation or assumption. I think it’s great Ruby is more mature/stable now and monkey patching is reserved for more special uses cases and not as the default approach.

Regarding your point #4, I would even go a step further and say that “Boring Ruby” beats everything. I think it’s great that frameworks provide a structure but when handling complexity of the domain logic, a well constructed constellation of POROs can take you a long way.

Hear hear. POROs are great! For Presenters, for Form Objects, for Service Objects, and business logic in general. One of the critiques of Rails, which I can appreciate, is the gravity of ActiveRecord, the natural desire to shove more stuff in there. And while I’m not as opposed to a little mixing of persistence and business for convenience as say the Repository Pattern folks, I’ve rarely regretted extracting a PORO out of an ActiveRecord model.

2 Likes

Interestingly enough I was recently using my company’s take-home challenge as a kata to try different things on a known problem.

One of my tests was no-rails/no-rspec/no-activesupport…

To my surprise it was easier for me to replace active_record with sequel than it was to go with no active_support.

To replace the very succint integer.days.ago and family I created a concern like this one:

module Dateable
  def change(options)
    ::Date.new(options.fetch(:year, year), options.fetch(:month, month), options.fetch(:day, day))
  end

  def all_month = beginning_of_month..end_of_month
  def beginning_of_month = change(day: 1)
  def end_of_month = days_since(days_in_month(month, year) - day)
  def days_ago(days) = advance(days: -days)
  def days_since(days) = advance(days: days)
  def months_ago(months) = advance(months: -months)
  def months_since(months) = advance(months: months)

  COMMON_YEAR_DAYS_IN_MONTH	=	[
    nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  ].freeze

  def days_in_month(month, year = current.year)
    month == 2 && ::Date.gregorian_leap?(year) ? 29 : COMMON_YEAR_DAYS_IN_MONTH[month]
  end

  def advance(options)
    d = self

    d >>= (options[:years] * 12) if options[:years]
    d >>= options[:months] if options[:months]
    d += (options[:weeks] * 7) if options[:weeks]
    d += options[:days] if options[:days]

    d
  end
end

As you can see, it doe snot cover all of the cases, only the ones I needed for my project, and the interface is less fancy, days_ago(3) instead of `3.days.ago.

On the other hand, it is reasonably small and easy to read.

2 Likes