typed_attrs 0.1.0 → 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 +4 -4
- data/CHANGELOG.md +13 -2
- data/README.md +11 -11
- data/lib/active_model/validations/typed_attrs_validator.rb +2 -2
- data/lib/tapioca/dsl/compilers/{typed_attribute.rb → typed_attrs.rb} +4 -4
- data/lib/typed_attrs/type.rb +49 -0
- data/lib/typed_attrs/version.rb +1 -1
- data/lib/typed_attrs.rb +11 -4
- metadata +4 -18
- data/lib/typed_attrs/types/type.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44632f0c666469500f60192d522da02554051be31c2545cafe5c2f50369f2a52
|
4
|
+
data.tar.gz: 8c984552a1ad3259ceacd94a56874c8c06e02e23a98373ad3c610ca35def5a93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66f6e290bd39b001903973b664e0d29d9afb2c4bdd7482b8ebddfbc509c919ee0eed3236aded31dece529db0abb0a539696543177c4899f44a02f1bbb311e1c6
|
7
|
+
data.tar.gz: e2a5b79e68bdcf79b3bc6808b45493b2e17eebaa9f8ce7a18cc40fb33fd6d600e8c7a9b14e6213150a8e13e591c71d4b5f635bf9064ffab9cab63042f4230567
|
data/CHANGELOG.md
CHANGED
@@ -5,9 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [
|
8
|
+
## [0.2.0] - 2025-10-11
|
9
9
|
|
10
|
-
|
10
|
+
### Changed
|
11
|
+
- **BREAKING**: Changed public API from `TypedAttrs.to_type(Product)` to `T::Attr[Product]`
|
12
|
+
- Users must update attribute definitions: `attribute :field, TypedAttrs.to_type(MyStruct)` → `attribute :field, T::Attr[MyStruct]`
|
13
|
+
- Public API is now under `T::Attr` module while implementation remains in `TypedAttrs` namespace
|
14
|
+
|
15
|
+
### Refactored
|
16
|
+
- Simplified type class from `TypedAttrs::Types::Type` to `TypedAttrs::Type`
|
17
|
+
- Renamed Tapioca DSL compiler from `TypedAttribute` to `TypedAttrs`
|
18
|
+
- File: `lib/tapioca/dsl/compilers/typed_attribute.rb` → `typed_attrs.rb`
|
19
|
+
- Class: `Tapioca::Dsl::Compilers::TypedAttribute` → `TypedAttrs`
|
20
|
+
|
21
|
+
## [0.1.0] - 2025-10-10
|
11
22
|
|
12
23
|
### Added
|
13
24
|
- Initial release
|
data/README.md
CHANGED
@@ -37,7 +37,7 @@ end
|
|
37
37
|
|
38
38
|
# Use it as a typed attribute
|
39
39
|
class Order < ActiveRecord::Base
|
40
|
-
attribute :product,
|
40
|
+
attribute :product, T::Attr[Product]
|
41
41
|
end
|
42
42
|
|
43
43
|
# Assign with Hash
|
@@ -64,7 +64,7 @@ class Color < T::Struct
|
|
64
64
|
end
|
65
65
|
|
66
66
|
class Palette < ActiveRecord::Base
|
67
|
-
attribute :colors,
|
67
|
+
attribute :colors, T::Attr[T::Array[Color]]
|
68
68
|
end
|
69
69
|
|
70
70
|
palette = Palette.new
|
@@ -85,7 +85,7 @@ class BoxSize < T::Struct
|
|
85
85
|
end
|
86
86
|
|
87
87
|
class Layout < ActiveRecord::Base
|
88
|
-
attribute :box_sizes,
|
88
|
+
attribute :box_sizes, T::Attr[T::Hash[String, BoxSize]]
|
89
89
|
end
|
90
90
|
|
91
91
|
layout = Layout.new
|
@@ -127,7 +127,7 @@ class Cat < T::Struct
|
|
127
127
|
end
|
128
128
|
|
129
129
|
class PetOwner < ActiveRecord::Base
|
130
|
-
attribute :pet,
|
130
|
+
attribute :pet, T::Attr[PetType]
|
131
131
|
end
|
132
132
|
|
133
133
|
owner = PetOwner.new
|
@@ -152,7 +152,7 @@ class Company < T::Struct
|
|
152
152
|
end
|
153
153
|
|
154
154
|
class Employee < ActiveRecord::Base
|
155
|
-
attribute :company,
|
155
|
+
attribute :company, T::Attr[Company]
|
156
156
|
end
|
157
157
|
|
158
158
|
employee = Employee.new
|
@@ -174,7 +174,7 @@ class Settings < T::Struct
|
|
174
174
|
end
|
175
175
|
|
176
176
|
class User < ActiveRecord::Base
|
177
|
-
attribute :settings,
|
177
|
+
attribute :settings, T::Attr[Settings]
|
178
178
|
end
|
179
179
|
|
180
180
|
user = User.new
|
@@ -200,7 +200,7 @@ class Product < T::Struct
|
|
200
200
|
end
|
201
201
|
|
202
202
|
class Order < ActiveRecord::Base
|
203
|
-
attribute :product,
|
203
|
+
attribute :product, T::Attr[Product]
|
204
204
|
validates :product, typed_attrs: true
|
205
205
|
end
|
206
206
|
|
@@ -227,7 +227,7 @@ class Article < T::Struct
|
|
227
227
|
end
|
228
228
|
|
229
229
|
class Post < ActiveRecord::Base
|
230
|
-
attribute :article,
|
230
|
+
attribute :article, T::Attr[Article]
|
231
231
|
validates :article, typed_attrs: true
|
232
232
|
end
|
233
233
|
|
@@ -305,12 +305,12 @@ end
|
|
305
305
|
# Ruby: Company.new(name: "Acme", address: Address.new(...))
|
306
306
|
|
307
307
|
# Array type - stored as JSON array
|
308
|
-
attribute :colors,
|
308
|
+
attribute :colors, T::Attr[T::Array[Color]]
|
309
309
|
# Database: [{ "name": "Red", "hex": "#FF0000" }, ...]
|
310
|
-
# Ruby: [Color.new(name: "Red", hex: "#FF0000"
|
310
|
+
# Ruby: [Color.new(name: "Red", hex: "#FF0000"], ...]
|
311
311
|
|
312
312
|
# Hash type - stored as JSON object
|
313
|
-
attribute :sizes,
|
313
|
+
attribute :sizes, T::Attr[T::Hash[String, BoxSize]]
|
314
314
|
# Database: { "small": { "width": 100, "height": 50 }, ... }
|
315
315
|
# Ruby: { "small" => BoxSize.new(width: 100, height: 50), ... }
|
316
316
|
```
|
@@ -36,12 +36,12 @@ module ActiveModel
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
sig { params(record: T.untyped, attribute: T.any(String, Symbol)).returns(T.nilable(TypedAttrs::
|
39
|
+
sig { params(record: T.untyped, attribute: T.any(String, Symbol)).returns(T.nilable(TypedAttrs::Type)) }
|
40
40
|
def get_typed_attribute_type(record, attribute)
|
41
41
|
return nil unless record.class.respond_to?(:attribute_types)
|
42
42
|
|
43
43
|
attribute_type = record.class.attribute_types[attribute.to_s]
|
44
|
-
attribute_type.is_a?(TypedAttrs::
|
44
|
+
attribute_type.is_a?(TypedAttrs::Type) ? attribute_type : nil
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -9,7 +9,7 @@ require "tapioca/dsl"
|
|
9
9
|
module Tapioca
|
10
10
|
module Dsl
|
11
11
|
module Compilers
|
12
|
-
class
|
12
|
+
class TypedAttrs < Tapioca::Dsl::Compiler
|
13
13
|
extend T::Sig
|
14
14
|
|
15
15
|
ConstantType = type_member { { fixed: T.class_of(ActiveRecord::Base) } }
|
@@ -46,7 +46,7 @@ module Tapioca
|
|
46
46
|
|
47
47
|
class << self
|
48
48
|
def typed_attribute_type?(type)
|
49
|
-
type.is_a?(TypedAttrs::
|
49
|
+
type.is_a?(::TypedAttrs::Type)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -60,12 +60,12 @@ module Tapioca
|
|
60
60
|
sig { params(klass: RBI::Scope, attr_name: String, type: ActiveModel::Type::Value).void }
|
61
61
|
def create_attribute_methods(klass, attr_name, type)
|
62
62
|
case type
|
63
|
-
when TypedAttrs::
|
63
|
+
when ::TypedAttrs::Type
|
64
64
|
create_type_attribute_methods(klass, attr_name, type)
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
sig { params(klass: RBI::Scope, attr_name: String, type: TypedAttrs::
|
68
|
+
sig { params(klass: RBI::Scope, attr_name: String, type: ::TypedAttrs::Type).void }
|
69
69
|
def create_type_attribute_methods(klass, attr_name, type)
|
70
70
|
type_spec = type.instance_variable_get(:@type_spec)
|
71
71
|
type_string = type_spec.name
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TypedAttrs
|
5
|
+
class Type < ActiveModel::Type::Value
|
6
|
+
include TypeHelpers
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(type_spec: T.untyped).void }
|
10
|
+
def initialize(type_spec)
|
11
|
+
super()
|
12
|
+
@type_spec = type_spec
|
13
|
+
|
14
|
+
registry = T.let(TypeProcessors.default_registry, TypeProcessors::Registry)
|
15
|
+
@processor = T.let(registry.serializer_for(type_spec), TypeProcessors::Base)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { returns(TypeProcessors::Base) }
|
19
|
+
attr_reader :processor
|
20
|
+
|
21
|
+
sig { params(value: T.untyped).returns(T.untyped) }
|
22
|
+
def cast(value)
|
23
|
+
return nil if value.nil?
|
24
|
+
|
25
|
+
@processor.deserialize(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { params(value: T.untyped).returns(T.untyped) }
|
29
|
+
def serialize(value)
|
30
|
+
return nil if value.nil?
|
31
|
+
|
32
|
+
serialized = @processor.serialize(value)
|
33
|
+
ActiveSupport::JSON.encode(serialized)
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { params(value: T.untyped).returns(T.untyped) }
|
37
|
+
def deserialize(value)
|
38
|
+
return nil if value.nil?
|
39
|
+
|
40
|
+
data = if value.is_a?(String)
|
41
|
+
ActiveSupport::JSON.decode(value)
|
42
|
+
else
|
43
|
+
value
|
44
|
+
end
|
45
|
+
|
46
|
+
@processor.deserialize(data)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/typed_attrs/version.rb
CHANGED
data/lib/typed_attrs.rb
CHANGED
@@ -23,7 +23,7 @@ require_relative "typed_attrs/type_processors/struct"
|
|
23
23
|
require_relative "typed_attrs/type_processors/array"
|
24
24
|
require_relative "typed_attrs/type_processors/hash"
|
25
25
|
require_relative "typed_attrs/type_processors/discriminated_union"
|
26
|
-
require_relative "typed_attrs/
|
26
|
+
require_relative "typed_attrs/type"
|
27
27
|
require_relative "typed_attrs/discriminated_union"
|
28
28
|
require_relative "active_model/validations/typed_attrs_validator"
|
29
29
|
|
@@ -95,9 +95,16 @@ module TypedAttrs
|
|
95
95
|
)
|
96
96
|
end
|
97
97
|
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add T::Attr as the public API for typed attributes
|
101
|
+
module T
|
102
|
+
module Attr
|
103
|
+
extend T::Sig
|
98
104
|
|
99
|
-
|
100
|
-
|
101
|
-
|
105
|
+
sig { params(type_spec: T.untyped).returns(TypedAttrs::Type) }
|
106
|
+
def self.[](type_spec)
|
107
|
+
TypedAttrs::Type.new(type_spec)
|
108
|
+
end
|
102
109
|
end
|
103
110
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: typed_attrs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Speria
|
@@ -23,20 +23,6 @@ dependencies:
|
|
23
23
|
- - ">="
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: '6.0'
|
26
|
-
- !ruby/object:Gem::Dependency
|
27
|
-
name: activerecord
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
29
|
-
requirements:
|
30
|
-
- - ">="
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '6.0'
|
33
|
-
type: :runtime
|
34
|
-
prerelease: false
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - ">="
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '6.0'
|
40
26
|
- !ruby/object:Gem::Dependency
|
41
27
|
name: sorbet-runtime
|
42
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -64,9 +50,10 @@ files:
|
|
64
50
|
- LICENSE
|
65
51
|
- README.md
|
66
52
|
- lib/active_model/validations/typed_attrs_validator.rb
|
67
|
-
- lib/tapioca/dsl/compilers/
|
53
|
+
- lib/tapioca/dsl/compilers/typed_attrs.rb
|
68
54
|
- lib/typed_attrs.rb
|
69
55
|
- lib/typed_attrs/discriminated_union.rb
|
56
|
+
- lib/typed_attrs/type.rb
|
70
57
|
- lib/typed_attrs/type_helpers.rb
|
71
58
|
- lib/typed_attrs/type_processors.rb
|
72
59
|
- lib/typed_attrs/type_processors/array.rb
|
@@ -85,7 +72,6 @@ files:
|
|
85
72
|
- lib/typed_attrs/type_processors/struct.rb
|
86
73
|
- lib/typed_attrs/type_processors/symbol.rb
|
87
74
|
- lib/typed_attrs/type_processors/time.rb
|
88
|
-
- lib/typed_attrs/types/type.rb
|
89
75
|
- lib/typed_attrs/version.rb
|
90
76
|
homepage: https://github.com/speria-jp/typed_attrs
|
91
77
|
licenses:
|
@@ -111,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
97
|
- !ruby/object:Gem::Version
|
112
98
|
version: '0'
|
113
99
|
requirements: []
|
114
|
-
rubygems_version: 3.6.
|
100
|
+
rubygems_version: 3.6.9
|
115
101
|
specification_version: 4
|
116
102
|
summary: Type-safe ActiveRecord attributes using Sorbet T::Struct
|
117
103
|
test_files: []
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module TypedAttrs
|
5
|
-
module Types
|
6
|
-
class Type < ActiveModel::Type::Value
|
7
|
-
include TypeHelpers
|
8
|
-
extend T::Sig
|
9
|
-
|
10
|
-
sig { params(type_spec: T.untyped).void }
|
11
|
-
def initialize(type_spec)
|
12
|
-
super()
|
13
|
-
@type_spec = type_spec
|
14
|
-
|
15
|
-
registry = T.let(TypeProcessors.default_registry, TypeProcessors::Registry)
|
16
|
-
@processor = T.let(registry.serializer_for(type_spec), TypeProcessors::Base)
|
17
|
-
end
|
18
|
-
|
19
|
-
sig { returns(TypeProcessors::Base) }
|
20
|
-
attr_reader :processor
|
21
|
-
|
22
|
-
sig { params(value: T.untyped).returns(T.untyped) }
|
23
|
-
def cast(value)
|
24
|
-
return nil if value.nil?
|
25
|
-
|
26
|
-
@processor.deserialize(value)
|
27
|
-
end
|
28
|
-
|
29
|
-
sig { params(value: T.untyped).returns(T.untyped) }
|
30
|
-
def serialize(value)
|
31
|
-
return nil if value.nil?
|
32
|
-
|
33
|
-
serialized = @processor.serialize(value)
|
34
|
-
ActiveSupport::JSON.encode(serialized)
|
35
|
-
end
|
36
|
-
|
37
|
-
sig { params(value: T.untyped).returns(T.untyped) }
|
38
|
-
def deserialize(value)
|
39
|
-
return nil if value.nil?
|
40
|
-
|
41
|
-
data = if value.is_a?(String)
|
42
|
-
ActiveSupport::JSON.decode(value)
|
43
|
-
else
|
44
|
-
value
|
45
|
-
end
|
46
|
-
|
47
|
-
@processor.deserialize(data)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|