dry-initializer 0.11.0 → 2.5.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/.codeclimate.yml +8 -0
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +14 -62
- data/.travis.yml +15 -11
- data/CHANGELOG.md +538 -158
- data/Gemfile +2 -2
- data/LICENSE.txt +1 -1
- data/README.md +6 -6
- data/Rakefile +2 -41
- data/benchmarks/{several_defaults.rb → compare_several_defaults.rb} +4 -4
- data/benchmarks/{without_options.rb → plain_options.rb} +21 -10
- data/benchmarks/{params.rb → plain_params.rb} +20 -9
- data/benchmarks/{with_types.rb → with_coercion.rb} +23 -16
- data/benchmarks/with_defaults.rb +19 -8
- data/benchmarks/{with_types_and_defaults.rb → with_defaults_and_coercion.rb} +21 -12
- data/dry-initializer.gemspec +4 -4
- data/lib/dry/initializer/builders/attribute.rb +81 -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 +172 -0
- data/lib/dry/initializer/definition.rb +116 -0
- data/lib/dry/initializer/dispatchers.rb +44 -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 +8 -70
- data/lib/dry/initializer.rb +49 -12
- data/lib/tasks/benchmark.rake +41 -0
- data/lib/tasks/profile.rake +78 -0
- data/spec/attributes_spec.rb +38 -0
- data/spec/coercion_of_nil_spec.rb +25 -0
- data/spec/custom_dispatchers_spec.rb +35 -0
- data/spec/custom_initializer_spec.rb +1 -1
- data/spec/default_values_spec.rb +8 -8
- data/spec/definition_spec.rb +107 -0
- data/spec/invalid_default_spec.rb +2 -2
- data/spec/missed_default_spec.rb +3 -3
- data/spec/optional_spec.rb +35 -8
- 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 +41 -21
- data/spec/several_assignments_spec.rb +41 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/subclassing_spec.rb +7 -3
- data/spec/type_argument_spec.rb +1 -1
- data/spec/type_constraint_spec.rb +38 -7
- data/spec/value_coercion_via_dry_types_spec.rb +12 -4
- metadata +37 -40
- data/benchmarks/options.rb +0 -54
- data/benchmarks/params_vs_options.rb +0 -35
- data/lib/dry/initializer/builder.rb +0 -100
- data/lib/dry/initializer/errors/default_value_error.rb +0 -6
- data/lib/dry/initializer/errors/order_error.rb +0 -7
- data/lib/dry/initializer/errors/plugin_error.rb +0 -6
- data/lib/dry/initializer/errors/redefinition_error.rb +0 -5
- data/lib/dry/initializer/errors/type_constraint_error.rb +0 -5
- data/lib/dry/initializer/errors.rb +0 -10
- data/lib/dry/initializer/plugins/base.rb +0 -47
- data/lib/dry/initializer/plugins/default_proc.rb +0 -28
- data/lib/dry/initializer/plugins/signature.rb +0 -28
- data/lib/dry/initializer/plugins/type_constraint.rb +0 -21
- data/lib/dry/initializer/plugins/variable_setter.rb +0 -30
- data/lib/dry/initializer/plugins.rb +0 -10
- data/lib/dry/initializer/signature.rb +0 -61
- data/spec/base_spec.rb +0 -21
- data/spec/container_spec.rb +0 -45
- data/spec/default_nil_spec.rb +0 -17
- data/spec/plugin_registry_spec.rb +0 -45
- data/spec/renaming_options_spec.rb +0 -20
@@ -0,0 +1,172 @@
|
|
1
|
+
module Dry::Initializer
|
2
|
+
#
|
3
|
+
# Gem-related configuration of some class
|
4
|
+
#
|
5
|
+
class Config
|
6
|
+
# @!attribute [r] null
|
7
|
+
# @return [Dry::Initializer::UNDEFINED, nil] value of unassigned variable
|
8
|
+
|
9
|
+
# @!attribute [r] extended_class
|
10
|
+
# @return [Class] the class whose config collected by current object
|
11
|
+
|
12
|
+
# @!attribute [r] parent
|
13
|
+
# @return [Dry::Initializer::Config] parent configuration
|
14
|
+
|
15
|
+
# @!attribute [r] definitions
|
16
|
+
# @return [Hash<Symbol, Dry::Initializer::Definition>]
|
17
|
+
# hash of attribute definitions with their source names
|
18
|
+
|
19
|
+
attr_reader :null, :extended_class, :parent, :definitions
|
20
|
+
|
21
|
+
# @!attribute [r] mixin
|
22
|
+
# @return [Module] reference to the module to be included into class
|
23
|
+
def mixin
|
24
|
+
@mixin ||= Module.new.tap do |mod|
|
25
|
+
__dry_initializer__ = self
|
26
|
+
mod.extend(Mixin::Local)
|
27
|
+
mod.send :define_method, :__dry_initializer_config__ do
|
28
|
+
__dry_initializer__
|
29
|
+
end
|
30
|
+
mod.send :private, :__dry_initializer_config__
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# List of configs of all subclasses of the [#extended_class]
|
35
|
+
# @return [Array<Dry::Initializer::Config>]
|
36
|
+
def children
|
37
|
+
@children ||= Set.new
|
38
|
+
end
|
39
|
+
|
40
|
+
# List of definitions for initializer params
|
41
|
+
# @return [Array<Dry::Initializer::Definition>]
|
42
|
+
def params
|
43
|
+
definitions.values.reject(&:option)
|
44
|
+
end
|
45
|
+
|
46
|
+
# List of definitions for initializer options
|
47
|
+
# @return [Array<Dry::Initializer::Definition>]
|
48
|
+
def options
|
49
|
+
definitions.values.select(&:option)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds or redefines a parameter
|
53
|
+
# @param [Symbol] name
|
54
|
+
# @param [#call, nil] type (nil)
|
55
|
+
# @option opts [Proc] :default
|
56
|
+
# @option opts [Boolean] :optional
|
57
|
+
# @option opts [Symbol] :as
|
58
|
+
# @option opts [true, false, :protected, :public, :private] :reader
|
59
|
+
# @return [self] itself
|
60
|
+
def param(name, type = nil, **opts)
|
61
|
+
add_definition(false, name, type, opts)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Adds or redefines an option of [#dry_initializer]
|
65
|
+
#
|
66
|
+
# @param (see #param)
|
67
|
+
# @option (see #param)
|
68
|
+
# @return (see #param)
|
69
|
+
#
|
70
|
+
def option(name, type = nil, **opts)
|
71
|
+
add_definition(true, name, type, opts)
|
72
|
+
end
|
73
|
+
|
74
|
+
# The hash of public attributes for an instance of the [#extended_class]
|
75
|
+
# @param [Dry::Initializer::Instance] instance
|
76
|
+
# @return [Hash<Symbol, Object>]
|
77
|
+
def public_attributes(instance)
|
78
|
+
definitions.values.each_with_object({}) do |item, obj|
|
79
|
+
key = item.target
|
80
|
+
next unless instance.respond_to? key
|
81
|
+
val = instance.send(key)
|
82
|
+
obj[key] = val unless null == val
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# The hash of assigned attributes for an instance of the [#extended_class]
|
87
|
+
# @param [Dry::Initializer::Instance] instance
|
88
|
+
# @return [Hash<Symbol, Object>]
|
89
|
+
def attributes(instance)
|
90
|
+
definitions.values.each_with_object({}) do |item, obj|
|
91
|
+
key = item.target
|
92
|
+
val = instance.send(:instance_variable_get, item.ivar)
|
93
|
+
obj[key] = val unless null == val
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Code of the `#__initialize__` method
|
98
|
+
# @return [String]
|
99
|
+
def code
|
100
|
+
Builders::Initializer[self]
|
101
|
+
end
|
102
|
+
|
103
|
+
# Finalizes config
|
104
|
+
# @return [self]
|
105
|
+
def finalize
|
106
|
+
@definitions = final_definitions
|
107
|
+
check_order_of_params
|
108
|
+
mixin.class_eval(code)
|
109
|
+
children.each(&:finalize)
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
# Human-readable representation of configured params and options
|
114
|
+
# @return [String]
|
115
|
+
def inch
|
116
|
+
line = Builders::Signature[self]
|
117
|
+
line = line.gsub("__dry_initializer_options__", "options")
|
118
|
+
lines = ["@!method initialize(#{line})"]
|
119
|
+
lines += ["Initializes an instance of #{extended_class}"]
|
120
|
+
lines += definitions.values.map(&:inch)
|
121
|
+
lines += ["@return [#{extended_class}]"]
|
122
|
+
lines.join("\n")
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def initialize(extended_class = nil, null: UNDEFINED)
|
128
|
+
@extended_class = extended_class.tap { |klass| klass&.include mixin }
|
129
|
+
sklass = extended_class&.superclass
|
130
|
+
@parent = sklass.dry_initializer if sklass.is_a? Dry::Initializer
|
131
|
+
@null = null || parent&.null
|
132
|
+
@definitions = {}
|
133
|
+
finalize
|
134
|
+
end
|
135
|
+
|
136
|
+
def add_definition(option, name, type, opts)
|
137
|
+
definition = Definition.new(option, null, name, type, Dispatchers[opts])
|
138
|
+
definitions[definition.source] = definition
|
139
|
+
finalize
|
140
|
+
|
141
|
+
mixin.class_eval definition.code
|
142
|
+
end
|
143
|
+
|
144
|
+
def final_definitions
|
145
|
+
parent_definitions = Hash(parent&.definitions&.dup)
|
146
|
+
definitions.each_with_object(parent_definitions) do |(key, val), obj|
|
147
|
+
obj[key] = check_type(obj[key], val)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def check_type(previous, current)
|
152
|
+
return current unless previous
|
153
|
+
return current if previous.option == current.option
|
154
|
+
raise SyntaxError,
|
155
|
+
"cannot reload #{previous} of #{extended_class.superclass}" \
|
156
|
+
" by #{current} of its subclass #{extended_class}"
|
157
|
+
end
|
158
|
+
|
159
|
+
def check_order_of_params
|
160
|
+
params.inject(nil) do |optional, current|
|
161
|
+
if current.optional
|
162
|
+
current
|
163
|
+
elsif optional
|
164
|
+
raise SyntaxError, "#{extended_class}: required #{current}" \
|
165
|
+
" goes after optional #{optional}"
|
166
|
+
else
|
167
|
+
optional
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Dry::Initializer
|
2
|
+
#
|
3
|
+
# @private
|
4
|
+
# @abstract
|
5
|
+
#
|
6
|
+
# Base class for parameter or option definitions
|
7
|
+
# Defines methods to add corresponding reader to the class,
|
8
|
+
# and build value of instance attribute.
|
9
|
+
#
|
10
|
+
class Definition
|
11
|
+
attr_reader :option, :null, :source, :target, :ivar,
|
12
|
+
:type, :optional, :default, :reader,
|
13
|
+
:desc
|
14
|
+
|
15
|
+
def options
|
16
|
+
{
|
17
|
+
as: target,
|
18
|
+
type: type,
|
19
|
+
optional: optional,
|
20
|
+
default: default,
|
21
|
+
reader: reader,
|
22
|
+
desc: desc
|
23
|
+
}.reject { |_, value| value.nil? }
|
24
|
+
end
|
25
|
+
|
26
|
+
def name
|
27
|
+
@name ||= (option ? "option" : "parameter") << " '#{source}'"
|
28
|
+
end
|
29
|
+
alias to_s name
|
30
|
+
alias to_str name
|
31
|
+
alias inspect name
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
other.instance_of?(self.class) && (other.source == source)
|
35
|
+
end
|
36
|
+
|
37
|
+
def code
|
38
|
+
Builders::Reader[self]
|
39
|
+
end
|
40
|
+
|
41
|
+
def inch
|
42
|
+
@inch ||= (option ? "@option" : "@param ").tap do |text|
|
43
|
+
text << " [Object]"
|
44
|
+
text << (option ? " :#{source}" : " #{source}")
|
45
|
+
text << (optional ? " (optional)" : " (required)")
|
46
|
+
text << " #{desc}" if desc
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def initialize(option, null, source, coercer = nil, **options)
|
53
|
+
@option = !!option
|
54
|
+
@null = null
|
55
|
+
@source = source.to_sym
|
56
|
+
@target = check_target options.fetch(:as, source).to_sym
|
57
|
+
@ivar = :"@#{target}"
|
58
|
+
@type = check_type(coercer || options[:type])
|
59
|
+
@reader = prepare_reader options.fetch(:reader, true)
|
60
|
+
@default = check_default options[:default]
|
61
|
+
@optional = options.fetch(:optional, @default)
|
62
|
+
@desc = options[:desc]&.to_s&.capitalize
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_source(value)
|
66
|
+
if RESERVED.include? value
|
67
|
+
raise ArgumentError, "Name #{value} is reserved by dry-initializer gem"
|
68
|
+
end
|
69
|
+
|
70
|
+
unless option || value[ATTRIBUTE]
|
71
|
+
raise ArgumentError, "Invalid parameter name :'#{value}'"
|
72
|
+
end
|
73
|
+
|
74
|
+
value
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_target(value)
|
78
|
+
return value if value[ATTRIBUTE]
|
79
|
+
raise ArgumentError, "Invalid variable name :'#{value}'"
|
80
|
+
end
|
81
|
+
|
82
|
+
def check_type(value)
|
83
|
+
return if value.nil?
|
84
|
+
arity = value.arity if value.is_a? Proc
|
85
|
+
arity ||= value.method(:call).arity if value.respond_to? :call
|
86
|
+
return value if [1, 2].include? arity.to_i.abs
|
87
|
+
raise TypeError,
|
88
|
+
"type of #{inspect} should respond to #call with 1..2 arguments"
|
89
|
+
end
|
90
|
+
|
91
|
+
def check_default(value)
|
92
|
+
return if value.nil?
|
93
|
+
return value if value.is_a?(Proc) && value.arity < 1
|
94
|
+
raise TypeError,
|
95
|
+
"default value of #{inspect} should be a proc without params"
|
96
|
+
end
|
97
|
+
|
98
|
+
def prepare_reader(value)
|
99
|
+
case value.to_s
|
100
|
+
when "", "false" then false
|
101
|
+
when "private" then :private
|
102
|
+
when "protected" then :protected
|
103
|
+
else :public
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
ATTRIBUTE = /\A\w+\z/
|
108
|
+
RESERVED = %i[
|
109
|
+
__dry_initializer_options__
|
110
|
+
__dry_initializer_config__
|
111
|
+
__dry_initializer_value__
|
112
|
+
__dry_initializer_definition__
|
113
|
+
__dry_initializer_initializer__
|
114
|
+
].freeze
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Dry::Initializer
|
2
|
+
#
|
3
|
+
# @private
|
4
|
+
#
|
5
|
+
# Dispatchers allow adding syntax sugar to `.param` and `.option` methods.
|
6
|
+
#
|
7
|
+
# Every dispatcher should convert the source hash of options into
|
8
|
+
# the resulting hash so that you can send additional keys to the helpers.
|
9
|
+
#
|
10
|
+
# @example Add special dispatcher
|
11
|
+
#
|
12
|
+
# # Define a dispatcher for key :integer
|
13
|
+
# dispatcher = proc do |opts|
|
14
|
+
# opts.merge(type: proc(&:to_i)) if opts[:integer]
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # Register a dispatcher
|
18
|
+
# Dry::Initializer::Dispatchers << dispatcher
|
19
|
+
#
|
20
|
+
# # Now you can use option `integer: true` instead of `type: proc(&:to_i)`
|
21
|
+
# class Foo
|
22
|
+
# extend Dry::Initializer
|
23
|
+
# param :id, integer: true
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
module Dispatchers
|
27
|
+
class << self
|
28
|
+
def <<(item)
|
29
|
+
list << item
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](options)
|
34
|
+
list.inject(options) { |opts, item| item.call(opts) }
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def list
|
40
|
+
@list ||= []
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Dry::Initializer
|
2
|
+
# Module-level DSL
|
3
|
+
module DSL
|
4
|
+
# Setting for null (undefined value)
|
5
|
+
# @return [nil, Dry::Initializer::UNDEFINED]
|
6
|
+
attr_reader :null
|
7
|
+
|
8
|
+
# Returns a version of the module with custom settings
|
9
|
+
# @option settings [Boolean] :undefined
|
10
|
+
# If unassigned params and options should be treated different from nil
|
11
|
+
# @return [Dry::Initializer]
|
12
|
+
def [](undefined: true, **)
|
13
|
+
null = (undefined == false) ? nil : UNDEFINED
|
14
|
+
Module.new.tap do |mod|
|
15
|
+
mod.extend DSL
|
16
|
+
mod.include self
|
17
|
+
mod.send(:instance_variable_set, :@null, null)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns mixin module to be included to target class by hand
|
22
|
+
# @return [Module]
|
23
|
+
# @yield proc defining params and options
|
24
|
+
def define(procedure = nil, &block)
|
25
|
+
config = Config.new(null: null)
|
26
|
+
config.instance_exec(&(procedure || block))
|
27
|
+
config.mixin.include Mixin::Root
|
28
|
+
config.mixin
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def extended(klass)
|
34
|
+
config = Config.new(klass, null: null)
|
35
|
+
klass.send :instance_variable_set, :@dry_initializer, config
|
36
|
+
klass.include Mixin::Root
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.extended(mod)
|
40
|
+
mod.instance_variable_set :@null, UNDEFINED
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dry::Initializer::Mixin
|
2
|
+
# @private
|
3
|
+
module Local
|
4
|
+
attr_reader :klass
|
5
|
+
|
6
|
+
def inspect
|
7
|
+
"Dry::Initializer::Mixin::Local[#{klass}]"
|
8
|
+
end
|
9
|
+
alias to_s inspect
|
10
|
+
alias to_str inspect
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def included(klass)
|
15
|
+
@klass = klass
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,77 +1,15 @@
|
|
1
1
|
module Dry::Initializer
|
2
|
-
#
|
2
|
+
# @private
|
3
3
|
module Mixin
|
4
|
-
#
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# @option options [#call] :type The type constraings via `dry-types`
|
10
|
-
# @option options [Boolean] :reader (true) Whether to define attr_reader
|
11
|
-
#
|
12
|
-
# @return [self] itself
|
13
|
-
#
|
14
|
-
def param(name, type = nil, **options)
|
15
|
-
options[:type] = type if type
|
16
|
-
options[:option] = false
|
17
|
-
@__initializer_builder__ = __initializer_builder__.define(name, **options)
|
18
|
-
__initializer_builder__.call(__initializer_mixin__)
|
19
|
-
end
|
20
|
-
|
21
|
-
# Declares a named argument
|
22
|
-
#
|
23
|
-
# @param (see #param)
|
24
|
-
# @option (see #param)
|
25
|
-
# @return (see #param)
|
26
|
-
#
|
27
|
-
def option(name, type = nil, **options)
|
28
|
-
options[:type] = type if type
|
29
|
-
options[:option] = true
|
30
|
-
@__initializer_builder__ = __initializer_builder__.define(name, **options)
|
31
|
-
__initializer_builder__.call(__initializer_mixin__)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Adds new plugin to the builder
|
35
|
-
#
|
36
|
-
# @param [Dry::Initializer::Plugins::Base] plugin
|
37
|
-
# @return [self] itself
|
38
|
-
#
|
39
|
-
def register_initializer_plugin(plugin)
|
40
|
-
@__initializer_builder__ = __initializer_builder__.register(plugin)
|
41
|
-
__initializer_builder__.call(__initializer_mixin__)
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def __initializer_mixin__
|
47
|
-
@__initializer_mixin__ ||= Module.new do
|
48
|
-
def initialize(*args)
|
49
|
-
__initialize__(*args)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def __initializer_builder__
|
55
|
-
@__initializer_builder__ ||= Builder.new
|
56
|
-
end
|
57
|
-
|
58
|
-
def inherited(klass)
|
59
|
-
new_builder = __initializer_builder__.dup
|
60
|
-
klass.instance_variable_set :@__initializer_builder__, new_builder
|
61
|
-
|
62
|
-
new_mixin = Module.new
|
63
|
-
new_builder.call(new_mixin)
|
64
|
-
klass.instance_variable_set :@__initializer_mixin__, new_mixin
|
65
|
-
klass.include new_mixin
|
66
|
-
|
4
|
+
extend DSL # @deprecated
|
5
|
+
include Dry::Initializer # @deprecated
|
6
|
+
def self.extended(klass) # @deprecated
|
7
|
+
warn "[DEPRECATED] Use Dry::Initializer instead of its alias" \
|
8
|
+
" Dry::Initializer::Mixin. The later will be removed in v2.1.0"
|
67
9
|
super
|
68
10
|
end
|
69
11
|
|
70
|
-
|
71
|
-
|
72
|
-
mixin = klass.send(:__initializer_mixin__)
|
73
|
-
klass.send(:__initializer_builder__).call(mixin)
|
74
|
-
klass.include mixin
|
75
|
-
end
|
12
|
+
require_relative "mixin/root"
|
13
|
+
require_relative "mixin/local"
|
76
14
|
end
|
77
15
|
end
|
data/lib/dry/initializer.rb
CHANGED
@@ -1,22 +1,59 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
# Namespace for gems in a dry-rb community
|
1
4
|
module Dry
|
2
|
-
# Declares arguments of the initializer (params and options)
|
3
5
|
#
|
4
|
-
#
|
6
|
+
# DSL for declaring params and options of class initializers
|
5
7
|
#
|
6
8
|
module Initializer
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require_relative "initializer/
|
9
|
+
# Singleton for unassigned values
|
10
|
+
UNDEFINED = Object.new.freeze
|
11
|
+
|
12
|
+
require_relative "initializer/dsl"
|
13
|
+
require_relative "initializer/definition"
|
14
|
+
require_relative "initializer/builders"
|
15
|
+
require_relative "initializer/config"
|
11
16
|
require_relative "initializer/mixin"
|
17
|
+
require_relative "initializer/dispatchers"
|
12
18
|
|
13
|
-
|
19
|
+
# Adds methods [.[]] and [.define]
|
20
|
+
extend DSL
|
21
|
+
|
22
|
+
# Gem-related configuration
|
23
|
+
# @return [Dry::Initializer::Config]
|
24
|
+
def dry_initializer
|
25
|
+
@dry_initializer ||= Config.new(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Adds or redefines a parameter of [#dry_initializer]
|
29
|
+
# @param [Symbol] name
|
30
|
+
# @param [#call, nil] type (nil)
|
31
|
+
# @option opts [Proc] :default
|
32
|
+
# @option opts [Boolean] :optional
|
33
|
+
# @option opts [Symbol] :as
|
34
|
+
# @option opts [true, false, :protected, :public, :private] :reader
|
35
|
+
# @return [self] itself
|
36
|
+
def param(name, type = nil, **opts)
|
37
|
+
dry_initializer.param(name, type, Dispatchers[opts])
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
# Adds or redefines an option of [#dry_initializer]
|
42
|
+
# @param (see #param)
|
43
|
+
# @option (see #param)
|
44
|
+
# @return (see #param)
|
45
|
+
def option(name, type = nil, **opts)
|
46
|
+
dry_initializer.option(name, type, Dispatchers[opts])
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
14
51
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
52
|
+
def inherited(klass)
|
53
|
+
super
|
54
|
+
config = Config.new(klass, null: dry_initializer.null)
|
55
|
+
klass.send(:instance_variable_set, :@dry_initializer, config)
|
56
|
+
dry_initializer.children << config
|
20
57
|
end
|
21
58
|
end
|
22
59
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
namespace :benchmark do
|
2
|
+
desc "Runs benchmarks for plain params"
|
3
|
+
task :plain_params do
|
4
|
+
system "ruby benchmarks/plain_params.rb"
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "Runs benchmarks for plain options"
|
8
|
+
task :plain_options do
|
9
|
+
system "ruby benchmarks/plain_options.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Runs benchmarks for value coercion"
|
13
|
+
task :with_coercion do
|
14
|
+
system "ruby benchmarks/with_coercion.rb"
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Runs benchmarks with defaults"
|
18
|
+
task :with_defaults do
|
19
|
+
system "ruby benchmarks/with_defaults.rb"
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Runs benchmarks with defaults and coercion"
|
23
|
+
task :with_defaults_and_coercion do
|
24
|
+
system "ruby benchmarks/with_defaults_and_coercion.rb"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Runs benchmarks for several defaults"
|
28
|
+
task :compare_several_defaults do
|
29
|
+
system "ruby benchmarks/with_several_defaults.rb"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Runs all benchmarks"
|
34
|
+
task benchmark: %i[
|
35
|
+
benchmark:plain_params
|
36
|
+
benchmark:plain_options
|
37
|
+
benchmark:with_coercion
|
38
|
+
benchmark:with_defaults
|
39
|
+
benchmark:with_defaults_and_coercion
|
40
|
+
benchmark:compare_several_defaults
|
41
|
+
]
|
@@ -0,0 +1,78 @@
|
|
1
|
+
namespace :profile do
|
2
|
+
def profile(name, execution, &definition)
|
3
|
+
require "dry-initializer"
|
4
|
+
require "ruby-prof"
|
5
|
+
require "fileutils"
|
6
|
+
|
7
|
+
definition.call
|
8
|
+
result = RubyProf.profile do
|
9
|
+
1_000.times { execution.call }
|
10
|
+
end
|
11
|
+
|
12
|
+
FileUtils.mkdir_p "./tmp"
|
13
|
+
|
14
|
+
FileUtils.touch "./tmp/#{name}.dot"
|
15
|
+
File.open("./tmp/#{name}.dot", "w+") do |output|
|
16
|
+
RubyProf::DotPrinter.new(result).print(output, min_percent: 0)
|
17
|
+
end
|
18
|
+
|
19
|
+
FileUtils.touch "./tmp/#{name}.html"
|
20
|
+
File.open("./tmp/#{name}.html", "w+") do |output|
|
21
|
+
RubyProf::CallStackPrinter.new(result).print(output, min_percent: 0)
|
22
|
+
end
|
23
|
+
|
24
|
+
system "dot -Tpng ./tmp/#{name}.dot > ./tmp/#{name}.png"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Profiles initialization with required param and option"
|
28
|
+
task :required do
|
29
|
+
profile("required", -> { User.new :Andy, email: "andy@example.com" }) do
|
30
|
+
class User
|
31
|
+
extend Dry::Initializer
|
32
|
+
param :name
|
33
|
+
option :email
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Profiles initialization with default param and option"
|
39
|
+
task :defaults do
|
40
|
+
profile("defaults", -> { User.new }) do
|
41
|
+
class User
|
42
|
+
extend Dry::Initializer
|
43
|
+
param :name, default: -> { :Andy }
|
44
|
+
option :email, default: -> { "andy@example.com" }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Profiles initialization with coerced param and option"
|
50
|
+
task :coercion do
|
51
|
+
profile("coercion", -> { User.new :Andy, email: :"andy@example.com" }) do
|
52
|
+
class User
|
53
|
+
extend Dry::Initializer
|
54
|
+
param :name, proc(&:to_s)
|
55
|
+
option :email, proc(&:to_s)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "Profiles initialization with coerced defaults of param and option"
|
61
|
+
task :default_coercion do
|
62
|
+
profile("default_coercion", -> { User.new }) do
|
63
|
+
class User
|
64
|
+
extend Dry::Initializer
|
65
|
+
param :name, proc(&:to_s), default: -> { :Andy }
|
66
|
+
option :email, proc(&:to_s), default: -> { :"andy@example.com" }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
desc "Makes all profiling at once"
|
73
|
+
task profile: %i[
|
74
|
+
profile:required
|
75
|
+
profile:defaults
|
76
|
+
profile:coercion
|
77
|
+
profile:default_coercion
|
78
|
+
]
|