vident-typed 0.1.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 +7 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/lib/tasks/vident/typed_tasks.rake +4 -0
- data/lib/vident/typed/attributes.rb +236 -0
- data/lib/vident/typed/component.rb +38 -0
- data/lib/vident/typed/engine.rb +17 -0
- data/lib/vident/typed/typed_niling_struct.rb +27 -0
- data/lib/vident/typed/types.rb +14 -0
- data/lib/vident/typed/version.rb +5 -0
- data/lib/vident/typed.rb +10 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cbddabe6bfb2a7023b558f6d901a017a40684f20626590768a10128b34bb97db
|
4
|
+
data.tar.gz: 1ead57f91090163f0fcc901438f57d1d753aa91913e33c7ac49579f5fc5b5867
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 66a1b29c91d37b3007c8b2d1d0862e5db94b97151c7304337bb802e6f6cffa88871a528678ffc00d3ed089423d454a271c1bfd9f7dd1854f5d205d805500877d
|
7
|
+
data.tar.gz: ee959f7e2968242d3f3b8a3e0ccf04c7fe162256f5b14b9705ed208208de63755bc052bc9d80e805cb0902b2c12c9799ca449e5f5c88aff6ff8ee28eccbec101
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Vident::Typed
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem "vident-typed"
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install vident-typed
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
require "vident/typed/version"
|
2
|
+
require "vident/typed/engine"
|
3
|
+
|
4
|
+
module Vident
|
5
|
+
module Typed
|
6
|
+
module Attributes
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
# TODO: better handling of when either class.schema is undefined (as no attributes configured) or when
|
10
|
+
# other methods ar called before prepare_attributes is called
|
11
|
+
def prepare_attributes(attributes)
|
12
|
+
@__attributes = self.class.schema.new(**attributes)
|
13
|
+
end
|
14
|
+
|
15
|
+
def attributes
|
16
|
+
@__attributes.attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
def attribute_names
|
20
|
+
@attribute_names ||= self.class.attribute_names
|
21
|
+
end
|
22
|
+
|
23
|
+
def attribute(key)
|
24
|
+
if Rails.env.development? && !key?(key)
|
25
|
+
raise StandardError, "Attribute #{key} not found in #{self.class.name}"
|
26
|
+
end
|
27
|
+
@__attributes.attributes[key]
|
28
|
+
end
|
29
|
+
alias_method :[], :attribute
|
30
|
+
|
31
|
+
def key?(key)
|
32
|
+
self.class.schema.attribute_names.include?(key)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_hash
|
36
|
+
@__attributes.to_h
|
37
|
+
end
|
38
|
+
|
39
|
+
class_methods do
|
40
|
+
def inherited(subclass)
|
41
|
+
subclass.instance_variable_set(:@schema, @schema.clone)
|
42
|
+
subclass.instance_variable_set(:@attribute_ivar_names, @attribute_ivar_names.clone)
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def attribute_names
|
47
|
+
schema.attribute_names
|
48
|
+
end
|
49
|
+
|
50
|
+
def attribute_metadata(key)
|
51
|
+
schema.schema.key(key).meta
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :schema, :attribute_ivar_names
|
55
|
+
|
56
|
+
def attribute(name, signature = :any, **options, &converter)
|
57
|
+
strict = !options[:convert]
|
58
|
+
signatures = extract_member_type_and_subclass(signature, options)
|
59
|
+
type_info = map_primitive_to_dry_type(signatures, strict, converter)
|
60
|
+
type_info = set_constraints(type_info, options)
|
61
|
+
type_info = set_metadata(type_info, signatures, options)
|
62
|
+
define_on_schema(name, type_info, options)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def set_constraints(type_info, options)
|
68
|
+
type_info = constrain_nil(type_info, options)
|
69
|
+
type_info = constrain_blank(type_info, options)
|
70
|
+
type_info = set_default(type_info, options)
|
71
|
+
constrain_inclusion(type_info, options)
|
72
|
+
end
|
73
|
+
|
74
|
+
def constrain_nil(type_info, options)
|
75
|
+
if allows_nil?(options)
|
76
|
+
type_info.optional.meta(required: false)
|
77
|
+
else
|
78
|
+
type_info
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def constrain_blank(type_info, options)
|
83
|
+
if allows_blank?(options)
|
84
|
+
type_info
|
85
|
+
else
|
86
|
+
type_info.constrained(filled: true)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def set_default(type_info, options)
|
91
|
+
default = options[:default]
|
92
|
+
|
93
|
+
if default.is_a?(Proc)
|
94
|
+
type_info.default(default.freeze)
|
95
|
+
elsif !default.nil?
|
96
|
+
type_info.default(->(_) { default }.freeze)
|
97
|
+
else
|
98
|
+
type_info
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def constrain_inclusion(type_info, options)
|
103
|
+
if options[:in]
|
104
|
+
type_info.constrained(included_in: options[:in].freeze)
|
105
|
+
else
|
106
|
+
type_info
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def set_metadata(type_info, signatures, options)
|
111
|
+
metadata = {typed_attribute_type: signatures, typed_attribute_options: options}
|
112
|
+
type_info.meta(**metadata)
|
113
|
+
end
|
114
|
+
|
115
|
+
def delegates?(options)
|
116
|
+
options[:delegates] != false
|
117
|
+
end
|
118
|
+
|
119
|
+
def define_on_schema(attribute_name, type_info, options)
|
120
|
+
@attribute_ivar_names ||= {}
|
121
|
+
@attribute_ivar_names[attribute_name] = :"@#{attribute_name}"
|
122
|
+
define_attribute_delegate(attribute_name) if delegates?(options)
|
123
|
+
@schema ||= const_set(:TypedSchema, Class.new(TypedNilingStruct))
|
124
|
+
@schema.attribute attribute_name, type_info
|
125
|
+
end
|
126
|
+
|
127
|
+
def define_attribute_delegate(attr_name)
|
128
|
+
# Define reader & presence check method
|
129
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
130
|
+
def #{attr_name}
|
131
|
+
@__attributes.attributes[:#{attr_name}]
|
132
|
+
end
|
133
|
+
|
134
|
+
def #{attr_name}?
|
135
|
+
send(:#{attr_name}).present?
|
136
|
+
end
|
137
|
+
RUBY
|
138
|
+
end
|
139
|
+
|
140
|
+
def allows_nil?(options)
|
141
|
+
return true unless options
|
142
|
+
allow_nil = options[:allow_nil]
|
143
|
+
return false if allow_nil == false
|
144
|
+
allow_nil || allows_blank?(options)
|
145
|
+
end
|
146
|
+
|
147
|
+
def allows_blank?(options)
|
148
|
+
return true unless options
|
149
|
+
allow_blank = options[:allow_blank]
|
150
|
+
allow_blank.nil? ? true : allow_blank
|
151
|
+
end
|
152
|
+
|
153
|
+
def map_primitive_to_dry_type(signatures, strict, converter)
|
154
|
+
types = signatures.map do |type, subtype|
|
155
|
+
dry_type = dry_type_from_primary_type(type, strict, converter)
|
156
|
+
if subtype && dry_type.respond_to?(:of)
|
157
|
+
subtype_info = dry_type_from_primary_type(subtype, strict, converter)
|
158
|
+
# Sub types of collections currently can be nil - this should be an option
|
159
|
+
dry_type.of(subtype_info.optional.meta(required: false))
|
160
|
+
else
|
161
|
+
dry_type
|
162
|
+
end
|
163
|
+
end
|
164
|
+
types.reduce(:|)
|
165
|
+
end
|
166
|
+
|
167
|
+
def extract_member_type_and_subclass(signature, options)
|
168
|
+
case signature
|
169
|
+
when Set
|
170
|
+
signature.flat_map { |s| extract_member_type_and_subclass(s, options) }
|
171
|
+
when Array
|
172
|
+
[[Array, signature.first]]
|
173
|
+
else
|
174
|
+
[[signature, options[:type] || options[:sub_type]]]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def dry_type_from_primary_type(type, strict, converter)
|
179
|
+
# If a converter is provided, we should use it to coerce the value
|
180
|
+
if converter && !strict && !type.is_a?(Symbol)
|
181
|
+
return Types.Constructor(type) do |value|
|
182
|
+
next value if value.is_a?(type)
|
183
|
+
|
184
|
+
converter.call(value).tap do |new_value|
|
185
|
+
unless new_value.is_a?(type)
|
186
|
+
raise ArgumentError, "Type conversion proc did not convert #{value} to #{type}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
if type == :any
|
193
|
+
Types::Nominal::Any
|
194
|
+
elsif type == Integer
|
195
|
+
strict ? Types::Strict::Integer : Types::Params::Integer
|
196
|
+
elsif type == BigDecimal
|
197
|
+
strict ? Types::Strict::Decimal : Types::Params::Decimal
|
198
|
+
elsif type == Float
|
199
|
+
strict ? Types::Strict::Float : Types::Params::Float
|
200
|
+
elsif type == Numeric
|
201
|
+
if strict
|
202
|
+
Types::Strict::Float | Types::Strict::Integer | Types::Strict::Decimal
|
203
|
+
else
|
204
|
+
Types::Params::Float | Types::Params::Integer | Types::Params::Decimal
|
205
|
+
end
|
206
|
+
elsif type == Symbol
|
207
|
+
strict ? Types::Strict::Symbol : Types::Coercible::Symbol
|
208
|
+
elsif type == String
|
209
|
+
strict ? Types::Strict::String : Types::Coercible::String
|
210
|
+
elsif type == Time
|
211
|
+
strict ? Types::Strict::Time : Types::Params::Time
|
212
|
+
elsif type == Date
|
213
|
+
strict ? Types::Strict::Date : Types::Params::Date
|
214
|
+
elsif type == Array
|
215
|
+
strict ? Types::Strict::Array : Types::Params::Array
|
216
|
+
elsif type == Hash
|
217
|
+
strict ? Types::Strict::Hash : Types::Coercible::Hash
|
218
|
+
elsif type == :boolean
|
219
|
+
strict ? Types::Strict::Bool : Types::Params::Bool
|
220
|
+
elsif strict
|
221
|
+
# when strict create a Nominal type with a is_a? constraint, otherwise create a Nominal type which constructs
|
222
|
+
# values using the default constructor, `new`.
|
223
|
+
Types.Instance(type)
|
224
|
+
else
|
225
|
+
# dry calls this when initialising the Type. Check if type of input is correct or create new instance
|
226
|
+
Types.Constructor(type) do |value|
|
227
|
+
next value if value.is_a?(type)
|
228
|
+
|
229
|
+
type.new(**value)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vident
|
4
|
+
module Typed
|
5
|
+
module Component
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include Vident::Base
|
10
|
+
include Vident::Typed::Attributes
|
11
|
+
|
12
|
+
attribute :id, String, delegates: false
|
13
|
+
attribute :html_options, Hash, delegates: false
|
14
|
+
attribute :element_tag, Symbol, delegates: false
|
15
|
+
|
16
|
+
# StimulusJS support
|
17
|
+
attribute :controllers, Array, default: [], delegates: false
|
18
|
+
attribute :actions, Array, default: [], delegates: false
|
19
|
+
attribute :targets, Array, default: [], delegates: false
|
20
|
+
attribute :data_maps, Array, default: [], delegates: false
|
21
|
+
|
22
|
+
# TODO normalise the syntax of defining actions, controllers, etc
|
23
|
+
attribute :named_classes, Hash, delegates: false
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(attrs = {})
|
27
|
+
before_initialise(attrs)
|
28
|
+
prepare_attributes(attrs)
|
29
|
+
# The attributes need to also be set as ivars
|
30
|
+
attributes.each do |attr_name, attr_value|
|
31
|
+
instance_variable_set(self.class.attribute_ivar_names[attr_name], attr_value)
|
32
|
+
end
|
33
|
+
after_initialise
|
34
|
+
super()
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Vident
|
2
|
+
module Typed
|
3
|
+
class Engine < ::Rails::Engine
|
4
|
+
lib_path = File.expand_path("../../../../lib/", __FILE__)
|
5
|
+
config.autoload_paths << lib_path
|
6
|
+
config.eager_load_paths << lib_path
|
7
|
+
|
8
|
+
config.before_initialize do
|
9
|
+
Rails.autoloaders.each do |autoloader|
|
10
|
+
autoloader.inflector.inflect(
|
11
|
+
"version" => "VERSION"
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vident
|
4
|
+
module Typed
|
5
|
+
# A dry struct that is loose about keys provided on initialization. It sets them to nil if not provided.
|
6
|
+
# It is strict about types but not about provided keys
|
7
|
+
class TypedNilingStruct < ::Dry::Struct
|
8
|
+
# convert string keys to symbols
|
9
|
+
transform_keys(&:to_sym)
|
10
|
+
|
11
|
+
# resolve default types on nil
|
12
|
+
transform_types do |type|
|
13
|
+
if type.default?
|
14
|
+
type.constructor { |value| value.nil? ? ::Dry::Types::Undefined : value }
|
15
|
+
else
|
16
|
+
type
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def check_schema_duplication(new_keys)
|
22
|
+
# allow overriding keys
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vident
|
4
|
+
module Typed
|
5
|
+
module Types
|
6
|
+
include ::Dry.Types
|
7
|
+
|
8
|
+
StrippedString = Types::String.constructor(&:strip)
|
9
|
+
BooleanDefaultFalse = Types::Bool.default(false)
|
10
|
+
BooleanDefaultTrue = Types::Bool.default(true)
|
11
|
+
HashDefaultEmpty = Types::Hash.default({}.freeze)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/vident/typed.rb
ADDED
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vident-typed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephen Ierodiaconou
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-04-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '8'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '7'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '8'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: vident
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.8.0
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '1'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.8.0
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '1'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: dry-struct
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 1.5.0
|
60
|
+
- - "<"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '2'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.5.0
|
70
|
+
- - "<"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '2'
|
73
|
+
description: Vident with typed attributes
|
74
|
+
email:
|
75
|
+
- stevegeek@gmail.com
|
76
|
+
executables: []
|
77
|
+
extensions: []
|
78
|
+
extra_rdoc_files: []
|
79
|
+
files:
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- lib/tasks/vident/typed_tasks.rake
|
83
|
+
- lib/vident/typed.rb
|
84
|
+
- lib/vident/typed/attributes.rb
|
85
|
+
- lib/vident/typed/component.rb
|
86
|
+
- lib/vident/typed/engine.rb
|
87
|
+
- lib/vident/typed/typed_niling_struct.rb
|
88
|
+
- lib/vident/typed/types.rb
|
89
|
+
- lib/vident/typed/version.rb
|
90
|
+
homepage: https://github.com/stevegeek/vident-typed
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata:
|
94
|
+
homepage_uri: https://github.com/stevegeek/vident-typed
|
95
|
+
source_code_uri: https://github.com/stevegeek/vident-typed
|
96
|
+
changelog_uri: https://github.com/stevegeek/vident-typed/blob/main/CHANGELOG.md
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubygems_version: 3.4.6
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Vident with typed attributes
|
116
|
+
test_files: []
|