dry-initializer 3.0.2 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +260 -241
  3. data/LICENSE +1 -1
  4. data/README.md +18 -77
  5. data/dry-initializer.gemspec +34 -19
  6. data/lib/dry/initializer/builders/attribute.rb +78 -69
  7. data/lib/dry/initializer/builders/initializer.rb +56 -58
  8. data/lib/dry/initializer/builders/reader.rb +55 -47
  9. data/lib/dry/initializer/builders/signature.rb +29 -23
  10. data/lib/dry/initializer/builders.rb +9 -5
  11. data/lib/dry/initializer/config.rb +162 -158
  12. data/lib/dry/initializer/definition.rb +58 -54
  13. data/lib/dry/initializer/dispatchers/build_nested_type.rb +54 -40
  14. data/lib/dry/initializer/dispatchers/check_type.rb +45 -39
  15. data/lib/dry/initializer/dispatchers/prepare_default.rb +32 -25
  16. data/lib/dry/initializer/dispatchers/prepare_ivar.rb +13 -6
  17. data/lib/dry/initializer/dispatchers/prepare_optional.rb +14 -7
  18. data/lib/dry/initializer/dispatchers/prepare_reader.rb +29 -22
  19. data/lib/dry/initializer/dispatchers/prepare_source.rb +12 -5
  20. data/lib/dry/initializer/dispatchers/prepare_target.rb +44 -37
  21. data/lib/dry/initializer/dispatchers/unwrap_type.rb +21 -10
  22. data/lib/dry/initializer/dispatchers/wrap_type.rb +25 -17
  23. data/lib/dry/initializer/dispatchers.rb +48 -43
  24. data/lib/dry/initializer/dsl.rb +42 -34
  25. data/lib/dry/initializer/mixin/local.rb +19 -13
  26. data/lib/dry/initializer/mixin/root.rb +12 -7
  27. data/lib/dry/initializer/mixin.rb +17 -12
  28. data/lib/dry/initializer/struct.rb +34 -29
  29. data/lib/dry/initializer/undefined.rb +7 -1
  30. data/lib/dry/initializer/version.rb +7 -0
  31. data/lib/dry/initializer.rb +2 -0
  32. data/lib/dry-initializer.rb +2 -0
  33. data/lib/tasks/benchmark.rake +2 -0
  34. data/lib/tasks/profile.rake +4 -0
  35. metadata +25 -125
  36. data/.codeclimate.yml +0 -12
  37. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  38. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -34
  39. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  40. data/.github/workflows/custom_ci.yml +0 -74
  41. data/.github/workflows/docsite.yml +0 -34
  42. data/.github/workflows/sync_configs.yml +0 -34
  43. data/.gitignore +0 -12
  44. data/.rspec +0 -4
  45. data/.rubocop.yml +0 -89
  46. data/CODE_OF_CONDUCT.md +0 -13
  47. data/CONTRIBUTING.md +0 -29
  48. data/Gemfile +0 -38
  49. data/Guardfile +0 -5
  50. data/LICENSE.txt +0 -21
  51. data/Rakefile +0 -8
  52. data/benchmarks/compare_several_defaults.rb +0 -82
  53. data/benchmarks/plain_options.rb +0 -63
  54. data/benchmarks/plain_params.rb +0 -84
  55. data/benchmarks/with_coercion.rb +0 -71
  56. data/benchmarks/with_defaults.rb +0 -66
  57. data/benchmarks/with_defaults_and_coercion.rb +0 -59
  58. data/docsite/source/attributes.html.md +0 -106
  59. data/docsite/source/container-version.html.md +0 -39
  60. data/docsite/source/index.html.md +0 -43
  61. data/docsite/source/inheritance.html.md +0 -43
  62. data/docsite/source/optionals-and-defaults.html.md +0 -130
  63. data/docsite/source/options-tolerance.html.md +0 -27
  64. data/docsite/source/params-and-options.html.md +0 -74
  65. data/docsite/source/rails-support.html.md +0 -101
  66. data/docsite/source/readers.html.md +0 -43
  67. data/docsite/source/skip-undefined.html.md +0 -59
  68. data/docsite/source/type-constraints.html.md +0 -160
  69. data/spec/attributes_spec.rb +0 -38
  70. data/spec/coercion_of_nil_spec.rb +0 -25
  71. data/spec/custom_dispatchers_spec.rb +0 -35
  72. data/spec/custom_initializer_spec.rb +0 -30
  73. data/spec/default_values_spec.rb +0 -83
  74. data/spec/definition_spec.rb +0 -111
  75. data/spec/invalid_default_spec.rb +0 -13
  76. data/spec/list_type_spec.rb +0 -32
  77. data/spec/missed_default_spec.rb +0 -14
  78. data/spec/nested_type_spec.rb +0 -48
  79. data/spec/optional_spec.rb +0 -71
  80. data/spec/options_tolerance_spec.rb +0 -11
  81. data/spec/public_attributes_utility_spec.rb +0 -22
  82. data/spec/reader_spec.rb +0 -87
  83. data/spec/repetitive_definitions_spec.rb +0 -69
  84. data/spec/several_assignments_spec.rb +0 -41
  85. data/spec/spec_helper.rb +0 -29
  86. data/spec/subclassing_spec.rb +0 -49
  87. data/spec/type_argument_spec.rb +0 -35
  88. data/spec/type_constraint_spec.rb +0 -78
  89. data/spec/value_coercion_via_dry_types_spec.rb +0 -29
@@ -1,59 +0,0 @@
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
@@ -1,106 +0,0 @@
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.
@@ -1,39 +0,0 @@
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
- ```
@@ -1,43 +0,0 @@
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
- ```
@@ -1,43 +0,0 @@
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
- ```
@@ -1,130 +0,0 @@
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
@@ -1,27 +0,0 @@
1
- ---
2
- title: Tolerance to Unknown Options
3
- layout: gem-single
4
- name: dry-initializer
5
- ---
6
-
7
- By default the initializer is strict for params (positional arguments), expecting them to be defined explicitly.
8
-
9
- ```ruby
10
- require 'dry-initializer'
11
-
12
- class User
13
- extend Dry::Initializer
14
- end
15
-
16
- user = User.new 'Joe' # raises ArgumentError
17
- ```
18
-
19
- At the same time it is tolerant to unknown options. All unknown options are accepted, but ignored:
20
-
21
- ```ruby
22
- # It accepts undefined options...
23
- user = User.new name: 'Joe'
24
-
25
- # ...but ignores them
26
- user.respond_to? :name # => false
27
- ```
@@ -1,74 +0,0 @@
1
- ---
2
- title: Params and Options
3
- layout: gem-single
4
- name: dry-initializer
5
- ---
6
-
7
- Use `param` to define plain argument:
8
-
9
- ```ruby
10
- require 'dry-initializer'
11
-
12
- class User
13
- extend Dry::Initializer
14
-
15
- param :name
16
- param :email
17
- end
18
-
19
- user = User.new 'Andrew', 'andrew@email.com'
20
- user.name # => 'Andrew'
21
- user.email # => 'andrew@email.com'
22
- ```
23
-
24
- Use `option` to define named (hash) argument:
25
-
26
- ```ruby
27
- require 'dry-initializer'
28
-
29
- class User
30
- extend Dry::Initializer
31
-
32
- option :name
33
- option :email
34
- end
35
-
36
- user = User.new email: 'andrew@email.com', name: 'Andrew'
37
- user.name # => 'Andrew'
38
- user.email # => 'andrew@email.com'
39
- ```
40
-
41
- Options can be renamed using `:as` key:
42
-
43
- ```ruby
44
- require 'dry-initializer'
45
-
46
- class User
47
- extend Dry::Initializer
48
-
49
- option :name, as: :username
50
- end
51
-
52
- user = User.new name: "Joe"
53
- user.username # => "Joe"
54
- user.instance_variable_get :@username # => "Joe"
55
- user.instance_variable_get :@name # => nil
56
- user.respond_to? :name # => false
57
- ```
58
-
59
- You can also define several ways of initializing the same argument via different options:
60
-
61
- ```ruby
62
- require 'dry-initializer'
63
-
64
- class User
65
- extend Dry::Initializer
66
-
67
- option :phone
68
- option :telephone, as: :phone
69
- option :name, optional: true
70
- end
71
-
72
- User.new(phone: '1234567890').phone # => '1234567890'
73
- User.new(telephone: '1234567890').phone # => '1234567890'
74
- ```
@@ -1,101 +0,0 @@
1
- ---
2
- title: Rails Support
3
- layout: gem-single
4
- name: dry-initializer
5
- ---
6
-
7
- Rails plugin is implemented in a separate [dry-initializer-rails](https://github.com/nepalez/dry-initializer-rails) gem.
8
-
9
- It provides coercion of assigned values to corresponding ActiveRecord instances.
10
-
11
- ### Base Example
12
-
13
- Add the `:model` setting to `param` or `option`:
14
-
15
- ```ruby
16
- require 'dry-initializer-rails'
17
-
18
- class CreateOrder
19
- extend Dry::Initializer
20
-
21
- # Params and options
22
- param :customer, model: 'Customer' # use either a name
23
- option :product, model: Product # or a class
24
-
25
- def call
26
- Order.create customer: customer, product: product
27
- end
28
- end
29
- ```
30
-
31
- Now you can assign values as pre-initialized model instances:
32
-
33
- ```ruby
34
- customer = Customer.find(1)
35
- product = Product.find(2)
36
-
37
- order = CreateOrder.new(customer, product: product).call
38
- order.customer # => <Customer @id=1 ...>
39
- order.product # => <Product @id=2 ...>
40
- ```
41
-
42
- ...or their ids:
43
-
44
- ```ruby
45
- order = CreateOrder.new(1, product: 2).call
46
- order.customer # => <Customer @id=1 ...>
47
- order.product # => <Product @id=2 ...>
48
- ```
49
-
50
- The instance is envoked using method `find_by(id: ...)`.
51
- With wrong ids `nil` values are assigned to corresponding params and options:
52
-
53
- ```ruby
54
- order = CreateOrder.new(0, product: 0).call
55
- order.customer # => nil
56
- order.product # => nil
57
- ```
58
-
59
- ### Custom Keys
60
-
61
- You can specify custom `key` for searching model instance:
62
-
63
- ```ruby
64
- require 'dry-initializer-rails'
65
-
66
- class CreateOrder
67
- extend Dry::Initializer
68
-
69
- param :customer, model: 'User', find_by: 'name'
70
- option :product, model: Item, find_by: :name
71
- end
72
- ```
73
-
74
- This time you can send names (not ids) to the initializer:
75
-
76
- ```ruby
77
- order = CreateOrder.new('Andrew', product: 'the_thing_no_123').call
78
-
79
- order.customer # => <User @name='Andrew' ...>
80
- order.product # => <Item @name='the_thing_no_123' ...>
81
- ```
82
-
83
- ### Container Syntax
84
-
85
- If you prefer [container syntax](docs::container-version), extend plugin inside the block:
86
-
87
- ```ruby
88
- require 'dry-initializer-rails'
89
-
90
- class CreateOrder
91
- include Dry::Initializer.define -> do
92
- # ... params/options declarations
93
- end
94
- end
95
- ```
96
-
97
- ### Types vs Models
98
-
99
- [Type constraints](docs::type-constraints) are checked before the coercion.
100
-
101
- When mixing `:type` and `:model` settings for the same param/option, you should use [sum types](/gems/dry-types/1.2/sum) that accept both model instances and their attributes.
@@ -1,43 +0,0 @@
1
- ---
2
- title: Readers
3
- layout: gem-single
4
- name: dry-initializer
5
- ---
6
-
7
- By default public attribute reader is defined for every param and option.
8
-
9
- You can define private or protected reader instead:
10
-
11
- ```ruby
12
- require 'dry-initializer'
13
-
14
- class User
15
- extend Dry::Initializer
16
-
17
- param :name, reader: :private # the same as adding `private :name`
18
- param :email, reader: :protected # the same as adding `protected :email`
19
- end
20
- ```
21
-
22
- To skip any reader, use `reader: false`:
23
-
24
- ```ruby
25
- require 'dry-initializer'
26
-
27
- class User
28
- extend Dry::Initializer
29
-
30
- param :name
31
- param :email, reader: false
32
- end
33
-
34
- user = User.new 'Luke', 'luke@example.com'
35
- user.name # => 'Luke'
36
-
37
- user.email # => #<NoMethodError ...>
38
- user.instance_variable_get :@email # => 'luke@example.com'
39
- ```
40
-
41
- Notice that any other value except for `false`, `:protected` and `:private` provides a public reader.
42
-
43
- No writers are defined. Define them using pure ruby `attr_writer` when necessary.