dry-initializer 2.4.0 → 3.0.3

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 (92) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +10 -21
  3. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  4. data/.github/ISSUE_TEMPLATE/---bug-report.md +30 -0
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  6. data/.github/workflows/custom_ci.yml +58 -0
  7. data/.github/workflows/docsite.yml +34 -0
  8. data/.github/workflows/sync_configs.yml +56 -0
  9. data/.gitignore +2 -0
  10. data/.rspec +1 -1
  11. data/.rubocop.yml +76 -25
  12. data/CHANGELOG.md +150 -14
  13. data/CODE_OF_CONDUCT.md +13 -0
  14. data/CONTRIBUTING.md +29 -0
  15. data/Gemfile +25 -18
  16. data/Gemfile.devtools +16 -0
  17. data/Guardfile +3 -3
  18. data/LICENSE +20 -0
  19. data/README.md +17 -79
  20. data/Rakefile +4 -4
  21. data/benchmarks/compare_several_defaults.rb +27 -27
  22. data/benchmarks/plain_options.rb +14 -14
  23. data/benchmarks/plain_params.rb +22 -22
  24. data/benchmarks/with_coercion.rb +14 -14
  25. data/benchmarks/with_defaults.rb +17 -17
  26. data/benchmarks/with_defaults_and_coercion.rb +14 -14
  27. data/bin/.gitkeep +0 -0
  28. data/docsite/source/attributes.html.md +106 -0
  29. data/docsite/source/container-version.html.md +39 -0
  30. data/docsite/source/index.html.md +43 -0
  31. data/docsite/source/inheritance.html.md +43 -0
  32. data/docsite/source/optionals-and-defaults.html.md +130 -0
  33. data/docsite/source/options-tolerance.html.md +27 -0
  34. data/docsite/source/params-and-options.html.md +74 -0
  35. data/docsite/source/rails-support.html.md +101 -0
  36. data/docsite/source/readers.html.md +43 -0
  37. data/docsite/source/skip-undefined.html.md +59 -0
  38. data/docsite/source/type-constraints.html.md +160 -0
  39. data/dry-initializer.gemspec +13 -13
  40. data/lib/dry-initializer.rb +1 -1
  41. data/lib/dry/initializer.rb +17 -16
  42. data/lib/dry/initializer/builders.rb +2 -2
  43. data/lib/dry/initializer/builders/attribute.rb +16 -11
  44. data/lib/dry/initializer/builders/initializer.rb +9 -13
  45. data/lib/dry/initializer/builders/reader.rb +4 -2
  46. data/lib/dry/initializer/builders/signature.rb +3 -3
  47. data/lib/dry/initializer/config.rb +25 -12
  48. data/lib/dry/initializer/definition.rb +20 -71
  49. data/lib/dry/initializer/dispatchers.rb +101 -33
  50. data/lib/dry/initializer/dispatchers/build_nested_type.rb +59 -0
  51. data/lib/dry/initializer/dispatchers/check_type.rb +43 -0
  52. data/lib/dry/initializer/dispatchers/prepare_default.rb +40 -0
  53. data/lib/dry/initializer/dispatchers/prepare_ivar.rb +12 -0
  54. data/lib/dry/initializer/dispatchers/prepare_optional.rb +13 -0
  55. data/lib/dry/initializer/dispatchers/prepare_reader.rb +30 -0
  56. data/lib/dry/initializer/dispatchers/prepare_source.rb +28 -0
  57. data/lib/dry/initializer/dispatchers/prepare_target.rb +44 -0
  58. data/lib/dry/initializer/dispatchers/unwrap_type.rb +22 -0
  59. data/lib/dry/initializer/dispatchers/wrap_type.rb +28 -0
  60. data/lib/dry/initializer/mixin.rb +4 -4
  61. data/lib/dry/initializer/mixin/root.rb +1 -0
  62. data/lib/dry/initializer/struct.rb +39 -0
  63. data/lib/dry/initializer/undefined.rb +2 -0
  64. data/lib/dry/initializer/version.rb +5 -0
  65. data/lib/tasks/benchmark.rake +13 -13
  66. data/lib/tasks/profile.rake +16 -16
  67. data/project.yml +2 -0
  68. data/spec/attributes_spec.rb +7 -7
  69. data/spec/coercion_of_nil_spec.rb +25 -0
  70. data/spec/custom_dispatchers_spec.rb +6 -6
  71. data/spec/custom_initializer_spec.rb +2 -2
  72. data/spec/default_values_spec.rb +9 -9
  73. data/spec/definition_spec.rb +16 -12
  74. data/spec/invalid_default_spec.rb +2 -2
  75. data/spec/list_type_spec.rb +32 -0
  76. data/spec/missed_default_spec.rb +2 -2
  77. data/spec/nested_type_spec.rb +48 -0
  78. data/spec/optional_spec.rb +16 -16
  79. data/spec/options_tolerance_spec.rb +2 -2
  80. data/spec/public_attributes_utility_spec.rb +5 -5
  81. data/spec/reader_spec.rb +13 -13
  82. data/spec/repetitive_definitions_spec.rb +9 -9
  83. data/spec/several_assignments_spec.rb +9 -9
  84. data/spec/spec_helper.rb +6 -3
  85. data/spec/subclassing_spec.rb +5 -5
  86. data/spec/support/coverage.rb +7 -0
  87. data/spec/support/warnings.rb +7 -0
  88. data/spec/type_argument_spec.rb +15 -15
  89. data/spec/type_constraint_spec.rb +46 -28
  90. data/spec/value_coercion_via_dry_types_spec.rb +8 -8
  91. metadata +51 -34
  92. data/.travis.yml +0 -24
@@ -1,6 +1,6 @@
1
1
  Bundler.require(:benchmarks)
2
2
 
3
- require "dry-initializer"
3
+ require 'dry-initializer'
4
4
  class DryTest
5
5
  extend Dry::Initializer[undefined: false]
6
6
 
@@ -24,7 +24,7 @@ class PlainRubyTest
24
24
  end
25
25
  end
26
26
 
27
- require "virtus"
27
+ require 'virtus'
28
28
  class VirtusTest
29
29
  include Virtus.model
30
30
 
@@ -32,7 +32,7 @@ class VirtusTest
32
32
  attribute :bar, String
33
33
  end
34
34
 
35
- require "fast_attributes"
35
+ require 'fast_attributes'
36
36
  class FastAttributesTest
37
37
  extend FastAttributes
38
38
 
@@ -42,29 +42,29 @@ class FastAttributesTest
42
42
  end
43
43
  end
44
44
 
45
- puts "Benchmark for instantiation with coercion"
45
+ puts 'Benchmark for instantiation with coercion'
46
46
 
47
47
  Benchmark.ips do |x|
48
48
  x.config time: 15, warmup: 10
49
49
 
50
- x.report("plain Ruby") do
51
- PlainRubyTest.new foo: "FOO", bar: "BAR"
50
+ x.report('plain Ruby') do
51
+ PlainRubyTest.new foo: 'FOO', bar: 'BAR'
52
52
  end
53
53
 
54
- x.report("dry-initializer") do
55
- DryTest.new foo: "FOO", bar: "BAR"
54
+ x.report('dry-initializer') do
55
+ DryTest.new foo: 'FOO', bar: 'BAR'
56
56
  end
57
57
 
58
- x.report("dry-initializer (with UNDEFINED)") do
59
- DryTestUndefined.new foo: "FOO", bar: "BAR"
58
+ x.report('dry-initializer (with UNDEFINED)') do
59
+ DryTestUndefined.new foo: 'FOO', bar: 'BAR'
60
60
  end
61
61
 
62
- x.report("virtus") do
63
- VirtusTest.new foo: "FOO", bar: "BAR"
62
+ x.report('virtus') do
63
+ VirtusTest.new foo: 'FOO', bar: 'BAR'
64
64
  end
65
65
 
66
- x.report("fast_attributes") do
67
- FastAttributesTest.new foo: "FOO", bar: "BAR"
66
+ x.report('fast_attributes') do
67
+ FastAttributesTest.new foo: 'FOO', bar: 'BAR'
68
68
  end
69
69
 
70
70
  x.compare!
@@ -1,64 +1,64 @@
1
1
  Bundler.require(:benchmarks)
2
2
 
3
- require "dry-initializer"
3
+ require 'dry-initializer'
4
4
  class DryTest
5
5
  extend Dry::Initializer[undefined: false]
6
6
 
7
- option :foo, default: -> { "FOO" }
8
- option :bar, default: -> { "BAR" }
7
+ option :foo, default: -> { 'FOO' }
8
+ option :bar, default: -> { 'BAR' }
9
9
  end
10
10
 
11
11
  class DryTestUndefined
12
12
  extend Dry::Initializer
13
13
 
14
- option :foo, default: -> { "FOO" }
15
- option :bar, default: -> { "BAR" }
14
+ option :foo, default: -> { 'FOO' }
15
+ option :bar, default: -> { 'BAR' }
16
16
  end
17
17
 
18
18
  class PlainRubyTest
19
19
  attr_reader :foo, :bar
20
20
 
21
- def initialize(foo: "FOO", bar: "BAR")
21
+ def initialize(foo: 'FOO', bar: 'BAR')
22
22
  @foo = foo
23
23
  @bar = bar
24
24
  end
25
25
  end
26
26
 
27
- require "kwattr"
27
+ require 'kwattr'
28
28
  class KwattrTest
29
- kwattr foo: "FOO", bar: "BAR"
29
+ kwattr foo: 'FOO', bar: 'BAR'
30
30
  end
31
31
 
32
- require "active_attr"
32
+ require 'active_attr'
33
33
  class ActiveAttrTest
34
34
  include ActiveAttr::AttributeDefaults
35
35
 
36
- attribute :foo, default: "FOO"
37
- attribute :bar, default: "BAR"
36
+ attribute :foo, default: 'FOO'
37
+ attribute :bar, default: 'BAR'
38
38
  end
39
39
 
40
- puts "Benchmark for instantiation with default values"
40
+ puts 'Benchmark for instantiation with default values'
41
41
 
42
42
  Benchmark.ips do |x|
43
43
  x.config time: 15, warmup: 10
44
44
 
45
- x.report("plain Ruby") do
45
+ x.report('plain Ruby') do
46
46
  PlainRubyTest.new
47
47
  end
48
48
 
49
- x.report("dry-initializer") do
49
+ x.report('dry-initializer') do
50
50
  DryTest.new
51
51
  end
52
52
 
53
- x.report("dry-initializer (with UNDEFINED)") do
53
+ x.report('dry-initializer (with UNDEFINED)') do
54
54
  DryTestUndefined.new
55
55
  end
56
56
 
57
- x.report("kwattr") do
57
+ x.report('kwattr') do
58
58
  KwattrTest.new
59
59
  end
60
60
 
61
- x.report("active_attr") do
61
+ x.report('active_attr') do
62
62
  ActiveAttrTest.new
63
63
  end
64
64
 
@@ -1,24 +1,24 @@
1
1
  Bundler.require(:benchmarks)
2
2
 
3
- require "dry-initializer"
3
+ require 'dry-initializer'
4
4
  class DryTest
5
5
  extend Dry::Initializer[undefined: false]
6
6
 
7
- option :foo, proc(&:to_s), default: -> { "FOO" }
8
- option :bar, proc(&:to_s), default: -> { "BAR" }
7
+ option :foo, proc(&:to_s), default: -> { 'FOO' }
8
+ option :bar, proc(&:to_s), default: -> { 'BAR' }
9
9
  end
10
10
 
11
11
  class DryTestUndefined
12
12
  extend Dry::Initializer
13
13
 
14
- option :foo, proc(&:to_s), default: -> { "FOO" }
15
- option :bar, proc(&:to_s), default: -> { "BAR" }
14
+ option :foo, proc(&:to_s), default: -> { 'FOO' }
15
+ option :bar, proc(&:to_s), default: -> { 'BAR' }
16
16
  end
17
17
 
18
18
  class PlainRubyTest
19
19
  attr_reader :foo, :bar
20
20
 
21
- def initialize(foo: "FOO", bar: "BAR")
21
+ def initialize(foo: 'FOO', bar: 'BAR')
22
22
  @foo = foo
23
23
  @bar = bar
24
24
  raise TypeError unless String === @foo
@@ -26,32 +26,32 @@ class PlainRubyTest
26
26
  end
27
27
  end
28
28
 
29
- require "virtus"
29
+ require 'virtus'
30
30
  class VirtusTest
31
31
  include Virtus.model
32
32
 
33
- attribute :foo, String, default: "FOO"
34
- attribute :bar, String, default: "BAR"
33
+ attribute :foo, String, default: 'FOO'
34
+ attribute :bar, String, default: 'BAR'
35
35
  end
36
36
 
37
- puts "Benchmark for instantiation with type constraints and default values"
37
+ puts 'Benchmark for instantiation with type constraints and default values'
38
38
 
39
39
  Benchmark.ips do |x|
40
40
  x.config time: 15, warmup: 10
41
41
 
42
- x.report("plain Ruby") do
42
+ x.report('plain Ruby') do
43
43
  PlainRubyTest.new
44
44
  end
45
45
 
46
- x.report("dry-initializer") do
46
+ x.report('dry-initializer') do
47
47
  DryTest.new
48
48
  end
49
49
 
50
- x.report("dry-initializer (with UNDEFINED)") do
50
+ x.report('dry-initializer (with UNDEFINED)') do
51
51
  DryTest.new
52
52
  end
53
53
 
54
- x.report("virtus") do
54
+ x.report('virtus') do
55
55
  VirtusTest.new
56
56
  end
57
57
 
data/bin/.gitkeep ADDED
File without changes
@@ -0,0 +1,106 @@
1
+ ---
2
+ title: Attributes
3
+ layout: gem-single
4
+ name: dry-initializer
5
+ ---
6
+
7
+ Sometimes you need to access all attributes assigned via params and options of the object constructor.
8
+
9
+ We support 2 methods: `attributes` and `public_attributes` for this goal. Both methods are wrapped into container accessible via `.dry_types` container:
10
+
11
+ ```ruby
12
+ require 'dry-initializer'
13
+
14
+ class User
15
+ extend Dry::Initializer
16
+
17
+ param :name
18
+ option :email, optional: true
19
+ option :telefon, optional: true, as: :phone
20
+ end
21
+
22
+ user = User.new "Andy", telefon: "71002003040"
23
+
24
+ User.dry_initializer.attributes(user)
25
+ # => { name: "Andy", phone: "71002003040" }
26
+ ```
27
+
28
+ What the method does is extracts *variables assigned* to the object (and skips unassigned ones like the `email` above). It doesn't matter whether you send it via `params` or `option`; we look at the result of the instantiation, not at the interface.
29
+
30
+ Method `public_attributes` works different. Let's look at the following example to see the difference:
31
+
32
+ ```ruby
33
+ require 'dry-initializer'
34
+
35
+ class User
36
+ extend Dry::Initializer
37
+
38
+ param :name
39
+ option :telefon, optional: true, as: :phone
40
+ option :email, optional: true
41
+ option :token, optional: true, reader: :private
42
+ option :password, optional: true, reader: false
43
+ end
44
+
45
+ user = User.new "Andy", telefon: "71002003040", token: "foo", password: "bar"
46
+
47
+ User.dry_initializer.attributes(user)
48
+ # => { name: "Andy", phone: "71002003040", token: "foo", password: "bar" }
49
+
50
+ User.dry_initializer.public_attributes(user)
51
+ # => { name: "Andy", phone: "71002003040", email: nil }
52
+ ```
53
+
54
+ Notice that `public_attribute` reads *public reader methods*, not variables. That's why it skips both the private `token`, and the `password` whose reader hasn't been defined.
55
+
56
+ Another difference concerns unassigned values. Because the reader `user.email` returns `nil` (its `@email` variable contains `Dry::Initializer::UNDEFINED` constant), the `public_attributes` adds this value to the hash using the method.
57
+
58
+ The third thing to mention is that you can override the reader, and it is the overriden method which will be used by `public_attributes`:
59
+
60
+ ```ruby
61
+ require 'dry-initializer'
62
+
63
+ class User
64
+ extend Dry::Initializer
65
+
66
+ param :name
67
+ option :password, optional: true
68
+
69
+ def password
70
+ super.hash.to_s
71
+ end
72
+ end
73
+
74
+ user = User.new "Joe", password: "foo"
75
+
76
+ User.dry_initializer.attributes(user)
77
+ # => { user: "Joe", password: "foo" }
78
+
79
+ User.dry_initializer.public_attributes(user)
80
+ # => { user: "Joe", password: "-1844874613000160009" }
81
+ ```
82
+
83
+ This feature works for the "extend Dry::Initializer" syntax. But what about "include Dry::Initializer.define ..."? Now we don't pollute class namespace with new methods, that's why `.dry_initializer` is absent.
84
+
85
+ To access config you can use a hack. Under the hood we define private instance method `#__dry_initializer_config__` which refers to the same container. So you can write:
86
+
87
+ ```ruby
88
+ require 'dry-initializer'
89
+
90
+ class User
91
+ extend Dry::Initializer
92
+ param :name
93
+ end
94
+
95
+ user = User.new "Joe"
96
+
97
+ user.send(:__dry_initializer_config__).attributes(user)
98
+ # => { user: "Joe" }
99
+
100
+ user.send(:__dry_initializer_config__).public_attributes(user)
101
+ # => { user: "Joe" }
102
+ ```
103
+
104
+ This is a hack because the `__dry_initializer_config__` is not a part of the gem's public interface; there's a possibility it can be changed or removed in the later releases.
105
+
106
+ We'll try to be careful with it, and mark it as deprecated method in case of such a removal.
@@ -0,0 +1,39 @@
1
+ ---
2
+ title: Container Version
3
+ layout: gem-single
4
+ name: dry-initializer
5
+ ---
6
+
7
+ Instead of extending a class with the `Dry::Initializer`, you can include a container with the `initializer` method only. This method should be preferred when you don't need subclassing.
8
+
9
+ ```ruby
10
+ require 'dry-initializer'
11
+
12
+ class User
13
+ # notice `-> do .. end` syntax
14
+ include Dry::Initializer.define -> do
15
+ param :name, proc(&:to_s)
16
+ param :role, default: proc { 'customer' }
17
+ option :admin, default: proc { false }
18
+ end
19
+ end
20
+ ```
21
+
22
+ If you still need the DSL (`param` and `option`) to be inherited, use the direct extension:
23
+
24
+ ```ruby
25
+ require 'dry-initializer'
26
+
27
+ class BaseService
28
+ extend Dry::Initializer
29
+ alias_method :dependency, :param
30
+ end
31
+
32
+ class ShowUser < BaseService
33
+ dependency :user
34
+
35
+ def call
36
+ puts user&.name
37
+ end
38
+ end
39
+ ```
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: Introduction & Usage
3
+ description: DSL for defining initializer params and options
4
+ layout: gem-single
5
+ order: 8
6
+ type: gem
7
+ name: dry-initializer
8
+ sections:
9
+ - container-version
10
+ - params-and-options
11
+ - options-tolerance
12
+ - optionals-and-defaults
13
+ - type-constraints
14
+ - readers
15
+ - inheritance
16
+ - skip-undefined
17
+ - attributes
18
+ - rails-support
19
+ ---
20
+
21
+ `dry-initializer` is a simple mixin of class methods `params` and `options` for instances.
22
+
23
+ ## Synopsis
24
+
25
+ ```ruby
26
+ require 'dry-initializer'
27
+
28
+ class User
29
+ extend Dry::Initializer
30
+
31
+ param :name, proc(&:to_s)
32
+ param :role, default: proc { 'customer' }
33
+ option :admin, default: proc { false }
34
+ option :phone, optional: true
35
+ end
36
+
37
+ user = User.new 'Vladimir', 'admin', admin: true
38
+
39
+ user.name # => 'Vladimir'
40
+ user.role # => 'admin'
41
+ user.admin # => true
42
+ user.phone # => nil
43
+ ```
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: Inheritance
3
+ layout: gem-single
4
+ name: dry-initializer
5
+ ---
6
+
7
+ Subclassing preserves all definitions being made inside a superclass.
8
+
9
+ ```ruby
10
+ require 'dry-initializer'
11
+
12
+ class User
13
+ extend Dry::Initializer
14
+
15
+ param :name
16
+ end
17
+
18
+ class Employee < User
19
+ param :position
20
+ end
21
+
22
+ employee = Employee.new('John', 'supercargo')
23
+ employee.name # => 'John'
24
+ employee.position # => 'supercargo'
25
+
26
+ employee = Employee.new # => fails because type
27
+ ```
28
+
29
+ You can override params and options.
30
+ Such overriding leaves initial order of params (positional arguments) unchanged:
31
+
32
+ ```ruby
33
+ class Employee < User
34
+ param :position, optional: true
35
+ param :name, default: proc { 'Unknown' }
36
+ end
37
+
38
+ user = User.new # => Boom! because User#name is required
39
+ employee = Employee.new # passes because who cares on employee's name
40
+
41
+ employee.name
42
+ # => 'Unknown' because it is the name that positioned first like in User
43
+ ```