dry-initializer 1.0.0 → 1.1.0

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