Skip to main content

Assigns attributes in constructor using Dry::Initializer

It is so common to write constructors and attributes in Ruby like so:

class FormatHeader
include ApplicationService::Config

attr_reader :parsed_content, :skip_frozen_string_literal

def initialize(parsed_content:, skip_frozen_string_literal: false)
@parsed_content = parsed_content
@skip_frozen_string_literal = skip_frozen_string_literal
end
# ...
end

If Ruby is your day-to-day language, such code probably seems too routine for you.

class RunShell
include ApplicationService::Config

attr_reader :command, :debug

def initialize(command:, debug: false)
@command = command
@debug = debug
end
# ...
end

Also, it is so annoying to make accidental typos, when you are already tired, but you still have some stuff to complete.

For example, the word command is typed 4 times in the example above, so there are 4 opportunities to make an automatic mistake.

Please, don't even tell that your IDE handles that for you.

When the whole team is feeling the same annoyance.

All of the members really believe in the benefits of the removal of such repeatable code.

You may consider to utilizing the Dry::Initializer integration that the AssignsAttributesInConstructor::UsingDryInitalizer plugin provides.

module ApplicationService
module Config
include ConvenientService::Concern

included do |service_class|
service_class.class_exec do
include ConvenientService::Standard::Config
# ...
concerns do
use ConvenientService::Plugins::Common::AssignsAttributesInConstructor::UsingDryInitalizer::Concern
end
end
end
end
end

Thus, FormatHeader and RunShell can be reduced in the following way:

class FormatHeader
include ApplicationService::Config

option :parsed_content
option :skip_frozen_string_literal, default: -> { false }
# ...
end
class RunShell
include ApplicationService::Config

option :command
option :debug, default: -> { false }
# ...
end
caution

Dry::Initializer does NOT call super in its initialize implementation.

If it causes problems for you, try to place AssignsAttributesInConstructor::UsingDryInitalizer closer to the top in the concerns stack.

See insert_before.

danger

Introduce new libraries to the projects only when you have strong arguments and complete ideas about why to do so.

In fast-paced enterprise projects, with a poor onboarding process, simplicity is almost always a better choice.

The simplicity in a sense, that the tech stack should be familiar to the people, who maintain the project for years.

Since you probably won't have a chance to talk with the "one-task" developers while they remember the initial reasoning behind their code.

Moreover, that code is frequently left without any diagrams, documentation, or even tests 😐.