dry-initializer 3.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  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 +34 -0
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  6. data/.github/workflows/custom_ci.yml +74 -0
  7. data/.github/workflows/docsite.yml +34 -0
  8. data/.github/workflows/sync_configs.yml +34 -0
  9. data/.gitignore +2 -0
  10. data/.rspec +2 -2
  11. data/.rubocop.yml +65 -27
  12. data/CHANGELOG.md +10 -3
  13. data/CODE_OF_CONDUCT.md +13 -0
  14. data/CONTRIBUTING.md +29 -0
  15. data/Gemfile +26 -17
  16. data/LICENSE +20 -0
  17. data/README.md +11 -12
  18. data/docsite/source/attributes.html.md +106 -0
  19. data/docsite/source/container-version.html.md +39 -0
  20. data/docsite/source/index.html.md +43 -0
  21. data/docsite/source/inheritance.html.md +43 -0
  22. data/docsite/source/optionals-and-defaults.html.md +130 -0
  23. data/docsite/source/options-tolerance.html.md +27 -0
  24. data/docsite/source/params-and-options.html.md +74 -0
  25. data/docsite/source/rails-support.html.md +101 -0
  26. data/docsite/source/readers.html.md +43 -0
  27. data/docsite/source/skip-undefined.html.md +59 -0
  28. data/docsite/source/type-constraints.html.md +160 -0
  29. data/dry-initializer.gemspec +1 -1
  30. data/lib/dry/initializer/config.rb +5 -5
  31. data/lib/dry/initializer/dispatchers.rb +2 -2
  32. data/lib/dry/initializer/mixin/root.rb +1 -0
  33. data/lib/dry/initializer/struct.rb +2 -3
  34. data/spec/nested_type_spec.rb +4 -0
  35. data/spec/spec_helper.rb +7 -0
  36. data/spec/type_argument_spec.rb +2 -2
  37. data/spec/type_constraint_spec.rb +1 -1
  38. data/spec/value_coercion_via_dry_types_spec.rb +1 -1
  39. metadata +24 -4
  40. data/.travis.yml +0 -28
@@ -0,0 +1,29 @@
1
+ # Issue Guidelines
2
+
3
+ ## Reporting bugs
4
+
5
+ If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
6
+
7
+ ## Reporting feature requests
8
+
9
+ Report a feature request **only after discussing it first on [discourse.dry-rb.org](https://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed.
10
+
11
+ ## Reporting questions, support requests, ideas, concerns etc.
12
+
13
+ **PLEASE DON'T** - use [discourse.dry-rb.org](http://discourse.dry-rb.org) instead.
14
+
15
+ # Pull Request Guidelines
16
+
17
+ A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
18
+
19
+ Other requirements:
20
+
21
+ 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
22
+ 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
23
+ 3) Add API documentation if it's a new feature
24
+ 4) Update API documentation if it changes an existing feature
25
+ 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
26
+
27
+ # Asking for help
28
+
29
+ If these guidelines aren't helpful, and you're stuck, please post a message on [discourse.dry-rb.org](https://discourse.dry-rb.org) or join [our chat](https://dry-rb.zulipchat.com).
data/Gemfile CHANGED
@@ -3,27 +3,36 @@ source "https://rubygems.org"
3
3
  # Specify your gem"s dependencies in dry-initializer.gemspec
4
4
  gemspec
5
5
 
6
- group :benchmarks do
7
- if RUBY_VERSION < "2.4"
8
- gem "activesupport", "< 5"
9
- else
10
- gem "activesupport"
11
- end
6
+ unless ENV['CI'].eql?('true')
7
+ group :benchmarks do
8
+ if RUBY_VERSION < "2.4"
9
+ gem "activesupport", "< 5"
10
+ else
11
+ gem "activesupport"
12
+ end
12
13
 
13
- gem "active_attr"
14
- gem "anima"
15
- gem "attr_extras"
16
- gem "benchmark-ips", "~> 2.5"
17
- gem "concord"
18
- gem "fast_attributes"
19
- gem "kwattr"
20
- gem "ruby-prof", platform: :mri
21
- gem "value_struct"
22
- gem "values"
23
- gem "virtus"
14
+ gem "active_attr"
15
+ gem "anima"
16
+ gem "attr_extras"
17
+ gem "benchmark-ips", "~> 2.5"
18
+ gem "concord"
19
+ gem "fast_attributes"
20
+ gem "kwattr"
21
+ gem "ruby-prof", platform: :mri
22
+ gem "value_struct"
23
+ gem "values"
24
+ gem "virtus"
25
+ end
24
26
  end
25
27
 
26
28
  group :development, :test do
27
29
  gem "pry", platform: :mri
28
30
  gem "pry-byebug", platform: :mri
31
+ if RUBY_VERSION >= "2.4"
32
+ gem 'ossy', git: 'https://github.com/solnic/ossy.git', branch: 'master', platform: :mri
33
+ end
34
+ end
35
+
36
+ group :test do
37
+ gem 'simplecov', require: false
29
38
  end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2019 dry-rb team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,20 +1,20 @@
1
- # dry-initializer [![Join the chat at https://gitter.im/dry-rb/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dry-rb/chat)
1
+ # dry-initializer [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/dry-initializer.svg)][gem]
4
- [![Build Status](https://travis-ci.org/dry-rb/dry-initializer.svg?branch=master)][travis]
4
+ [![Build Status](https://github.com/dry-rb/dry-initializer/workflows/ci/badge.svg)][ci]
5
5
  [![Code Climate](https://codeclimate.com/github/dry-rb/dry-initializer/badges/gpa.svg)][codeclimate]
6
- [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-initializer/badges/coverage.svg)][coveralls]
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/73cb64231f3fb2c86e26/test_coverage)][codeclimate]
7
7
  [![Inline docs](http://inch-ci.org/github/dry-rb/dry-initializer.svg?branch=master)][inchpages]
8
8
 
9
9
  [gem]: https://rubygems.org/gems/dry-initializer
10
- [travis]: https://travis-ci.org/dry-rb/dry-initializer
11
- [gemnasium]: https://gemnasium.com/dry-rb/dry-initializer
10
+ [ci]: https://github.com/dry-rb/dry-initializer/actions?query=workflow%3Aci
12
11
  [codeclimate]: https://codeclimate.com/github/dry-rb/dry-initializer
13
12
  [coveralls]: https://coveralls.io/r/dry-rb/dry-initializer
14
13
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-initializer
15
14
  [docs]: http://dry-rb.org/gems/dry-initializer/
16
15
  [benchmarks]: https://github.com/dry-rb/dry-initializer/wiki
17
16
  [license]: http://opensource.org/licenses/MIT
17
+ [chat]: https://dry-rb.zulipchat.com
18
18
 
19
19
  DSL for building class initializer with params and options.
20
20
 
@@ -77,14 +77,13 @@ Tested under rubies [compatible to MRI 2.3+](.travis.yml).
77
77
 
78
78
  ## Contributing
79
79
 
80
- * [Fork the project](https://github.com/dry-rb/dry-initializer)
81
- * Create your feature branch (`git checkout -b my-new-feature`)
82
- * Add tests for it
83
- * Commit your changes (`git commit -am '[UPDATE] Add some feature'`)
84
- * Push to the branch (`git push origin my-new-feature`)
85
- * Create a new Pull Request
80
+ - [Fork the project](https://github.com/dry-rb/dry-initializer)
81
+ - Create your feature branch (`git checkout -b my-new-feature`)
82
+ - Add tests for it
83
+ - Commit your changes (`git commit -am '[UPDATE] Add some feature'`)
84
+ - Push to the branch (`git push origin my-new-feature`)
85
+ - Create a new Pull Request
86
86
 
87
87
  ## License
88
88
 
89
89
  The gem is available as open source under the terms of the [MIT License][license].
90
-
@@ -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
+ ```
@@ -0,0 +1,130 @@
1
+ ---
2
+ title: Optional Attributes and Default Values
3
+ layout: gem-single
4
+ name: dry-initializer
5
+ ---
6
+
7
+ By default both params and options are mandatory. Use `:default` key to make them optional:
8
+
9
+ ```ruby
10
+ require 'dry-initializer'
11
+
12
+ class User
13
+ extend Dry::Initializer
14
+
15
+ param :name, default: proc { 'Unknown user' }
16
+ option :email, default: proc { 'unknown@example.com' }
17
+ option :phone, optional: true
18
+ end
19
+
20
+ user = User.new
21
+ user.name # => 'Unknown user'
22
+ user.email # => 'unknown@example.com'
23
+ user.phone # => Dry::Initializer::UNDEFINED
24
+
25
+ user = User.new 'Vladimir', email: 'vladimir@example.com', phone: '71234567788'
26
+ user.name # => 'Vladimir'
27
+ user.email # => 'vladimir@example.com'
28
+ user.phone # => '71234567788'
29
+ ```
30
+
31
+ You cannot define required **parameter** after optional one. The following example raises `SyntaxError` exception:
32
+
33
+ ```ruby
34
+ require 'dry-initializer'
35
+
36
+ class User
37
+ extend Dry::Initializer
38
+
39
+ param :name, default: proc { 'Unknown name' }
40
+ param :email # => #<SyntaxError ...>
41
+ end
42
+ ```
43
+
44
+ You should assign `nil` value explicitly. Otherwise an instance variable it will be left undefined. In both cases attribute reader method will return `nil`.
45
+
46
+ ```ruby
47
+ require 'dry-initializer'
48
+
49
+ class User
50
+ extend Dry::Initializer
51
+
52
+ param :name
53
+ option :email, optional: true
54
+ end
55
+
56
+ user = User.new 'Andrew'
57
+ user.email # => nil
58
+ user.instance_variable_get :@email
59
+ # => Dry::Initializer::UNDEFINED
60
+
61
+ user = User.new 'Andrew', email: nil
62
+ user.email # => nil
63
+ user.instance_variable_get :@email
64
+ # => nil
65
+ ```
66
+
67
+ You can also set `nil` as a default value:
68
+
69
+ ```ruby
70
+ require 'dry-initializer'
71
+
72
+ class User
73
+ extend Dry::Initializer
74
+
75
+ param :name
76
+ option :email, default: proc { nil }
77
+ end
78
+
79
+ user = User.new 'Andrew'
80
+ user.email # => nil
81
+ user.instance_variable_get :@email
82
+ # => nil
83
+ ```
84
+
85
+ You **must** wrap default values into procs.
86
+
87
+ If you need to **assign** proc as a default value, wrap it to another one:
88
+
89
+ ```ruby
90
+ require 'dry-initializer'
91
+
92
+ class User
93
+ extend Dry::Initializer
94
+
95
+ param :name_proc, default: proc { proc { 'Unknown user' } }
96
+ end
97
+
98
+ user = User.new
99
+ user.name_proc.call # => 'Unknown user'
100
+ ```
101
+
102
+ Proc will be executed in a scope of new instance. You can refer to other arguments:
103
+
104
+ ```ruby
105
+ require 'dry-initializer'
106
+
107
+ class User
108
+ extend Dry::Initializer
109
+
110
+ param :name
111
+ param :email, default: proc { "#{name.downcase}@example.com" }
112
+ end
113
+
114
+ user = User.new 'Andrew'
115
+ user.email # => 'andrew@example.com'
116
+ ```
117
+
118
+ **Warning**: when using lambdas instead of procs, don't forget an argument, required by [instance_eval][instance_eval] (you can skip in in a proc).
119
+
120
+ ```ruby
121
+ require 'dry-initializer'
122
+
123
+ class User
124
+ extend Dry::Initializer
125
+
126
+ param :name, default: -> (obj) { 'Dude' }
127
+ end
128
+ ```
129
+
130
+ [instance_eval]: http://ruby-doc.org/core-2.2.0/BasicObject.html#method-i-instance_eval