dry-initializer 1.4.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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