dry-initializer 0.11.0 → 1.1.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/.gitignore +1 -0
- data/.rubocop.yml +4 -61
- data/.travis.yml +18 -11
- data/CHANGELOG.md +108 -43
- data/Gemfile +1 -0
- data/Rakefile +6 -0
- data/benchmarks/options.rb +4 -4
- data/benchmarks/params.rb +1 -1
- data/benchmarks/profiler.rb +28 -0
- data/benchmarks/with_types.rb +5 -9
- data/benchmarks/with_types_and_defaults.rb +2 -4
- data/benchmarks/without_options.rb +3 -3
- data/dry-initializer.gemspec +1 -1
- data/lib/dry/initializer/attribute.rb +92 -0
- data/lib/dry/initializer/builder.rb +76 -72
- data/lib/dry/initializer/exceptions/default_value_error.rb +8 -0
- data/lib/dry/initializer/exceptions/params_order_error.rb +8 -0
- data/lib/dry/initializer/exceptions/type_constraint_error.rb +7 -0
- data/lib/dry/initializer/option.rb +62 -0
- data/lib/dry/initializer/param.rb +54 -0
- data/lib/dry/initializer.rb +77 -13
- data/spec/enhancement_spec.rb +18 -0
- data/spec/missed_default_spec.rb +2 -2
- data/spec/optional_spec.rb +16 -6
- data/spec/options_var_spec.rb +39 -0
- data/spec/repetitive_definitions_spec.rb +38 -18
- data/spec/several_assignments_spec.rb +41 -0
- data/spec/type_constraint_spec.rb +6 -5
- metadata +15 -20
- 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/mixin.rb +0 -77
- 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/plugin_registry_spec.rb +0 -45
- data/spec/renaming_options_spec.rb +0 -20
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/benchmarks/options.rb
CHANGED
@@ -18,15 +18,15 @@ end
|
|
18
18
|
class TypesTest
|
19
19
|
extend Dry::Initializer::Mixin
|
20
20
|
|
21
|
-
param :foo,
|
22
|
-
option :bar,
|
21
|
+
param :foo, proc(&:to_s)
|
22
|
+
option :bar, proc(&:to_s)
|
23
23
|
end
|
24
24
|
|
25
25
|
class DefaultsAndTypesTest
|
26
26
|
extend Dry::Initializer::Mixin
|
27
27
|
|
28
|
-
param :foo,
|
29
|
-
option :bar,
|
28
|
+
param :foo, proc(&:to_s), default: proc { "FOO" }
|
29
|
+
option :bar, proc(&:to_s), default: proc { "BAR" }
|
30
30
|
end
|
31
31
|
|
32
32
|
puts "Benchmark for various options"
|
data/benchmarks/params.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "dry-initializer"
|
2
|
+
require "ruby-prof"
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
class User
|
6
|
+
extend Dry::Initializer
|
7
|
+
|
8
|
+
param :first_name, proc(&:to_s), default: proc { "Unknown" }
|
9
|
+
param :second_name, proc(&:to_s), default: proc { "Unknown" }
|
10
|
+
option :email, proc(&:to_s), optional: true
|
11
|
+
option :phone, proc(&:to_s), optional: true
|
12
|
+
end
|
13
|
+
|
14
|
+
result = RubyProf.profile do
|
15
|
+
1_000.times { User.new :Andy, email: :"andy@example.com" }
|
16
|
+
end
|
17
|
+
|
18
|
+
FileUtils.mkdir_p "./tmp"
|
19
|
+
|
20
|
+
FileUtils.touch "./tmp/profile.dot"
|
21
|
+
File.open("./tmp/profile.dot", "w+") do |output|
|
22
|
+
RubyProf::DotPrinter.new(result).print(output, min_percent: 0)
|
23
|
+
end
|
24
|
+
|
25
|
+
FileUtils.touch "./tmp/profile.html"
|
26
|
+
File.open("./tmp/profile.html", "w+") do |output|
|
27
|
+
RubyProf::CallStackPrinter.new(result).print(output, min_percent: 0)
|
28
|
+
end
|
data/benchmarks/with_types.rb
CHANGED
@@ -3,22 +3,18 @@ Bundler.require(:benchmarks)
|
|
3
3
|
class PlainRubyTest
|
4
4
|
attr_reader :foo, :bar
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@foo = foo
|
8
|
-
@bar = bar
|
9
|
-
fail TypeError unless String === @foo
|
10
|
-
fail TypeError unless String === @bar
|
6
|
+
def initialize(options)
|
7
|
+
@foo = options[:foo].to_s
|
8
|
+
@bar = options[:bar].to_s
|
11
9
|
end
|
12
10
|
end
|
13
11
|
|
14
12
|
require "dry-initializer"
|
15
|
-
require "dry/initializer/types"
|
16
13
|
class DryTest
|
17
14
|
extend Dry::Initializer::Mixin
|
18
|
-
extend Dry::Initializer::Types
|
19
15
|
|
20
|
-
option :foo,
|
21
|
-
option :bar,
|
16
|
+
option :foo, &(:to_s)
|
17
|
+
option :bar, &(:to_s)
|
22
18
|
end
|
23
19
|
|
24
20
|
require "virtus"
|
@@ -12,13 +12,11 @@ class PlainRubyTest
|
|
12
12
|
end
|
13
13
|
|
14
14
|
require "dry-initializer"
|
15
|
-
require "dry/initializer/types"
|
16
15
|
class DryTest
|
17
16
|
extend Dry::Initializer::Mixin
|
18
|
-
extend Dry::Initializer::Types
|
19
17
|
|
20
|
-
option :foo,
|
21
|
-
option :bar,
|
18
|
+
option :foo, proc(&:to_s), default: proc { "FOO" }
|
19
|
+
option :bar, proc(&:to_s), default: proc { "BAR" }
|
22
20
|
end
|
23
21
|
|
24
22
|
require "virtus"
|
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 = "
|
3
|
+
gem.version = "1.1.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"
|
@@ -0,0 +1,92 @@
|
|
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
|
+
validate
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
source == other.source
|
54
|
+
end
|
55
|
+
|
56
|
+
# definition for the getter method
|
57
|
+
def getter
|
58
|
+
return unless reader
|
59
|
+
command = %w(private protected).include?(reader.to_s) ? reader : :public
|
60
|
+
|
61
|
+
<<-RUBY.gsub(/^ *\|/, "")
|
62
|
+
|def #{target}
|
63
|
+
| @#{target} unless @#{target} == Dry::Initializer::UNDEFINED
|
64
|
+
|end
|
65
|
+
|#{command} :#{target}
|
66
|
+
RUBY
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def validate
|
72
|
+
validate_target
|
73
|
+
validate_default
|
74
|
+
validate_coercer
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate_target
|
78
|
+
return if target =~ /\A\w+\Z/
|
79
|
+
fail ArgumentError.new("Invalid name '#{target}' for the target variable")
|
80
|
+
end
|
81
|
+
|
82
|
+
def validate_default
|
83
|
+
return if default.nil? || default.is_a?(Proc)
|
84
|
+
fail DefaultValueError.new(source, default)
|
85
|
+
end
|
86
|
+
|
87
|
+
def validate_coercer
|
88
|
+
return if coercer.nil? || coercer.respond_to?(:call)
|
89
|
+
fail TypeConstraintError.new(source, coercer)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -1,100 +1,104 @@
|
|
1
|
-
require "set"
|
2
1
|
module Dry::Initializer
|
3
|
-
# Rebuilds the initializer every time a new argument defined
|
4
|
-
#
|
5
|
-
# @api private
|
6
|
-
#
|
7
2
|
class Builder
|
8
|
-
|
3
|
+
def param(*args)
|
4
|
+
@params = insert(@params, Attribute.param(*args))
|
5
|
+
validate_collections
|
6
|
+
end
|
7
|
+
|
8
|
+
def option(*args)
|
9
|
+
@options = insert(@options, Attribute.option(*args))
|
10
|
+
validate_collections
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(mixin)
|
14
|
+
defaults = send(:defaults)
|
15
|
+
coercers = send(:coercers)
|
16
|
+
mixin.send(:define_method, :__defaults__) { defaults }
|
17
|
+
mixin.send(:define_method, :__coercers__) { coercers }
|
18
|
+
mixin.class_eval(code, __FILE__, __LINE__ + 1)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
9
22
|
|
10
23
|
def initialize
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@parts = []
|
24
|
+
@params = []
|
25
|
+
@options = []
|
14
26
|
end
|
15
27
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# @param [Dry::Initializer::Plugin]
|
20
|
-
#
|
21
|
-
# @return [Dry::Initializer::Builder]
|
22
|
-
#
|
23
|
-
def register(plugin)
|
24
|
-
plugins = @plugins + [plugin]
|
25
|
-
copy { @plugins = plugins }
|
28
|
+
def insert(collection, new_item)
|
29
|
+
index = collection.index(new_item) || collection.count
|
30
|
+
collection.dup.tap { |list| list[index] = new_item }
|
26
31
|
end
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@parts = parts
|
42
|
-
end
|
33
|
+
def code
|
34
|
+
<<-RUBY.gsub(/^ +\|/, "")
|
35
|
+
|def __initialize__(#{initializer_signatures})
|
36
|
+
| @__options__ = __options__
|
37
|
+
|#{initializer_presetters}
|
38
|
+
|#{initializer_setters}
|
39
|
+
|end
|
40
|
+
|private :__initialize__
|
41
|
+
|private :__defaults__
|
42
|
+
|private :__coercers__
|
43
|
+
|
|
44
|
+
|#{getters}
|
45
|
+
RUBY
|
43
46
|
end
|
44
47
|
|
45
|
-
|
46
|
-
|
47
|
-
# @param [Module] mixin
|
48
|
-
#
|
49
|
-
def call(mixin)
|
50
|
-
define_readers(mixin)
|
51
|
-
reload_initializer(mixin)
|
52
|
-
reload_callback(mixin)
|
53
|
-
mixin
|
48
|
+
def attributes
|
49
|
+
@params + @options
|
54
50
|
end
|
55
51
|
|
56
|
-
|
52
|
+
def duplications
|
53
|
+
attributes.group_by(&:target)
|
54
|
+
.reject { |_, val| val.count == 1 }
|
55
|
+
.keys
|
56
|
+
end
|
57
57
|
|
58
|
-
def
|
59
|
-
|
58
|
+
def initializer_signatures
|
59
|
+
sig = @params.map(&:initializer_signature).compact.uniq
|
60
|
+
sig << (sig.any? && @options.any? ? "**__options__" : "__options__ = {}")
|
61
|
+
sig.join(", ")
|
60
62
|
end
|
61
63
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
)
|
68
|
-
define_reader mixin, :private
|
69
|
-
define_reader mixin, :protected
|
64
|
+
def initializer_presetters
|
65
|
+
dups = duplications
|
66
|
+
attributes
|
67
|
+
.map { |a| " #{a.presetter}" if dups.include? a.target }
|
68
|
+
.compact.uniq.join("\n")
|
70
69
|
end
|
71
70
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
|
71
|
+
def initializer_setters
|
72
|
+
dups = duplications
|
73
|
+
attributes.map do |a|
|
74
|
+
dups.include?(a.target) ? " #{a.safe_setter}" : " #{a.fast_setter}"
|
75
|
+
end.compact.uniq.join("\n")
|
76
76
|
end
|
77
77
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
RUBY
|
78
|
+
def getters
|
79
|
+
attributes.map(&:getter).compact.uniq.join("\n")
|
80
|
+
end
|
81
|
+
|
82
|
+
def defaults
|
83
|
+
attributes.map(&:default_hash).reduce({}, :merge)
|
84
|
+
end
|
86
85
|
|
87
|
-
|
86
|
+
def coercers
|
87
|
+
attributes.map(&:coercer_hash).reduce({}, :merge)
|
88
88
|
end
|
89
89
|
|
90
|
-
def
|
91
|
-
|
90
|
+
def validate_collections
|
91
|
+
optional_param = nil
|
92
92
|
|
93
|
-
|
94
|
-
|
93
|
+
@params.each do |param|
|
94
|
+
if param.optional
|
95
|
+
optional_param = param.source if param.optional
|
96
|
+
elsif optional_param
|
97
|
+
fail ParamsOrderError.new(param.source, optional_param)
|
98
|
+
end
|
95
99
|
end
|
96
100
|
|
97
|
-
|
101
|
+
self
|
98
102
|
end
|
99
103
|
end
|
100
104
|
end
|