dry-initializer 2.4.0 → 3.0.3

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