initable

An automatic object initializer.

https://github.com/bkuhlmann/initable

Science Score: 44.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Academic email domains
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (10.8%) to scientific vocabulary

Keywords

automatic construction initialization initializer
Last synced: 6 months ago · JSON representation ·

Repository

An automatic object initializer.

Basic Info
Statistics
  • Stars: 3
  • Watchers: 1
  • Forks: 0
  • Open Issues: 0
  • Releases: 0
Topics
automatic construction initialization initializer
Created about 1 year ago · Last pushed 7 months ago
Metadata Files
Readme Funding License Citation

README.adoc

:toc: macro
:toclevels: 5
:figure-caption!:

:barewords_link: link:https://alchemists.io/articles/barewords_pattern[Barewords]
:containable_link: link:https://alchemists.io/projects/containable[Containable]
:infusible_link: link:https://alchemists.io/projects/infusible[Infusible]
:marameters_link: link:https://alchemists.io/projects/marameters[Marameters]
:method_parameters_and_arguments_link: link:https://alchemists.io/articles/ruby_method_parameters_and_arguments[Method Parameters and Arguments]
:method_parameters_link: link:https://docs.ruby-lang.org/en/master/Method.html#method-i-parameters[Method#parameters]

= Initable

Initable provides automatic initialization of your objects by leveraging the same parameter structure as provided by {method_parameters_link} while adhering to the {barewords_link} pattern for scoping of attributes. This allows you to quickly define what data/dependencies your object should be constructed with while minimizing the amount of code written. 🎉

toc::[]

== Features

* Provides a Domain Specific Language (DSL) for initializing objects.
* Built atop the {marameters_link}.
* Uses the same data structure as answered by {method_parameters_link}.
* Adheres to the {barewords_link} pattern.
* Reduces the amount of code necessary to implement an object.
* Pairs well with {infusible_link}.

== Requirements

. link:https://www.ruby-lang.org[Ruby].
. A solid understanding of {method_parameters_and_arguments_link}.

== Setup

To install _with_ security, run:

[source,bash]
----
# 💡 Skip this line if you already have the public certificate installed.
gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem)
gem install initable --trust-policy HighSecurity
----

To install _without_ security, run:

[source,bash]
----
gem install initable
----

You can also add the gem directly to your project:

[source,bash]
----
bundle add initable
----

Once the gem is installed, you only need to require it:

[source,ruby]
----
require "initable"
----

== Usage

You only need to require this gem, include the module within your class, and configure the necessary parameters for object initialization. The following provides a simple `Person` object that is  implemented with and without the use of this gem so you can compare, contrast, and notice the reduction in code used:

*With*

[source,ruby]
----
require "initable"

class Person
  include Initable[%i[keyreq first], %i[keyreq last], %i[key middle]]

  def name = [first, middle, last].compact.join " "
end

person = Person.new first: "Alfred", last: "Pennyworth"
#

person.name
# "Alfred Pennyworth"

person.first
# private method `first' called for # (NoMethodError)
----

*Without*

[source,ruby]
----
class Person
  def initialize first:, last:, middle: nil
    @first = first
    @last = last
    @middle = middle
  end

  def name = [first, middle, last].compact.join " "

  private

  attr_reader :first, :last, :middle
end

person = Person.new first: "Alfred", last: "Pennyworth"
#

person.name
# "Alfred Pennyworth"

person.first
# private method `first' called for # (NoMethodError)
----

Notice, in the examples above, we are able to obtain an instance of `Person` with identical behavior. Even better, using this gem requires less code. We can also see the associated attributes are properly initialized as instance variables. All attributes are _privately_ scoped, by default, so your object doesn't break encapsulation.

The rest of this documentation will focus on how to use this gem with the parameters data structure shared {method_parameters_link}.

ℹ️ Please note, for the rest of this documentation, anonymous classes will be used for code examples which makes local experimentation a smoother experience within your IRB console since you get a new instance of a class each time without having to create new constants or deal with constant collisions.

=== Parameters

There are eight _kinds_ of parameters you can use in method signatures as supported by {method_parameters_link} and detailed in the {method_parameters_and_arguments_link} article. The format is always kind, name, and default. Example:

----
[, , ]
----

💡 The default (third element) is always optional and isn't supported by {method_parameters_link} but is part of this DSL so you can supply a default value for optional positional or keyword parameters with minimal effort.

As detailed in the {method_parameters_and_arguments_link} article, the order of each kind of parameter matters because if you define them out of order, you'll get a syntax error as you would get when not using this gem to initialize an object. For reference, here's the natural order of parameters for a method signature in case it helps:

----
%i[req opt rest nokey keyreq key keyrest block]
----

Simply speaking, this means `req` is always in the first position and `block` is always in the last position. You can skip parameters in between, as necessary, but position is always important regardless of what you use.

Each _kind_ of parameter is detailed in the following sections.

==== req

Use `req` when you need a _required positional_ parameter:

[source,ruby]
----
demo = Class.new do
  include Initable[%i[req example]]
end

demo.new    # wrong number of arguments (given 0, expected 1) (ArgumentError)
demo.new 1  #<#:0x0000000122244500 @example=1>
----

==== opt

Use `opt` when you need an _optional positional_ parameter:

[source,ruby]
----
demo = Class.new do
  include Initable[%i[opt example]]
end

demo.new    #<#:0x0000000124c3d000 @example=nil>
demo.new 1  #<#:0x00000001248b3ee8 @example=1>
----

You can also provide a default value by supplying a third element for the parameter:

[source,ruby]
----
demo = Class.new do
  include Initable[[:opt, :example, 1]]
end

demo.new     #<#:0x0000000131c31c98 @example=1>
demo.new 10  #<#:0x0000000131d1fb00 @example=10>
----

==== rest

Use `rest` when you need any number of _optional positional_ parameters:

[source,ruby]
----
demo = Class.new do
  include Initable[%i[rest example]]
end

demo.new          #<#:0x0000000125272f88 @example=[]>
demo.new 1, 2, 3  #<#:0x0000000124f9c228 @example=[1, 2, 3]>
----

For anonymous single splats (i.e. `+*+`), don't provide a name. Use only the kind:

[source,ruby]
----
demo = Class.new do
  include Initable[[:rest]]
end
----

This is useful when needing to forward all positional arguments to the super class.

==== nokey

Use `nokey` when you want to prevent use of any _keyword_ parameter (i.e. `+**nil+`):

[source,ruby]
----
demo = Class.new do
  include Initable[[:nokey]]
end

demo.new       #<#:0x00000001300baf78>
demo.new a: 1  # wrong number of arguments (given 1, expected 0) (ArgumentError)
----

==== keyreq

Use `keyreq` when you need a _required keyword_ parameter:

[source,ruby]
----
demo = Class.new do
  include Initable[%i[keyreq example]]
end

demo.new             # missing keyword: :example (ArgumentError)
demo.new example: 1  #<#:0x0000000130655ed8 @example=1>
----

==== key

Use `key` when you need an _optional keyword_ parameter:

[source,ruby]
----
demo = Class.new do
  include Initable[%i[key example]]
end

demo.new             #<#:0x00000001307b0008 @example=nil>
demo.new example: 1  #<#:0x0000000130655ed8 @example=1>
----

You can also provide a default value by supplying a third element for the parameter:

[source,ruby]
----
demo = Class.new do
  include Initable[[:key, :example, 1]]
end

demo.new              #<#:0x000000013007ee88 @example=1>
demo.new example: 10  #<#:0x00000001300ff998 @example=10>
----

==== keyrest

Use `keyrest` when you need any number of _keyword_ parameters:

[source,ruby]
----
demo = Class.new do
  include Initable[%i[keyrest example]]
end

demo.new
#<#:0x000000013051e3f8 @example={}>

demo.new a: 1, b: 2
#<#:0x000000013069e2c8 @example={:a=>1, :b=>2}>
----

For anonymous double splats (i.e. `+**+`), don't provide a name. Use only the kind:

[source,ruby]
----
demo = Class.new do
  include Initable[[:keyrest]]
end
----

This is useful when needing to forward all keyword arguments to the super class.

==== block

Use `block` when you need a _block_ parameter:

[source,ruby]
----
demo = Class.new do
  include Initable[%i[block example]]
end

demo.new
#<#:0x000000013193bac0 @example=nil>

instance = demo.new { "Hi" }
#<#:0x0000000131a9a380 @example=#>
----

For anonymous blocks (i.e. `+&+`), don't provide a name. Use only the kind:

[source,ruby]
----
demo = Class.new do
  include Initable[[:block]]
end
----

This is useful when needing to forward a block to the super class.

=== Keywords

As an added convenience, you can use keywords in addition to positional arguments when initializing your class. Example:

[source,ruby]
----
demo = Class.new do
  include Initable[one: 1, two: 2]
end

demo.new  #<#:0x0000000136091f00 @one=1, @two=2>
----

The above is identical to the following but with more typing:

[source,ruby]
----
demo = Class.new do
  include Initable[[:key, :one, 1], [:key, :two, 2]]
end

demo.new  #<#:0x00000001518775b0 @one=1, @two=2>
----

You can also combine positionals with keywords:

[source,ruby]
----
demo = Class.new do
  include Initable[%i[req one], [:key, :two, 2], three: 3]
end

demo.new 1  # #<#:0x0000000151ebab98 @one=1, @two=2, @three=3>
----

⚠️ As with default `+#initialize+` behavior, ensure you don't duplicate parameter names to avoid naming collisions.

In most cases, you'll want to use xref:_parameters[Parameters] as documented earlier. Otherwise, this is a nice way to initialize with safe defaults or utilize lightweight dependency injection without reaching for {infusible_link} when you don't need a full fledged {containable_link} container.

=== Defaults

You've already seen you can provide a third element for defaults with optional positional and keyword parameters. Sometimes, though, you might want to use a more complex object as a default (especially if you want the default to be lazy loaded/initialized). For those situations use a `Proc`. Example:

[source,ruby]
----
demo = Class.new do
  include Initable[
    [:opt, :one, proc { %w[O n e].join }],
    [:key, :two, proc { Object.new }],
    three: proc { StringIO.new }
  ]
end

demo.new
# <#:0x0000000153a9b0b0
#    @one="One",
#    @two=#,
#    @three=#
# >
----

Notice, for the `one` optional positional parameter, we get a default value of `"One"` once evaluated. For the `two` optional keyword parameter, we get a new instance of `Object` as a default value.

⚠️ There a few caveats to be aware of when using defaults:

* Use procs because lambdas will throw a `TypeError`.
* Use procs _with no arguments_ because only the body of the `Proc` is parsed. Otherwise, you'll get an `ArgumentError`.
* Ensure each parameter -- with a default -- is defined on a distinct line because the body of the `Proc` is extracted at runtime from the source location of the `Proc`. The goal is to improve upon this further once Ruby link:https://bugs.ruby-lang.org/issues/21005[adds] source location with line start, line end, column start, and column end information.
* This does not work consistently in IRB due to the above mentioned Ruby issue.

=== Barewords

As mentioned earlier, all instances adhere to the {barewords_link} pattern so you have direct access to all data/dependencies via bare word methods. Here's an example with an instance using a required positional and optional keyword parameter.

[source,ruby]
----
demo = Class.new do
  include Initable[%i[req one], [:key, :two, 2]]

  def debug = puts "One: #{one}, Two: #{two}."
end

demo.new(1).debug  # One: 1, Two: 2.
----

Notice, with the `debug` method, only bare words are used as provided by the attribute readers.

=== Scopes

As mentioned earlier, all attributes are scoped -- via `attr_reader` -- as `private` by default but `protected` and `public` scopes are supported too. Here are examples of each:

==== Private

[source,ruby]
----
demo = Class.new do
  include Initable[%i[req example]]
end

demo.new(1).example
# private method `example' called for an instance of # (NoMethodError)
----

==== Protected

[source,ruby]
----
demo = Class.new do
  include Initable.protected(%i[req example])
end

demo.new(1).example
# protected method `example' called for an instance of # (NoMethodError)
----

==== Public

[source,ruby]
----
demo = Class.new do
  include Initable.public(%i[req example])
end

demo.new(1).example
# 1
----

==== Combinations

You can combine scopes, if desired, as well. Here's an example using three required positional parameters with different scopes:

[source,ruby]
----
demo = Class.new do
  include Initable[%i[req one]]
  include Initable.protected(%i[req two])
  include Initable.public(%i[req three])
end

instance = demo.new 1, 2, 3
#<#:0x00000001501fbc78 @one=1, @two=2, @three=3>

instance.one
# private method `one' called for an instance of # (NoMethodError)

instance.two
# protected method `two' called for an instance of # (NoMethodError)

instance.three
# 3
----

⚠️ While convenient to initialize an object with different scopes, this does introduce additional multiple inheritance in your object ancestry. While not necessarily bad, if your object isn't overly complicated or requires more than three parameters (🎗️ Don't forget to adhere to the _rule of three_), you might need to break your class into smaller dependencies and/or switch to manually defining the `initialize` method.

=== Inheritance

Inheritance works similar to parent/child relationships as found in standard Ruby classes with a few enhancements thrown in for convenience. Several examples are provided below. For each, there is an identical implementation using Plain Old Ruby Objects (POROs) so you can contrast/compare for clarity.

[source,ruby]
----
parent = Class.new do
  include Initable.protected(%i[req one])
end

child = Class.new parent do
  include Initable[[:opt, :two, 2]]
end

parent.new 1
#<#:0x00000001265f0c90 @one=1>

child.new 1
#<#:0x00000001254beb20 @one=1, @two=2>

child.new 10, 20
#<#:0x0000000126973d40 @one=10, @two=20>
----

.Plain Implementation
[%collapsible]
====
[source,ruby]
----
parent = Class.new do
  def initialize one
    @one = one
  end

  protected

  attr_reader :one
end

child = Class.new parent do
  def initialize one, two = 2
    super one
    @two = two
  end

  private

  attr_reader :two
end

parent.new 1
#<#:0x0000000134abe368 @one=1>

child.new 1
#<#:0x0000000134b16748 @one=1, @two=2>

child.new 10, 20
#<#:0x0000000134b91880 @one=10, @two=20>
----
====

Notice the `child` instance has access to both the `one` and `two` attributes where `one` is defined as _protected_ by the `parent` and `two` is defined as _private_ for the `child`. This is no different in how you'd subclass without using this gem. You only need to define the attributes you need in the `child` class since there is no need to redefine what the `parent` already has defined. This gem will handle proper setup of your instance variables as well as forwarding, via `super`, any/all attributes to the `parent` as necessary. The automatic forwarding, via `super`, applies for all parameters.

==== Positionals

[source,ruby]
----
parent = Class.new do
  include Initable.protected(%i[req one], [:opt, :two, 2])
end

child = Class.new parent do
  include Initable[%i[req three], [:opt, :two, 2]]
end

child.new 1, 3
#<#:0x0000000128591478 @one=1, @two=2, @three=3>

child.new 1, 3, 20
#<#:0x00000001286353e8 @one=1, @two=20, @three=3>
----

.Plain Implementation
[%collapsible]
====
[source,ruby]
----
parent = Class.new do
  def initialize one, two = 2
    @one = one
    @two = two
  end

  private

  attr_reader :one, :two
end

child = Class.new parent do
  def initialize one, three, two = 2
    super one, two
    @three = three
  end

  private

  attr_reader :three
end

child.new 1, 3
#<#:0x0000000128297240 @one=1, @two=2, @three=3>

child.new 1, 3, 20
#<#:0x00000001284344b8 @one=1, @two=20, @three=3>
----
====

Positional parameters are less flexible than keyword parameters especially when optional parameters are involved because the order of parameters matters and the `two` parameter with a default of `2` has to be repeated in the child so `two` can be forwarded by `super` when not supplied.

==== Keywords

[source,ruby]
----
parent = Class.new do
  include Initable.protected(%i[keyreq one], [:key, :two, 2])
end

child = Class.new parent do
  include Initable[%i[keyreq three], [:key, :four, 4]]
end

child.new one: 1, three: 3
#<#:0x0000000138311800 @one=1, @two=2, @three=3, @four=4>

child.new one: 1, two: 20, three: 3, four: 40
#<#:0x00000001383d0b10 @one=1, @two=20, @three=3, @four=40>
----

.Plain Implementation
[%collapsible]
====
[source,ruby]
----
parent = Class.new do
  def initialize one:, two: 2
    @one = one
    @two = two
  end

  private

  attr_reader :one, :two
end

child = Class.new parent do
  def initialize(three:, four: 4, **)
    super(**)
    @three = three
    @four = four
  end

  private

  attr_reader :three, :four
end

child.new one: 1, three: 3
#<#:0x0000000138311800 @one=1, @two=2, @three=3, @four=4>

child.new one: 1, two: 20, three: 3, four: 40
#<#:0x0000000139831c80 @one=1, @two=20, @three=3, @four=40>
----
====

Due to the power of keyword parameters, we don't have to redefine defaults in the `child` and can simply forward any/all missing arguments to the `parent`. This happens automatically but you can see how this done in the plain implementation.

==== Blocks

[source,ruby]
----
parent = Class.new do
  include Initable.protected(%i[block function])
end

child = Class.new parent


child.new { "demo" }
#<#:0x0000000139c95538 @function=#>
----

.Plain Implementation
[%collapsible]
====
[source,ruby]
----
parent = Class.new do
  def initialize &function
    @function = function
  end

  private

  attr_reader :function
end

child = Class.new parent

child.new { "demo" }
#<#:0x0000000138375580 @function=#>
----
====

With blocks, you only have to name them in the `parent` and they will be forwarded by the child. Keep in mind that if you only need to pass the block to the parent but want to use a `block_given?` check before messaging the function in your parent class, then you don't need to use this gem for those situations.

=== Infusible

This gem pairs well with the {infusible_link} gem and requires no additional effort on your part. In terms of style, stick with including Initiable before Infusible because you'll most likely be using Initable to define basic parameters while Infusible will be used to inject dependencies from your container. This way your parameters will read sequentially left-to-right or top-to-bottom when looking at the implementation which improves readability. Example:

[source,ruby]
----
class Demo
  include Initable[%i[req label]]
  include Infusible[:logger]
end
----

You can include Initiable and Infusible in any order, though. Lastly, as with all keyword parameters, make sure you don't define the same key for both or you'll have an order of operations issue where one key overrides the other.

=== Guidelines

The following is worth adhering to:

* Use the _rule of three_ where you only don't use more than three parameters for your method signature. Anything more than that and you have an unborn object that needs a name for dependency injection instead. 💡 For advanced dependency management, consider using {containable_link} and/or {infusible_link}.
* Avoid using complex logic in proc-wrapped defaults. Procs should only be used for lazy loading of default objects.

== Development

To contribute, run:

[source,bash]
----
git clone https://github.com/bkuhlmann/initable
cd initable
bin/setup
----

You can also use the IRB console for direct access to all objects:

[source,bash]
----
bin/console
----

== Tests

To test, run:

[source,bash]
----
bin/rake
----

== link:https://alchemists.io/policies/license[License]

== link:https://alchemists.io/policies/security[Security]

== link:https://alchemists.io/policies/code_of_conduct[Code of Conduct]

== link:https://alchemists.io/policies/contributions[Contributions]

== link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin]

== link:https://alchemists.io/projects/initable/versions[Versions]

== link:https://alchemists.io/community[Community]

== Credits

* Built with link:https://alchemists.io/projects/gemsmith[Gemsmith].
* Engineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].

Owner

  • Name: Brooke Kuhlmann
  • Login: bkuhlmann
  • Kind: user
  • Location: Boulder, CO USA
  • Company: Alchemists

Quality over quantity.

Citation (CITATION.cff)

cff-version: 1.2.0
message: Please use the following metadata when citing this project in your work.
title: Initable
abstract: An automatic object initializer.
version: 0.5.0
license: Hippocratic-2.1
date-released: 2025-07-15
authors:
  - family-names: Kuhlmann
    given-names: Brooke
    affiliation: Alchemists
    orcid: https://orcid.org/0000-0002-5810-6268
keywords:
 - ruby
 - initializer
 - initialization
 - construction
 - automatic
repository-code: https://github.com/bkuhlmann/initable
repository-artifact: https://rubygems.org/gems/initable
url: https://alchemists.io/projects/initable

GitHub Events

Total
  • Watch event: 3
  • Delete event: 3
  • Push event: 26
  • Create event: 10
Last Year
  • Watch event: 3
  • Delete event: 3
  • Push event: 26
  • Create event: 10

Packages

  • Total packages: 1
  • Total downloads:
    • rubygems 2,726 total
  • Total dependent packages: 0
  • Total dependent repositories: 0
  • Total versions: 6
  • Total maintainers: 1
rubygems.org: initable

An automatic object initializer.

  • Versions: 6
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 2,726 Total
Rankings
Dependent packages count: 14.5%
Dependent repos count: 44.5%
Average: 50.6%
Downloads: 92.8%
Maintainers (1)
Funding
  • https://github.com/sponsors/bkuhlmann
Last synced: 7 months ago

Dependencies

Gemfile rubygems
  • amazing_print ~> 1.6 development
  • caliber ~> 0.68 development
  • debug ~> 1.10 development
  • git-lint ~> 9.0 development
  • irb-kit ~> 1.0 development
  • rake ~> 13.2 development
  • reek ~> 6.3 development
  • repl_type_completor ~> 0.1 development
  • rspec ~> 3.13 development
  • simplecov ~> 0.22 development
initable.gemspec rubygems
  • marameters ~> 4.0