sleeping_king_studios-tools 1.3.0 → 1.4.0.rc.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.
@@ -5,20 +5,36 @@ require 'sleeping_king_studios/tools/messages/strategies/hash_strategy'
5
5
 
6
6
  module SleepingKingStudios::Tools::Messages::Strategies
7
7
  # Messaging strategy that loads message templates from the given file.
8
+ #
9
+ # Internally, the parsed templates are flattened - a nested Hash of
10
+ # { foo: { bar: { baz: 'template' } } } is stored as a flat Hash with
11
+ # { 'foo.bar.baz' => 'template' }. This makes lookup of scoped keys more
12
+ # efficient but prevents retrieving non-leaf nodes (such as "foo" or "foo.bar"
13
+ # in the above Hash). If you need to retrieve Hash values, initialize the
14
+ # strategy with flatten_templates: false.
8
15
  class FileStrategy < HashStrategy
9
16
  # Exception raised when reading the templates file.
10
17
  class FileError < StandardError; end
11
18
 
12
- # @param file_name [String] the full path to the file with the templates
13
- # data.
14
- def initialize(file_name)
19
+ # @overload initialize(file_name, **options)
20
+ # @param file_name [String] the full path to the file with the templates
21
+ # data.
22
+ # @param options [Hash] additional options for the strategy.
23
+ #
24
+ # @option options flatten_templates [true, false] if true, the templates
25
+ # are flattened internally. Defaults to true.
26
+ # @option options validate_values [Proc, true, false] if false, skips
27
+ # validation of template values. If given a Proc, calls the Proc with
28
+ # each template value; the value fails validation if the Proc returns
29
+ # an error message. Defaults to true.
30
+ def initialize(file_name, flatten_templates: true, validate_values: true)
15
31
  validate_file_name(file_name)
16
32
 
17
33
  @file_name = file_name
18
34
  raw_data = read_file(file_name)
19
35
  templates = parse_templates(raw_data)
20
36
 
21
- super(templates)
37
+ super(templates, flatten_templates:, validate_values:)
22
38
  end
23
39
 
24
40
  # @return [String] the full path to the file with the templates data.
@@ -68,6 +84,8 @@ module SleepingKingStudios::Tools::Messages::Strategies
68
84
  end
69
85
  end
70
86
 
87
+ def template_value_error_message = 'value is not a String'
88
+
71
89
  def validate_file_name(file_name)
72
90
  raise ArgumentError, "file name can't be blank" if file_name.nil?
73
91
 
@@ -4,19 +4,40 @@ require 'sleeping_king_studios/tools/messages/strategies'
4
4
 
5
5
  module SleepingKingStudios::Tools::Messages::Strategies
6
6
  # Messaging strategy that refers to an internal Hash of templates.
7
+ #
8
+ # Internally, the templates Hash is flattened - a nested Hash of
9
+ # { foo: { bar: { baz: 'template' } } } is stored as a flat Hash with
10
+ # { 'foo.bar.baz' => 'template' }. This makes lookup of scoped keys more
11
+ # efficient but prevents retrieving non-leaf nodes (such as "foo" or "foo.bar"
12
+ # in the above Hash). If you need to retrieve Hash values, initialize the
13
+ # strategy with flatten_templates: false.
7
14
  class HashStrategy < SleepingKingStudios::Tools::Messages::Strategy
8
15
  # Exception raised when parsing the templates input.
9
16
  class ParseError < StandardError; end
10
17
 
11
- # @param templates [Hash] the templates used to generate messages.
12
- def initialize(templates)
18
+ VALID_TEMPLATE_VALUES = [Proc, String, Symbol].freeze
19
+ private_constant :VALID_TEMPLATE_VALUES
20
+
21
+ # @overload initialize(templates, **options)
22
+ # @param templates [Hash] the templates used to generate messages.
23
+ # @param options [Hash] additional options for the strategy.
24
+ #
25
+ # @option options flatten_templates [true, false] if true, the templates
26
+ # are flattened internally. Defaults to true.
27
+ # @option options validate_values [Proc, true, false] if false, skips
28
+ # validation of template values. If given a Proc, calls the Proc with
29
+ # each template value; the value fails validation if the Proc returns
30
+ # an error message. Defaults to true.
31
+ def initialize(templates, flatten_templates: true, validate_values: true)
13
32
  super()
14
33
 
15
34
  unless templates.is_a?(Hash)
16
35
  raise ArgumentError, 'templates is not an instance of Hash'
17
36
  end
18
37
 
19
- @templates = flatten_templates(templates).freeze
38
+ @flatten_templates = flatten_templates
39
+ @validate_values = validate_values
40
+ @templates = self.flatten_templates(templates).freeze
20
41
  end
21
42
 
22
43
  # @return [Hash] the templates used to generate messages.
@@ -24,27 +45,66 @@ module SleepingKingStudios::Tools::Messages::Strategies
24
45
 
25
46
  private
26
47
 
27
- def flatten_templates(hsh, scope: nil, templates: {})
48
+ def convert_keys_to_strings(hsh, scope: nil) # rubocop:disable Metrics/MethodLength
49
+ hsh
50
+ .to_h do |key, value|
51
+ validate_template_key(key, scope:)
52
+
53
+ if value.is_a?(Hash)
54
+ local = scope ? "#{scope}.#{key}" : key.to_s
55
+
56
+ value = convert_keys_to_strings(value, scope: local)
57
+ else
58
+ validate_template_value(key, value, scope:)
59
+ end
60
+
61
+ [key.to_s, value]
62
+ end
63
+ .freeze
64
+ end
65
+
66
+ def flatten_templates(hsh, scope: nil, templates: {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
67
+ return convert_keys_to_strings(hsh, scope:) unless flatten_templates?
68
+
28
69
  hsh.each do |(key, value)|
29
70
  validate_template_key(key, scope:)
30
- validate_template_value(key, value, scope:)
71
+
72
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
31
73
 
32
74
  local = scope ? "#{scope}.#{key}" : key.to_s
33
75
 
34
- next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
76
+ if value.is_a?(Hash)
77
+ next flatten_templates(value, scope: local, templates:)
78
+ end
35
79
 
36
- next templates[local] = value unless value.is_a?(Hash)
80
+ validate_template_value(key, value, scope:)
37
81
 
38
- flatten_templates(value, scope: local, templates:)
82
+ templates[local] = value
39
83
  end
40
84
 
41
85
  templates
42
86
  end
43
87
 
88
+ def flatten_templates? = @flatten_templates
89
+
44
90
  def template_for(scoped_key, **)
45
- templates[scoped_key]
91
+ return templates[scoped_key] if flatten_templates?
92
+
93
+ templates.dig(*scoped_key.split('.'))
94
+ end
95
+
96
+ def template_value_error(value)
97
+ return unless @validate_values
98
+
99
+ return @validate_values.call(value) if @validate_values.is_a?(Proc)
100
+
101
+ return if VALID_TEMPLATE_VALUES.any? { |klass| value.is_a?(klass) }
102
+
103
+ template_value_error_message
46
104
  end
47
105
 
106
+ def template_value_error_message = 'value is not a String or a Proc'
107
+
48
108
  def validate_template_key(key, scope:)
49
109
  return if (key.is_a?(String) || key.is_a?(Symbol)) && !key.empty?
50
110
 
@@ -57,14 +117,13 @@ module SleepingKingStudios::Tools::Messages::Strategies
57
117
  end
58
118
 
59
119
  def validate_template_value(key, value, scope:)
60
- return if value.nil? || value.is_a?(Hash) || value.is_a?(Proc)
61
- return if value.is_a?(String) || value.is_a?(Symbol)
120
+ error_message = template_value_error(value)
121
+
122
+ return if error_message.nil?
62
123
 
63
124
  message = 'invalid value in templates'
64
125
  message = "#{message}.#{scope}" if scope
65
- message = "#{message}.#{key}"
66
- message =
67
- "#{message} - expected Hash, Proc, or String, got #{value.inspect}"
126
+ message = "#{message}.#{key} - #{error_message}, got #{value.inspect}"
68
127
 
69
128
  raise ParseError, message
70
129
  end
@@ -5,10 +5,27 @@ require 'sleeping_king_studios/tools/messages'
5
5
  module SleepingKingStudios::Tools
6
6
  # Abstract class for converting parameterized keys to user-readable messages.
7
7
  class Messages::Strategy
8
+ UNDEFINED = SleepingKingStudios::Tools::UNDEFINED
9
+ private_constant :UNDEFINED
10
+
8
11
  # @overload call(key, parameters: {}, scope: nil, **options)
9
12
  # Generates a formatted string for the given key, parameters, and options.
10
13
  #
14
+ # If the strategy does not define a message for the key, you can provide a
15
+ # default value.
16
+ #
17
+ # - If the default value is a Proc, it will be called with the fully
18
+ # scoped key, as a positional argument as well as any additional
19
+ # keywords passed to #call. The value returned by the Proc will be
20
+ # returned by #call.
21
+ # - If the default value is any other Object (including nil), the default
22
+ # value will be returned by #call.
23
+ # - If a default value is not provided, a missing message warning will be
24
+ # generated and returned.
25
+ #
11
26
  # @param key [String, Symbol] the key used to resolve the message.
27
+ # @param default [Object] the default value to return if the strategy does
28
+ # not define a message for the key.
12
29
  # @param parameters [Hash] the parameters used to generate the message,
13
30
  # such as values for a template string.
14
31
  # @param scope [String] the namespace for the key. Combined with the given
@@ -18,18 +35,98 @@ module SleepingKingStudios::Tools
18
35
  #
19
36
  # @return [String] the generated template string, or a generic error
20
37
  # string if a message is not defined for the given key and parameters.
21
- def call(key, parameters: {}, scope: nil, **)
38
+ def call(key, default: UNDEFINED, parameters: {}, scope: nil, **)
39
+ scoped_key = join_scope(key:, scope:)
40
+ template = fetch(key, scope:, **) do
41
+ return apply_default(scoped_key, default, **)
42
+ end
43
+
44
+ generate(template, parameters:, scoped_key:, **)
45
+ end
46
+
47
+ # @overload fetch(key, default = nil, scope: nil, **options)
48
+ # Retrieves the messages template with the given scope and options.
49
+ #
50
+ # If there is no template matching the given key, scope, and options,
51
+ # returns the default value or raises a KeyError if no default value is
52
+ # provided.
53
+ #
54
+ # @param key [String, Symbol] the key used to resolve the message.
55
+ # @param default [Object] the default value to return if the strategy does
56
+ # not define a message for the key.
57
+ # @param scope [String] the namespace for the key. Combined with the given
58
+ # key to generate the scoped key value.
59
+ # @param options [Hash] additional options for resolving or generating the
60
+ # message.
61
+ #
62
+ # @return [Object] the stored template for the requested key, or the
63
+ # provided default value.
64
+ #
65
+ # @raise [KeyError] if there is no matching template and no default value
66
+ # is provided.
67
+ #
68
+ # @overload fetch(key, scope: nil, **options, &default)
69
+ # If there is no template matching the given key, scope, and options,
70
+ # the block is called with the scoped key and the value returned by the
71
+ # block is returned by #fetch.
72
+ #
73
+ # @param key [String, Symbol] the key used to resolve the message.
74
+ # @param scope [String] the namespace for the key. Combined with the given
75
+ # key to generate the scoped key value.
76
+ # @param options [Hash] additional options for resolving or generating the
77
+ # message.
78
+ #
79
+ # @yieldparam [String] scoped_key the fully scoped key for the requested
80
+ # template, combining the given key and the given scope, if any.
81
+ # @yieldparam options [Hash] additional options for resolving the message.
82
+ #
83
+ # @yieldreturn [Object] the default value for the given scoped key.
84
+ #
85
+ # @return [Object] the stored template for the requested key, or the value
86
+ # returned by the provided default block.
87
+ def fetch(key, default = UNDEFINED, scope: nil, **, &block)
22
88
  scoped_key = join_scope(key:, scope:)
23
89
  template = template_for(scoped_key, **)
24
90
 
25
- return missing_message(scoped_key, **) unless template
91
+ return template unless template.nil?
26
92
 
27
- generate(template, parameters:, scoped_key:, **)
93
+ return block.call(scoped_key, **) if block_given?
94
+
95
+ return default unless default == UNDEFINED
96
+
97
+ raise KeyError, "template not found: #{scoped_key.inspect}"
28
98
  end
29
99
 
100
+ # @overload get(key, scope: nil, **options)
101
+ # Retrieves the messages template with the given scope and options.
102
+ #
103
+ # @param key [String, Symbol] the key used to resolve the message.
104
+ # @param scope [String] the namespace for the key. Combined with the given
105
+ # key to generate the scoped key value.
106
+ # @param options [Hash] additional options for resolving or generating the
107
+ # message.
108
+ #
109
+ # @return [Object] the stored template for the requested key, or nil if
110
+ # there is no matching template.
111
+ def get(key, scope: nil, **) = fetch(key, nil, scope:, **)
112
+
30
113
  private
31
114
 
32
- def generate(template, scoped_key: nil, parameters: {}, **) # rubocop:disable Metrics/MethodLength
115
+ def apply_default(key, default, **)
116
+ return missing_message(key, **) if default == UNDEFINED
117
+
118
+ return default unless default.is_a?(Proc)
119
+
120
+ default.call(key, **)
121
+ end
122
+
123
+ def generate( # rubocop:disable Metrics/MethodLength
124
+ template,
125
+ parameters: {},
126
+ reraise_exceptions: false,
127
+ scoped_key: nil,
128
+ **
129
+ )
33
130
  raise ArgumentError, "template can't be blank" if template.nil?
34
131
 
35
132
  case template
@@ -41,6 +138,8 @@ module SleepingKingStudios::Tools
41
138
  raise ArgumentError, "invalid template #{template.inspect}"
42
139
  end
43
140
  rescue KeyError => exception
141
+ raise if reraise_exceptions
142
+
44
143
  missing_parameters_message(scoped_key, exception:, **)
45
144
  end
46
145
 
@@ -9,6 +9,9 @@ module SleepingKingStudios::Tools
9
9
  autoload :Strategies, 'sleeping_king_studios/tools/messages/strategies'
10
10
  autoload :Strategy, 'sleeping_king_studios/tools/messages/strategy'
11
11
 
12
+ UNDEFINED = SleepingKingStudios::Tools::UNDEFINED
13
+ private_constant :UNDEFINED
14
+
12
15
  # @param registry [SleepingKingStudios::Tools::Messages::Registry] the
13
16
  # strategies registry to use for the tool. Defaults to the value of
14
17
  # Registry.global.
@@ -29,27 +32,50 @@ module SleepingKingStudios::Tools
29
32
  # Generates a message from the given key, scope, and parameters.
30
33
  #
31
34
  # Internally, finds a messages strategy matching the combined key and
32
- # scope and calls that strategy. If a strategy is not found, instead
33
- # returns a generic failure message.
35
+ # scope and calls that strategy.
36
+ #
37
+ # If a strategy is not found, or if the strategy does not define a message
38
+ # for the key, you can provide a default value.
39
+ #
40
+ # - If the default value is a Proc, it will be called with the fully
41
+ # scoped key, as a positional argument as well as any additional
42
+ # keywords passed to #call. The value returned by the Proc will be
43
+ # returned by #call.
44
+ # - If the default value is any other Object (including nil), the default
45
+ # value will be returned by #call.
46
+ # - If a default value is not provided, a missing message warning will be
47
+ # generated and returned.
34
48
  #
35
49
  # @param key [String, Symbol] the key used to resolve the message.
50
+ # @param default [Object] the default value to return if there is no
51
+ # defined registry for the message, or if the strategy does not define a
52
+ # message for the key.
36
53
  # @param parameters [Hash] the parameters used to generate the message,
37
54
  # such as values for a template string.
38
55
  # @param scope [String] the namespace for the key. Combined with the given
39
56
  # key to generate the scoped key value.
40
57
  # @param options [Hash] additional options for resolving or generating the
41
58
  # message.
42
- def message(key, parameters: {}, scope: nil, **)
59
+ def message(key, default: UNDEFINED, parameters: {}, scope: nil, **)
43
60
  scoped_key = join_scope(key:, scope:)
44
61
  strategy = registry.get(scoped_key)
45
62
 
46
- return missing_message(scoped_key, **) unless strategy
63
+ return apply_default(scoped_key, default, **) unless strategy
47
64
 
48
- strategy.call(key, parameters:, scope:, **)
65
+ strategy.call(key, default:, parameters:, scope:, **)
49
66
  end
67
+ alias get message
50
68
 
51
69
  private
52
70
 
71
+ def apply_default(key, default, **)
72
+ return missing_message(key, **) if default == UNDEFINED
73
+
74
+ return default unless default.is_a?(Proc)
75
+
76
+ default.call(key, **)
77
+ end
78
+
53
79
  def missing_message(scoped_key, **)
54
80
  "Message missing: #{scoped_key}"
55
81
  end
@@ -5,97 +5,12 @@ require 'sleeping_king_studios/tools/toolbox'
5
5
  module SleepingKingStudios::Tools::Toolbox
6
6
  # Mixin to allow defining subclasses of Data classes.
7
7
  module HeritableData
8
+ # Special thanks to `@havenwood` from the Ruby Discord on the solution here.
9
+ DEFINE = Data.singleton_class.instance_method(:define)
10
+ private_constant :DEFINE
11
+
8
12
  # Methods prepended onto the singleton class when including HeritableData.
9
13
  module ClassMethods
10
- # Compares `self` and `other`.
11
- #
12
- # @param other [Object] the object to compare.
13
- #
14
- # @return [true] if self is a subclass of other, or if self inherits Data
15
- # members and methods from other.
16
- # @return [false] if self and other are the same class, if other is a
17
- # subclass of self, or if other inherits Data members and methods from
18
- # self.
19
- # @return [nil] if none of the above is true.
20
- def <(other)
21
- (self <=> other)&.then { |cmp| cmp == -1 }
22
- end
23
-
24
- # Compares `self` and `other`.
25
- #
26
- # @param other [Object] the object to compare.
27
- #
28
- # @return [true] if self and other are the same class, if self is a
29
- # subclass of other, or if self inherits Data members and methods from
30
- # other.
31
- # @return [false] if other is a subclass of self, or if other inherits
32
- # Data members and methods from self.
33
- # @return [nil] if none of the above is true.
34
- def <=(other)
35
- (self <=> other)&.then { |cmp| cmp < 1 }
36
- end
37
-
38
- # Compares `self` and `other`.
39
- #
40
- # @param other [Object] the object to compare.
41
- #
42
- # @return [-1] if self is a subclass of other, or if self inherits Data
43
- # members and methods from other.
44
- # @return [0] if self and other are the same class.
45
- # @return [1] if self is a parent class of other, or if other inherits
46
- # Data members and methods from self.
47
- # @return [nil] if none of the above is true.
48
- def <=>(other)
49
- return super unless other.is_a?(Class)
50
-
51
- return -1 if other.equal?(Data)
52
- return 0 if other.equal?(self)
53
-
54
- return nil unless other.const_defined?(:HeritableMethods)
55
-
56
- return -1 if include?(other::HeritableMethods)
57
- return 1 if other.include?(self::HeritableMethods)
58
-
59
- nil
60
- end
61
-
62
- # @return [true] if the object is an instance of the data class, or if the
63
- # object is an instance of a Data class that inherits members and
64
- # methods from self.
65
- def ===(object)
66
- return true if super
67
-
68
- (self > object.class) || false
69
- end
70
-
71
- # Compares `self` and `other`.
72
- #
73
- # @param other [Object] the object to compare.
74
- #
75
- # @return [true] if other is a subclass of other, or if other inherits
76
- # Data members and methods from self.
77
- # @return [false] if self and other are the same class, if self is a
78
- # subclass of other, or if self inherits Data members and methods from
79
- # other.
80
- # @return [nil] if none of the above is true.
81
- def >(other)
82
- (self <=> other)&.then { |cmp| cmp == 1 }
83
- end
84
-
85
- # Compares `self` and `other`.
86
- #
87
- # @param other [Object] the object to compare.
88
- #
89
- # @return [true] if self and other are the same class, if other is a
90
- # subclass of other, or if other inherits Data members and methods from
91
- # self.
92
- # @return [false] if self is a subclass of other, or if self inherits Data
93
- # members and methods from other.
94
- # @return [nil] if none of the above is true.
95
- def >=(other)
96
- (self <=> other)&.then { |cmp| cmp > -1 }
97
- end
98
-
99
14
  # Defines a new Data class including the members/methods of this class.
100
15
  #
101
16
  # The defined data class will include HeritableData, and will be able to
@@ -106,40 +21,53 @@ module SleepingKingStudios::Tools::Toolbox
106
21
  # parent Data class.
107
22
  #
108
23
  # @yield additional methods to define on the new Data class.
109
- def define(*symbols, &)
110
- HeritableData.define(*symbols, parent_class: self, &)
24
+ def define(*symbols, &block)
25
+ # @deprecated v2.0
26
+ block = wrap_class_block(block)
27
+
28
+ DEFINE
29
+ .bind_call(self, *members, *symbols, &block)
30
+ .include(HeritableData)
31
+ .tap do |data_class|
32
+ # @deprecated v2.0
33
+ handle_deprecated_lifecycle_hooks(data_class)
34
+ end
111
35
  end
112
- end
113
-
114
- # Custom module used to propagate heritable methods across Data classes.
115
- #
116
- # @private
117
- class HeritableMethods < Module
118
- # @param parent_class [Class, nil] the parent data class, if any.
119
- #
120
- # @yield additional methods to define on the Data class.
121
- def initialize(parent_class: nil, &methods)
122
- super(&nil)
36
+ alias with define
123
37
 
124
- @parent_class = parent_class
38
+ private
125
39
 
126
- class_exec(&methods) if methods
40
+ # @deprecated v2.0
41
+ def handle_deprecated_lifecycle_hooks(data_class)
42
+ return if data_class.method(:included).owner == Module
43
+
44
+ SleepingKingStudios::Tools::Toolbelt
45
+ .instance
46
+ .core_tools
47
+ .deprecate(
48
+ 'Defining class methods or including modules using an `included` ' \
49
+ 'hook is deprecated. Define the methods or include the modules ' \
50
+ 'directly in the block.'
51
+ )
52
+
53
+ # Ensure legacy class methods defined via .included hook are defined for
54
+ # the child class.
55
+ data_class.send(:included, data_class)
127
56
  end
128
57
 
129
- # @return [Class, nil] the parent data class.
130
- attr_reader :parent_class
131
-
132
- # Iterates over the ::HeritableMethods modules for parent classes.
133
- def each_heritable_module
134
- return enum_for(:each_heritable_module) unless block_given?
58
+ # @deprecated v2.0
59
+ def wrap_class_block(block)
60
+ return block unless block&.lambda? && block.arity.zero?
135
61
 
136
- ancestor = self
62
+ SleepingKingStudios::Tools::Toolbelt
63
+ .instance
64
+ .core_tools
65
+ .deprecate(
66
+ 'Data.define with a lambda of arity zero is not supported. This ' \
67
+ 'will raise an ArgumentError starting in version 2.0.'
68
+ )
137
69
 
138
- while ancestor
139
- yield ancestor
140
-
141
- ancestor = ancestor.parent_class&.then { |mod| mod::HeritableMethods }
142
- end
70
+ ->(mod) { mod.class_exec(&block) }
143
71
  end
144
72
  end
145
73
 
@@ -151,23 +79,51 @@ module SleepingKingStudios::Tools::Toolbox
151
79
  # methods from.
152
80
  #
153
81
  # @yield additional methods to define on the new Data class.
154
- def define(*symbols, parent_class: nil, &)
155
- Data.define(*parent_class&.members, *symbols).tap do |data_class|
156
- data_class.include(HeritableData)
157
-
158
- define_mixin_for(data_class, parent_class, &)
82
+ def define(*symbols, parent_class: nil, &block) # rubocop:disable Metrics/MethodLength
83
+ # @deprecated v2.0
84
+ if parent_class
85
+ SleepingKingStudios::Tools::Toolbelt
86
+ .instance
87
+ .core_tools
88
+ .deprecate(
89
+ 'HeritableData.define with a parent class is deprecated and ' \
90
+ 'will be removed starting in version 2.0.'
91
+ )
92
+
93
+ return parent_class.define(*symbols, &block)
159
94
  end
95
+
96
+ # @deprecated v2.0
97
+ block = wrap_class_block(block)
98
+
99
+ Data
100
+ .define(*symbols, &block)
101
+ .include(HeritableData)
102
+ .tap do |data_class|
103
+ # @deprecated v2.0
104
+ handle_deprecated_lifecycle_hooks(data_class)
105
+ end
160
106
  end
107
+ alias with define
161
108
 
162
109
  private
163
110
 
164
- def define_mixin_for(data_class, parent_class = nil, &)
165
- data_class.const_set(
166
- :HeritableMethods,
167
- HeritableData::HeritableMethods.new(parent_class:, &)
168
- )
169
-
170
- data_class.include(*data_class::HeritableMethods.each_heritable_module)
111
+ # @deprecated v2.0
112
+ def handle_deprecated_lifecycle_hooks(data_class)
113
+ return if data_class.method(:included).owner == Module
114
+
115
+ SleepingKingStudios::Tools::Toolbelt
116
+ .instance
117
+ .core_tools
118
+ .deprecate(
119
+ 'Defining class methods or including modules using an `included` ' \
120
+ 'hook is deprecated. Define the methods or include the modules ' \
121
+ 'directly in the block.'
122
+ )
123
+
124
+ # Ensure legacy class methods defined via .included hook are defined for
125
+ # the child class.
126
+ data_class.send(:included, data_class)
171
127
  end
172
128
 
173
129
  def included(other)
@@ -175,17 +131,21 @@ module SleepingKingStudios::Tools::Toolbox
175
131
 
176
132
  other.singleton_class.prepend(ClassMethods)
177
133
  end
178
- end
179
134
 
180
- # Checks if the object class inherits from type.
181
- #
182
- # @param type [Module] the type to check.
183
- #
184
- # @return [true] if the object class inherits from type, or if the object
185
- # class inherits members and methods from the given type.
186
- def is_a?(type)
187
- super || self.class < type || false
135
+ # @deprecated v2.0
136
+ def wrap_class_block(block)
137
+ return block unless block&.lambda? && block.arity.zero?
138
+
139
+ SleepingKingStudios::Tools::Toolbelt
140
+ .instance
141
+ .core_tools
142
+ .deprecate(
143
+ 'Data.define with a lambda of arity zero is not supported. This ' \
144
+ 'will raise an ArgumentError starting in version 2.0.'
145
+ )
146
+
147
+ ->(mod) { mod.class_exec(&block) }
148
+ end
188
149
  end
189
- alias kind_of? is_a?
190
150
  end
191
151
  end
@@ -9,13 +9,13 @@ module SleepingKingStudios
9
9
  # Major version.
10
10
  MAJOR = 1
11
11
  # Minor version.
12
- MINOR = 3
12
+ MINOR = 4
13
13
  # Patch version.
14
14
  PATCH = 0
15
15
  # Prerelease version.
16
- PRERELEASE = nil
16
+ PRERELEASE = :rc
17
17
  # Build metadata.
18
- BUILD = nil
18
+ BUILD = 0
19
19
 
20
20
  class << self
21
21
  # Generates the gem version string from the Version constants.