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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -8
  3. data/.travis.yml +0 -3
  4. data/CHANGELOG.md +339 -196
  5. data/LICENSE.txt +1 -1
  6. data/README.md +3 -3
  7. data/Rakefile +2 -47
  8. data/benchmarks/{several_defaults.rb → compare_several_defaults.rb} +4 -4
  9. data/benchmarks/{without_options.rb → plain_options.rb} +20 -9
  10. data/benchmarks/{params.rb → plain_params.rb} +20 -9
  11. data/benchmarks/{with_types.rb → with_coercion.rb} +20 -9
  12. data/benchmarks/with_defaults.rb +19 -8
  13. data/benchmarks/{with_types_and_defaults.rb → with_defaults_and_coercion.rb} +21 -10
  14. data/dry-initializer.gemspec +3 -3
  15. data/lib/dry/initializer/builders/attribute.rb +76 -0
  16. data/lib/dry/initializer/builders/initializer.rb +61 -0
  17. data/lib/dry/initializer/builders/reader.rb +50 -0
  18. data/lib/dry/initializer/builders/signature.rb +32 -0
  19. data/lib/dry/initializer/builders.rb +7 -0
  20. data/lib/dry/initializer/config.rb +161 -0
  21. data/lib/dry/initializer/definition.rb +93 -0
  22. data/lib/dry/initializer/dsl.rb +43 -0
  23. data/lib/dry/initializer/mixin/local.rb +19 -0
  24. data/lib/dry/initializer/mixin/root.rb +10 -0
  25. data/lib/dry/initializer/mixin.rb +15 -0
  26. data/lib/dry/initializer.rb +45 -41
  27. data/lib/tasks/benchmark.rake +41 -0
  28. data/lib/tasks/profile.rake +78 -0
  29. data/spec/{options_var_spec.rb → attributes_spec.rb} +9 -9
  30. data/spec/custom_initializer_spec.rb +1 -1
  31. data/spec/default_values_spec.rb +6 -6
  32. data/spec/definition_spec.rb +21 -14
  33. data/spec/invalid_default_spec.rb +2 -2
  34. data/spec/missed_default_spec.rb +2 -2
  35. data/spec/optional_spec.rb +2 -2
  36. data/spec/options_tolerance_spec.rb +1 -1
  37. data/spec/public_attributes_utility_spec.rb +22 -0
  38. data/spec/reader_spec.rb +11 -11
  39. data/spec/repetitive_definitions_spec.rb +5 -5
  40. data/spec/several_assignments_spec.rb +1 -1
  41. data/spec/spec_helper.rb +5 -0
  42. data/spec/subclassing_spec.rb +7 -3
  43. data/spec/type_argument_spec.rb +1 -1
  44. data/spec/type_constraint_spec.rb +2 -2
  45. data/spec/value_coercion_via_dry_types_spec.rb +1 -1
  46. metadata +27 -27
  47. data/benchmarks/options.rb +0 -54
  48. data/benchmarks/params_vs_options.rb +0 -35
  49. data/benchmarks/profiler.rb +0 -28
  50. data/lib/dry/initializer/attribute.rb +0 -123
  51. data/lib/dry/initializer/builder.rb +0 -127
  52. data/lib/dry/initializer/class_dsl.rb +0 -37
  53. data/lib/dry/initializer/exceptions/default_value_error.rb +0 -8
  54. data/lib/dry/initializer/exceptions/params_order_error.rb +0 -8
  55. data/lib/dry/initializer/exceptions/type_constraint_error.rb +0 -7
  56. data/lib/dry/initializer/instance_dsl.rb +0 -15
  57. data/lib/dry/initializer/option.rb +0 -61
  58. data/lib/dry/initializer/param.rb +0 -52
  59. data/spec/gem_enhancement_spec.rb +0 -18
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016 Andrew Kozin (nepalez), Vladimir Kochnev (marshall-lee)
3
+ Copyright (c) 2016-2017 Andrew Kozin (nepalez), Vladimir Kochnev (marshall-lee)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -49,10 +49,10 @@ class User
49
49
  extend Dry::Initializer::Mixin
50
50
 
51
51
  # Params of the initializer along with corresponding readers
52
- param :name, type: Dry::Types["strict.string"]
53
- param :role, default: proc { 'customer' }
52
+ param :name, proc(&:to_s)
53
+ param :role, default: -> { 'customer' }
54
54
  # Options of the initializer along with corresponding readers
55
- option :admin, default: proc { false }
55
+ option :admin, default: -> { false }
56
56
  option :vip, optional: true
57
57
  end
58
58
 
data/Rakefile CHANGED
@@ -4,50 +4,5 @@ Bundler::GemHelper.install_tasks
4
4
  require "rspec/core/rake_task"
5
5
  RSpec::Core::RakeTask.new :default
6
6
 
7
- namespace :benchmark do
8
- desc "Runs benchmarks without options"
9
- task :without_options do
10
- system "ruby benchmarks/without_options.rb"
11
- end
12
-
13
- desc "Runs benchmarks for several defaults"
14
- task :several_defaults do
15
- system "ruby benchmarks/several_defaults.rb"
16
- end
17
-
18
- desc "Runs benchmarks for defaults of params vs. options"
19
- task :params_vs_options do
20
- system "ruby benchmarks/params_vs_options.rb"
21
- end
22
-
23
- desc "Runs benchmarks with types"
24
- task :with_types do
25
- system "ruby benchmarks/with_types.rb"
26
- end
27
-
28
- desc "Runs benchmarks with defaults"
29
- task :with_defaults do
30
- system "ruby benchmarks/with_defaults.rb"
31
- end
32
-
33
- desc "Runs benchmarks with types and defaults"
34
- task :with_types_and_defaults do
35
- system "ruby benchmarks/with_types_and_defaults.rb"
36
- end
37
-
38
- desc "Runs benchmarks for plain params"
39
- task :params do
40
- system "ruby benchmarks/params.rb"
41
- end
42
-
43
- desc "Runs benchmarks various opts"
44
- task :options do
45
- system "ruby benchmarks/options.rb"
46
- end
47
- end
48
-
49
- desc "Runs profiler"
50
- task :profile do
51
- system "ruby benchmarks/profiler.rb && " \
52
- "dot -Tpng ./tmp/profile.dot > ./tmp/profile.png"
53
- end
7
+ load "lib/tasks/benchmark.rake"
8
+ load "lib/tasks/profile.rake"
@@ -2,7 +2,7 @@ Bundler.require(:benchmarks)
2
2
 
3
3
  require "dry-initializer"
4
4
  class WithoutDefaults
5
- extend Dry::Initializer::Mixin
5
+ extend Dry::Initializer
6
6
 
7
7
  param :foo
8
8
  param :bar
@@ -10,7 +10,7 @@ class WithoutDefaults
10
10
  end
11
11
 
12
12
  class WithOneDefault
13
- extend Dry::Initializer::Mixin
13
+ extend Dry::Initializer
14
14
 
15
15
  param :foo
16
16
  param :bar
@@ -18,7 +18,7 @@ class WithOneDefault
18
18
  end
19
19
 
20
20
  class WithTwoDefaults
21
- extend Dry::Initializer::Mixin
21
+ extend Dry::Initializer
22
22
 
23
23
  param :foo
24
24
  param :bar, default: proc { "BAR" }
@@ -26,7 +26,7 @@ class WithTwoDefaults
26
26
  end
27
27
 
28
28
  class WithThreeDefaults
29
- extend Dry::Initializer::Mixin
29
+ extend Dry::Initializer
30
30
 
31
31
  param :foo, default: proc { "FOO" }
32
32
  param :bar, default: proc { "BAR" }
@@ -1,5 +1,20 @@
1
1
  Bundler.require(:benchmarks)
2
2
 
3
+ require "dry-initializer"
4
+ class DryTest
5
+ extend Dry::Initializer[undefined: false]
6
+
7
+ option :foo
8
+ option :bar
9
+ end
10
+
11
+ class DryTestUndefined
12
+ extend Dry::Initializer
13
+
14
+ option :foo
15
+ option :bar
16
+ end
17
+
3
18
  class PlainRubyTest
4
19
  attr_reader :foo, :bar
5
20
 
@@ -9,14 +24,6 @@ class PlainRubyTest
9
24
  end
10
25
  end
11
26
 
12
- require "dry-initializer"
13
- class DryTest
14
- extend Dry::Initializer::Mixin
15
-
16
- option :foo
17
- option :bar
18
- end
19
-
20
27
  require "anima"
21
28
  class AnimaTest
22
29
  include Anima.new(:foo, :bar)
@@ -27,7 +34,7 @@ class KwattrTest
27
34
  kwattr :foo, :bar
28
35
  end
29
36
 
30
- puts "Benchmark for instantiation without options"
37
+ puts "Benchmark for instantiation with plain options"
31
38
 
32
39
  Benchmark.ips do |x|
33
40
  x.config time: 15, warmup: 10
@@ -40,6 +47,10 @@ Benchmark.ips do |x|
40
47
  DryTest.new foo: "FOO", bar: "BAR"
41
48
  end
42
49
 
50
+ x.report("dry-initializer (with UNDEFINED)") do
51
+ DryTestUndefined.new foo: "FOO", bar: "BAR"
52
+ end
53
+
43
54
  x.report("anima") do
44
55
  AnimaTest.new foo: "FOO", bar: "BAR"
45
56
  end
@@ -1,5 +1,20 @@
1
1
  Bundler.require(:benchmarks)
2
2
 
3
+ require "dry-initializer"
4
+ class DryTest
5
+ extend Dry::Initializer[undefined: false]
6
+
7
+ param :foo
8
+ param :bar
9
+ end
10
+
11
+ class DryTestUndefined
12
+ extend Dry::Initializer
13
+
14
+ param :foo
15
+ param :bar
16
+ end
17
+
3
18
  class PlainRubyTest
4
19
  attr_reader :foo, :bar
5
20
 
@@ -11,14 +26,6 @@ end
11
26
 
12
27
  StructTest = Struct.new(:foo, :bar)
13
28
 
14
- require "dry-initializer"
15
- class DryTest
16
- extend Dry::Initializer
17
-
18
- param :foo
19
- param :bar
20
- end
21
-
22
29
  require "concord"
23
30
  class ConcordTest
24
31
  include Concord.new(:foo, :bar)
@@ -36,7 +43,7 @@ class AttrExtrasText
36
43
  attr_reader :foo, :bar
37
44
  end
38
45
 
39
- puts "Benchmark for instantiation of plain params"
46
+ puts "Benchmark for instantiation with plain params"
40
47
 
41
48
  Benchmark.ips do |x|
42
49
  x.config time: 15, warmup: 10
@@ -61,6 +68,10 @@ Benchmark.ips do |x|
61
68
  DryTest.new "FOO", "BAR"
62
69
  end
63
70
 
71
+ x.report("dry-initializer (with UNDEFINED)") do
72
+ DryTestUndefined.new "FOO", "BAR"
73
+ end
74
+
64
75
  x.report("concord") do
65
76
  ConcordTest.new "FOO", "BAR"
66
77
  end
@@ -1,5 +1,20 @@
1
1
  Bundler.require(:benchmarks)
2
2
 
3
+ require "dry-initializer"
4
+ class DryTest
5
+ extend Dry::Initializer[undefined: false]
6
+
7
+ option :foo, proc(&:to_s)
8
+ option :bar, proc(&:to_s)
9
+ end
10
+
11
+ class DryTestUndefined
12
+ extend Dry::Initializer
13
+
14
+ option :foo, proc(&:to_s)
15
+ option :bar, proc(&:to_s)
16
+ end
17
+
3
18
  class PlainRubyTest
4
19
  attr_reader :foo, :bar
5
20
 
@@ -9,14 +24,6 @@ class PlainRubyTest
9
24
  end
10
25
  end
11
26
 
12
- require "dry-initializer"
13
- class DryTest
14
- extend Dry::Initializer::Mixin
15
-
16
- option :foo, &(:to_s)
17
- option :bar, &(:to_s)
18
- end
19
-
20
27
  require "virtus"
21
28
  class VirtusTest
22
29
  include Virtus.model
@@ -35,7 +42,7 @@ class FastAttributesTest
35
42
  end
36
43
  end
37
44
 
38
- puts "Benchmark for instantiation with type constraints"
45
+ puts "Benchmark for instantiation with coercion"
39
46
 
40
47
  Benchmark.ips do |x|
41
48
  x.config time: 15, warmup: 10
@@ -48,6 +55,10 @@ Benchmark.ips do |x|
48
55
  DryTest.new foo: "FOO", bar: "BAR"
49
56
  end
50
57
 
58
+ x.report("dry-initializer (with UNDEFINED)") do
59
+ DryTestUndefined.new foo: "FOO", bar: "BAR"
60
+ end
61
+
51
62
  x.report("virtus") do
52
63
  VirtusTest.new foo: "FOO", bar: "BAR"
53
64
  end
@@ -1,5 +1,20 @@
1
1
  Bundler.require(:benchmarks)
2
2
 
3
+ require "dry-initializer"
4
+ class DryTest
5
+ extend Dry::Initializer[undefined: false]
6
+
7
+ option :foo, default: -> { "FOO" }
8
+ option :bar, default: -> { "BAR" }
9
+ end
10
+
11
+ class DryTestUndefined
12
+ extend Dry::Initializer
13
+
14
+ option :foo, default: -> { "FOO" }
15
+ option :bar, default: -> { "BAR" }
16
+ end
17
+
3
18
  class PlainRubyTest
4
19
  attr_reader :foo, :bar
5
20
 
@@ -9,14 +24,6 @@ class PlainRubyTest
9
24
  end
10
25
  end
11
26
 
12
- require "dry-initializer"
13
- class DryTest
14
- extend Dry::Initializer::Mixin
15
-
16
- option :foo, default: proc { "FOO" }
17
- option :bar, default: proc { "BAR" }
18
- end
19
-
20
27
  require "kwattr"
21
28
  class KwattrTest
22
29
  kwattr foo: "FOO", bar: "BAR"
@@ -43,6 +50,10 @@ Benchmark.ips do |x|
43
50
  DryTest.new
44
51
  end
45
52
 
53
+ x.report("dry-initializer (with UNDEFINED)") do
54
+ DryTestUndefined.new
55
+ end
56
+
46
57
  x.report("kwattr") do
47
58
  KwattrTest.new
48
59
  end
@@ -1,24 +1,31 @@
1
1
  Bundler.require(:benchmarks)
2
2
 
3
+ require "dry-initializer"
4
+ class DryTest
5
+ extend Dry::Initializer[undefined: false]
6
+
7
+ option :foo, proc(&:to_s), default: -> { "FOO" }
8
+ option :bar, proc(&:to_s), default: -> { "BAR" }
9
+ end
10
+
11
+ class DryTestUndefined
12
+ extend Dry::Initializer
13
+
14
+ option :foo, proc(&:to_s), default: -> { "FOO" }
15
+ option :bar, proc(&:to_s), default: -> { "BAR" }
16
+ end
17
+
3
18
  class PlainRubyTest
4
19
  attr_reader :foo, :bar
5
20
 
6
21
  def initialize(foo: "FOO", bar: "BAR")
7
22
  @foo = foo
8
23
  @bar = bar
9
- fail TypeError unless String === @foo
10
- fail TypeError unless String === @bar
24
+ raise TypeError unless String === @foo
25
+ raise TypeError unless String === @bar
11
26
  end
12
27
  end
13
28
 
14
- require "dry-initializer"
15
- class DryTest
16
- extend Dry::Initializer::Mixin
17
-
18
- option :foo, proc(&:to_s), default: proc { "FOO" }
19
- option :bar, proc(&:to_s), default: proc { "BAR" }
20
- end
21
-
22
29
  require "virtus"
23
30
  class VirtusTest
24
31
  include Virtus.model
@@ -40,6 +47,10 @@ Benchmark.ips do |x|
40
47
  DryTest.new
41
48
  end
42
49
 
50
+ x.report("dry-initializer (with UNDEFINED)") do
51
+ DryTest.new
52
+ end
53
+
43
54
  x.report("virtus") do
44
55
  VirtusTest.new
45
56
  end
@@ -1,8 +1,8 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "dry-initializer"
3
- gem.version = "1.4.1"
3
+ gem.version = "2.0.0"
4
4
  gem.author = ["Vladimir Kochnev (marshall-lee)", "Andrew Kozin (nepalez)"]
5
- gem.email = ["hashtable@yandex.ru", "andrew.kozin@gmail.com"]
5
+ gem.email = "andrew.kozin@gmail.com"
6
6
  gem.homepage = "https://github.com/dryrb/dry-initializer"
7
7
  gem.summary = "DSL for declaring params and options of the initializer"
8
8
  gem.license = "MIT"
@@ -11,7 +11,7 @@ Gem::Specification.new do |gem|
11
11
  gem.test_files = gem.files.grep(/^spec/)
12
12
  gem.extra_rdoc_files = Dir["README.md", "LICENSE", "CHANGELOG.md"]
13
13
 
14
- gem.required_ruby_version = ">= 2.2"
14
+ gem.required_ruby_version = ">= 2.3"
15
15
 
16
16
  gem.add_development_dependency "rspec", "~> 3.0"
17
17
  gem.add_development_dependency "rake", "> 10"
@@ -0,0 +1,76 @@
1
+ module Dry::Initializer::Builders
2
+ # @private
3
+ class Attribute
4
+ def self.[](definition)
5
+ new(definition).call
6
+ end
7
+
8
+ def call
9
+ lines.compact
10
+ end
11
+
12
+ private
13
+
14
+ # rubocop: disable Style/MethodLength
15
+ def initialize(definition)
16
+ @definition = definition
17
+ @option = definition.option
18
+ @type = definition.type
19
+ @optional = definition.optional
20
+ @default = definition.default
21
+ @source = definition.source
22
+ @ivar = definition.ivar
23
+ @null = definition.null ? "Dry::Initializer::UNDEFINED" : "nil"
24
+ @opts = "__dry_initializer_options__"
25
+ @congif = "__dry_initializer_config__"
26
+ @item = "__dry_initializer_definition__"
27
+ @val = @option ? "__dry_initializer_value__" : @source
28
+ end
29
+ # rubocop: enable Style/MethodLength
30
+
31
+ def lines
32
+ [
33
+ "",
34
+ definition_line,
35
+ reader_line,
36
+ default_line,
37
+ coercion_line,
38
+ assignment_line
39
+ ]
40
+ end
41
+
42
+ def reader_line
43
+ return unless @option
44
+ @optional ? optional_reader : required_reader
45
+ end
46
+
47
+ def optional_reader
48
+ "#{@val} = #{@opts}.fetch(:'#{@source}', #{@null})"
49
+ end
50
+
51
+ def required_reader
52
+ "#{@val} = #{@opts}.fetch(:'#{@source}')" \
53
+ " { raise KeyError, \"\#{self.class}: #{@definition} is required\" }"
54
+ end
55
+
56
+ def definition_line
57
+ return unless @type || @default
58
+ "#{@item} = __dry_initializer_config__.definitions[:'#{@source}']"
59
+ end
60
+
61
+ def default_line
62
+ return unless @default
63
+ "#{@val} = instance_exec(&#{@item}.default) if #{@val} == #{@null}"
64
+ end
65
+
66
+ def coercion_line
67
+ return unless @type
68
+ "#{@val} = #{@item}.type.call(#{@val}) unless #{@val} == #{@null}"
69
+ end
70
+
71
+ def assignment_line
72
+ "#{@ivar} = #{@val}" \
73
+ " unless #{@val} == #{@null} && instance_variable_defined?(:#{@ivar})"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,61 @@
1
+ module Dry::Initializer::Builders
2
+ # @private
3
+ class Initializer
4
+ require_relative "signature"
5
+ require_relative "attribute"
6
+
7
+ def self.[](config)
8
+ new(config).call
9
+ end
10
+
11
+ def call
12
+ lines.flatten.compact.join("\n")
13
+ end
14
+
15
+ private
16
+
17
+ def initialize(config)
18
+ @config = config
19
+ @definitions = config.definitions.values
20
+ end
21
+
22
+ def lines
23
+ [
24
+ undef_line,
25
+ define_line,
26
+ params_lines,
27
+ options_lines,
28
+ end_line
29
+ ]
30
+ end
31
+
32
+ def undef_line
33
+ "undef :__dry_initializer_initialize__" \
34
+ " if private_method_defined? :__dry_initializer_initialize__"
35
+ end
36
+
37
+ def define_line
38
+ "private def __dry_initializer_initialize__(#{Signature[@config]})"
39
+ end
40
+
41
+ def params_lines
42
+ @definitions.reject(&:option)
43
+ .flat_map { |item| Attribute[item] }
44
+ .map { |line| " " << line }
45
+ end
46
+
47
+ def options_lines
48
+ @definitions.select(&:option)
49
+ .flat_map { |item| Attribute[item] }
50
+ .map { |line| " " << line }
51
+ end
52
+
53
+ def end_line
54
+ "end"
55
+ end
56
+
57
+ def private_line
58
+ "private :__dry_initializer_initialize__"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,50 @@
1
+ module Dry::Initializer::Builders
2
+ # @private
3
+ class Reader
4
+ def self.[](definition)
5
+ new(definition).call
6
+ end
7
+
8
+ def call
9
+ lines.flatten.compact.join("\n")
10
+ end
11
+
12
+ private
13
+
14
+ def initialize(definition)
15
+ @target = definition.target
16
+ @ivar = definition.ivar
17
+ @null = definition.null
18
+ @reader = definition.reader
19
+ end
20
+
21
+ def lines
22
+ [undef_line, attribute_line, method_lines, type_line]
23
+ end
24
+
25
+ def undef_line
26
+ "undef :#{@target} if method_defined?(:#{@target})" \
27
+ " || private_method_defined?(:#{@target})" \
28
+ " || protected_method_defined?(:#{@target})"
29
+ end
30
+
31
+ def attribute_line
32
+ return unless @reader
33
+ "attr_reader :#{@target}" unless @null
34
+ end
35
+
36
+ def method_lines
37
+ return unless @reader
38
+ return unless @null
39
+ [
40
+ "def #{@target}",
41
+ " #{@ivar} unless #{@ivar} == Dry::Initializer::UNDEFINED",
42
+ "end"
43
+ ]
44
+ end
45
+
46
+ def type_line
47
+ "#{@reader} :#{@target}" if %i[private protected].include? @reader
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ module Dry::Initializer::Builders
2
+ # @private
3
+ class Signature
4
+ def self.[](config)
5
+ new(config).call
6
+ end
7
+
8
+ def call
9
+ [*required_params, *optional_params, "*", options].compact.join(", ")
10
+ end
11
+
12
+ private
13
+
14
+ def initialize(config)
15
+ @config = config
16
+ @options = config.options.any?
17
+ @null = config.null ? "Dry::Initializer::UNDEFINED" : "nil"
18
+ end
19
+
20
+ def required_params
21
+ @config.params.reject(&:optional).map(&:source)
22
+ end
23
+
24
+ def optional_params
25
+ @config.params.select(&:optional).map { |rec| "#{rec.source} = #{@null}" }
26
+ end
27
+
28
+ def options
29
+ "**__dry_initializer_options__" if @options
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ module Dry::Initializer
2
+ # @private
3
+ module Builders
4
+ require_relative "builders/reader"
5
+ require_relative "builders/initializer"
6
+ end
7
+ end