vident-typed 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []