Saturday, April 27, 2024
HomeRuby On RailsFully customized Zeitwerk inflector | Arkency Weblog

Fully customized Zeitwerk inflector | Arkency Weblog


In my earlier publish, I mentioned the
distinction between how the traditional autoloader and Zeitwerk autoloader match fixed and file names. Brief reminder:

  • Traditional autoloader maps lacking fixed title Report::PL::X123 to a file title by
    calling Report::PL::X123.to_s.underscore
  • Zeitwerk autoloader finds lib/report/pl/x123/merchandise.rb and maps it to Report::PL::X123::Merchandise fixed title
    with the assistance of outlined inflectors guidelines.

What’s an inflector?

On the whole, an inflector is a software program element answerable for reworking phrases in keeping with predefined guidelines.
Within the context of internet frameworks like Ruby on Rails, inflectors are used to deal with totally different linguistic transformations,
akin to pluralization, singularization, acronym dealing with, and humanization of attribute names.

Rails::Autoloader::Inflector is the one that’s utilized by default in Rails integration with Zeitwerk:

module Rails
  class Autoloaders
    module Inflector # :nodoc:
      @overrides = {}

      def self.camelize(basename, _abspath)
        @overrides[basename] || basename.camelize
      finish

      def self.inflect(overrides)
        @overrides.merge!(overrides)
      finish
    finish
  finish
finish

Its camelize technique checks for the overrides and if it finds one, it makes use of it, in any other case it calls String#camelize
technique, which is a part of ActiveSupport core extensions for String.

def camelize(first_letter = :higher)
  case first_letter
  when :higher
    ActiveSupport::Inflector.camelize(self, true)
  when :decrease
    ActiveSupport::Inflector.camelize(self, false)
  else
    elevate ArgumentError, "Invalid possibility, use both :higher or :decrease."
  finish
finish

As you possibly can see String#camelize delegates to ActiveSupport::Inflector underneath the hood.

ActiveSupport::Inflector has been part of Rails for the reason that very starting and is used to remodel phrases from
singular to plural, class names to desk names, modularized class names to ones with out, and sophistication names to international
keys.

Nevertheless, within the context, of Zeitwerk, acronym dealing with is an important function of inflector.

An instance of acronym is “REST” (Representational State Switch). It’s not unusual to have a continuing together with it,
akin to API::REST::Consumer.

When the traditional autoloader encounters an undefined fixed API::REST::Consumer, it
calls API::REST::Consumer.to_s.underscore to seek out the api/relaxation/shopper.rb file within the autoloaded directories.

Alternatively, Zeitwerk locates api/relaxation/shopper.rb and invokes 'api/relaxation/shopper'.camelize. With out acronym
dealing with guidelines, this ends in Api::Relaxation::Consumer. To get API::REST::Consumer, we have to provide an inflector with
acronym dealing with guidelines. On this publish, I’ll display 4 distinct strategies to perform that.

1. Configure ActiveSupport::Inflector

An intuitive and fairly frequent means is to configure ActiveSupport::Inflector immediately.
However doing so impacts how ActiveSupport inflects these phrases globally. It’s not at all times desired.

# config/initializers/inflections.rb

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'API'
  inflect.acronym 'REST'
finish

2. Set overrides for Rails::Autoloader::Inflector

In some circumstances, you don’t need to add sure class or module naming guidelines to the ActiveSupport inflector.
It’s not obligatory.
You will have the choice to override specific inflections just for Zeitwerk and go away the Rails world inflector as it’s.
Nevertheless, even in the event you do this, Zeitwerk will nonetheless fall again to String#camelize and ActiveSupport::Inflector when it
can’t discover a particular key.

# config/initializers/zeitwerk.rb

Rails.autoloaders.every do |autoloader|
  autoloader.inflector.inflect(
    "api" => "API",
    "relaxation" => "REST",
  )
finish

3. Use Zeitwerk::Inflector

Zeitwerk is a gem designed for use independently from Rails and it supplies another implementation of
inflector that you should use as a substitute of Rails::Autoloader::Inflector.
By doing so, you should have full management over the acronyms you employ in modules and lessons naming conventions in a single place.
Moreover, it should allow you to keep away from polluting the ActiveSupport general-purpose inflector with autoloader-specific guidelines.

# config/initializers/zeitwerk.rb

Rails.autoloaders.every do |autoloader|
  autoloader.inflector = Zeitwerk::Inflector.new
  autoloader.inflector.inflect(
    "api" => "API",
    "relaxation" => "REST",
  )
finish

4. Implement your customized inflector

Contemplate a state of affairs the place, other than the API::REST::Consumer, you even have the Person::Actions::Relaxation fixed in
your codebase. Each of them embody the /relaxation/i substring, however you can’t use the identical inflection rule to derive the
fixed title from the file title.

This can be a good instance of when chances are you’ll want to supply a customized inflector implementation.

Let’s revisit the usual Rails::Autoloader::Inflector#camelize technique implementation to raised perceive this.

def self.camelize(basename, _abspath)
  @overrides[basename] || basename.camelize
finish

As you possibly can see it’s designed to take 2 arguments: basename and _abspath.
The basename is the file title with out the extension and the _abspath is absolutely the path to the file.

Notice that the _abspath just isn’t utilized in both the Rails::Autoloader::Inflector or the Zeitwerk::Inflector
implementation.

Nevertheless, you possibly can nonetheless benefit from this argument presence in your customized implementation.

# config/initializers/zeitwerk.rb

class UnconventionalInflector
  def self.conditional_inflection_for(basename:, inflection:, path:)
    Module.new do
      define_method :camelize do |basename_, abspath|
        if basename_ == basename && path.match?(abspath)
          inflection
        else
          tremendous(basename_, abspath)
        finish
      finish
    finish
  finish

  prepend conditional_inflection_for(
            basename: 'relaxation',
            inflection: 'REST',
            path: /A#{Rails.root.be part of('lib', 'api')}/,
          )

  # ...

  def initialize
    @inflector = Rails::Autoloader::Inflector
  finish

  def camelize(basename, abspath)
    @inflector.camelize(basename, abspath)
  finish

  def inflect(overrides)
    @inflector.inflect(overrides)
  finish
finish

Rails.autoloaders.every do |autoloader|
  autoloader.inflector = UnconventionalInflector.new
  autoloader.inflector.inflect(
    'api' => 'API'
  )
finish

The implementation above makes use of Rails::Autoloader::Inflector module. Nevertheless, it prepends its camelize
implementation with the one which first checks if the file path matches an unconventional inflection rule.
If it does, the strategy makes use of an non-standard inflection. If not, it falls again to the default implementation.


I perceive that the instance of Relaxation and REST could appear contrived, nevertheless it serves as an instance the purpose. In
real-life conditions, there could also be extra convincing causes to implement a customized inflector, simply as we did on a
mission we have been consulting, the place it proved to be very useful.



RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments