dry-initializer 1.0.0 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c99b0349e94feff82082c286d57dab769c73e6c
4
- data.tar.gz: b4c23613507bd539f72dda1ae60b68ebf81024fd
3
+ metadata.gz: 852519c02a938d27a827cff9c4cfd1e40c9c9dac
4
+ data.tar.gz: 94923136d38c0afbc74ffa76ed35c4074c9ba75b
5
5
  SHA512:
6
- metadata.gz: 2a916174077ac5ebdd6894bf416c10e0b6802658f1c9f848dd3902890c2e5b9c000468a60cea0b17ba4545a16f8d5c39474fe05849b14f5d67caeb51b5f67ada
7
- data.tar.gz: d42931e206096f1dfd528675891ffbae169324f5c80a08204fb118d17c2ebce0c9a2daec91dfcb8f9063cd7172e0769603d75686ac60f6fed7613401e32e3b64
6
+ metadata.gz: c4195fe8176762d89b1593f05cde79e4c249dda71ace60fb070a75f92222f344d349cc97b3567e032d3aa67ac4556e97bc4282133fac724ea655decc1a4e95e7
7
+ data.tar.gz: 0d50b2012d31467424ff6c232c5c3d8fff0e5d601eacf533c23f080933205aaa384948b98cc0831496ec6ac30b38697bf99554ef8d79b3f04f0922ef10de0b7e
data/.rubocop.yml CHANGED
@@ -5,62 +5,24 @@ AllCops:
5
5
  StyleGuideCopsOnly: true
6
6
  TargetRubyVersion: 2.2
7
7
 
8
- Lint/HandleExceptions:
9
- Exclude:
10
- - spec/**/*_spec.rb
11
-
12
- Lint/RescueException:
13
- Exclude:
14
- - spec/**/*_spec.rb
15
-
16
- Metrics/LineLength:
17
- Max: 80
18
- Exclude:
19
- - spec/**/*_spec.rb
20
-
21
- Style/AccessorMethodName:
22
- Exclude:
23
- - spec/**/*_spec.rb
24
-
25
- Style/Alias:
26
- EnforcedStyle: prefer_alias_method
27
-
28
- Style/AsciiComments:
29
- Enabled: false
30
-
31
- Style/BlockDelimiters:
32
- Exclude:
33
- - spec/**/*_spec.rb
34
-
35
8
  Style/CaseEquality:
36
9
  Enabled: false
37
10
 
38
- Style/ClassAndModuleChildren:
39
- Enabled: false
40
-
41
- Style/Documentation:
11
+ Style/ClassVars:
42
12
  Enabled: false
43
13
 
44
14
  Style/DoubleNegation:
45
15
  Enabled: false
46
16
 
47
- Style/EmptyLinesAroundClassBody:
48
- Enabled: false
49
-
50
- Style/EmptyLinesAroundModuleBody:
51
- Enabled: false
52
-
53
- Style/EmptyLineBetweenDefs:
54
- Enabled: false
55
-
56
17
  Style/FileName:
57
- Enabled: false
18
+ Exclude:
19
+ - lib/dry-initializer.rb
58
20
 
59
21
  Style/Lambda:
60
22
  Exclude:
61
23
  - spec/**/*_spec.rb
62
24
 
63
- Style/ModuleFunction:
25
+ Style/LambdaCall:
64
26
  Enabled: false
65
27
 
66
28
  Style/RaiseArgs:
@@ -73,27 +35,8 @@ Style/Semicolon:
73
35
  Style/SignalException:
74
36
  EnforcedStyle: semantic
75
37
 
76
- Style/SingleLineBlockParams:
77
- Enabled: false
78
-
79
- Style/SingleLineMethods:
80
- Exclude:
81
- - spec/**/*_spec.rb
82
-
83
- Style/SpaceBeforeFirstArg:
84
- Enabled: false
85
-
86
- Style/SpecialGlobalVars:
87
- Exclude:
88
- - Gemfile
89
- - dry-initializer.gemspec
90
-
91
38
  Style/StringLiterals:
92
39
  EnforcedStyle: double_quotes
93
40
 
94
41
  Style/StringLiteralsInInterpolation:
95
42
  EnforcedStyle: double_quotes
96
-
97
- Style/TrivialAccessors:
98
- Exclude:
99
- - spec/**/*_spec.rb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## v1.1.0 2017-01-28
2
+
3
+ # Added:
4
+ - enhancement via `Dry::Initializer::Attribute.dispatchers` registry (nepalez)
5
+
6
+ # Register dispatcher for `:string` option
7
+ Dry::Initializer::Attribute.dispatchers << ->(string: nil, **op) do
8
+ string ? op.merge(type: proc(&:to_s)) : op
9
+ end
10
+
11
+ # Now you can use the `:string` key for `param` and `option`
12
+ class User
13
+ extend Dry::Initializer
14
+ param :name, string: true
15
+ end
16
+
17
+ User.new(:Andy).name # => "Andy"
18
+
19
+ # Internals:
20
+ - optimize assignments for performance (nepalez)
21
+
22
+ [Compare v1.0.0...v1.1.0](https://github.com/dry-rb/dry-initializer/compare/v1.0.0...v1.1.0)
23
+
1
24
  ## v1.0.0 2017-01-22
2
25
 
3
26
  In this version the code has been rewritten for simplicity
data/Gemfile CHANGED
@@ -21,6 +21,7 @@ group :benchmarks do
21
21
  gem "value_struct"
22
22
  gem "values"
23
23
  gem "virtus"
24
+ gem "ruby-prof"
24
25
  end
25
26
 
26
27
  group :development, :test do
data/Rakefile CHANGED
@@ -45,3 +45,9 @@ namespace :benchmark do
45
45
  system "ruby benchmarks/options.rb"
46
46
  end
47
47
  end
48
+
49
+ desc "Runs profiler"
50
+ task :profile do
51
+ system "ruby benchmarks/profiler.rb && " \
52
+ "dot -Tpng ./tmp/profile.dot > ./tmp/profile.png"
53
+ end
data/benchmarks/params.rb CHANGED
@@ -13,7 +13,7 @@ StructTest = Struct.new(:foo, :bar)
13
13
 
14
14
  require "dry-initializer"
15
15
  class DryTest
16
- extend Dry::Initializer::Mixin
16
+ extend Dry::Initializer
17
17
 
18
18
  param :foo
19
19
  param :bar
@@ -0,0 +1,28 @@
1
+ require "dry-initializer"
2
+ require "ruby-prof"
3
+ require "fileutils"
4
+
5
+ class User
6
+ extend Dry::Initializer
7
+
8
+ param :first_name, proc(&:to_s), default: proc { "Unknown" }
9
+ param :second_name, proc(&:to_s), default: proc { "Unknown" }
10
+ option :email, proc(&:to_s), optional: true
11
+ option :phone, proc(&:to_s), optional: true
12
+ end
13
+
14
+ result = RubyProf.profile do
15
+ 1_000.times { User.new :Andy, email: :"andy@example.com" }
16
+ end
17
+
18
+ FileUtils.mkdir_p "./tmp"
19
+
20
+ FileUtils.touch "./tmp/profile.dot"
21
+ File.open("./tmp/profile.dot", "w+") do |output|
22
+ RubyProf::DotPrinter.new(result).print(output, min_percent: 0)
23
+ end
24
+
25
+ FileUtils.touch "./tmp/profile.html"
26
+ File.open("./tmp/profile.html", "w+") do |output|
27
+ RubyProf::CallStackPrinter.new(result).print(output, min_percent: 0)
28
+ end
@@ -3,11 +3,9 @@ Bundler.require(:benchmarks)
3
3
  class PlainRubyTest
4
4
  attr_reader :foo, :bar
5
5
 
6
- def initialize(foo:, bar:)
7
- @foo = foo
8
- @bar = bar
9
- fail TypeError unless String === @foo
10
- fail TypeError unless String === @bar
6
+ def initialize(options)
7
+ @foo = options[:foo].to_s
8
+ @bar = options[:bar].to_s
11
9
  end
12
10
  end
13
11
 
@@ -3,9 +3,9 @@ Bundler.require(:benchmarks)
3
3
  class PlainRubyTest
4
4
  attr_reader :foo, :bar
5
5
 
6
- def initialize(foo:, bar:)
7
- @foo = foo
8
- @bar = bar
6
+ def initialize(options = {})
7
+ @foo = options[:foo]
8
+ @bar = options[:bar]
9
9
  end
10
10
  end
11
11
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "dry-initializer"
3
- gem.version = "1.0.0"
3
+ gem.version = "1.1.0"
4
4
  gem.author = ["Vladimir Kochnev (marshall-lee)", "Andrew Kozin (nepalez)"]
5
5
  gem.email = ["hashtable@yandex.ru", "andrew.kozin@gmail.com"]
6
6
  gem.homepage = "https://github.com/dryrb/dry-initializer"
@@ -1,8 +1,58 @@
1
1
  module Dry::Initializer
2
2
  # Contains definitions for a single attribute, and builds its parts of mixin
3
3
  class Attribute
4
+ class << self
5
+ # Collection of additional dispatchers for method options
6
+ #
7
+ # @example Enhance the gem by adding :coercer alias for type
8
+ # Dry::Initializer::Attribute.dispatchers << -> (string: nil, **op) do
9
+ # op[:type] = proc(&:to_s) if string
10
+ # op
11
+ # end
12
+ #
13
+ # class User
14
+ # extend Dry::Initializer
15
+ # param :name, string: true # same as `type: proc(&:to_s)`
16
+ # end
17
+ #
18
+ def dispatchers
19
+ @@dispatchers ||= []
20
+ end
21
+
22
+ def new(source, coercer = nil, **options)
23
+ options[:source] = source
24
+ options[:target] = options.delete(:as) || source
25
+ options[:type] ||= coercer
26
+ params = dispatchers.inject(options) { |h, m| m.call(h) }
27
+
28
+ super(params)
29
+ end
30
+
31
+ def param(*args)
32
+ Param.new(*args)
33
+ end
34
+
35
+ def option(*args)
36
+ Option.new(*args)
37
+ end
38
+ end
39
+
4
40
  attr_reader :source, :target, :coercer, :default, :optional, :reader
5
41
 
42
+ def initialize(options)
43
+ @source = options[:source]
44
+ @target = options[:target]
45
+ @coercer = options[:type]
46
+ @default = options[:default]
47
+ @optional = !!(options[:optional] || @default)
48
+ @reader = options.fetch(:reader, :public)
49
+ validate
50
+ end
51
+
52
+ def ==(other)
53
+ source == other.source
54
+ end
55
+
6
56
  # definition for the getter method
7
57
  def getter
8
58
  return unless reader
@@ -18,16 +68,6 @@ module Dry::Initializer
18
68
 
19
69
  private
20
70
 
21
- def initialize(source, coercer = nil, **options)
22
- @source = source
23
- @target = options.fetch(:as, source)
24
- @coercer = coercer || options[:type]
25
- @reader = options.fetch(:reader, :public)
26
- @default = options[:default]
27
- @optional = !!(options[:optional] || @default)
28
- validate
29
- end
30
-
31
71
  def validate
32
72
  validate_target
33
73
  validate_default
@@ -1,12 +1,12 @@
1
1
  module Dry::Initializer
2
2
  class Builder
3
3
  def param(*args)
4
- @params = insert(@params, Param, *args)
4
+ @params = insert(@params, Attribute.param(*args))
5
5
  validate_collections
6
6
  end
7
7
 
8
8
  def option(*args)
9
- @options = insert(@options, Option, *args)
9
+ @options = insert(@options, Attribute.option(*args))
10
10
  validate_collections
11
11
  end
12
12
 
@@ -25,16 +25,9 @@ module Dry::Initializer
25
25
  @options = []
26
26
  end
27
27
 
28
- def insert(collection, klass, source, *args)
29
- index = collection.index { |option| option.source == source.to_s }
30
-
31
- if index
32
- new_item = klass.new(source, *args)
33
- collection.dup.tap { |list| list[index] = new_item }
34
- else
35
- new_item = klass.new(source, *args)
36
- collection + [new_item]
37
- end
28
+ def insert(collection, new_item)
29
+ index = collection.index(new_item) || collection.count
30
+ collection.dup.tap { |list| list[index] = new_item }
38
31
  end
39
32
 
40
33
  def code
@@ -56,26 +49,30 @@ module Dry::Initializer
56
49
  @params + @options
57
50
  end
58
51
 
52
+ def duplications
53
+ attributes.group_by(&:target)
54
+ .reject { |_, val| val.count == 1 }
55
+ .keys
56
+ end
57
+
59
58
  def initializer_signatures
60
59
  sig = @params.map(&:initializer_signature).compact.uniq
61
- sig << (@options.any? ? "**__options__" : "__options__ = {}")
60
+ sig << (sig.any? && @options.any? ? "**__options__" : "__options__ = {}")
62
61
  sig.join(", ")
63
62
  end
64
63
 
65
64
  def initializer_presetters
66
- attributes.map(&:initializer_presetter)
67
- .compact
68
- .uniq
69
- .map { |line| " #{line}" }
70
- .join("\n")
65
+ dups = duplications
66
+ attributes
67
+ .map { |a| " #{a.presetter}" if dups.include? a.target }
68
+ .compact.uniq.join("\n")
71
69
  end
72
70
 
73
71
  def initializer_setters
74
- attributes.map(&:initializer_setter)
75
- .compact
76
- .uniq
77
- .map { |text| text.lines.map { |line| " #{line}" }.join }
78
- .join("\n")
72
+ dups = duplications
73
+ attributes.map do |a|
74
+ dups.include?(a.target) ? " #{a.safe_setter}" : " #{a.fast_setter}"
75
+ end.compact.uniq.join("\n")
79
76
  end
80
77
 
81
78
  def getters
@@ -5,14 +5,19 @@ module Dry::Initializer
5
5
  "**__options__"
6
6
  end
7
7
 
8
- # part of __initializer__ body
9
- def initializer_presetter
10
- "@#{target} = Dry::Initializer::UNDEFINED"
8
+ # parts of __initalizer__
9
+ def presetter
10
+ "@#{target} = Dry::Initializer::UNDEFINED" if dispensable?
11
11
  end
12
12
 
13
- # part of __initializer__ body
14
- def initializer_setter
15
- "#{setter_part}#{maybe_optional}"
13
+ def safe_setter
14
+ "@#{target} = #{safe_coerced}#{maybe_optional}"
15
+ end
16
+
17
+ def fast_setter
18
+ return safe_setter unless dispensable?
19
+ "@#{target} = __options__.key?(:'#{source}') ? #{safe_coerced} : " \
20
+ "Dry::Initializer::UNDEFINED"
16
21
  end
17
22
 
18
23
  # part of __defaults__
@@ -22,33 +27,35 @@ module Dry::Initializer
22
27
 
23
28
  # part of __coercers__
24
29
  def coercer_hash
25
- coercer ? { :"option_#{source}" => coercer } : {}
30
+ return {} unless coercer
31
+ value = proc { |v| (v == Dry::Initializer::UNDEFINED) ? v : coercer.(v) }
32
+ { :"option_#{source}" => value }
26
33
  end
27
34
 
28
35
  private
29
36
 
30
- def maybe_optional
31
- " if __options__.key? :'#{source}'" if optional && !default
37
+ def dispensable?
38
+ optional && !default
32
39
  end
33
40
 
34
- def setter_part
35
- "@#{target} = #{maybe_coerced}"
41
+ def maybe_optional
42
+ " if __options__.key? :'#{source}'" if dispensable?
36
43
  end
37
44
 
38
- def maybe_coerced
39
- return maybe_default unless coercer
40
- "__coercers__[:'option_#{source}'].call(#{maybe_default})"
45
+ def safe_coerced
46
+ return safe_default unless coercer
47
+ "__coercers__[:'option_#{source}'].call(#{safe_default})"
41
48
  end
42
49
 
43
- def maybe_default
44
- "__options__.fetch(:'#{source}') { #{default_part} }"
50
+ def safe_default
51
+ "__options__.fetch(:'#{source}')#{default_part}"
45
52
  end
46
53
 
47
54
  def default_part
48
55
  if default
49
- "instance_eval(&__defaults__[:'option_#{source}'])"
50
- else
51
- "raise ArgumentError, \"option :'#{source}' is required\""
56
+ " { instance_eval(&__defaults__[:'option_#{source}']) }"
57
+ elsif !optional
58
+ " { raise ArgumentError, \"option :'#{source}' is required\" }"
52
59
  end
53
60
  end
54
61
  end
@@ -5,14 +5,17 @@ module Dry::Initializer
5
5
  optional ? "#{target} = Dry::Initializer::UNDEFINED" : target
6
6
  end
7
7
 
8
- # part of __initializer__ body
9
- def initializer_presetter; end
8
+ # parts of __initalizer__
9
+ def presetter; end
10
10
 
11
- # part of __initializer__ body
12
- def initializer_setter
11
+ def safe_setter
13
12
  "@#{target} = #{maybe_coerced}"
14
13
  end
15
14
 
15
+ def fast_setter
16
+ safe_setter
17
+ end
18
+
16
19
  # part of __defaults__
17
20
  def default_hash
18
21
  default ? { :"param_#{target}" => default } : {}
@@ -20,7 +23,9 @@ module Dry::Initializer
20
23
 
21
24
  # part of __coercers__
22
25
  def coercer_hash
23
- coercer ? { :"param_#{target}" => coercer } : {}
26
+ return {} unless coercer
27
+ value = proc { |v| (v == Dry::Initializer::UNDEFINED) ? v : coercer.(v) }
28
+ { :"param_#{target}" => value }
24
29
  end
25
30
 
26
31
  private
@@ -0,0 +1,18 @@
1
+ describe "gem enhancement" do
2
+ before do
3
+ Dry::Initializer::Attribute.dispatchers << ->(string: false, **op) do
4
+ op[:type] = proc(&:to_s) if string
5
+ op
6
+ end
7
+
8
+ class Test::Foo
9
+ extend Dry::Initializer
10
+ param :bar, string: true
11
+ end
12
+ end
13
+
14
+ it "works" do
15
+ foo = Test::Foo.new(:BAZ)
16
+ expect(foo.bar).to eq "BAZ"
17
+ end
18
+ end
@@ -0,0 +1,41 @@
1
+ describe "attribute with several assignments" do
2
+ before do
3
+ class Test::Foo
4
+ extend Dry::Initializer::Mixin
5
+
6
+ option :bar, proc(&:to_s), optional: true
7
+ option :"some foo", as: :bar, optional: true
8
+ end
9
+ end
10
+
11
+ context "when not defined" do
12
+ subject { Test::Foo.new }
13
+
14
+ it "is left undefined" do
15
+ expect(subject.bar).to be_nil
16
+ expect(subject.instance_variable_get :@bar)
17
+ .to eq Dry::Initializer::UNDEFINED
18
+ end
19
+ end
20
+
21
+ context "when set directly" do
22
+ subject { Test::Foo.new bar: :BAZ }
23
+
24
+ it "sets the attribute" do
25
+ expect(subject.bar).to eq "BAZ"
26
+ end
27
+ end
28
+
29
+ context "when renamed" do
30
+ subject { Test::Foo.new "some foo": :BAZ }
31
+
32
+ it "renames the attribute" do
33
+ expect(subject.bar).to eq :BAZ
34
+ expect(subject).not_to respond_to :foo
35
+ end
36
+
37
+ it "renames the variable" do
38
+ expect(subject.instance_variable_get(:@bar)).to eq :BAZ
39
+ end
40
+ end
41
+ end
@@ -28,20 +28,21 @@ describe "type constraint" do
28
28
  context "if optional value not set" do
29
29
  subject { Test::Foo.new }
30
30
 
31
- it "applies type constraint to Dry::Initializer::UNDEFINED" do
32
- expect { subject }.to raise_error Dry::Types::ConstraintError
31
+ it "not applicable to Dry::Initializer::UNDEFINED" do
32
+ expect(subject.instance_variable_get(:@foo))
33
+ .to eq Dry::Initializer::UNDEFINED
33
34
  end
34
35
  end
35
36
  end
36
37
 
37
38
  context "by invalid constraint" do
38
39
  it "raises TypeError" do
39
- expect {
40
+ expect do
40
41
  class Test::Foo
41
42
  extend Dry::Initializer::Mixin
42
43
  param :foo, type: String
43
44
  end
44
- }.to raise_error(TypeError)
45
+ end.to raise_error(TypeError)
45
46
  end
46
47
  end
47
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-initializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Kochnev (marshall-lee)
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-01-22 00:00:00.000000000 Z
12
+ date: 2017-01-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -91,6 +91,7 @@ files:
91
91
  - benchmarks/options.rb
92
92
  - benchmarks/params.rb
93
93
  - benchmarks/params_vs_options.rb
94
+ - benchmarks/profiler.rb
94
95
  - benchmarks/several_defaults.rb
95
96
  - benchmarks/with_defaults.rb
96
97
  - benchmarks/with_types.rb
@@ -111,14 +112,15 @@ files:
111
112
  - spec/custom_initializer_spec.rb
112
113
  - spec/default_nil_spec.rb
113
114
  - spec/default_values_spec.rb
115
+ - spec/enhancement_spec.rb
114
116
  - spec/invalid_default_spec.rb
115
117
  - spec/missed_default_spec.rb
116
118
  - spec/optional_spec.rb
117
119
  - spec/options_tolerance_spec.rb
118
120
  - spec/options_var_spec.rb
119
121
  - spec/reader_spec.rb
120
- - spec/renaming_options_spec.rb
121
122
  - spec/repetitive_definitions_spec.rb
123
+ - spec/several_assignments_spec.rb
122
124
  - spec/spec_helper.rb
123
125
  - spec/subclassing_spec.rb
124
126
  - spec/type_argument_spec.rb
@@ -154,14 +156,15 @@ test_files:
154
156
  - spec/custom_initializer_spec.rb
155
157
  - spec/default_nil_spec.rb
156
158
  - spec/default_values_spec.rb
159
+ - spec/enhancement_spec.rb
157
160
  - spec/invalid_default_spec.rb
158
161
  - spec/missed_default_spec.rb
159
162
  - spec/optional_spec.rb
160
163
  - spec/options_tolerance_spec.rb
161
164
  - spec/options_var_spec.rb
162
165
  - spec/reader_spec.rb
163
- - spec/renaming_options_spec.rb
164
166
  - spec/repetitive_definitions_spec.rb
167
+ - spec/several_assignments_spec.rb
165
168
  - spec/spec_helper.rb
166
169
  - spec/subclassing_spec.rb
167
170
  - spec/type_argument_spec.rb
@@ -1,20 +0,0 @@
1
- describe "renaming options" do
2
- before do
3
- class Test::Foo
4
- extend Dry::Initializer::Mixin
5
-
6
- option :"some foo", as: :bar
7
- end
8
- end
9
-
10
- subject { Test::Foo.new "some foo": :BAZ }
11
-
12
- it "renames the attribute" do
13
- expect(subject.bar).to eq :BAZ
14
- expect(subject).not_to respond_to :foo
15
- end
16
-
17
- it "renames the variable" do
18
- expect(subject.instance_variable_get(:@bar)).to eq :BAZ
19
- end
20
- end