castkit 0.3.0 → 0.3.1

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +11 -3
  4. data/castkit.gemspec +2 -0
  5. data/lib/castkit/attribute.rb +82 -59
  6. data/lib/castkit/attributes/definition.rb +64 -0
  7. data/lib/castkit/attributes/options.rb +214 -0
  8. data/lib/castkit/castkit.rb +13 -3
  9. data/lib/castkit/cli/generate.rb +14 -0
  10. data/lib/castkit/contract/base.rb +0 -12
  11. data/lib/castkit/contract/validator.rb +5 -1
  12. data/lib/castkit/core/attributes.rb +87 -44
  13. data/lib/castkit/data_object.rb +2 -25
  14. data/lib/castkit/{ext → dsl}/attribute/access.rb +1 -1
  15. data/lib/castkit/{ext → dsl}/attribute/error_handling.rb +1 -1
  16. data/lib/castkit/{ext → dsl}/attribute/options.rb +1 -1
  17. data/lib/castkit/{ext → dsl}/attribute/validation.rb +3 -3
  18. data/lib/castkit/dsl/attribute.rb +47 -0
  19. data/lib/castkit/{ext → dsl}/data_object/contract.rb +1 -1
  20. data/lib/castkit/{ext → dsl}/data_object/deserialization.rb +6 -2
  21. data/lib/castkit/{ext → dsl}/data_object/plugins.rb +1 -1
  22. data/lib/castkit/{ext → dsl}/data_object/serialization.rb +1 -1
  23. data/lib/castkit/dsl/data_object.rb +61 -0
  24. data/lib/castkit/types/base.rb +24 -3
  25. data/lib/castkit/validators/boolean_validator.rb +3 -3
  26. data/lib/castkit/version.rb +1 -1
  27. data/lib/castkit.rb +1 -4
  28. data/lib/generators/attribute.rb +39 -0
  29. data/lib/generators/templates/attribute.rb.tt +21 -0
  30. data/lib/generators/templates/attribute_spec.rb.tt +41 -0
  31. data/lib/generators/templates/contract.rb.tt +2 -0
  32. data/lib/generators/templates/data_object.rb.tt +2 -0
  33. data/lib/generators/templates/type.rb.tt +2 -0
  34. data/lib/generators/templates/validator.rb.tt +1 -1
  35. metadata +45 -12
  36. data/.rspec_status +0 -195
  37. data/lib/castkit/core/registerable.rb +0 -59
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d47af535f6e3f14f248ba864df4761e5ca2918f7153ec5336a932842a2329576
4
- data.tar.gz: f7c5112a38faa006476b4758ad1b80be18323cbb5300c04cb5a39a92aa559fa8
3
+ metadata.gz: f048458a9f967984b6b590c1e6bf2348a43c90936374b4d8755c6f7151057fe8
4
+ data.tar.gz: 182e73cec494a329bc670999cceab4a3c1e057eb6108097c759989102232ec94
5
5
  SHA512:
6
- metadata.gz: cfcb5f523b1c875af1685e9643dd19e144984cc528c6cfcc7914b8def979023905e17eddbefda4ba65eb2756098565af77226fc67ed427a084de60e476652f05
7
- data.tar.gz: 3ed9aef95fd9f1634ca38544a6b3e937e4da5a88c3b99a29f93c6481e48171f9780462dbd8f41d41162a8e1b40cf657143d756c4fdcf6cc887b8d4e5dd71d02f
6
+ metadata.gz: 4d8bc7bdd0ed6c26f7fb7a621306995b4fbfbbe6d1d331ffa813ffc4c13a2d2795b72af9f977bc618e950c8dfdc5b017bb95563a436520e4e545efdf1a9a0b50
7
+ data.tar.gz: 196ed35dd105ad854ac74fa4540cbfbb04de30885069719ab80bb84d5a41b8ea8e64df1e085753477dab3bf54f998db20684b5cd6460af4c87447594c320edd3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-04-16
4
+
5
+ ### Added
6
+
7
+ - **CLI System (`castkit`)**:
8
+ - Introduced a full-featured CLI for generating and inspecting Castkit components.
9
+ - Supports:
10
+ - `castkit generate [component]` for scaffolding types, data objects, contracts, serializers, validators, and plugins.
11
+ - `castkit list [types|validators|contracts|dataobjects|serializers]` for inspecting the internal registry and available classes.
12
+ - Enables developer productivity and exploration through a single entry point.
13
+
14
+ - **Plugin System**:
15
+ - Added `Castkit::Plugins` for modular runtime extensions.
16
+ - Plugins can be registered and activated on DTO classes via:
17
+
18
+ ```ruby
19
+ Castkit.configure do |config|
20
+ config.register_plugin(:oj, MyOjPlugin)
21
+ end
22
+
23
+ class MyDto < Castkit::DataObject
24
+ enable_plugins :oj
25
+ end
26
+ ```
27
+
28
+ - Plugins support `setup!(klass)` for optional initialization logic.
29
+
30
+ - **Base Class Renames**:
31
+ - Introduced consistent naming conventions with `Castkit::Types::Base`, `Castkit::Serializers::Base`, `Castkit::Validators::Base`, etc.
32
+ - These will be excluded from list output automatically in CLI commands.
33
+
34
+ ---
35
+
3
36
  ## [0.2.0] - 2025-04-14
4
37
 
5
38
  ### Added
@@ -42,6 +75,8 @@
42
75
 
43
76
  - **Union type validation support** for both `Castkit::DataObject` and contracts, allowing attributes to accept multiple types (e.g., `[:string, :integer]`).
44
77
 
78
+ ---
79
+
45
80
  ## [0.1.2] - 2025-04-14
46
81
 
47
82
  ### Added
@@ -71,6 +106,8 @@
71
106
  end
72
107
  ```
73
108
 
109
+ ---
110
+
74
111
  ## [0.1.1] - 2025-04-13
75
112
 
76
113
  ### Added
@@ -87,6 +124,8 @@
87
124
 
88
125
  Useful when working with nested DataObject collections (e.g., for integration with ActiveRecord or serialization logic).
89
126
 
127
+ ---
128
+
90
129
  ## [0.1.0] - 2025-04-12
91
130
 
92
131
  - Initial release
data/README.md CHANGED
@@ -215,7 +215,7 @@ class Metadata < Castkit::DataObject
215
215
  end
216
216
 
217
217
  class PageDto < Castkit::DataObject
218
- dataobject :metadata, unwrapped: true, prefix: "meta"
218
+ dataobject :metadata, Metadata, unwrapped: true, prefix: "meta"
219
219
  end
220
220
 
221
221
  # Serializes as:
@@ -523,7 +523,7 @@ end
523
523
 
524
524
  class UserDto < Castkit::DataObject
525
525
  string :id
526
- dataobject :address, of: AddressDto
526
+ dataobject :address, AddressDto
527
527
  end
528
528
 
529
529
  UserContract = Castkit::Contract.from_dataobject(UserDto)
@@ -576,7 +576,7 @@ Castkit.configure do |config|
576
576
  end
577
577
 
578
578
  class MyDto < Castkit::DataObject
579
- Castkit::Plugins.activate(self, :my_plugin)
579
+ enable_plugins :my_plugin
580
580
  end
581
581
  ```
582
582
 
@@ -600,6 +600,14 @@ You can then activate them:
600
600
  Castkit::Plugins.activate(MyDto, :oj)
601
601
  ```
602
602
 
603
+ Or by using the `enable_plugins` helper method in `Castkit::DataObject`:
604
+
605
+ ```ruby
606
+ class MyDto < Castkit::DataObject
607
+ enable_plugins :oj, :yaml
608
+ end
609
+ ```
610
+
603
611
  ---
604
612
 
605
613
  ### 🧰 Plugin API
data/castkit.gemspec CHANGED
@@ -38,5 +38,7 @@ Gem::Specification.new do |spec|
38
38
  # Development dependencies
39
39
  spec.add_development_dependency "rspec"
40
40
  spec.add_development_dependency "rubocop"
41
+ spec.add_development_dependency "simplecov"
42
+ spec.add_development_dependency "simplecov-cobertura"
41
43
  spec.add_development_dependency "yard"
42
44
  end
@@ -1,46 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "castkit"
3
4
  require_relative "error"
4
- require_relative "ext/attribute/options"
5
- require_relative "ext/attribute/access"
6
- require_relative "ext/attribute/validation"
5
+ require_relative "attributes/options"
6
+ require_relative "dsl/attribute"
7
7
 
8
8
  module Castkit
9
- # Represents a typed attribute on a Castkit::DataObject.
9
+ # Represents a typed attribute on a `Castkit::DataObject`.
10
10
  #
11
- # Provides casting, validation, access control, and serialization behavior.
11
+ # This class is responsible for:
12
+ # - Type normalization (symbol, class, or data object)
13
+ # - Default and option resolution
14
+ # - Validation hooks
15
+ # - Access and serialization control
16
+ #
17
+ # Attributes are created automatically when using the DSL in `DataObject`, but
18
+ # can also be created manually or through reusable definitions.
19
+ #
20
+ # @see Castkit::Attributes::Definition
21
+ # @see Castkit::DSL::Attribute::Options
22
+ # @see Castkit::DSL::Attribute::Access
23
+ # @see Castkit::DSL::Attribute::Validation
12
24
  class Attribute
13
- include Castkit::Ext::Attribute::Options
14
- include Castkit::Ext::Attribute::Access
15
- include Castkit::Ext::Attribute::Validation
25
+ include Castkit::DSL::Attribute
26
+
27
+ class << self
28
+ # Defines a reusable attribute definition via a DSL wrapper.
29
+ #
30
+ # @param type [Symbol, Class] The base type to define.
31
+ # @param options [Hash] Additional attribute options.
32
+ # @yield The block to configure options or transformations.
33
+ # @return [Array<(Symbol, Hash)>] a tuple of the final type and options hash
34
+ def define(type, **options, &block)
35
+ normalized_type = normalize_type(type)
36
+ Castkit::Attributes::Definition.define(normalized_type, **options, &block)
37
+ end
38
+
39
+ # Normalizes a declared type (symbol, class, or array) for internal usage.
40
+ #
41
+ # @param type [Symbol, Class, Array] the input type
42
+ # @return [Symbol, Class<Castkit::DataObject>] the normalized form
43
+ def normalize_type(type)
44
+ return type.map { |t| normalize_type(t) } if type.is_a?(Array)
45
+ return type if Castkit.dataobject?(type)
46
+
47
+ process_type(type).to_sym
48
+ end
49
+
50
+ # Converts a raw type into a normalized symbol.
51
+ #
52
+ # Recognized forms:
53
+ # - `TrueClass`/`FalseClass` → `:boolean`
54
+ # - Class → `class.name.downcase.to_sym`
55
+ # - Symbol → passed through
56
+ #
57
+ # @param type [Symbol, Class] the type to convert
58
+ # @return [Symbol] normalized type symbol
59
+ # @raise [Castkit::AttributeError] if the type is invalid
60
+ def process_type(type)
61
+ case type
62
+ when Class
63
+ return :boolean if [TrueClass, FalseClass].include?(type)
64
+
65
+ type.name.downcase.to_sym
66
+ when Symbol
67
+ type
68
+ else
69
+ raise Castkit::AttributeError, "Unknown type: #{type.inspect}"
70
+ end
71
+ end
72
+ end
16
73
 
17
74
  # @return [Symbol] the attribute name
18
75
  attr_reader :field
19
76
 
20
- # @return [Symbol, Class, Array] the declared type (normalized)
77
+ # @return [Symbol, Class, Array] the declared or normalized type
21
78
  attr_reader :type
22
79
 
23
- # @return [Hash] attribute options (including aliases, default, access, etc.)
80
+ # @return [Hash] full option hash, including merged defaults
24
81
  attr_reader :options
25
82
 
26
83
  # Initializes a new attribute definition.
27
84
  #
28
- # @param field [Symbol] the name of the attribute
29
- # @param type [Symbol, Class, Array] the type or array of types
30
- # @param default [Object, Proc] optional default value
31
- # @param options [Hash] additional configuration options
85
+ # @param field [Symbol] the attribute name
86
+ # @param type [Symbol, Class, Array<Symbol, Class>] the type (or list of types)
87
+ # @param default [Object, Proc, nil] optional static or callable default
88
+ # @param options [Hash] additional attribute options
32
89
  def initialize(field, type, default: nil, **options)
33
90
  @field = field
34
- @type = normalize_type(type)
91
+ @type = self.class.normalize_type(type)
35
92
  @default = default
36
93
  @options = populate_options(options)
37
94
 
38
95
  validate!
39
96
  end
40
97
 
41
- # Returns a hash representation of the attribute definition.
98
+ # Converts the attribute definition to a serializable hash.
42
99
  #
43
- # @return [Hash]
100
+ # @return [Hash] the full attribute metadata
44
101
  def to_hash
45
102
  {
46
103
  field: field,
@@ -55,56 +112,22 @@ module Castkit
55
112
 
56
113
  private
57
114
 
58
- # Populates default values and normalizes internal options.
115
+ # Populates default values and prepares internal options.
59
116
  #
60
- # @param options [Hash]
61
- # @return [Hash]
117
+ # @param options [Hash] the user-provided options
118
+ # @return [Hash] the merged and normalized options
62
119
  def populate_options(options)
63
- options = DEFAULT_OPTIONS.merge(options)
120
+ options = Castkit::Attributes::Options::DEFAULTS.merge(options)
64
121
  options[:aliases] = Array(options[:aliases] || [])
65
- options[:of] = normalize_type(options[:of]) if options[:of]
122
+ options[:of] = self.class.normalize_type(options[:of]) if options[:of]
66
123
 
67
124
  options
68
125
  end
69
126
 
70
- # Normalizes a declared type to a symbol or class reference.
71
- #
72
- # @param type [Symbol, Class, Array]
73
- # @return [Symbol, Class, Array]
74
- # @raise [Castkit::AttributeError] if the type is not valid
75
- def normalize_type(type)
76
- return type.map { |t| normalize_type(t) } if type.is_a?(Array)
77
- return type if Castkit.dataobject?(type)
78
-
79
- process_type(type)
80
- end
81
-
82
- # Converts a single type value into a normalized internal representation.
83
- #
84
- # - Maps `TrueClass`/`FalseClass` to `:boolean`
85
- # - Converts class names (e.g., `String`, `Integer`) to lowercase symbols
86
- # - Accepts already-symbolized types (e.g., `:string`)
87
- #
88
- # @param type [Class, Symbol] the declared type to process
89
- # @return [Symbol] the normalized type
90
- # @raise [Castkit::AttributeError] if the type is not a recognized form
91
- def process_type(type)
92
- case type
93
- when Class
94
- return :boolean if [TrueClass, FalseClass].include?(type)
95
-
96
- type.name.downcase.to_sym
97
- when Symbol
98
- type
99
- else
100
- raise_error!("Unknown type: #{type.inspect}")
101
- end
102
- end
103
-
104
- # Raises a Castkit::AttributeError with optional context.
127
+ # Raises a standardized attribute error with context.
105
128
  #
106
- # @param message [String]
107
- # @param context [Hash, nil]
129
+ # @param message [String] the error message
130
+ # @param context [Hash, nil] optional override for context payload
108
131
  # @raise [Castkit::AttributeError]
109
132
  def raise_error!(message, context: nil)
110
133
  raise Castkit::AttributeError.new(message, context: context || to_h)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "options"
4
+
5
+ module Castkit
6
+ module Attributes
7
+ # Provides a class-based DSL for defining reusable attribute definitions.
8
+ #
9
+ # Extend this class in a subclass of `Castkit::Attributes::Base` to define
10
+ # shared attribute settings that can be reused across multiple DataObjects.
11
+ #
12
+ # @example Defining a reusable attribute
13
+ # class UuidDefinition < Castkit::Attributes::Base
14
+ # type :string
15
+ # required true
16
+ # format /\A[0-9a-f\-]{36}\z/
17
+ # end
18
+ #
19
+ # attribute :id, UuidDefinition.definition
20
+ #
21
+ class Definition
22
+ extend Castkit::Attributes::Options
23
+
24
+ class << self
25
+ # @return [Hash] the internal definition hash, containing the type and options
26
+ def definition
27
+ @definition ||= {
28
+ type: nil,
29
+ options: Castkit::Attributes::Options::DEFAULTS.dup
30
+ }
31
+ end
32
+
33
+ # @return [Hash] the attribute options defined on this class
34
+ def options
35
+ definition[:options]
36
+ end
37
+
38
+ # Defines the attribute's type and configuration using a DSL block.
39
+ #
40
+ # @param type [Symbol, Class<Castkit::DataObject>] the attribute type (e.g., :string, :integer)
41
+ # @param options [Hash] additional options to merge after the block (e.g., default:, access:)
42
+ # @yield DSL block used to set options like `required`, `format`, `readonly`, etc.
43
+ # @return [Array<(Symbol, Hash)>] a tuple of the final type and options hash
44
+ #
45
+ # @example
46
+ # define :string, default: "none" do
47
+ # required true
48
+ # access [:read]
49
+ # end
50
+ def define(type, **options, &block)
51
+ @__castkit_attribute_dsl = true
52
+
53
+ definition[:type] = type
54
+ instance_eval(&block)
55
+ definition[:options] = definition[:options].merge(options)
56
+
57
+ definition
58
+ ensure
59
+ @__castkit_attribute_dsl = false
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castkit
4
+ module Attributes
5
+ # Provides a DSL for configuring attribute options within an attribute definition.
6
+ #
7
+ # This module is designed to be extended by class-level definition objects such as
8
+ # `Castkit::Attributes::Definition`, and is used to build reusable sets of options
9
+ # for attributes declared within `Castkit::DataObject` classes.
10
+ #
11
+ # @example
12
+ # class OptionalString < Castkit::Attributes::Definition
13
+ # type :string
14
+ # required false
15
+ # ignore_blank true
16
+ # end
17
+ module Options
18
+ # Valid access modes for an attribute.
19
+ #
20
+ # @return [Array<Symbol>]
21
+ ACCESS_MODES = %i[read write].freeze
22
+
23
+ # Default configuration for attribute options.
24
+ #
25
+ # @return [Hash{Symbol => Object}]
26
+ DEFAULTS = {
27
+ required: true,
28
+ ignore_nil: false,
29
+ ignore_blank: false,
30
+ ignore: false,
31
+ composite: false,
32
+ transient: false,
33
+ unwrapped: false,
34
+ prefix: nil,
35
+ access: ACCESS_MODES,
36
+ force_type: !Castkit.configuration.enforce_typing
37
+ }.freeze
38
+
39
+ # Sets or retrieves the attribute type.
40
+ #
41
+ # @param value [Symbol, nil] The type to assign (e.g., :string), or nil to fetch.
42
+ # @return [Symbol]
43
+ def type(value = nil)
44
+ value.nil? ? definition[:type] : (definition[:type] = value.to_sym)
45
+ end
46
+
47
+ # Sets the element type for array attributes.
48
+ #
49
+ # @param value [Symbol, Class] the type of elements in the array
50
+ # @return [void]
51
+ def of(value)
52
+ return unless @type == :array
53
+
54
+ set_option(:of, value)
55
+ end
56
+
57
+ # Sets the default value or proc for the attribute.
58
+ #
59
+ # @param value [Object, Proc] the default value or lambda
60
+ # @return [void]
61
+ def default(value = nil)
62
+ set_option(:default, value)
63
+ end
64
+
65
+ # Enables or disables forced typecasting, or sets a custom flag.
66
+ #
67
+ # @param value [Boolean, nil] the forced type flag
68
+ # @return [void]
69
+ def force_type(value = nil)
70
+ set_option(:force_type, value || !Castkit.configuration.enforce_typing)
71
+ end
72
+
73
+ # Marks the attribute as required or optional.
74
+ #
75
+ # @param value [Boolean]
76
+ # @return [void]
77
+ def required(value = nil)
78
+ set_option(:required, value || true)
79
+ end
80
+
81
+ # Marks the attribute to be ignored entirely.
82
+ #
83
+ # @param value [Boolean]
84
+ # @return [void]
85
+ def ignore(value = nil)
86
+ set_option(:ignore, value || true)
87
+ end
88
+
89
+ # Ignores `nil` values during serialization or persistence.
90
+ #
91
+ # @param value [Boolean]
92
+ # @return [void]
93
+ def ignore_nil(value = nil)
94
+ set_option(:ignore_nil, value || true)
95
+ end
96
+
97
+ # Ignores blank values (`""`, `[]`, `{}`) during serialization.
98
+ #
99
+ # @param value [Boolean]
100
+ # @return [void]
101
+ def ignore_blank(value = nil)
102
+ set_option(:ignore_blank, value || true)
103
+ end
104
+
105
+ # Adds a prefix for unwrapped attribute keys.
106
+ #
107
+ # @param value [String, Symbol, nil]
108
+ # @return [void]
109
+ def prefix(value = nil)
110
+ set_option(:prefix, value)
111
+ end
112
+
113
+ # Marks the attribute as unwrapped (inline merging of nested fields).
114
+ #
115
+ # @param value [Boolean]
116
+ # @return [void]
117
+ def unwrapped(value = nil)
118
+ set_option(:unwrapped, value || true)
119
+ end
120
+
121
+ # Sets access modes for the attribute.
122
+ #
123
+ # @param value [Array<Symbol>, Symbol] valid values: `:read`, `:write`, or both
124
+ # @return [void]
125
+ def access(value = nil)
126
+ value = validate_access_modes!(value)
127
+ set_option(:access, value)
128
+ end
129
+
130
+ # Shortcut to make the attribute readonly (`access: [:read]`).
131
+ #
132
+ # @param value [Boolean]
133
+ # @return [void]
134
+ def readonly(value = nil)
135
+ value = value || true ? [:read] : ACCESS_MODES
136
+ set_option(:access, value)
137
+ end
138
+
139
+ # Marks the attribute as a composite (e.g., nested `DataObject`).
140
+ #
141
+ # @param value [Boolean]
142
+ # @return [void]
143
+ def composite(value = nil)
144
+ set_option(:composite, value || true)
145
+ end
146
+
147
+ # Marks the attribute as transient (not included in persistence or serialization).
148
+ #
149
+ # @param value [Boolean]
150
+ # @return [void]
151
+ def transient(value = nil)
152
+ set_option(:transient, value || true)
153
+ end
154
+
155
+ # Sets a format constraint (e.g., regex validation).
156
+ #
157
+ # @param value [Regexp]
158
+ # @return [void]
159
+ def format(value)
160
+ set_option(:format, value)
161
+ end
162
+
163
+ # Attaches a custom validator callable for this attribute.
164
+ #
165
+ # @param value [Proc, #call]
166
+ # @return [void]
167
+ def validator(value)
168
+ set_option(:validator, value)
169
+ end
170
+
171
+ private
172
+
173
+ # Converts class or symbol into a normalized type symbol.
174
+ #
175
+ # @param type [Class, Symbol]
176
+ # @return [Symbol]
177
+ # @raise [Castkit::AttributeError] if type cannot be resolved
178
+ def process_type(type)
179
+ case type
180
+ when Class
181
+ return :boolean if [TrueClass, FalseClass].include?(type)
182
+
183
+ type.name.downcase.to_sym
184
+ when Symbol
185
+ type
186
+ else
187
+ raise Castkit::AttributeError.new("Unknown type: #{type.inspect}", context: to_h)
188
+ end
189
+ end
190
+
191
+ # Sets an option key-value pair in the current definition.
192
+ #
193
+ # @param option [Symbol]
194
+ # @param value [Object, nil]
195
+ # @return [Object, nil]
196
+ def set_option(option, value)
197
+ value.nil? ? definition[:options][option] : (definition[:options][option] = value)
198
+ end
199
+
200
+ # Validates and normalizes access mode array.
201
+ #
202
+ # @param value [Array<Symbol>, Symbol, nil]
203
+ # @return [Array<Symbol>]
204
+ # @raise [Castkit::AttributeError] if invalid modes are present
205
+ def validate_access_modes!(value)
206
+ value_array = Array(value || ACCESS_MODES).compact
207
+ unknown_modes = value_array - ACCESS_MODES
208
+ return value_array if unknown_modes.empty?
209
+
210
+ raise Castkit::AttributeError.new("Unknown access flags: #{unknown_modes.inspect}", context: to_h)
211
+ end
212
+ end
213
+ end
214
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "configuration"
4
- require_relative "inflector"
3
+ require_relative "core/attribute_types"
5
4
 
6
5
  # Castkit is a lightweight, type-safe data object system for Ruby.
7
6
  #
@@ -63,7 +62,10 @@ module Castkit
63
62
  # @param obj [Object] the object to test
64
63
  # @return [Boolean] true if obj is a Castkit::DataObject class
65
64
  def dataobject?(obj)
66
- obj.is_a?(Class) && obj.ancestors.include?(Castkit::DataObject)
65
+ obj.is_a?(Class) && (
66
+ obj <= Castkit::DataObject ||
67
+ obj.ancestors.include?(Castkit::DSL::DataObject)
68
+ )
67
69
  end
68
70
 
69
71
  # Returns a type caster lambda for the given type.
@@ -105,3 +107,11 @@ module Castkit
105
107
  end
106
108
  end
107
109
  end
110
+
111
+ require_relative "configuration"
112
+ require_relative "plugins"
113
+ require_relative "inflector"
114
+ require_relative "version"
115
+ require_relative "attribute"
116
+ require_relative "contract"
117
+ require_relative "data_object"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
+ require_relative "../../generators/attribute"
4
5
  require_relative "../../generators/contract"
5
6
  require_relative "../../generators/data_object"
6
7
  require_relative "../../generators/plugin"
@@ -30,6 +31,19 @@ module Castkit
30
31
  Castkit::Generators::Contract.start(args)
31
32
  end
32
33
 
34
+ desc "attribute NAME", "Generates a new Castkit attribute"
35
+ method_option :spec, type: :boolean, default: true
36
+ # Generates a new attribute definition class.
37
+ #
38
+ # @param name [String] the class name for the attribute
39
+ # @param fields [Array<String>] optional attribute options
40
+ # @return [void]
41
+ def attribute(name, *fields)
42
+ args = [Castkit::Inflector.pascalize(name), fields]
43
+ args << "--no-spec" unless options[:spec]
44
+ Castkit::Generators::Attribute.start(args)
45
+ end
46
+
33
47
  desc "dataobject NAME", "Generates a new Castkit DataObject"
34
48
  method_option :spec, type: :boolean, default: true
35
49
  # Generates a new DataObject class.
@@ -2,7 +2,6 @@
2
2
 
3
3
  require_relative "../core/config"
4
4
  require_relative "../core/attribute_types"
5
- require_relative "../core/registerable"
6
5
  require_relative "result"
7
6
 
8
7
  module Castkit
@@ -33,23 +32,12 @@ module Castkit
33
32
  class Base
34
33
  extend Castkit::Core::Config
35
34
  extend Castkit::Core::AttributeTypes
36
- extend Castkit::Core::Registerable
37
35
 
38
36
  ATTRIBUTE_OPTIONS = %i[
39
37
  required aliases min max format of validator unwrapped prefix force_type
40
38
  ].freeze
41
39
 
42
40
  class << self
43
- # Registers the current class under `Castkit::Contracts`.
44
- #
45
- # @param as [String, Symbol, nil] The constant name to use (PascalCase). Defaults to the name used when building
46
- # the contract. If no name was provided, an error is raised.
47
- # @return [Class] the registered contract class
48
- # @raise [Castkit::Error] If a name cannot be resolved.
49
- def register!(as: nil)
50
- super(namespace: :contracts, as: as || definition[:name])
51
- end
52
-
53
41
  # Defines an attribute for the contract.
54
42
  #
55
43
  # Only a subset of options is allowed inside a contract.