class Teyu::GenericInitializer

Public Class Methods

new(klass, argument) click to toggle source
# File lib/teyu.rb, line 50
def initialize(klass, argument)
  @klass = klass
  @argument = argument
end

Public Instance Methods

define() click to toggle source
# File lib/teyu.rb, line 55
def define
  # NOTE: accessing local vars is faster than method calls, so cache to local vars
  required_positional_args = @argument.required_positional_args
  required_keyword_args = @argument.required_keyword_args
  optional_keyword_args = @argument.optional_keyword_args
  keyword_args = @argument.keyword_args

  @klass.define_method(:initialize) do |*given_args|
    if given_args.last.is_a?(Hash)
      given_positional_args = given_args[0...-1]
      given_keyword_args = given_args.last
    else
      given_positional_args = given_args
      given_keyword_args = {}
    end
    given_keyword_args_keys = given_keyword_args.keys

    if required_positional_args.size != given_positional_args.size
      raise ArgumentError, "wrong number of arguments (given #{given_positional_args.size}, expected #{required_positional_args.size})"
    end
    missing_keywords = required_keyword_args - given_keyword_args_keys
    raise ArgumentError, "missing keywords: #{missing_keywords.join(', ')}" unless missing_keywords.empty?
    unknown_keywords = given_keyword_args_keys - keyword_args
    raise ArgumentError, "unknown keywords: #{unknown_keywords.join(', ')}" unless unknown_keywords.empty?

    # NOTE: `while` is faster than `each` because it does not create a so-called "environment"
    i = 0
    while i < required_positional_args.size
      name = required_positional_args[i]
      value = given_positional_args[i]
      instance_variable_set(:"@#{name}", value)
      i += 1
    end

    default_keyword_args_keys = optional_keyword_args.keys - given_keyword_args_keys
    i = 0
    while i < default_keyword_args_keys.size
      name = default_keyword_args_keys[i]
      value = optional_keyword_args[name]
      # NOTE: In Ruby, objects of default arguments are newly created everytime on a method call.
      #
      #     def test(a: "a")
      #       puts a.object_id
      #     end
      #     test #=> 70273097887660
      #     test #=> 70273097887860
      #
      # In a method argument, it is possible to suppress the new object creation like:
      #
      #     $a = "a"
      #     def test(a: $a)
      #       puts a.object_id
      #     end
      #     test #=> 70273097887860
      #     test #=> 70273097887860
      #
      # But, we do not support a such feature in this gem. That's why we `dup` here.
      value = value.dup
      instance_variable_set(:"@#{name}", value)
      i += 1
    end

    i = 0
    while i < given_keyword_args_keys.size
      name = given_keyword_args_keys[i]
      value = given_keyword_args[name]
      instance_variable_set(:"@#{name}", value)
      i += 1
    end
  end
end