Sha256: f3d732475c04eb0fe1960b508314614bd81a74dd216e3293cc8e05ca053f309f

Contents?: true

Size: 1.99 KB

Versions: 3

Compression:

Stored size: 1.99 KB

Contents

class AttrExtras::AttrInitialize
  def initialize(klass, names, block)
    @klass, @names, @block = klass, names, block
  end

  attr_reader :klass, :names
  private :klass, :names

  def apply
    # The define_method block can't call our methods, so we need to make
    # things available via local variables.
    names = @names
    block = @block
    validate_arity = method(:validate_arity)
    set_ivar_from_hash = method(:set_ivar_from_hash)

    klass.send(:define_method, :initialize) do |*values|
      validate_arity.call(values.length, self.class)

      names.zip(values).each do |name_or_names, value|
        if name_or_names.is_a?(Array)
          hash = value || {}

          known_keys = name_or_names.map { |name| name.to_s.sub(/!\z/, "").to_sym }
          unknown_keys = hash.keys - known_keys
          if unknown_keys.any?
            raise ArgumentError, "Got unknown keys: #{unknown_keys.inspect}; allowed keys: #{known_keys.inspect}"
          end

          name_or_names.each do |name|
            set_ivar_from_hash.call(self, name, hash)
          end
        else
          name = name_or_names
          instance_variable_set("@#{name}", value)
        end
      end

      if block
        instance_eval(&block)
      end
    end
  end

  private

  def validate_arity(provided_arity, klass)
    arity_without_hashes = names.count { |name| not name.is_a?(Array) }
    arity_with_hashes    = names.length

    unless (arity_without_hashes..arity_with_hashes).include?(provided_arity)
      arity_range = [ arity_without_hashes, arity_with_hashes ].uniq.join("..")
      raise ArgumentError, "wrong number of arguments (#{provided_arity} for #{arity_range}) for #{klass.name} initializer"
    end
  end

  def set_ivar_from_hash(instance, name, hash)
    if name.to_s.end_with?("!")
      actual_name = name.to_s.chop.to_sym
      value = hash.fetch(actual_name)
    else
      actual_name = name
      value = hash[actual_name]
    end

    instance.instance_variable_set("@#{actual_name}", value)
  end
end

Version data entries

3 entries across 3 versions & 1 rubygems

Version Path
attr_extras-5.2.0 lib/attr_extras/attr_initialize.rb
attr_extras-5.1.0 lib/attr_extras/attr_initialize.rb
attr_extras-5.0.0 lib/attr_extras/attr_initialize.rb