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 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,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :vident_typed do
3
+ # # Task goes here
4
+ # end
@@ -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
@@ -0,0 +1,5 @@
1
+ module Vident
2
+ module Typed
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require "vident/typed/version"
2
+ require "vident/typed/engine"
3
+
4
+ require "dry-struct"
5
+
6
+ module Vident
7
+ module Typed
8
+ # Your code goes here...
9
+ end
10
+ end
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: []