dry-initializer 3.0.1

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +23 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +51 -0
  6. data/.travis.yml +28 -0
  7. data/CHANGELOG.md +883 -0
  8. data/Gemfile +29 -0
  9. data/Guardfile +5 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +90 -0
  12. data/Rakefile +8 -0
  13. data/benchmarks/compare_several_defaults.rb +82 -0
  14. data/benchmarks/plain_options.rb +63 -0
  15. data/benchmarks/plain_params.rb +84 -0
  16. data/benchmarks/with_coercion.rb +71 -0
  17. data/benchmarks/with_defaults.rb +66 -0
  18. data/benchmarks/with_defaults_and_coercion.rb +59 -0
  19. data/dry-initializer.gemspec +20 -0
  20. data/lib/dry-initializer.rb +1 -0
  21. data/lib/dry/initializer.rb +61 -0
  22. data/lib/dry/initializer/builders.rb +7 -0
  23. data/lib/dry/initializer/builders/attribute.rb +81 -0
  24. data/lib/dry/initializer/builders/initializer.rb +61 -0
  25. data/lib/dry/initializer/builders/reader.rb +50 -0
  26. data/lib/dry/initializer/builders/signature.rb +32 -0
  27. data/lib/dry/initializer/config.rb +184 -0
  28. data/lib/dry/initializer/definition.rb +65 -0
  29. data/lib/dry/initializer/dispatchers.rb +112 -0
  30. data/lib/dry/initializer/dispatchers/build_nested_type.rb +59 -0
  31. data/lib/dry/initializer/dispatchers/check_type.rb +43 -0
  32. data/lib/dry/initializer/dispatchers/prepare_default.rb +40 -0
  33. data/lib/dry/initializer/dispatchers/prepare_ivar.rb +12 -0
  34. data/lib/dry/initializer/dispatchers/prepare_optional.rb +13 -0
  35. data/lib/dry/initializer/dispatchers/prepare_reader.rb +30 -0
  36. data/lib/dry/initializer/dispatchers/prepare_source.rb +28 -0
  37. data/lib/dry/initializer/dispatchers/prepare_target.rb +44 -0
  38. data/lib/dry/initializer/dispatchers/unwrap_type.rb +22 -0
  39. data/lib/dry/initializer/dispatchers/wrap_type.rb +27 -0
  40. data/lib/dry/initializer/dsl.rb +43 -0
  41. data/lib/dry/initializer/mixin.rb +15 -0
  42. data/lib/dry/initializer/mixin/local.rb +19 -0
  43. data/lib/dry/initializer/mixin/root.rb +10 -0
  44. data/lib/dry/initializer/struct.rb +40 -0
  45. data/lib/dry/initializer/undefined.rb +2 -0
  46. data/lib/tasks/benchmark.rake +41 -0
  47. data/lib/tasks/profile.rake +78 -0
  48. data/spec/attributes_spec.rb +38 -0
  49. data/spec/coercion_of_nil_spec.rb +25 -0
  50. data/spec/custom_dispatchers_spec.rb +35 -0
  51. data/spec/custom_initializer_spec.rb +30 -0
  52. data/spec/default_values_spec.rb +83 -0
  53. data/spec/definition_spec.rb +111 -0
  54. data/spec/invalid_default_spec.rb +13 -0
  55. data/spec/list_type_spec.rb +32 -0
  56. data/spec/missed_default_spec.rb +14 -0
  57. data/spec/nested_type_spec.rb +44 -0
  58. data/spec/optional_spec.rb +71 -0
  59. data/spec/options_tolerance_spec.rb +11 -0
  60. data/spec/public_attributes_utility_spec.rb +22 -0
  61. data/spec/reader_spec.rb +87 -0
  62. data/spec/repetitive_definitions_spec.rb +69 -0
  63. data/spec/several_assignments_spec.rb +41 -0
  64. data/spec/spec_helper.rb +22 -0
  65. data/spec/subclassing_spec.rb +49 -0
  66. data/spec/type_argument_spec.rb +35 -0
  67. data/spec/type_constraint_spec.rb +78 -0
  68. data/spec/value_coercion_via_dry_types_spec.rb +29 -0
  69. metadata +189 -0
@@ -0,0 +1,66 @@
1
+ Bundler.require(:benchmarks)
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
+
18
+ class PlainRubyTest
19
+ attr_reader :foo, :bar
20
+
21
+ def initialize(foo: "FOO", bar: "BAR")
22
+ @foo = foo
23
+ @bar = bar
24
+ end
25
+ end
26
+
27
+ require "kwattr"
28
+ class KwattrTest
29
+ kwattr foo: "FOO", bar: "BAR"
30
+ end
31
+
32
+ require "active_attr"
33
+ class ActiveAttrTest
34
+ include ActiveAttr::AttributeDefaults
35
+
36
+ attribute :foo, default: "FOO"
37
+ attribute :bar, default: "BAR"
38
+ end
39
+
40
+ puts "Benchmark for instantiation with default values"
41
+
42
+ Benchmark.ips do |x|
43
+ x.config time: 15, warmup: 10
44
+
45
+ x.report("plain Ruby") do
46
+ PlainRubyTest.new
47
+ end
48
+
49
+ x.report("dry-initializer") do
50
+ DryTest.new
51
+ end
52
+
53
+ x.report("dry-initializer (with UNDEFINED)") do
54
+ DryTestUndefined.new
55
+ end
56
+
57
+ x.report("kwattr") do
58
+ KwattrTest.new
59
+ end
60
+
61
+ x.report("active_attr") do
62
+ ActiveAttrTest.new
63
+ end
64
+
65
+ x.compare!
66
+ end
@@ -0,0 +1,59 @@
1
+ Bundler.require(:benchmarks)
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
+
18
+ class PlainRubyTest
19
+ attr_reader :foo, :bar
20
+
21
+ def initialize(foo: "FOO", bar: "BAR")
22
+ @foo = foo
23
+ @bar = bar
24
+ raise TypeError unless String === @foo
25
+ raise TypeError unless String === @bar
26
+ end
27
+ end
28
+
29
+ require "virtus"
30
+ class VirtusTest
31
+ include Virtus.model
32
+
33
+ attribute :foo, String, default: "FOO"
34
+ attribute :bar, String, default: "BAR"
35
+ end
36
+
37
+ puts "Benchmark for instantiation with type constraints and default values"
38
+
39
+ Benchmark.ips do |x|
40
+ x.config time: 15, warmup: 10
41
+
42
+ x.report("plain Ruby") do
43
+ PlainRubyTest.new
44
+ end
45
+
46
+ x.report("dry-initializer") do
47
+ DryTest.new
48
+ end
49
+
50
+ x.report("dry-initializer (with UNDEFINED)") do
51
+ DryTest.new
52
+ end
53
+
54
+ x.report("virtus") do
55
+ VirtusTest.new
56
+ end
57
+
58
+ x.compare!
59
+ end
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = "dry-initializer"
3
+ gem.version = "3.0.1"
4
+ gem.author = ["Vladimir Kochnev (marshall-lee)", "Andrew Kozin (nepalez)"]
5
+ gem.email = "andrew.kozin@gmail.com"
6
+ gem.homepage = "https://github.com/dry-rb/dry-initializer"
7
+ gem.summary = "DSL for declaring params and options of the initializer"
8
+ gem.license = "MIT"
9
+
10
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
11
+ gem.test_files = gem.files.grep(/^spec/)
12
+ gem.extra_rdoc_files = Dir["README.md", "LICENSE", "CHANGELOG.md"]
13
+
14
+ gem.required_ruby_version = ">= 2.3"
15
+
16
+ gem.add_development_dependency "rspec", "~> 3.0"
17
+ gem.add_development_dependency "rake", "> 10"
18
+ gem.add_development_dependency "dry-types", "> 0.5.1"
19
+ gem.add_development_dependency "rubocop", "~> 0.49.0"
20
+ end
@@ -0,0 +1 @@
1
+ require_relative "dry/initializer"
@@ -0,0 +1,61 @@
1
+ require "set"
2
+
3
+ # Namespace for gems in a dry-rb community
4
+ module Dry
5
+ #
6
+ # DSL for declaring params and options of class initializers
7
+ #
8
+ module Initializer
9
+ require_relative "initializer/undefined"
10
+ require_relative "initializer/dsl"
11
+ require_relative "initializer/definition"
12
+ require_relative "initializer/builders"
13
+ require_relative "initializer/config"
14
+ require_relative "initializer/mixin"
15
+ require_relative "initializer/dispatchers"
16
+
17
+ # Adds methods [.[]] and [.define]
18
+ extend DSL
19
+
20
+ # Gem-related configuration
21
+ # @return [Dry::Initializer::Config]
22
+ def dry_initializer
23
+ @dry_initializer ||= Config.new(self)
24
+ end
25
+
26
+ # Adds or redefines a parameter of [#dry_initializer]
27
+ # @param [Symbol] name
28
+ # @param [#call, nil] type (nil)
29
+ # @option opts [Proc] :default
30
+ # @option opts [Boolean] :optional
31
+ # @option opts [Symbol] :as
32
+ # @option opts [true, false, :protected, :public, :private] :reader
33
+ # @yield block with nested definition
34
+ # @return [self] itself
35
+ def param(name, type = nil, **opts, &block)
36
+ dry_initializer.param(name, type, **opts, &block)
37
+ self
38
+ end
39
+
40
+ # Adds or redefines an option of [#dry_initializer]
41
+ # @param (see #param)
42
+ # @option (see #param)
43
+ # @yield (see #param)
44
+ # @return (see #param)
45
+ def option(name, type = nil, **opts, &block)
46
+ dry_initializer.option(name, type, **opts, &block)
47
+ self
48
+ end
49
+
50
+ private
51
+
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
57
+ end
58
+
59
+ require_relative "initializer/struct"
60
+ end
61
+ 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
@@ -0,0 +1,81 @@
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 Metrics/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 Metrics/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 #{@null} == #{@val}"
64
+ end
65
+
66
+ def coercion_line
67
+ return unless @type
68
+ arity = @type.is_a?(Proc) ? @type.arity : @type.method(:call).arity
69
+ if arity.abs == 1
70
+ "#{@val} = #{@item}.type.call(#{@val}) unless #{@null} == #{@val}"
71
+ else
72
+ "#{@val} = #{@item}.type.call(#{@val}, self) unless #{@null} == #{@val}"
73
+ end
74
+ end
75
+
76
+ def assignment_line
77
+ "#{@ivar} = #{@val}" \
78
+ " unless #{@null} == #{@val} && instance_variable_defined?(:#{@ivar})"
79
+ end
80
+ end
81
+ 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 Dry::Initializer::UNDEFINED == #{@ivar}",
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