declarative_initialization 0.1.1 → 0.2.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
  SHA256:
3
- metadata.gz: 3e75bb2a0843cd7164493d2acedd4c50671ad63a010b0a2ca0061ec85caa2238
4
- data.tar.gz: fd8829609b1523653b8b33820e04d28714c75a08e19056b555dd35e43d8a2cbd
3
+ metadata.gz: 0e2dbea81aa3125522d94fde6f67206de5d3c828fefd7c1001a4b5b37a77cdae
4
+ data.tar.gz: 39f96acbc627c8da767dc864a4bc983795788bc3e27a67a375270252d81936dc
5
5
  SHA512:
6
- metadata.gz: 0bf42818fb4f780f28fdaa2a9da06db4f1c7332ef9393cb93d676a875459b63efebc904c267a0cb7815593b17123003763b3c30de97a314fd928d45a72f4a067
7
- data.tar.gz: 5b3dc48637e3b8b8bdf226e06185da514c0ebb6b35c7d3aaacc6364d9342ef0f694e0df47052640986333aa6ee1533b1449814efe80f56d203f2dc569947e7e1
6
+ metadata.gz: 379472ad15222200b17f1550501d2b2e2db1807d3c76e73da59e789014582b9609bed23f1699b77bb2a8c41e5d673c9dd055c3f528130559a34599a5dffa1287
7
+ data.tar.gz: acafc11dd1d73ab41c2401de250c185dc2a71c0826c8a262f0692fa8f0b4d376264efae117cbddf216c23e4b3d29b0b1f9b37cf255e6460ad6ed561fa6feeee0
data/.rubocop.yml CHANGED
@@ -22,6 +22,7 @@ Metrics/BlockLength:
22
22
  Enabled: false
23
23
 
24
24
  Metrics/MethodLength:
25
+ Max: 15
25
26
  AllowedMethods: [initialize, initialize_with]
26
27
 
27
28
  Metrics/AbcSize:
data/CHANGELOG.md CHANGED
@@ -1,9 +1,33 @@
1
1
  ## [Unreleased]
2
2
 
3
+ * N/A
4
+
5
+ ## [0.2.0] - 2026-02-19
6
+
7
+ ### Changed
8
+ - `initialize_with` readers are now always defined, even if a method with the same name already exists. This makes `foo` consistently return the init-arg value.
9
+
10
+ ### Breaking
11
+ - Previously, if a method `#foo` existed (on the class or an ancestor), the gem skipped defining the reader and logged a warning; callers had to use `@foo` to access the init-arg. Now the reader is defined and overrides the existing method.
12
+
13
+ ### Added
14
+ - Optional override warnings in Rails development/test, or when logger level is `DEBUG`.
15
+
16
+ ### Fixed
17
+ - No warning on Rails reload when the existing method was originally defined by this gem.
18
+ - No warning when a subclass re-declares an attribute already declared by an ancestor’s `initialize_with`.
19
+ - Duplicate common mutable default values (`Array`, `Hash`, `Set`, `String`) per instance when the caller omits that keyword, preventing accidental cross-instance mutation. Copy is shallow; caller-provided values are not duplicated.
20
+
21
+
3
22
  ## [0.1.1] - 2025-05-02
4
- - Refactor internals
5
- - [BUGFIX] Only trigger `attr_reader` creation on initial class load (vs on every call to `#new`)
6
23
 
24
+ ### Changed
25
+ - Refactor internals.
26
+
27
+ ### Fixed
28
+ - Only define `attr_reader`s on initial class setup (avoid re-defining on each call to `.new`).
7
29
 
8
30
  ## [0.1.0] - 2025-03-05
9
- - Initial release
31
+
32
+ ### Added
33
+ - Initial release.
data/CODE_OF_CONDUCT.md CHANGED
@@ -39,7 +39,7 @@ This Code of Conduct applies within all community spaces, and also applies when
39
39
 
40
40
  ## Enforcement
41
41
 
42
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at kali@teamshares.com. All complaints will be reviewed and investigated promptly and fairly.
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at oss@teamshares.com. All complaints will be reviewed and investigated promptly and fairly.
43
43
 
44
44
  All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
45
 
data/README.md CHANGED
@@ -1,106 +1,175 @@
1
1
  # DeclarativeInitialization
2
2
 
3
- Boilerplate slows down devs and irritates everyone, plus the added cruft makes it harder to scan for the actual logic in a given file.
3
+ Declare a class’s keyword inputs once and get a keyword-only `initialize` with assignments, readers, and helpful argument errors.
4
4
 
5
- This is a small layer to support declarative initialization _specifically for simple keyword-based classes_.
5
+ - **Keyword-only initializer**: rejects positional args and unknown keywords
6
+ - **Assignments**: sets `@keyword` instance variables from declared inputs
7
+ - **Readers**: defines `attr_reader` for each input (and a `block` reader)
8
+ - **Defaults**: supports optional keywords with default values
9
+ - **No dependencies**: plain Ruby (\(>= 3.0\))
6
10
 
7
- ## Usage
11
+ ## When to use it
12
+
13
+ Use this when you have small POROs that take keyword inputs and you’re tired of repeating the same initializer boilerplate:
14
+
15
+ - **Service / command objects** that take dependencies and parameters
16
+ - **Value objects** with a fixed set of attributes
17
+ - **Configuration objects** with a handful of optional flags
18
+ - **Components / presenters** that accept a stable set of inputs
19
+
20
+ If you need complex inheritance initialization, multiple initializer “shapes”, or highly dynamic defaults, a handwritten `initialize` may be clearer.
8
21
 
9
- Given a standard ruby class like so:
22
+ ## Installation
23
+
24
+ Add to your Gemfile:
10
25
 
11
26
  ```ruby
12
- class SomeObject
13
- def initialize(foo:, bar:, baz: "default value")
14
- @foo = foo
15
- @bar = bar
16
- @baz = baz
17
- end
27
+ gem "declarative_initialization"
28
+ ```
29
+
30
+ Then install:
31
+
32
+ ```bash
33
+ bundle install
34
+ ```
35
+
36
+ In non-Bundler contexts, require it directly:
37
+
38
+ ```ruby
39
+ require "declarative_initialization"
40
+ ```
41
+
42
+ ## Quick start
18
43
 
19
- attr_reader :foo, :bar, :baz
44
+ ```ruby
45
+ class UserGreeter
46
+ include DeclarativeInitialization
47
+
48
+ initialize_with :user
49
+
50
+ def call
51
+ "Hello, #{user.name}!"
52
+ end
20
53
  end
54
+
55
+ UserGreeter.new(user: current_user).call
56
+ # => "Hello, Alice!"
57
+
58
+ UserGreeter.new
59
+ # ArgumentError: [UserGreeter] Missing keyword argument(s): user
60
+
61
+ UserGreeter.new(user: current_user, extra: true)
62
+ # ArgumentError: [UserGreeter] Unknown keyword argument(s): extra
21
63
  ```
22
64
 
23
- With this library it can be simplified to:
65
+ ## Usage
66
+
67
+ ### Required vs optional keywords (defaults)
68
+
69
+ Declare required keywords as symbols, and optional keywords as keyword arguments:
24
70
 
25
71
  ```ruby
26
- class SomeObject
72
+ class Search
27
73
  include DeclarativeInitialization
28
74
 
29
- initialize_with :foo, :bar, baz: "default value"
75
+ initialize_with :query, limit: 10, order: :desc
76
+
77
+ def call
78
+ results = perform_search(query).take(limit)
79
+ order == :desc ? results.reverse : results
80
+ end
30
81
  end
82
+
83
+ Search.new(query: "ruby").call
84
+ Search.new(query: "ruby", limit: 50).call
31
85
  ```
32
- ## Quick note on naming
33
86
 
34
- The gem name is `declarative_initialization` because there's already a very outdated gem claiming the `initialize_with` name.
87
+ ### Post-initialize hook
35
88
 
36
- We've set up an alias, however, so you can do either `include DeclarativeInitialization` _or_ `include InitializeWith`.
89
+ Pass a block to `initialize_with` to run code after assignments. The block runs in the instance context.
37
90
 
38
- FWIW in practice at Teamshares we just `include DeclarativeInitialization` in the base class for all our View Components.
91
+ ```ruby
92
+ class Rectangle
93
+ include DeclarativeInitialization
94
+
95
+ initialize_with :width, :height do
96
+ raise ArgumentError, "Dimensions must be positive" if width <= 0 || height <= 0
97
+ @area = width * height
98
+ end
99
+
100
+ attr_reader :area
101
+ end
102
+ ```
39
103
 
40
- ### Custom logic
104
+ ### Capturing a block passed to `.new`
41
105
 
42
- Sometimes the existing `initialize` method also does other work, for instance setting initial values for additional instance variables that aren't passed in directly.
106
+ If the caller passes a block to `.new`, it’s stored in `@block` and available via the `block` reader.
43
107
 
44
- We support that by passing an optional block to `initialize_with` -- for instance, in the example above if the original version also set `@bang = foo * bar`, we could support that by changing the updated version to:
108
+ ```ruby
109
+ class Wrapper
110
+ include DeclarativeInitialization
45
111
 
46
- ```ruby
47
- initialize_with :foo, :bar, baz: "default value" do
48
- @bang = @foo * @bar
112
+ initialize_with :tag
113
+
114
+ def render
115
+ "<#{tag}>#{block&.call}</#{tag}>"
49
116
  end
50
- ```
117
+ end
118
+
119
+ Wrapper.new(tag: "div") { "Content" }.render
120
+ # => "<div>Content</div>"
121
+ ```
122
+
123
+ ## Behavior notes / gotchas
51
124
 
52
- ### Edge cases
125
+ ### Only keyword arguments are accepted
53
126
 
54
- * Accepting a block: this is handled automatically -- if a block was provided to the Foo.new call, it'll be made available as `@block`/`attr_reader :block`
127
+ The generated initializer is keyword-only. Passing positional arguments raises an `ArgumentError`.
55
128
 
56
- * If a method with same name already exists, we log a warning and do not create the `attr_reader`. In that case you'll need to reference the instance variable directly.
129
+ ### Readers are public by default
57
130
 
58
- * Because of this, best practice when referencing variables in the post-initialize block is to use `@foo` rather than relying on the `foo` attr_reader
131
+ Inputs are exposed with `attr_reader`. If you prefer private readers, make them private after the declaration:
59
132
 
60
- * Due to ruby syntax limitations, we do not support referencing other fields directly in the declaration:
133
+ ```ruby
134
+ class Example
135
+ include DeclarativeInitialization
136
+ initialize_with :user, admin: false
61
137
 
62
- * Does _not_ work:
63
- ```ruby
64
- initialize_with :user, company: user.employer
65
- ```
66
- * Workaround:
67
- ```ruby
68
- initialize_with :user, company: nil do
69
- @company ||= @user.employer
70
- end
71
- ```
138
+ private :user, :admin
139
+ end
140
+ ```
72
141
 
73
- * If using `initialize_with` on a subclass where the superclass defines `initialize`, we will _not_ automatically call `super`, because if we do we get this `RuntimeError`:
74
- > implicit argument passing of super from method defined by define_method() is not supported. Specify all arguments explicitly.
142
+ ### Defaults are literal values
75
143
 
76
- * If you need to call `super` from the block passed into `initialize_with` (unusual edge case, subclass requires different arguments than parent):
144
+ Defaults are applied when the caller omits that keyword. For common mutable defaults (`Array`, `Hash`, `Set`, `String`), the value is duplicated per instance (shallow). If you need deeper setup (or derived values), use the post-initialize block.
77
145
 
78
- * Does _not_ work (due to `instance_exec` changing execution context but _not_ the method lookup chain):
79
- ```ruby
80
- initialize_with :foo do
81
- super(bar: 123)
82
- end
83
- ```
84
- * Workaround _possible_ (but really, probably more understandable to just fall back to manually writing `def initialize`):
85
- ```ruby
86
- initialize_with :foo do
87
- parent_initialize = method(:initialize).super_method
88
- parent_initialize.call(bar: 123)
89
- end
90
- ```
146
+ ### Method name conflicts
91
147
 
92
- * If you find yourself backed into a weird corner, just use a plain ole `def initialize`! This library is meant to make the easy cases less work, but there's no requirement that you must use it for every super complex case you run into. :)
148
+ `initialize_with` defines readers for each declared input (and `block`). If a method with the same name already exists, it will be overridden.
93
149
 
94
- ## Development
150
+ In Rails development/test (or when your logger level allows it), the gem logs a warning when it overrides an existing method.
95
151
 
96
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
152
+ ### Referencing other inputs in defaults
97
153
 
98
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
154
+ You can’t reference one declared input from another input’s default at declaration time:
99
155
 
100
- ## Contributing
156
+ ```ruby
157
+ initialize_with :user, account: user.account # user is not available here
158
+ ```
101
159
 
102
- Bug reports and pull requests are welcome on GitHub at https://github.com/teamshares/declarative_initialization. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/teamshares/declarative_initialization/blob/main/CODE_OF_CONDUCT.md).
160
+ Use the post-initialize block instead:
103
161
 
104
- ## Code of Conduct
162
+ ```ruby
163
+ initialize_with :user, account: nil do
164
+ @account ||= user.account
165
+ end
166
+ ```
167
+
168
+ ### Inheritance and `super`
169
+
170
+ `initialize_with` generates an `initialize` method. If a subclass calls `initialize_with`, it replaces the parent initializer and **does not call `super`**. Prefer a single initializer per hierarchy, or avoid this gem for complex inheritance chains.
171
+
172
+ ## Contributing
105
173
 
106
- Everyone interacting in the DeclarativeInitialization project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/teamshares/declarative_initialization/blob/main/CODE_OF_CONDUCT.md).
174
+ - **Source**: [teamshares/declarative_initialization](https://github.com/teamshares/declarative_initialization)
175
+ - **Code of conduct**: [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md)
data/Rakefile CHANGED
@@ -10,3 +10,7 @@ require "rubocop/rake_task"
10
10
  RuboCop::RakeTask.new
11
11
 
12
12
  task default: %i[spec rubocop]
13
+
14
+ # Require default to pass before release. This relies on the default gem release task
15
+ # (from bundler/gem_tasks) depending on "build"; default runs before build, so before push.
16
+ Rake::Task["build"].enhance([:default])
@@ -1,74 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "logger"
3
+ require "set"
4
4
 
5
5
  module DeclarativeInitialization
6
6
  module ClassMethods
7
7
  # Defines an initializer expecting the specified keyword arguments.
8
8
  # @param args [Array<Symbol>] Required keyword arguments
9
- # @param kwargs [Hash<Symbol, Object>] Optional keyword arguments (required, but have default values)
10
- # @param post_initialize_block [Proc] Block to execute after initialization (optional)
11
- def initialize_with(*args, **kwargs, &post_initialize_block)
9
+ # @param kwargs [Hash<Symbol, Object>] Optional keyword arguments with default values
10
+ # @param post_initialize [Proc] Block to execute after initialization (optional)
11
+ def initialize_with(*args, **kwargs, &post_initialize)
12
12
  declared = args + kwargs.keys
13
- _validate_arguments!(declared)
14
-
15
- _set_up_attribute_readers(declared)
16
- _set_up_block_reader
17
- _define_initializer(declared, kwargs, post_initialize_block)
13
+ Internal.validate_arguments!(self, declared)
14
+ declared.each { |key| _define_reader(key) }
15
+ _define_reader(:block, block_reader: true)
16
+ _define_generated_initializer(declared, kwargs, post_initialize)
18
17
  end
19
18
 
20
19
  private
21
20
 
22
- def _class_name
23
- name || "Anonymous Class"
21
+ def _declared_readers
22
+ @_declared_readers ||= Set.new
24
23
  end
25
24
 
26
- def _logger
27
- @_logger ||= if defined?(Rails) && Rails.respond_to?(:logger)
28
- Rails.logger
29
- else
30
- logger = Logger.new($stdout)
31
- logger.level = Logger::WARN
32
- logger
33
- end
25
+ def _ancestor_declared_reader?(key)
26
+ ancestors.drop(1).any? do |ancestor|
27
+ ancestor.instance_variable_get(:@_declared_readers)&.include?(key)
28
+ end
34
29
  end
35
30
 
36
- def _validate_arguments!(declared)
37
- return if declared.all? { |arg| arg.is_a?(Symbol) }
31
+ def _define_reader(key, block_reader: false)
32
+ return if _declared_readers.include?(key)
33
+ return if _ancestor_declared_reader?(key)
38
34
 
39
- raise ArgumentError, "[#{_class_name}] All arguments to #initialize_with must be symbols"
40
- end
41
-
42
- def _set_up_attribute_readers(declared)
43
- declared.each do |key|
44
- if method_defined?(key)
45
- _logger.warn "[#{_class_name}] Method ##{key} already exists -- skipping attr_reader generation"
46
- else
47
- attr_reader key
48
- end
49
- end
50
- end
35
+ Internal.warn_override(self, key, block_reader: block_reader) if method_defined?(key)
51
36
 
52
- def _set_up_block_reader
53
- if method_defined?(:block)
54
- _logger.warn "[#{_class_name}] Method #block already exists -- may NOT be able to reference a block " \
55
- "passed to #new as #block (use @block instead)"
56
- else
57
- attr_reader :block
58
- end
37
+ _declared_readers.add(key)
38
+ attr_reader key
59
39
  end
60
40
 
61
- def _define_initializer(declared, defaults, post_initialize_block)
41
+ def _define_generated_initializer(declared, defaults, post_initialize)
62
42
  define_method(:initialize) do |*given_args, **given_kwargs, &given_block|
63
- class_name = self.class.name || "Anonymous Class"
64
- _validate_initialization_arguments!(class_name, given_args, given_kwargs, declared, defaults)
43
+ Internal.validate_initialization_arguments!(self.class, given_args, given_kwargs, declared, defaults)
65
44
 
66
- declared.each do |key|
67
- instance_variable_set(:"@#{key}", given_kwargs.fetch(key, defaults[key]))
45
+ defaults.each do |key, value|
46
+ next if given_kwargs.key?(key)
47
+
48
+ instance_variable_set(:"@#{key}", Internal.copy_default(value))
68
49
  end
69
50
 
51
+ given_kwargs.each { |key, value| instance_variable_set(:"@#{key}", value) }
52
+
70
53
  instance_variable_set(:@block, given_block) if given_block
71
- instance_exec(&post_initialize_block) if post_initialize_block
54
+ instance_exec(&post_initialize) if post_initialize
72
55
  end
73
56
  end
74
57
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "set"
5
+
6
+ module DeclarativeInitialization
7
+ # Internal helpers that don't need to be injected into user classes.
8
+ # All methods are module functions - stateless and callable as Internal.method_name
9
+ module Internal
10
+ module_function
11
+
12
+ def logger
13
+ @logger ||= if defined?(Rails) && Rails.respond_to?(:logger)
14
+ Rails.logger
15
+ else
16
+ Logger.new($stdout).tap { |l| l.level = Logger::WARN }
17
+ end
18
+ end
19
+
20
+ def display_name(klass)
21
+ klass.name || "Anonymous Class"
22
+ end
23
+
24
+ def format_message(klass, message)
25
+ "[#{display_name(klass)}] #{message}"
26
+ end
27
+
28
+ def validate_arguments!(klass, declared)
29
+ return if declared.all?(Symbol)
30
+
31
+ raise ArgumentError, format_message(klass, "All arguments to #initialize_with must be symbols")
32
+ end
33
+
34
+ def validate_initialization_arguments!(klass, given_args, given_kwargs, declared, defaults)
35
+ raise ArgumentError, format_message(klass, "Only keyword arguments are accepted") unless given_args.empty?
36
+
37
+ missing = declared - given_kwargs.keys - defaults.keys
38
+ unless missing.empty?
39
+ raise ArgumentError, format_message(klass, "Missing keyword argument(s): #{missing.join(", ")}")
40
+ end
41
+
42
+ extra = given_kwargs.keys - declared
43
+ return if extra.empty?
44
+
45
+ raise ArgumentError, format_message(klass, "Unknown keyword argument(s): #{extra.join(", ")}")
46
+ end
47
+
48
+ def warn_override(klass, key, block_reader:)
49
+ return unless warn_override?
50
+
51
+ location = override_location(klass, key)
52
+ reader_type = block_reader ? "block" : "init-arg"
53
+ logger.warn format_message(klass,
54
+ "Method ##{key} already exists #{location} -- overriding with #{reader_type} reader")
55
+ end
56
+
57
+ def warn_override?
58
+ return true if defined?(Rails) && Rails.respond_to?(:env) && (Rails.env.development? || Rails.env.test?)
59
+
60
+ logger.level <= Logger::DEBUG
61
+ end
62
+
63
+ def override_location(klass, key)
64
+ return "on this class" if klass.method_defined?(key, false)
65
+
66
+ owner = klass.instance_method(key).owner
67
+ "in #{owner.name || "an anonymous ancestor"}"
68
+ end
69
+
70
+ # Defensive copy for common mutable default values.
71
+ #
72
+ # Defaults passed to `initialize_with` are created once at class definition
73
+ # time. Without copying, `[]` / `{}` / `Set.new` defaults can be shared across
74
+ # instances and accidentally mutated.
75
+ #
76
+ # This is intentionally shallow, and only for common core mutable types.
77
+ def copy_default(value)
78
+ return value if value.nil? || value.frozen?
79
+
80
+ case value
81
+ when Array, Hash, Set, String
82
+ value.dup
83
+ else
84
+ value
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclarativeInitialization
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -1,23 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "declarative_initialization/version"
4
+ require_relative "declarative_initialization/internal"
4
5
  require_relative "declarative_initialization/class_methods"
5
- require_relative "declarative_initialization/instance_methods"
6
6
 
7
7
  module DeclarativeInitialization
8
8
  def self.included(base)
9
- base.class_eval do
10
- include InstanceMethods
11
- extend ClassMethods
12
- end
9
+ base.extend ClassMethods
13
10
  end
14
11
  end
15
12
 
16
- # Set up an alias so you can also do `include InitializeWith`
17
- module InitializeWith
18
- def self.included(base)
19
- base.class_eval do
20
- include DeclarativeInitialization
21
- end
22
- end
23
- end
13
+ # Alias so you can also do `include InitializeWith`
14
+ InitializeWith = DeclarativeInitialization
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: declarative_initialization
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kali Donovan
@@ -26,7 +26,7 @@ files:
26
26
  - Rakefile
27
27
  - lib/declarative_initialization.rb
28
28
  - lib/declarative_initialization/class_methods.rb
29
- - lib/declarative_initialization/instance_methods.rb
29
+ - lib/declarative_initialization/internal.rb
30
30
  - lib/declarative_initialization/version.rb
31
31
  homepage: https://github.com/teamshares/declarative_initialization
32
32
  licenses: []
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeclarativeInitialization
4
- module InstanceMethods
5
- private
6
-
7
- def _validate_initialization_arguments!(class_name, given_args, given_kwargs, declared, defaults)
8
- raise ArgumentError, "[#{class_name}] Only keyword arguments are accepted" unless given_args.empty?
9
-
10
- missing = declared - given_kwargs.keys - defaults.keys
11
- extra = given_kwargs.keys - declared
12
-
13
- raise ArgumentError, "[#{class_name}] Missing keyword argument(s): #{missing.join(", ")}" unless missing.empty?
14
- raise ArgumentError, "[#{class_name}] Unknown keyword argument(s): #{extra.join(", ")}" unless extra.empty?
15
- end
16
- end
17
- end