[dry-initializer] Process initialization data

#1

I like using dry-initializer in the context of function objects mainly because of the dry-types integration it provides. As the initializer method is the main entry point for the data to be handled, it is very convenient in order to adding a DSL layer of type safety.

However, sometimes the state to be consumed by the “function” within the object (usually from the #call method) is not the same that the data from which the object is initialized. This is because you might want your consumer to provide some data but process it in some way before being consumed. A very simple example:

attr_reader :to_be_consumed

def initializer(to_initialize:)
  @to_be_consumed = to_initialize + 1
end

I know in dry-initializer you can do something like:

extend Dry::Initializer

option :to_initialize
option :to_be_consumed, default: proc { to_initialize + 1 }

But this is not semantically the same, as we are getting an object which require two options to be initialized. I also know I can easily define my own initialize and call super from there, and it is a quite satisfactory solution. However, due that I feel it is quite a common scenario, I think It could be nice to support it with the DSL. For example:

attr_reader :to_consume

option :to_initialize

process do |to_initialize:|
   @to_consume = to_initialize + 1
end

What do you think?

#3

I would say coercer solves your problem more directly:

extend Dry::Initializer
option :to_be_consumed, ->(v) { v.to_i + 1 }

alternatively you can use the :type option

extend Dry::Initializer
option :to_be_consumed, type: ->(v) { v.to_i + 1 }
#4

Hey @nepalez, thanks for your answer.

I see my example was too contrived. As you say, coercer solves a lot of scenarios, but sometimes you want to initialize something from two options, or just initialize something without the need of any of the arguments. Another contrived example:

def initialize(a, b)
  @c = C.new(a, b)
  @d = D.new
end
#5

@waiting-for-dev yes, you’re right that not any process of initialization is covered by the gem.

The most obvious example is the gem doesn’t allow processing a block of code like the following initializer does:

def initialize(&block)
  @block = block
end

But I don’t think we ever should try doing this. In my opinion, overloading the initializer with super method is much cleaner than adding a special DSL method for post-processing. Personally I treat DSL as a necessary evil, but it must be necessary.

#6

I share with you your reticence about adding DSL to every need that we, library consumers, can experience. In my use case, I’m not using dry-initializer out of necessity but just because I feel its convenience to apply type checking to initialization data pays off. I could do the same without it, but my code would have the same pattern applied one time and another. Then, now I find myself repeating the pattern of calling super to do post-processing and, anyway, I never find clean having to call super. Surely, it is just that we have different views here :slightly_smiling_face: Thanks anyway for your feedback and for your great work on this :smile: