dry-initializer 0.1.1 → 0.2.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 +13 -9
- data/CHANGELOG.md +72 -3
- data/LICENSE.txt +1 -1
- data/README.md +7 -279
- data/benchmarks/with_types.rb +2 -0
- data/benchmarks/with_types_and_defaults.rb +2 -0
- data/dry-initializer.gemspec +1 -1
- data/lib/dry/initializer.rb +5 -4
- data/lib/dry/initializer/builder.rb +66 -13
- data/lib/dry/initializer/errors.rb +5 -7
- data/lib/dry/initializer/errors/default_value_error.rb +6 -0
- data/lib/dry/initializer/errors/order_error.rb +7 -0
- data/lib/dry/initializer/errors/plugin_error.rb +6 -0
- data/lib/dry/initializer/errors/{existing_argument_error.rb → redefinition_error.rb} +1 -1
- data/lib/dry/initializer/errors/type_constraint_error.rb +6 -0
- data/lib/dry/initializer/errors/type_error.rb +4 -3
- data/lib/dry/initializer/mixin.rb +7 -7
- data/lib/dry/initializer/plugins.rb +10 -0
- data/lib/dry/initializer/plugins/base.rb +42 -0
- data/lib/dry/initializer/plugins/default_proc.rb +28 -0
- data/lib/dry/initializer/plugins/signature.rb +35 -0
- data/lib/dry/initializer/plugins/type_constraint.rb +58 -0
- data/lib/dry/initializer/plugins/variable_setter.rb +12 -0
- data/lib/dry/initializer/signature.rb +47 -0
- data/spec/dry/container_spec.rb +3 -3
- data/spec/dry/dry_type_constraint_spec.rb +30 -0
- data/spec/dry/invalid_default_spec.rb +1 -1
- data/spec/dry/{proc_type_spec.rb → object_type_constraint_spec.rb} +4 -4
- data/spec/dry/{poro_type_spec.rb → plain_type_constraint_spec.rb} +1 -1
- data/spec/dry/value_coercion_via_dry_types_spec.rb +21 -0
- metadata +29 -25
- data/lib/dry/initializer/argument.rb +0 -96
- data/lib/dry/initializer/arguments.rb +0 -85
- data/lib/dry/initializer/errors/invalid_default_value_error.rb +0 -6
- data/lib/dry/initializer/errors/invalid_type_error.rb +0 -6
- data/lib/dry/initializer/errors/key_error.rb +0 -5
- data/lib/dry/initializer/errors/missed_default_value_error.rb +0 -5
- data/spec/dry/dry_type_spec.rb +0 -25
- data/spec/dry/invalid_type_spec.rb +0 -13
data/benchmarks/with_types.rb
CHANGED
@@ -12,8 +12,10 @@ class PlainRubyTest
|
|
12
12
|
end
|
13
13
|
|
14
14
|
require "dry-initializer"
|
15
|
+
require "dry/initializer/types"
|
15
16
|
class DryTest
|
16
17
|
extend Dry::Initializer::Mixin
|
18
|
+
extend Dry::Initializer::Types
|
17
19
|
|
18
20
|
option :foo, type: String, default: proc { "FOO" }
|
19
21
|
option :bar, type: String, default: proc { "BAR" }
|
data/dry-initializer.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = "dry-initializer"
|
3
|
-
gem.version = "0.
|
3
|
+
gem.version = "0.2.0"
|
4
4
|
gem.author = ["Vladimir Kochnev (marshall-lee)", "Andrew Kozin (nepalez)"]
|
5
5
|
gem.email = ["hashtable@yandex.ru", "andrew.kozin@gmail.com"]
|
6
6
|
gem.homepage = "https://github.com/dryrb/dry-initializer"
|
data/lib/dry/initializer.rb
CHANGED
@@ -4,16 +4,17 @@ module Dry
|
|
4
4
|
# @api public
|
5
5
|
#
|
6
6
|
module Initializer
|
7
|
-
|
8
7
|
require_relative "initializer/errors"
|
9
|
-
require_relative "initializer/
|
10
|
-
require_relative "initializer/
|
8
|
+
require_relative "initializer/plugins"
|
9
|
+
require_relative "initializer/signature"
|
11
10
|
require_relative "initializer/builder"
|
12
11
|
require_relative "initializer/mixin"
|
13
12
|
|
13
|
+
UNDEFINED = Object.new.freeze
|
14
|
+
|
14
15
|
def self.define(proc = nil, &block)
|
15
16
|
Module.new do |container|
|
16
|
-
container.extend
|
17
|
+
container.extend Mixin
|
17
18
|
container.instance_exec(&(proc || block))
|
18
19
|
end
|
19
20
|
end
|
@@ -1,33 +1,86 @@
|
|
1
1
|
module Dry::Initializer
|
2
|
-
#
|
2
|
+
# Rebuilds the initializer every time a new argument defined
|
3
3
|
#
|
4
4
|
# @api private
|
5
5
|
#
|
6
6
|
class Builder
|
7
|
-
|
8
|
-
|
7
|
+
include Plugins
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@signature = Signature.new
|
11
|
+
@plugins = Set.new [VariableSetter, TypeConstraint, DefaultProc]
|
12
|
+
@parts = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# Register new plugin to be applied as a chunk of code, or a proc
|
16
|
+
# to be evaluated in the instance's scope
|
17
|
+
#
|
18
|
+
# @param [Dry::Initializer::Plugin]
|
19
|
+
#
|
20
|
+
def register(plugin)
|
21
|
+
@plugins << plugin
|
22
|
+
end
|
23
|
+
|
24
|
+
# Defines new agrument and rebuilds the initializer
|
25
|
+
#
|
26
|
+
# @param [#to_sym] name
|
27
|
+
# @param [Hash<Symbol, Object>] settings
|
28
|
+
#
|
29
|
+
# @return [self] itself
|
30
|
+
#
|
31
|
+
def define(name, settings)
|
32
|
+
update_signature(name, settings)
|
33
|
+
update_parts(name, settings)
|
34
|
+
|
35
|
+
define_reader(name, settings)
|
36
|
+
reload_initializer
|
37
|
+
reload_callback
|
38
|
+
|
39
|
+
self
|
9
40
|
end
|
10
41
|
|
42
|
+
# The module with two methods: `#initialize` and `##__after_initialize__`
|
43
|
+
# to be mixed into the target class
|
44
|
+
#
|
45
|
+
# @return [Module]
|
46
|
+
#
|
11
47
|
def mixin
|
12
48
|
@mixin ||= Module.new
|
13
49
|
end
|
14
50
|
|
15
|
-
|
16
|
-
|
17
|
-
|
51
|
+
private
|
52
|
+
|
53
|
+
def update_signature(name, settings)
|
54
|
+
@signature.add(name, settings)
|
55
|
+
end
|
56
|
+
|
57
|
+
def update_parts(name, settings)
|
58
|
+
@parts += @plugins.map { |klass| klass.call(name, settings) }.compact
|
59
|
+
end
|
60
|
+
|
61
|
+
def define_reader(name, settings)
|
62
|
+
mixin.send :attr_reader, name unless settings[:reader] == false
|
18
63
|
end
|
19
64
|
|
20
|
-
def
|
21
|
-
|
22
|
-
key = '@#{key}'
|
65
|
+
def reload_initializer
|
66
|
+
strings = @parts.select { |part| String === part }
|
23
67
|
|
24
68
|
mixin.class_eval <<-RUBY
|
25
|
-
def #{
|
26
|
-
|
27
|
-
|
28
|
-
end
|
69
|
+
def initialize(#{@signature.call})
|
70
|
+
#{strings.join("\n")}
|
71
|
+
__after_initialize__
|
29
72
|
end
|
30
73
|
RUBY
|
31
74
|
end
|
75
|
+
|
76
|
+
def reload_callback
|
77
|
+
blocks = @parts.select { |part| Proc === part }
|
78
|
+
|
79
|
+
mixin.send :define_method, :__after_initialize__ do
|
80
|
+
blocks.each { |block| instance_eval(&block) }
|
81
|
+
end
|
82
|
+
|
83
|
+
mixin.send :private, :__after_initialize__
|
84
|
+
end
|
32
85
|
end
|
33
86
|
end
|
@@ -1,13 +1,11 @@
|
|
1
1
|
module Dry::Initializer
|
2
2
|
# Collection of gem-specific exceptions
|
3
3
|
module Errors
|
4
|
-
|
5
|
-
require_relative "errors/
|
6
|
-
require_relative "errors/
|
7
|
-
require_relative "errors/
|
8
|
-
require_relative "errors/key_error"
|
9
|
-
require_relative "errors/missed_default_value_error"
|
4
|
+
require_relative "errors/default_value_error"
|
5
|
+
require_relative "errors/order_error"
|
6
|
+
require_relative "errors/plugin_error"
|
7
|
+
require_relative "errors/redefinition_error"
|
10
8
|
require_relative "errors/type_error"
|
11
|
-
|
9
|
+
require_relative "errors/type_constraint_error"
|
12
10
|
end
|
13
11
|
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class Dry::Initializer::Errors::OrderError < SyntaxError
|
2
|
+
def initialize(name)
|
3
|
+
super "Cannot define the required param '#{name}' after optional ones." \
|
4
|
+
" Either provide a default value for the '#{name}', or declare it" \
|
5
|
+
" before params with default values."
|
6
|
+
end
|
7
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
class Dry::Initializer::Errors::TypeError <
|
2
|
-
def initialize(type, value)
|
3
|
-
super "#{value.inspect}
|
1
|
+
class Dry::Initializer::Errors::TypeError < TypeError
|
2
|
+
def initialize(name, type, value)
|
3
|
+
super "A value #{value.inspect} assigned to the argument '#{name}'" \
|
4
|
+
" mismatches type constraint: #{type}."
|
4
5
|
end
|
5
6
|
end
|
@@ -12,7 +12,7 @@ module Dry::Initializer
|
|
12
12
|
# @return [self] itself
|
13
13
|
#
|
14
14
|
def param(name, **options)
|
15
|
-
|
15
|
+
initializer_builder.define(name, option: false, **options)
|
16
16
|
self
|
17
17
|
end
|
18
18
|
|
@@ -23,22 +23,22 @@ module Dry::Initializer
|
|
23
23
|
# @return (see #param)
|
24
24
|
#
|
25
25
|
def option(name, **options)
|
26
|
-
|
26
|
+
initializer_builder.define(name, option: true, **options)
|
27
27
|
self
|
28
28
|
end
|
29
29
|
|
30
|
-
private
|
31
|
-
|
32
|
-
|
33
|
-
@arguments_builder ||= begin
|
30
|
+
# @private
|
31
|
+
def initializer_builder
|
32
|
+
@initializer_builder ||= begin
|
34
33
|
builder = Builder.new
|
35
34
|
include builder.mixin
|
36
35
|
builder
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
39
|
+
# @private
|
40
40
|
def inherited(klass)
|
41
|
-
klass.instance_variable_set(:@
|
41
|
+
klass.instance_variable_set(:@initializer_builder, initializer_builder)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Dry::Initializer
|
2
|
+
# Namespace for code plugins builders
|
3
|
+
module Plugins
|
4
|
+
require_relative "plugins/base"
|
5
|
+
require_relative "plugins/default_proc"
|
6
|
+
require_relative "plugins/signature"
|
7
|
+
require_relative "plugins/type_constraint"
|
8
|
+
require_relative "plugins/variable_setter"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Dry::Initializer::Plugins
|
2
|
+
# Base class for plugins
|
3
|
+
#
|
4
|
+
# A plugin should has class method [.call] that takes argument name and
|
5
|
+
# settings and return a chunk of code for the #initialize method body.
|
6
|
+
#
|
7
|
+
class Base
|
8
|
+
include Dry::Initializer::Errors
|
9
|
+
|
10
|
+
# Builds the proc for the `__after_initializer__` callback
|
11
|
+
#
|
12
|
+
# @param [#to_s] name
|
13
|
+
# @param [Hash<Symbol, Object>] settings
|
14
|
+
#
|
15
|
+
# @return [String, Proc, nil]
|
16
|
+
#
|
17
|
+
def self.call(name, settings)
|
18
|
+
new(name, settings).call
|
19
|
+
end
|
20
|
+
|
21
|
+
# @private
|
22
|
+
attr_reader :name, :settings
|
23
|
+
|
24
|
+
# Initializes a builder with argument name and settings
|
25
|
+
# @param (see .call)
|
26
|
+
def initialize(name, settings)
|
27
|
+
@name = name
|
28
|
+
@settings = settings
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks equality to another instance by name
|
32
|
+
# @return [Boolean]
|
33
|
+
def ==(other)
|
34
|
+
other.instance_of?(self.class) && (other.name == name)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Builds a chunk of code
|
38
|
+
# @return (see .call)
|
39
|
+
def call
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Dry::Initializer::Plugins
|
2
|
+
# Builds a block to be evaluated by initializer (__after_initialize__)
|
3
|
+
# to assign a default value to the argument
|
4
|
+
class DefaultProc < Base
|
5
|
+
def call
|
6
|
+
return unless default
|
7
|
+
|
8
|
+
ivar = :"@#{name}"
|
9
|
+
default_proc = default
|
10
|
+
|
11
|
+
proc do
|
12
|
+
if instance_variable_get(ivar) == Dry::Initializer::UNDEFINED
|
13
|
+
instance_variable_set ivar, instance_eval(&default_proc)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def default
|
21
|
+
return unless settings.key? :default
|
22
|
+
|
23
|
+
@default ||= settings[:default].tap do |value|
|
24
|
+
fail DefaultValueError.new(name, value) unless Proc === value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Dry::Initializer::Plugins
|
2
|
+
# Plugin builds a chunk of code for the initializer's signature:
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# Signature.call(:user, option: true)
|
6
|
+
# # => "user:"
|
7
|
+
#
|
8
|
+
# Signature.call(:user, default: -> { nil })
|
9
|
+
# # => "user = Dry::Initializer::UNDEFINED"
|
10
|
+
#
|
11
|
+
class Signature < Base
|
12
|
+
def param?
|
13
|
+
settings[:option] != true
|
14
|
+
end
|
15
|
+
|
16
|
+
def default?
|
17
|
+
settings.key? :default
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
case [param?, default?]
|
22
|
+
when [true, false] then name.to_s
|
23
|
+
when [false, false] then "#{name}:"
|
24
|
+
when [true, true] then "#{name} = #{undefined}"
|
25
|
+
when [false, true] then "#{name}: #{undefined}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def undefined
|
32
|
+
@undefined ||= "Dry::Initializer::UNDEFINED"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Dry::Initializer::Plugins
|
2
|
+
# Plugin builds either chunk of code for the #initializer,
|
3
|
+
# or a proc for the ##__after_initialize__ callback.
|
4
|
+
class TypeConstraint < Base
|
5
|
+
def call
|
6
|
+
return unless settings.key? :type
|
7
|
+
dry_type_constraint || module_type_constraint || object_type_constraint
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def type
|
13
|
+
@type ||= settings[:type]
|
14
|
+
end
|
15
|
+
|
16
|
+
def dry_type?
|
17
|
+
type.class.ancestors.map(&:name).include? "Dry::Types::Builder"
|
18
|
+
end
|
19
|
+
|
20
|
+
def plain_type?
|
21
|
+
Module === type
|
22
|
+
end
|
23
|
+
|
24
|
+
def module_type_constraint
|
25
|
+
return unless plain_type?
|
26
|
+
|
27
|
+
"fail #{TypeError}.new(:#{name}, #{type}, @#{name})" \
|
28
|
+
" unless @#{name} == Dry::Initializer::UNDEFINED ||" \
|
29
|
+
" #{type} === @#{name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def dry_type_constraint
|
33
|
+
return unless dry_type?
|
34
|
+
|
35
|
+
ivar = :"@#{name}"
|
36
|
+
constraint = type
|
37
|
+
|
38
|
+
lambda do |*|
|
39
|
+
value = instance_variable_get(ivar)
|
40
|
+
return if value == Dry::Initializer::UNDEFINED
|
41
|
+
|
42
|
+
instance_variable_set ivar, constraint[value]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def object_type_constraint
|
47
|
+
ivar = :"@#{name}"
|
48
|
+
constraint = type
|
49
|
+
|
50
|
+
lambda do |*|
|
51
|
+
value = instance_variable_get(ivar)
|
52
|
+
return if value == Dry::Initializer::UNDEFINED
|
53
|
+
|
54
|
+
fail TypeError.new(ivar, constraint, value) unless constraint === value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|