dry-initializer 1.4.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -8
- data/.travis.yml +0 -3
- data/CHANGELOG.md +339 -196
- data/LICENSE.txt +1 -1
- data/README.md +3 -3
- data/Rakefile +2 -47
- data/benchmarks/{several_defaults.rb → compare_several_defaults.rb} +4 -4
- data/benchmarks/{without_options.rb → plain_options.rb} +20 -9
- data/benchmarks/{params.rb → plain_params.rb} +20 -9
- data/benchmarks/{with_types.rb → with_coercion.rb} +20 -9
- data/benchmarks/with_defaults.rb +19 -8
- data/benchmarks/{with_types_and_defaults.rb → with_defaults_and_coercion.rb} +21 -10
- data/dry-initializer.gemspec +3 -3
- data/lib/dry/initializer/builders/attribute.rb +76 -0
- data/lib/dry/initializer/builders/initializer.rb +61 -0
- data/lib/dry/initializer/builders/reader.rb +50 -0
- data/lib/dry/initializer/builders/signature.rb +32 -0
- data/lib/dry/initializer/builders.rb +7 -0
- data/lib/dry/initializer/config.rb +161 -0
- data/lib/dry/initializer/definition.rb +93 -0
- data/lib/dry/initializer/dsl.rb +43 -0
- data/lib/dry/initializer/mixin/local.rb +19 -0
- data/lib/dry/initializer/mixin/root.rb +10 -0
- data/lib/dry/initializer/mixin.rb +15 -0
- data/lib/dry/initializer.rb +45 -41
- data/lib/tasks/benchmark.rake +41 -0
- data/lib/tasks/profile.rake +78 -0
- data/spec/{options_var_spec.rb → attributes_spec.rb} +9 -9
- data/spec/custom_initializer_spec.rb +1 -1
- data/spec/default_values_spec.rb +6 -6
- data/spec/definition_spec.rb +21 -14
- data/spec/invalid_default_spec.rb +2 -2
- data/spec/missed_default_spec.rb +2 -2
- data/spec/optional_spec.rb +2 -2
- data/spec/options_tolerance_spec.rb +1 -1
- data/spec/public_attributes_utility_spec.rb +22 -0
- data/spec/reader_spec.rb +11 -11
- data/spec/repetitive_definitions_spec.rb +5 -5
- data/spec/several_assignments_spec.rb +1 -1
- data/spec/spec_helper.rb +5 -0
- data/spec/subclassing_spec.rb +7 -3
- data/spec/type_argument_spec.rb +1 -1
- data/spec/type_constraint_spec.rb +2 -2
- data/spec/value_coercion_via_dry_types_spec.rb +1 -1
- metadata +27 -27
- data/benchmarks/options.rb +0 -54
- data/benchmarks/params_vs_options.rb +0 -35
- data/benchmarks/profiler.rb +0 -28
- data/lib/dry/initializer/attribute.rb +0 -123
- data/lib/dry/initializer/builder.rb +0 -127
- data/lib/dry/initializer/class_dsl.rb +0 -37
- data/lib/dry/initializer/exceptions/default_value_error.rb +0 -8
- data/lib/dry/initializer/exceptions/params_order_error.rb +0 -8
- data/lib/dry/initializer/exceptions/type_constraint_error.rb +0 -7
- data/lib/dry/initializer/instance_dsl.rb +0 -15
- data/lib/dry/initializer/option.rb +0 -61
- data/lib/dry/initializer/param.rb +0 -52
- data/spec/gem_enhancement_spec.rb +0 -18
@@ -1,123 +0,0 @@
|
|
1
|
-
module Dry::Initializer
|
2
|
-
# Contains definitions for a single attribute, and builds its parts of mixin
|
3
|
-
class Attribute
|
4
|
-
class << self
|
5
|
-
# Collection of additional dispatchers for method options
|
6
|
-
#
|
7
|
-
# @example Enhance the gem by adding :coercer alias for type
|
8
|
-
# Dry::Initializer::Attribute.dispatchers << -> (string: nil, **op) do
|
9
|
-
# op[:type] = proc(&:to_s) if string
|
10
|
-
# op
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
# class User
|
14
|
-
# extend Dry::Initializer
|
15
|
-
# param :name, string: true # same as `type: proc(&:to_s)`
|
16
|
-
# end
|
17
|
-
#
|
18
|
-
def dispatchers
|
19
|
-
@@dispatchers ||= []
|
20
|
-
end
|
21
|
-
|
22
|
-
def new(source, coercer = nil, **options)
|
23
|
-
options[:source] = source
|
24
|
-
options[:target] = options.delete(:as) || source
|
25
|
-
options[:type] ||= coercer
|
26
|
-
params = dispatchers.inject(options) { |h, m| m.call(h) }
|
27
|
-
|
28
|
-
super(params)
|
29
|
-
end
|
30
|
-
|
31
|
-
def param(*args)
|
32
|
-
Param.new(*args)
|
33
|
-
end
|
34
|
-
|
35
|
-
def option(*args)
|
36
|
-
Option.new(*args)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
attr_reader :source, :target, :coercer, :default, :optional, :reader
|
41
|
-
|
42
|
-
def initialize(options)
|
43
|
-
@source = options[:source]
|
44
|
-
@target = options[:target]
|
45
|
-
@coercer = options[:type]
|
46
|
-
@default = options[:default]
|
47
|
-
@optional = !!(options[:optional] || @default)
|
48
|
-
@reader = options.fetch(:reader, :public)
|
49
|
-
@undefined = options.fetch(:undefined, true)
|
50
|
-
validate
|
51
|
-
end
|
52
|
-
|
53
|
-
def ==(other)
|
54
|
-
source == other.source
|
55
|
-
end
|
56
|
-
|
57
|
-
def postsetter
|
58
|
-
"@__options__[:#{target}] = @#{target}" \
|
59
|
-
" unless @#{target} == #{undefined}"
|
60
|
-
end
|
61
|
-
|
62
|
-
def getter
|
63
|
-
return unless reader
|
64
|
-
command = %w(private protected).include?(reader.to_s) ? reader : :public
|
65
|
-
|
66
|
-
<<-RUBY
|
67
|
-
undef_method :#{target} if method_defined?(:#{target}) ||
|
68
|
-
protected_method_defined?(:#{target}) ||
|
69
|
-
private_method_defined?(:#{target})
|
70
|
-
#{reader_definition}
|
71
|
-
#{command} :#{target}
|
72
|
-
RUBY
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def validate
|
78
|
-
validate_target
|
79
|
-
validate_default
|
80
|
-
validate_coercer
|
81
|
-
end
|
82
|
-
|
83
|
-
def undefined
|
84
|
-
@undefined ? "Dry::Initializer::UNDEFINED" : "nil"
|
85
|
-
end
|
86
|
-
|
87
|
-
def reader_definition
|
88
|
-
if @undefined
|
89
|
-
"def #{target}; @#{target} unless @#{target} == #{undefined}; end"
|
90
|
-
else
|
91
|
-
"attr_reader :#{target}"
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def validate_target
|
96
|
-
return if target =~ /\A\w+\Z/
|
97
|
-
fail ArgumentError.new("Invalid name '#{target}' for the target variable")
|
98
|
-
end
|
99
|
-
|
100
|
-
def validate_default
|
101
|
-
return if default.nil? || default.is_a?(Proc)
|
102
|
-
fail DefaultValueError.new(source, default)
|
103
|
-
end
|
104
|
-
|
105
|
-
def validate_coercer
|
106
|
-
return if coercer.nil? || coercer.respond_to?(:call)
|
107
|
-
fail TypeConstraintError.new(source, coercer)
|
108
|
-
end
|
109
|
-
|
110
|
-
def default_hash(type)
|
111
|
-
default ? { :"#{type}_#{source}" => default } : {}
|
112
|
-
end
|
113
|
-
|
114
|
-
def coercer_hash(type)
|
115
|
-
return {} unless coercer
|
116
|
-
|
117
|
-
value = coercer unless @undefined
|
118
|
-
value ||= proc { |v| v == Dry::Initializer::UNDEFINED ? v : coercer.(v) }
|
119
|
-
|
120
|
-
{ :"#{type}_#{source}" => value }
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
@@ -1,127 +0,0 @@
|
|
1
|
-
module Dry::Initializer
|
2
|
-
class Builder
|
3
|
-
def param(*args, **opts)
|
4
|
-
@params = insert @params, Attribute.param(*args, **@config.merge(opts))
|
5
|
-
validate_collections
|
6
|
-
end
|
7
|
-
|
8
|
-
def option(*args, **opts)
|
9
|
-
@options = insert @options, Attribute.option(*args, **@config.merge(opts))
|
10
|
-
validate_collections
|
11
|
-
end
|
12
|
-
|
13
|
-
def call(mixin)
|
14
|
-
clear_method(mixin, :__defaults__)
|
15
|
-
clear_method(mixin, :__coercers__)
|
16
|
-
clear_method(mixin, :__initialize__)
|
17
|
-
|
18
|
-
defaults = send(:defaults)
|
19
|
-
coercers = send(:coercers)
|
20
|
-
|
21
|
-
mixin.send(:define_method, :__defaults__) { defaults }
|
22
|
-
mixin.send(:define_method, :__coercers__) { coercers }
|
23
|
-
mixin.class_eval(code)
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def initialize(**config)
|
29
|
-
@config = config
|
30
|
-
@params = []
|
31
|
-
@options = []
|
32
|
-
end
|
33
|
-
|
34
|
-
def insert(collection, new_item)
|
35
|
-
index = collection.index(new_item) || collection.count
|
36
|
-
collection.dup.tap { |list| list[index] = new_item }
|
37
|
-
end
|
38
|
-
|
39
|
-
def clear_method(mixin, name)
|
40
|
-
mixin.send(:undef_method, name) if mixin.private_method_defined? name
|
41
|
-
end
|
42
|
-
|
43
|
-
def code
|
44
|
-
<<-RUBY
|
45
|
-
def __initialize__(#{initializer_signatures})
|
46
|
-
@__options__ = {}
|
47
|
-
#{initializer_presetters}
|
48
|
-
#{initializer_setters}
|
49
|
-
#{initializer_postsetters}
|
50
|
-
end
|
51
|
-
private :__initialize__
|
52
|
-
private :__defaults__
|
53
|
-
private :__coercers__
|
54
|
-
|
55
|
-
#{getters}
|
56
|
-
RUBY
|
57
|
-
end
|
58
|
-
|
59
|
-
def attributes
|
60
|
-
@params + @options
|
61
|
-
end
|
62
|
-
|
63
|
-
def duplications
|
64
|
-
attributes.group_by(&:target)
|
65
|
-
.reject { |_, val| val.count == 1 }
|
66
|
-
.keys
|
67
|
-
end
|
68
|
-
|
69
|
-
def initializer_signatures
|
70
|
-
sig = @params.map(&:initializer_signature).compact.uniq
|
71
|
-
sig << (sig.any? && @options.any? ? "**__options__" : "__options__ = {}")
|
72
|
-
sig.join(", ")
|
73
|
-
end
|
74
|
-
|
75
|
-
def initializer_presetters
|
76
|
-
dups = duplications
|
77
|
-
attributes.map { |a| " #{a.presetter}" if dups.include? a.target }
|
78
|
-
.compact.uniq.join("\n")
|
79
|
-
end
|
80
|
-
|
81
|
-
def initializer_setters
|
82
|
-
dups = duplications
|
83
|
-
attributes.map do |a|
|
84
|
-
dups.include?(a.target) ? " #{a.safe_setter}" : " #{a.fast_setter}"
|
85
|
-
end.compact.uniq.join("\n")
|
86
|
-
end
|
87
|
-
|
88
|
-
def initializer_postsetters
|
89
|
-
attributes.map { |a| " #{a.postsetter}" }.compact.uniq.join("\n")
|
90
|
-
end
|
91
|
-
|
92
|
-
def defined_options
|
93
|
-
if @options.any?
|
94
|
-
keys = @options.map(&:target).join(" ")
|
95
|
-
"__options__.select { |key| %i(#{keys}).include? key }"
|
96
|
-
else
|
97
|
-
"{}"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def getters
|
102
|
-
attributes.map(&:getter).compact.uniq.join("\n")
|
103
|
-
end
|
104
|
-
|
105
|
-
def defaults
|
106
|
-
attributes.map(&:default_hash).reduce({}, :merge)
|
107
|
-
end
|
108
|
-
|
109
|
-
def coercers
|
110
|
-
attributes.map(&:coercer_hash).reduce({}, :merge)
|
111
|
-
end
|
112
|
-
|
113
|
-
def validate_collections
|
114
|
-
optional_param = nil
|
115
|
-
|
116
|
-
@params.each do |param|
|
117
|
-
if param.optional
|
118
|
-
optional_param = param.source if param.optional
|
119
|
-
elsif optional_param
|
120
|
-
fail ParamsOrderError.new(param.source, optional_param)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
self
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Dry::Initializer
|
2
|
-
module ClassDSL
|
3
|
-
attr_reader :config
|
4
|
-
|
5
|
-
def [](**settings)
|
6
|
-
Module.new do
|
7
|
-
extend Dry::Initializer::ClassDSL
|
8
|
-
include Dry::Initializer
|
9
|
-
@config = settings
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def define(fn = nil, &block)
|
14
|
-
mixin = Module.new { include InstanceDSL }
|
15
|
-
builder = Builder.new Hash(config)
|
16
|
-
builder.instance_exec(&(fn || block))
|
17
|
-
builder.call(mixin)
|
18
|
-
mixin
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def extended(klass)
|
24
|
-
super
|
25
|
-
mixin = klass.send(:__initializer_mixin__)
|
26
|
-
builder = klass.send(:__initializer_builder__, Hash(config))
|
27
|
-
builder.call(mixin)
|
28
|
-
|
29
|
-
klass.include(InstanceDSL) # defines #initialize
|
30
|
-
klass.include(mixin) # defines #__initialize__ (to be redefined)
|
31
|
-
end
|
32
|
-
|
33
|
-
def mixin(fn = nil, &block)
|
34
|
-
define(fn, &block)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module Dry::Initializer
|
2
|
-
module InstanceDSL
|
3
|
-
private
|
4
|
-
|
5
|
-
# The method is reloaded explicitly
|
6
|
-
# in a class that extend [Dry::Initializer], or in its subclasses.
|
7
|
-
def initialize(*args)
|
8
|
-
__initialize__(*args)
|
9
|
-
end
|
10
|
-
|
11
|
-
# The method is redefined implicitly every time
|
12
|
-
# a `param` or `option` is invoked.
|
13
|
-
def __initialize__(*); end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
module Dry::Initializer
|
2
|
-
class Option < Attribute
|
3
|
-
# part of __initializer__ definition
|
4
|
-
def initializer_signature
|
5
|
-
"**__options__"
|
6
|
-
end
|
7
|
-
|
8
|
-
# parts of __initalizer__
|
9
|
-
def presetter
|
10
|
-
"@#{target} = #{undefined}" if dispensable? && @undefined
|
11
|
-
end
|
12
|
-
|
13
|
-
def safe_setter
|
14
|
-
"@#{target} = #{safe_coerced}#{maybe_optional}"
|
15
|
-
end
|
16
|
-
|
17
|
-
def fast_setter
|
18
|
-
return safe_setter unless dispensable?
|
19
|
-
"@#{target} = __options__.key?(:'#{source}')" \
|
20
|
-
" ? #{safe_coerced}" \
|
21
|
-
" : #{undefined}"
|
22
|
-
end
|
23
|
-
|
24
|
-
# part of __defaults__
|
25
|
-
def default_hash
|
26
|
-
super :option
|
27
|
-
end
|
28
|
-
|
29
|
-
# part of __coercers__
|
30
|
-
def coercer_hash
|
31
|
-
super :option
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def dispensable?
|
37
|
-
optional && !default
|
38
|
-
end
|
39
|
-
|
40
|
-
def maybe_optional
|
41
|
-
" if __options__.key? :'#{source}'" if dispensable?
|
42
|
-
end
|
43
|
-
|
44
|
-
def safe_coerced
|
45
|
-
return safe_default unless coercer
|
46
|
-
"__coercers__[:'option_#{source}'].call(#{safe_default})"
|
47
|
-
end
|
48
|
-
|
49
|
-
def safe_default
|
50
|
-
"__options__.fetch(:'#{source}')#{default_part}"
|
51
|
-
end
|
52
|
-
|
53
|
-
def default_part
|
54
|
-
if default
|
55
|
-
" { instance_exec(&__defaults__[:'option_#{source}']) }"
|
56
|
-
elsif !optional
|
57
|
-
" { raise ArgumentError, \"option :'#{source}' is required\" }"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
module Dry::Initializer
|
2
|
-
class Param < Attribute
|
3
|
-
# part of __initializer__ definition
|
4
|
-
def initializer_signature
|
5
|
-
optional ? "#{target} = #{undefined}" : target
|
6
|
-
end
|
7
|
-
|
8
|
-
# parts of __initalizer__
|
9
|
-
def presetter; end
|
10
|
-
|
11
|
-
def safe_setter
|
12
|
-
"@#{target} = #{maybe_coerced}"
|
13
|
-
end
|
14
|
-
|
15
|
-
def fast_setter
|
16
|
-
safe_setter
|
17
|
-
end
|
18
|
-
|
19
|
-
# part of __defaults__
|
20
|
-
def default_hash
|
21
|
-
super :param
|
22
|
-
end
|
23
|
-
|
24
|
-
# part of __coercers__
|
25
|
-
def coercer_hash
|
26
|
-
super :param
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def initialize(*args, **options)
|
32
|
-
fail ArgumentError.new("Do not rename params") if options.key? :as
|
33
|
-
super
|
34
|
-
end
|
35
|
-
|
36
|
-
def maybe_coerced
|
37
|
-
return maybe_default unless coercer
|
38
|
-
"__coercers__[:param_#{target}].call(#{maybe_default})"
|
39
|
-
end
|
40
|
-
|
41
|
-
def maybe_default
|
42
|
-
"#{target}#{default_part}"
|
43
|
-
end
|
44
|
-
|
45
|
-
def default_part
|
46
|
-
return unless default
|
47
|
-
" == #{undefined} ?" \
|
48
|
-
" instance_exec(&__defaults__[:param_#{target}]) :" \
|
49
|
-
" #{target}"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
describe "gem enhancement" do
|
2
|
-
before do
|
3
|
-
Dry::Initializer::Attribute.dispatchers << ->(string: false, **op) do
|
4
|
-
op[:type] = proc(&:to_s) if string
|
5
|
-
op
|
6
|
-
end
|
7
|
-
|
8
|
-
class Test::Foo
|
9
|
-
extend Dry::Initializer
|
10
|
-
param :bar, string: true
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
it "works" do
|
15
|
-
foo = Test::Foo.new(:BAZ)
|
16
|
-
expect(foo.bar).to eq "BAZ"
|
17
|
-
end
|
18
|
-
end
|