dry-initializer 0.11.0 → 2.5.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +8 -0
  3. data/.gitignore +1 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +14 -62
  6. data/.travis.yml +15 -11
  7. data/CHANGELOG.md +538 -158
  8. data/Gemfile +2 -2
  9. data/LICENSE.txt +1 -1
  10. data/README.md +6 -6
  11. data/Rakefile +2 -41
  12. data/benchmarks/{several_defaults.rb → compare_several_defaults.rb} +4 -4
  13. data/benchmarks/{without_options.rb → plain_options.rb} +21 -10
  14. data/benchmarks/{params.rb → plain_params.rb} +20 -9
  15. data/benchmarks/{with_types.rb → with_coercion.rb} +23 -16
  16. data/benchmarks/with_defaults.rb +19 -8
  17. data/benchmarks/{with_types_and_defaults.rb → with_defaults_and_coercion.rb} +21 -12
  18. data/dry-initializer.gemspec +4 -4
  19. data/lib/dry/initializer/builders/attribute.rb +81 -0
  20. data/lib/dry/initializer/builders/initializer.rb +61 -0
  21. data/lib/dry/initializer/builders/reader.rb +50 -0
  22. data/lib/dry/initializer/builders/signature.rb +32 -0
  23. data/lib/dry/initializer/builders.rb +7 -0
  24. data/lib/dry/initializer/config.rb +172 -0
  25. data/lib/dry/initializer/definition.rb +116 -0
  26. data/lib/dry/initializer/dispatchers.rb +44 -0
  27. data/lib/dry/initializer/dsl.rb +43 -0
  28. data/lib/dry/initializer/mixin/local.rb +19 -0
  29. data/lib/dry/initializer/mixin/root.rb +10 -0
  30. data/lib/dry/initializer/mixin.rb +8 -70
  31. data/lib/dry/initializer.rb +49 -12
  32. data/lib/tasks/benchmark.rake +41 -0
  33. data/lib/tasks/profile.rake +78 -0
  34. data/spec/attributes_spec.rb +38 -0
  35. data/spec/coercion_of_nil_spec.rb +25 -0
  36. data/spec/custom_dispatchers_spec.rb +35 -0
  37. data/spec/custom_initializer_spec.rb +1 -1
  38. data/spec/default_values_spec.rb +8 -8
  39. data/spec/definition_spec.rb +107 -0
  40. data/spec/invalid_default_spec.rb +2 -2
  41. data/spec/missed_default_spec.rb +3 -3
  42. data/spec/optional_spec.rb +35 -8
  43. data/spec/options_tolerance_spec.rb +1 -1
  44. data/spec/public_attributes_utility_spec.rb +22 -0
  45. data/spec/reader_spec.rb +11 -11
  46. data/spec/repetitive_definitions_spec.rb +41 -21
  47. data/spec/several_assignments_spec.rb +41 -0
  48. data/spec/spec_helper.rb +7 -0
  49. data/spec/subclassing_spec.rb +7 -3
  50. data/spec/type_argument_spec.rb +1 -1
  51. data/spec/type_constraint_spec.rb +38 -7
  52. data/spec/value_coercion_via_dry_types_spec.rb +12 -4
  53. metadata +37 -40
  54. data/benchmarks/options.rb +0 -54
  55. data/benchmarks/params_vs_options.rb +0 -35
  56. data/lib/dry/initializer/builder.rb +0 -100
  57. data/lib/dry/initializer/errors/default_value_error.rb +0 -6
  58. data/lib/dry/initializer/errors/order_error.rb +0 -7
  59. data/lib/dry/initializer/errors/plugin_error.rb +0 -6
  60. data/lib/dry/initializer/errors/redefinition_error.rb +0 -5
  61. data/lib/dry/initializer/errors/type_constraint_error.rb +0 -5
  62. data/lib/dry/initializer/errors.rb +0 -10
  63. data/lib/dry/initializer/plugins/base.rb +0 -47
  64. data/lib/dry/initializer/plugins/default_proc.rb +0 -28
  65. data/lib/dry/initializer/plugins/signature.rb +0 -28
  66. data/lib/dry/initializer/plugins/type_constraint.rb +0 -21
  67. data/lib/dry/initializer/plugins/variable_setter.rb +0 -30
  68. data/lib/dry/initializer/plugins.rb +0 -10
  69. data/lib/dry/initializer/signature.rb +0 -61
  70. data/spec/base_spec.rb +0 -21
  71. data/spec/container_spec.rb +0 -45
  72. data/spec/default_nil_spec.rb +0 -17
  73. data/spec/plugin_registry_spec.rb +0 -45
  74. data/spec/renaming_options_spec.rb +0 -20
@@ -0,0 +1,172 @@
1
+ module Dry::Initializer
2
+ #
3
+ # Gem-related configuration of some class
4
+ #
5
+ class Config
6
+ # @!attribute [r] null
7
+ # @return [Dry::Initializer::UNDEFINED, nil] value of unassigned variable
8
+
9
+ # @!attribute [r] extended_class
10
+ # @return [Class] the class whose config collected by current object
11
+
12
+ # @!attribute [r] parent
13
+ # @return [Dry::Initializer::Config] parent configuration
14
+
15
+ # @!attribute [r] definitions
16
+ # @return [Hash<Symbol, Dry::Initializer::Definition>]
17
+ # hash of attribute definitions with their source names
18
+
19
+ attr_reader :null, :extended_class, :parent, :definitions
20
+
21
+ # @!attribute [r] mixin
22
+ # @return [Module] reference to the module to be included into class
23
+ def mixin
24
+ @mixin ||= Module.new.tap do |mod|
25
+ __dry_initializer__ = self
26
+ mod.extend(Mixin::Local)
27
+ mod.send :define_method, :__dry_initializer_config__ do
28
+ __dry_initializer__
29
+ end
30
+ mod.send :private, :__dry_initializer_config__
31
+ end
32
+ end
33
+
34
+ # List of configs of all subclasses of the [#extended_class]
35
+ # @return [Array<Dry::Initializer::Config>]
36
+ def children
37
+ @children ||= Set.new
38
+ end
39
+
40
+ # List of definitions for initializer params
41
+ # @return [Array<Dry::Initializer::Definition>]
42
+ def params
43
+ definitions.values.reject(&:option)
44
+ end
45
+
46
+ # List of definitions for initializer options
47
+ # @return [Array<Dry::Initializer::Definition>]
48
+ def options
49
+ definitions.values.select(&:option)
50
+ end
51
+
52
+ # Adds or redefines a parameter
53
+ # @param [Symbol] name
54
+ # @param [#call, nil] type (nil)
55
+ # @option opts [Proc] :default
56
+ # @option opts [Boolean] :optional
57
+ # @option opts [Symbol] :as
58
+ # @option opts [true, false, :protected, :public, :private] :reader
59
+ # @return [self] itself
60
+ def param(name, type = nil, **opts)
61
+ add_definition(false, name, type, opts)
62
+ end
63
+
64
+ # Adds or redefines an option of [#dry_initializer]
65
+ #
66
+ # @param (see #param)
67
+ # @option (see #param)
68
+ # @return (see #param)
69
+ #
70
+ def option(name, type = nil, **opts)
71
+ add_definition(true, name, type, opts)
72
+ end
73
+
74
+ # The hash of public attributes for an instance of the [#extended_class]
75
+ # @param [Dry::Initializer::Instance] instance
76
+ # @return [Hash<Symbol, Object>]
77
+ def public_attributes(instance)
78
+ definitions.values.each_with_object({}) do |item, obj|
79
+ key = item.target
80
+ next unless instance.respond_to? key
81
+ val = instance.send(key)
82
+ obj[key] = val unless null == val
83
+ end
84
+ end
85
+
86
+ # The hash of assigned attributes for an instance of the [#extended_class]
87
+ # @param [Dry::Initializer::Instance] instance
88
+ # @return [Hash<Symbol, Object>]
89
+ def attributes(instance)
90
+ definitions.values.each_with_object({}) do |item, obj|
91
+ key = item.target
92
+ val = instance.send(:instance_variable_get, item.ivar)
93
+ obj[key] = val unless null == val
94
+ end
95
+ end
96
+
97
+ # Code of the `#__initialize__` method
98
+ # @return [String]
99
+ def code
100
+ Builders::Initializer[self]
101
+ end
102
+
103
+ # Finalizes config
104
+ # @return [self]
105
+ def finalize
106
+ @definitions = final_definitions
107
+ check_order_of_params
108
+ mixin.class_eval(code)
109
+ children.each(&:finalize)
110
+ self
111
+ end
112
+
113
+ # Human-readable representation of configured params and options
114
+ # @return [String]
115
+ def inch
116
+ line = Builders::Signature[self]
117
+ line = line.gsub("__dry_initializer_options__", "options")
118
+ lines = ["@!method initialize(#{line})"]
119
+ lines += ["Initializes an instance of #{extended_class}"]
120
+ lines += definitions.values.map(&:inch)
121
+ lines += ["@return [#{extended_class}]"]
122
+ lines.join("\n")
123
+ end
124
+
125
+ private
126
+
127
+ def initialize(extended_class = nil, null: UNDEFINED)
128
+ @extended_class = extended_class.tap { |klass| klass&.include mixin }
129
+ sklass = extended_class&.superclass
130
+ @parent = sklass.dry_initializer if sklass.is_a? Dry::Initializer
131
+ @null = null || parent&.null
132
+ @definitions = {}
133
+ finalize
134
+ end
135
+
136
+ def add_definition(option, name, type, opts)
137
+ definition = Definition.new(option, null, name, type, Dispatchers[opts])
138
+ definitions[definition.source] = definition
139
+ finalize
140
+
141
+ mixin.class_eval definition.code
142
+ end
143
+
144
+ def final_definitions
145
+ parent_definitions = Hash(parent&.definitions&.dup)
146
+ definitions.each_with_object(parent_definitions) do |(key, val), obj|
147
+ obj[key] = check_type(obj[key], val)
148
+ end
149
+ end
150
+
151
+ def check_type(previous, current)
152
+ return current unless previous
153
+ return current if previous.option == current.option
154
+ raise SyntaxError,
155
+ "cannot reload #{previous} of #{extended_class.superclass}" \
156
+ " by #{current} of its subclass #{extended_class}"
157
+ end
158
+
159
+ def check_order_of_params
160
+ params.inject(nil) do |optional, current|
161
+ if current.optional
162
+ current
163
+ elsif optional
164
+ raise SyntaxError, "#{extended_class}: required #{current}" \
165
+ " goes after optional #{optional}"
166
+ else
167
+ optional
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,116 @@
1
+ module Dry::Initializer
2
+ #
3
+ # @private
4
+ # @abstract
5
+ #
6
+ # Base class for parameter or option definitions
7
+ # Defines methods to add corresponding reader to the class,
8
+ # and build value of instance attribute.
9
+ #
10
+ class Definition
11
+ attr_reader :option, :null, :source, :target, :ivar,
12
+ :type, :optional, :default, :reader,
13
+ :desc
14
+
15
+ def options
16
+ {
17
+ as: target,
18
+ type: type,
19
+ optional: optional,
20
+ default: default,
21
+ reader: reader,
22
+ desc: desc
23
+ }.reject { |_, value| value.nil? }
24
+ end
25
+
26
+ def name
27
+ @name ||= (option ? "option" : "parameter") << " '#{source}'"
28
+ end
29
+ alias to_s name
30
+ alias to_str name
31
+ alias inspect name
32
+
33
+ def ==(other)
34
+ other.instance_of?(self.class) && (other.source == source)
35
+ end
36
+
37
+ def code
38
+ Builders::Reader[self]
39
+ end
40
+
41
+ def inch
42
+ @inch ||= (option ? "@option" : "@param ").tap do |text|
43
+ text << " [Object]"
44
+ text << (option ? " :#{source}" : " #{source}")
45
+ text << (optional ? " (optional)" : " (required)")
46
+ text << " #{desc}" if desc
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def initialize(option, null, source, coercer = nil, **options)
53
+ @option = !!option
54
+ @null = null
55
+ @source = source.to_sym
56
+ @target = check_target options.fetch(:as, source).to_sym
57
+ @ivar = :"@#{target}"
58
+ @type = check_type(coercer || options[:type])
59
+ @reader = prepare_reader options.fetch(:reader, true)
60
+ @default = check_default options[:default]
61
+ @optional = options.fetch(:optional, @default)
62
+ @desc = options[:desc]&.to_s&.capitalize
63
+ end
64
+
65
+ def check_source(value)
66
+ if RESERVED.include? value
67
+ raise ArgumentError, "Name #{value} is reserved by dry-initializer gem"
68
+ end
69
+
70
+ unless option || value[ATTRIBUTE]
71
+ raise ArgumentError, "Invalid parameter name :'#{value}'"
72
+ end
73
+
74
+ value
75
+ end
76
+
77
+ def check_target(value)
78
+ return value if value[ATTRIBUTE]
79
+ raise ArgumentError, "Invalid variable name :'#{value}'"
80
+ end
81
+
82
+ def check_type(value)
83
+ return if value.nil?
84
+ arity = value.arity if value.is_a? Proc
85
+ arity ||= value.method(:call).arity if value.respond_to? :call
86
+ return value if [1, 2].include? arity.to_i.abs
87
+ raise TypeError,
88
+ "type of #{inspect} should respond to #call with 1..2 arguments"
89
+ end
90
+
91
+ def check_default(value)
92
+ return if value.nil?
93
+ return value if value.is_a?(Proc) && value.arity < 1
94
+ raise TypeError,
95
+ "default value of #{inspect} should be a proc without params"
96
+ end
97
+
98
+ def prepare_reader(value)
99
+ case value.to_s
100
+ when "", "false" then false
101
+ when "private" then :private
102
+ when "protected" then :protected
103
+ else :public
104
+ end
105
+ end
106
+
107
+ ATTRIBUTE = /\A\w+\z/
108
+ RESERVED = %i[
109
+ __dry_initializer_options__
110
+ __dry_initializer_config__
111
+ __dry_initializer_value__
112
+ __dry_initializer_definition__
113
+ __dry_initializer_initializer__
114
+ ].freeze
115
+ end
116
+ end
@@ -0,0 +1,44 @@
1
+ module Dry::Initializer
2
+ #
3
+ # @private
4
+ #
5
+ # Dispatchers allow adding syntax sugar to `.param` and `.option` methods.
6
+ #
7
+ # Every dispatcher should convert the source hash of options into
8
+ # the resulting hash so that you can send additional keys to the helpers.
9
+ #
10
+ # @example Add special dispatcher
11
+ #
12
+ # # Define a dispatcher for key :integer
13
+ # dispatcher = proc do |opts|
14
+ # opts.merge(type: proc(&:to_i)) if opts[:integer]
15
+ # end
16
+ #
17
+ # # Register a dispatcher
18
+ # Dry::Initializer::Dispatchers << dispatcher
19
+ #
20
+ # # Now you can use option `integer: true` instead of `type: proc(&:to_i)`
21
+ # class Foo
22
+ # extend Dry::Initializer
23
+ # param :id, integer: true
24
+ # end
25
+ #
26
+ module Dispatchers
27
+ class << self
28
+ def <<(item)
29
+ list << item
30
+ self
31
+ end
32
+
33
+ def [](options)
34
+ list.inject(options) { |opts, item| item.call(opts) }
35
+ end
36
+
37
+ private
38
+
39
+ def list
40
+ @list ||= []
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ module Dry::Initializer
2
+ # Module-level DSL
3
+ module DSL
4
+ # Setting for null (undefined value)
5
+ # @return [nil, Dry::Initializer::UNDEFINED]
6
+ attr_reader :null
7
+
8
+ # Returns a version of the module with custom settings
9
+ # @option settings [Boolean] :undefined
10
+ # If unassigned params and options should be treated different from nil
11
+ # @return [Dry::Initializer]
12
+ def [](undefined: true, **)
13
+ null = (undefined == false) ? nil : UNDEFINED
14
+ Module.new.tap do |mod|
15
+ mod.extend DSL
16
+ mod.include self
17
+ mod.send(:instance_variable_set, :@null, null)
18
+ end
19
+ end
20
+
21
+ # Returns mixin module to be included to target class by hand
22
+ # @return [Module]
23
+ # @yield proc defining params and options
24
+ def define(procedure = nil, &block)
25
+ config = Config.new(null: null)
26
+ config.instance_exec(&(procedure || block))
27
+ config.mixin.include Mixin::Root
28
+ config.mixin
29
+ end
30
+
31
+ private
32
+
33
+ def extended(klass)
34
+ config = Config.new(klass, null: null)
35
+ klass.send :instance_variable_set, :@dry_initializer, config
36
+ klass.include Mixin::Root
37
+ end
38
+
39
+ def self.extended(mod)
40
+ mod.instance_variable_set :@null, UNDEFINED
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ module Dry::Initializer::Mixin
2
+ # @private
3
+ module Local
4
+ attr_reader :klass
5
+
6
+ def inspect
7
+ "Dry::Initializer::Mixin::Local[#{klass}]"
8
+ end
9
+ alias to_s inspect
10
+ alias to_str inspect
11
+
12
+ private
13
+
14
+ def included(klass)
15
+ @klass = klass
16
+ super
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module Dry::Initializer::Mixin
2
+ # @private
3
+ module Root
4
+ private
5
+
6
+ def initialize(*args)
7
+ __dry_initializer_initialize__(*args)
8
+ end
9
+ end
10
+ end
@@ -1,77 +1,15 @@
1
1
  module Dry::Initializer
2
- # Class-level DSL for the initializer
2
+ # @private
3
3
  module Mixin
4
- # Declares a plain argument
5
- #
6
- # @param [#to_sym] name
7
- #
8
- # @option options [Object] :default The default value
9
- # @option options [#call] :type The type constraings via `dry-types`
10
- # @option options [Boolean] :reader (true) Whether to define attr_reader
11
- #
12
- # @return [self] itself
13
- #
14
- def param(name, type = nil, **options)
15
- options[:type] = type if type
16
- options[:option] = false
17
- @__initializer_builder__ = __initializer_builder__.define(name, **options)
18
- __initializer_builder__.call(__initializer_mixin__)
19
- end
20
-
21
- # Declares a named argument
22
- #
23
- # @param (see #param)
24
- # @option (see #param)
25
- # @return (see #param)
26
- #
27
- def option(name, type = nil, **options)
28
- options[:type] = type if type
29
- options[:option] = true
30
- @__initializer_builder__ = __initializer_builder__.define(name, **options)
31
- __initializer_builder__.call(__initializer_mixin__)
32
- end
33
-
34
- # Adds new plugin to the builder
35
- #
36
- # @param [Dry::Initializer::Plugins::Base] plugin
37
- # @return [self] itself
38
- #
39
- def register_initializer_plugin(plugin)
40
- @__initializer_builder__ = __initializer_builder__.register(plugin)
41
- __initializer_builder__.call(__initializer_mixin__)
42
- end
43
-
44
- private
45
-
46
- def __initializer_mixin__
47
- @__initializer_mixin__ ||= Module.new do
48
- def initialize(*args)
49
- __initialize__(*args)
50
- end
51
- end
52
- end
53
-
54
- def __initializer_builder__
55
- @__initializer_builder__ ||= Builder.new
56
- end
57
-
58
- def inherited(klass)
59
- new_builder = __initializer_builder__.dup
60
- klass.instance_variable_set :@__initializer_builder__, new_builder
61
-
62
- new_mixin = Module.new
63
- new_builder.call(new_mixin)
64
- klass.instance_variable_set :@__initializer_mixin__, new_mixin
65
- klass.include new_mixin
66
-
4
+ extend DSL # @deprecated
5
+ include Dry::Initializer # @deprecated
6
+ def self.extended(klass) # @deprecated
7
+ warn "[DEPRECATED] Use Dry::Initializer instead of its alias" \
8
+ " Dry::Initializer::Mixin. The later will be removed in v2.1.0"
67
9
  super
68
10
  end
69
11
 
70
- def self.extended(klass)
71
- super
72
- mixin = klass.send(:__initializer_mixin__)
73
- klass.send(:__initializer_builder__).call(mixin)
74
- klass.include mixin
75
- end
12
+ require_relative "mixin/root"
13
+ require_relative "mixin/local"
76
14
  end
77
15
  end
@@ -1,22 +1,59 @@
1
+ require "set"
2
+
3
+ # Namespace for gems in a dry-rb community
1
4
  module Dry
2
- # Declares arguments of the initializer (params and options)
3
5
  #
4
- # @api public
6
+ # DSL for declaring params and options of class initializers
5
7
  #
6
8
  module Initializer
7
- require_relative "initializer/errors"
8
- require_relative "initializer/plugins"
9
- require_relative "initializer/signature"
10
- require_relative "initializer/builder"
9
+ # Singleton for unassigned values
10
+ UNDEFINED = Object.new.freeze
11
+
12
+ require_relative "initializer/dsl"
13
+ require_relative "initializer/definition"
14
+ require_relative "initializer/builders"
15
+ require_relative "initializer/config"
11
16
  require_relative "initializer/mixin"
17
+ require_relative "initializer/dispatchers"
12
18
 
13
- UNDEFINED = Object.new.freeze
19
+ # Adds methods [.[]] and [.define]
20
+ extend DSL
21
+
22
+ # Gem-related configuration
23
+ # @return [Dry::Initializer::Config]
24
+ def dry_initializer
25
+ @dry_initializer ||= Config.new(self)
26
+ end
27
+
28
+ # Adds or redefines a parameter of [#dry_initializer]
29
+ # @param [Symbol] name
30
+ # @param [#call, nil] type (nil)
31
+ # @option opts [Proc] :default
32
+ # @option opts [Boolean] :optional
33
+ # @option opts [Symbol] :as
34
+ # @option opts [true, false, :protected, :public, :private] :reader
35
+ # @return [self] itself
36
+ def param(name, type = nil, **opts)
37
+ dry_initializer.param(name, type, Dispatchers[opts])
38
+ self
39
+ end
40
+
41
+ # Adds or redefines an option of [#dry_initializer]
42
+ # @param (see #param)
43
+ # @option (see #param)
44
+ # @return (see #param)
45
+ def option(name, type = nil, **opts)
46
+ dry_initializer.option(name, type, Dispatchers[opts])
47
+ self
48
+ end
49
+
50
+ private
14
51
 
15
- def self.define(proc = nil, &block)
16
- Module.new do |container|
17
- container.extend Mixin
18
- container.instance_exec(&(proc || block))
19
- end
52
+ def inherited(klass)
53
+ super
54
+ config = Config.new(klass, null: dry_initializer.null)
55
+ klass.send(:instance_variable_set, :@dry_initializer, config)
56
+ dry_initializer.children << config
20
57
  end
21
58
  end
22
59
  end
@@ -0,0 +1,41 @@
1
+ namespace :benchmark do
2
+ desc "Runs benchmarks for plain params"
3
+ task :plain_params do
4
+ system "ruby benchmarks/plain_params.rb"
5
+ end
6
+
7
+ desc "Runs benchmarks for plain options"
8
+ task :plain_options do
9
+ system "ruby benchmarks/plain_options.rb"
10
+ end
11
+
12
+ desc "Runs benchmarks for value coercion"
13
+ task :with_coercion do
14
+ system "ruby benchmarks/with_coercion.rb"
15
+ end
16
+
17
+ desc "Runs benchmarks with defaults"
18
+ task :with_defaults do
19
+ system "ruby benchmarks/with_defaults.rb"
20
+ end
21
+
22
+ desc "Runs benchmarks with defaults and coercion"
23
+ task :with_defaults_and_coercion do
24
+ system "ruby benchmarks/with_defaults_and_coercion.rb"
25
+ end
26
+
27
+ desc "Runs benchmarks for several defaults"
28
+ task :compare_several_defaults do
29
+ system "ruby benchmarks/with_several_defaults.rb"
30
+ end
31
+ end
32
+
33
+ desc "Runs all benchmarks"
34
+ task benchmark: %i[
35
+ benchmark:plain_params
36
+ benchmark:plain_options
37
+ benchmark:with_coercion
38
+ benchmark:with_defaults
39
+ benchmark:with_defaults_and_coercion
40
+ benchmark:compare_several_defaults
41
+ ]
@@ -0,0 +1,78 @@
1
+ namespace :profile do
2
+ def profile(name, execution, &definition)
3
+ require "dry-initializer"
4
+ require "ruby-prof"
5
+ require "fileutils"
6
+
7
+ definition.call
8
+ result = RubyProf.profile do
9
+ 1_000.times { execution.call }
10
+ end
11
+
12
+ FileUtils.mkdir_p "./tmp"
13
+
14
+ FileUtils.touch "./tmp/#{name}.dot"
15
+ File.open("./tmp/#{name}.dot", "w+") do |output|
16
+ RubyProf::DotPrinter.new(result).print(output, min_percent: 0)
17
+ end
18
+
19
+ FileUtils.touch "./tmp/#{name}.html"
20
+ File.open("./tmp/#{name}.html", "w+") do |output|
21
+ RubyProf::CallStackPrinter.new(result).print(output, min_percent: 0)
22
+ end
23
+
24
+ system "dot -Tpng ./tmp/#{name}.dot > ./tmp/#{name}.png"
25
+ end
26
+
27
+ desc "Profiles initialization with required param and option"
28
+ task :required do
29
+ profile("required", -> { User.new :Andy, email: "andy@example.com" }) do
30
+ class User
31
+ extend Dry::Initializer
32
+ param :name
33
+ option :email
34
+ end
35
+ end
36
+ end
37
+
38
+ desc "Profiles initialization with default param and option"
39
+ task :defaults do
40
+ profile("defaults", -> { User.new }) do
41
+ class User
42
+ extend Dry::Initializer
43
+ param :name, default: -> { :Andy }
44
+ option :email, default: -> { "andy@example.com" }
45
+ end
46
+ end
47
+ end
48
+
49
+ desc "Profiles initialization with coerced param and option"
50
+ task :coercion do
51
+ profile("coercion", -> { User.new :Andy, email: :"andy@example.com" }) do
52
+ class User
53
+ extend Dry::Initializer
54
+ param :name, proc(&:to_s)
55
+ option :email, proc(&:to_s)
56
+ end
57
+ end
58
+ end
59
+
60
+ desc "Profiles initialization with coerced defaults of param and option"
61
+ task :default_coercion do
62
+ profile("default_coercion", -> { User.new }) do
63
+ class User
64
+ extend Dry::Initializer
65
+ param :name, proc(&:to_s), default: -> { :Andy }
66
+ option :email, proc(&:to_s), default: -> { :"andy@example.com" }
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ desc "Makes all profiling at once"
73
+ task profile: %i[
74
+ profile:required
75
+ profile:defaults
76
+ profile:coercion
77
+ profile:default_coercion
78
+ ]