rasti-form 3.1.2 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +36 -0
- data/README.md +6 -61
- data/lib/rasti/form/errors.rb +6 -52
- data/lib/rasti/form/validable.rb +10 -7
- data/lib/rasti/form.rb +40 -131
- data/rasti-form.gemspec +2 -12
- data/spec/coverage_helper.rb +1 -3
- data/spec/minitest_helper.rb +1 -7
- data/spec/validations_spec.rb +333 -0
- metadata +19 -48
- data/.travis.yml +0 -20
- data/lib/rasti/form/castable.rb +0 -25
- data/lib/rasti/form/types/array.rb +0 -54
- data/lib/rasti/form/types/boolean.rb +0 -42
- data/lib/rasti/form/types/enum.rb +0 -48
- data/lib/rasti/form/types/float.rb +0 -33
- data/lib/rasti/form/types/form.rb +0 -40
- data/lib/rasti/form/types/hash.rb +0 -39
- data/lib/rasti/form/types/integer.rb +0 -21
- data/lib/rasti/form/types/io.rb +0 -23
- data/lib/rasti/form/types/regexp.rb +0 -23
- data/lib/rasti/form/types/string.rb +0 -40
- data/lib/rasti/form/types/symbol.rb +0 -23
- data/lib/rasti/form/types/time.rb +0 -40
- data/lib/rasti/form/types/uuid.rb +0 -7
- data/lib/rasti/form/version.rb +0 -5
- data/spec/form_spec.rb +0 -411
- data/spec/types/array_spec.rb +0 -54
- data/spec/types/boolean_spec.rb +0 -24
- data/spec/types/enum_spec.rb +0 -28
- data/spec/types/float_spec.rb +0 -18
- data/spec/types/form_spec.rb +0 -41
- data/spec/types/hash_spec.rb +0 -20
- data/spec/types/integer_spec.rb +0 -18
- data/spec/types/io_spec.rb +0 -18
- data/spec/types/regexp_spec.rb +0 -18
- data/spec/types/string_formatted_spec.rb +0 -20
- data/spec/types/string_spec.rb +0 -16
- data/spec/types/symbol_spec.rb +0 -16
- data/spec/types/time_spec.rb +0 -25
- data/spec/types/uuid_spec.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efb9cfdea85fcce07f977a6bf54a183aface5cce17bfa3f9720f0b60684f48c3
|
4
|
+
data.tar.gz: f1aa1973dad9c2f363bd8f9f030ca48fd158a9897821439ab9e0d93c334a79de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38d34d94241efe87628538d8f6e59baf91e4f46ecf4afe72aebaddf69136de15e2cde9713329cb20d3fb80d8bbb9cbcb52c9041972f949051b925b89b3ce42fb
|
7
|
+
data.tar.gz: cd250e2f305c5836e6b70b6d1e3d0a0d964311af5ac68d5b033461d74b9461d8a4b1cc1948cc6c7d3e7b1bd399a980d5c84765aeced72cadee7d51408dc35d45
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: CI
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ master ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ master ]
|
15
|
+
|
16
|
+
permissions:
|
17
|
+
contents: read
|
18
|
+
|
19
|
+
jobs:
|
20
|
+
test:
|
21
|
+
|
22
|
+
runs-on: ubuntu-latest
|
23
|
+
name: Tests
|
24
|
+
strategy:
|
25
|
+
matrix:
|
26
|
+
ruby-version: ['2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', 'jruby-9.2.9.0']
|
27
|
+
|
28
|
+
steps:
|
29
|
+
- uses: actions/checkout@v3
|
30
|
+
- name: Set up Ruby
|
31
|
+
uses: ruby/setup-ruby@v1
|
32
|
+
with:
|
33
|
+
ruby-version: ${{ matrix.ruby-version }}
|
34
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
35
|
+
- name: Run tests
|
36
|
+
run: bundle exec rake
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# Rasti::Form
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/rasti-form.svg)](https://rubygems.org/gems/rasti-form)
|
4
|
-
[![
|
4
|
+
[![CI](https://github.com/gabynaiman/rasti-form/actions/workflows/ci.yml/badge.svg)](https://github.com/gabynaiman/rasti-form/actions/workflows/ci.yml)
|
5
5
|
[![Coverage Status](https://coveralls.io/repos/github/gabynaiman/rasti-form/badge.svg?branch=master)](https://coveralls.io/github/gabynaiman/rasti-form?branch=master)
|
6
6
|
[![Code Climate](https://codeclimate.com/github/gabynaiman/rasti-form.svg)](https://codeclimate.com/github/gabynaiman/rasti-form)
|
7
7
|
|
8
|
-
Forms validations
|
8
|
+
Forms validations
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
@@ -25,37 +25,18 @@ Or install it yourself as:
|
|
25
25
|
|
26
26
|
## Usage
|
27
27
|
|
28
|
-
```ruby
|
29
|
-
T = Rasti::Form::Types
|
30
|
-
```
|
31
|
-
|
32
|
-
### Type casting
|
33
|
-
|
34
|
-
```ruby
|
35
|
-
T::Integer.cast '10' # => 10
|
36
|
-
T::Integer.cast '10.5' # => 10
|
37
|
-
T::Integer.cast 'text' # => Rasti::Types::CastError: Invalid cast: 'text' -> Rasti::Types::Integer
|
38
|
-
|
39
|
-
T::Boolean.cast 'true' # => true
|
40
|
-
T::Boolean.cast 'FALSE' # => false
|
41
|
-
T::Boolean.cast 'text' # => Rasti::Types::CastError: Invalid cast: 'text' -> Rasti::Types::Boolean
|
42
|
-
|
43
|
-
T::Time['%Y-%m-%d'].cast '2016-10-22' # => 2016-10-22 00:00:00 -0300
|
44
|
-
T::Time['%Y-%m-%d'].cast '2016-10' # => Rasti::Types::CastError: Invalid cast: '2016-10' -> Rasti::Types::Time['%Y-%m-%d']
|
45
|
-
|
46
|
-
T::Array[T::Symbol].cast [1, 'test', :sym] # => [:"1", :test, :sym]
|
47
|
-
```
|
48
|
-
|
49
28
|
### Form type coercion
|
50
29
|
|
51
30
|
```ruby
|
31
|
+
T = Rasti::Types
|
32
|
+
|
52
33
|
PointForm = Rasti::Form[x: T::Integer, y: T::Integer] # => PointForm[:x, :y]
|
53
34
|
form = PointForm.new x: '1', y: 2 # => #<PointForm[x: 1, y: 2]>
|
54
35
|
form.x # => 1
|
55
36
|
form.y # => 2
|
56
|
-
form.
|
37
|
+
form.to_h # => {x: 1, y: 2}
|
57
38
|
|
58
|
-
PointForm.new x: true # => Validation error: {"x":["Invalid cast: true -> Rasti::
|
39
|
+
PointForm.new x: true # => Validation error: {"x":["Invalid cast: true -> Rasti::Types::Integer"]}
|
59
40
|
```
|
60
41
|
|
61
42
|
### Form validations
|
@@ -84,42 +65,6 @@ form.from # => 2016-10-20 00:00:00 -0300
|
|
84
65
|
form.to # => 2016-10-28 00:00:00 -0300
|
85
66
|
```
|
86
67
|
|
87
|
-
### Built-in types
|
88
|
-
|
89
|
-
- Array
|
90
|
-
- Boolean
|
91
|
-
- Enum
|
92
|
-
- Float
|
93
|
-
- Form
|
94
|
-
- Hash
|
95
|
-
- Integer
|
96
|
-
- IO
|
97
|
-
- Regexp
|
98
|
-
- String
|
99
|
-
- Symbol
|
100
|
-
- Time
|
101
|
-
- UUID
|
102
|
-
|
103
|
-
### Plugable types
|
104
|
-
|
105
|
-
```ruby
|
106
|
-
class CustomType
|
107
|
-
class << self
|
108
|
-
extend Castable
|
109
|
-
|
110
|
-
private
|
111
|
-
|
112
|
-
def valid?(value)
|
113
|
-
valid.is_a?(String)
|
114
|
-
end
|
115
|
-
|
116
|
-
def transform(value)
|
117
|
-
value.upcase
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
```
|
122
|
-
|
123
68
|
## Contributing
|
124
69
|
|
125
70
|
Bug reports and pull requests are welcome on GitHub at https://github.com/gabynaiman/rasti-form.
|
data/lib/rasti/form/errors.rb
CHANGED
@@ -1,65 +1,19 @@
|
|
1
1
|
module Rasti
|
2
2
|
class Form
|
3
|
-
|
4
|
-
class CastError < StandardError
|
5
3
|
|
6
|
-
|
4
|
+
class ValidationError < Rasti::Types::CompoundError
|
7
5
|
|
8
|
-
|
9
|
-
@type = type
|
10
|
-
@value = value
|
11
|
-
end
|
12
|
-
|
13
|
-
def message
|
14
|
-
"Invalid cast: #{display_value} -> #{type}"
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def display_value
|
20
|
-
value.is_a?(::String) ? "'#{value}'" : value.inspect
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
class MultiCastError < StandardError
|
6
|
+
attr_reader :scope
|
27
7
|
|
28
|
-
attr_reader :type, :value, :errors
|
29
|
-
|
30
|
-
def initialize(type, value, errors)
|
31
|
-
@type = type
|
32
|
-
@value = value
|
33
|
-
@errors = errors
|
34
|
-
end
|
35
|
-
|
36
|
-
def message
|
37
|
-
"Invalid cast: #{display_value} -> #{type} - #{JSON.dump(errors)}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def display_value
|
41
|
-
value.is_a?(::String) ? "'#{value}'" : value.inspect
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
class ValidationError < StandardError
|
47
|
-
|
48
|
-
attr_reader :scope, :errors
|
49
|
-
|
50
8
|
def initialize(scope, errors)
|
51
9
|
@scope = scope
|
52
|
-
|
10
|
+
super errors
|
53
11
|
end
|
54
12
|
|
55
|
-
|
56
|
-
lines = ['Validation errors:']
|
57
|
-
|
58
|
-
errors.each do |key, value|
|
59
|
-
lines << "- #{key}: #{value}"
|
60
|
-
end
|
13
|
+
private
|
61
14
|
|
62
|
-
|
15
|
+
def message_title
|
16
|
+
'Validation errors:'
|
63
17
|
end
|
64
18
|
|
65
19
|
end
|
data/lib/rasti/form/validable.rb
CHANGED
@@ -10,24 +10,27 @@ module Rasti
|
|
10
10
|
|
11
11
|
def validate!
|
12
12
|
validate
|
13
|
-
|
13
|
+
raise_if_errors!
|
14
14
|
end
|
15
15
|
|
16
16
|
def validate
|
17
17
|
end
|
18
18
|
|
19
|
+
def raise_if_errors!
|
20
|
+
raise ValidationError.new(self, errors) unless errors.empty?
|
21
|
+
end
|
22
|
+
|
19
23
|
def assert(key, condition, message)
|
20
|
-
|
21
|
-
|
22
|
-
errors[key] << message
|
23
|
-
false
|
24
|
+
errors[key] << message unless condition
|
25
|
+
condition
|
24
26
|
end
|
25
27
|
|
26
28
|
def assert_not_error(key)
|
27
29
|
yield
|
28
30
|
true
|
29
|
-
rescue =>
|
30
|
-
|
31
|
+
rescue => ex
|
32
|
+
errors[key] << ex.message
|
33
|
+
false
|
31
34
|
end
|
32
35
|
|
33
36
|
end
|
data/lib/rasti/form.rb
CHANGED
@@ -1,171 +1,80 @@
|
|
1
|
-
require '
|
1
|
+
require 'rasti-model'
|
2
2
|
require 'multi_require'
|
3
3
|
|
4
4
|
module Rasti
|
5
|
-
class Form
|
5
|
+
class Form < Rasti::Model
|
6
6
|
|
7
7
|
extend MultiRequire
|
8
8
|
|
9
9
|
require_relative_pattern 'form/*'
|
10
|
-
require_relative_pattern 'form/types/*'
|
11
10
|
|
12
11
|
include Validable
|
13
12
|
|
14
|
-
|
13
|
+
alias_method :__initialize__, :initialize
|
14
|
+
private :__initialize__
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
attribute name, type, options
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def inherited(subclass)
|
25
|
-
subclass.instance_variable_set :@attributes, attributes.dup
|
26
|
-
end
|
27
|
-
|
28
|
-
def attribute(name, type, options={})
|
29
|
-
attributes[name.to_sym] = options.merge(type: type)
|
30
|
-
attr_reader name
|
31
|
-
end
|
32
|
-
|
33
|
-
def attributes
|
34
|
-
@attributes ||= {}
|
35
|
-
end
|
36
|
-
|
37
|
-
def attribute_names
|
38
|
-
attributes.keys
|
39
|
-
end
|
40
|
-
|
41
|
-
def to_s
|
42
|
-
"#{name || self.superclass.name}[#{attribute_names.map(&:inspect).join(', ')}]"
|
43
|
-
end
|
44
|
-
alias_method :inspect, :to_s
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
def initialize(attrs={})
|
49
|
-
assign_attributes attrs
|
50
|
-
set_defaults
|
16
|
+
def initialize(attributes={})
|
17
|
+
assign_attributes! attributes
|
18
|
+
validate_type_casting!
|
51
19
|
validate!
|
52
20
|
end
|
53
21
|
|
54
|
-
def
|
55
|
-
|
56
|
-
end
|
57
|
-
alias_method :inspect, :to_s
|
58
|
-
|
59
|
-
def attributes(options={})
|
60
|
-
attributes_filter = {only: assigned_attribute_names, except: []}.merge(options)
|
61
|
-
(attributes_filter[:only] - attributes_filter[:except]).each_with_object({}) do |name, hash|
|
62
|
-
hash[name] = serialize(read_attribute(name))
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def to_h
|
67
|
-
attributes
|
68
|
-
end
|
69
|
-
|
70
|
-
def assigned?(name)
|
71
|
-
assigned_attribute_names.include? name
|
72
|
-
end
|
73
|
-
|
74
|
-
def ==(other)
|
75
|
-
other.kind_of?(self.class) && other.attributes == attributes
|
76
|
-
end
|
77
|
-
|
78
|
-
def eql?(other)
|
79
|
-
other.instance_of?(self.class) && other.attributes == attributes
|
80
|
-
end
|
81
|
-
|
82
|
-
def hash
|
83
|
-
[self.class, attributes].hash
|
22
|
+
def assigned?(attr_name)
|
23
|
+
assigned_attribute? attr_name.to_sym
|
84
24
|
end
|
85
25
|
|
86
26
|
private
|
87
27
|
|
88
|
-
def assign_attributes(
|
89
|
-
|
90
|
-
attr_name = name.to_sym
|
91
|
-
begin
|
92
|
-
if self.class.attributes.key? attr_name
|
93
|
-
write_attribute attr_name, value
|
94
|
-
else
|
95
|
-
errors[attr_name] << 'unexpected attribute'
|
96
|
-
end
|
97
|
-
|
98
|
-
rescue CastError => error
|
99
|
-
errors[attr_name] << error.message
|
100
|
-
|
101
|
-
rescue MultiCastError, ValidationError => error
|
102
|
-
error.errors.each do |inner_name, inner_errors|
|
103
|
-
inner_errors.each { |message| errors["#{attr_name}.#{inner_name}"] << message }
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
28
|
+
def assign_attributes!(attributes)
|
29
|
+
__initialize__ attributes
|
108
30
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
value = self.class.attributes[name][:default]
|
113
|
-
write_attribute name, value.is_a?(Proc) ? value.call(self) : value
|
114
|
-
end
|
31
|
+
rescue Rasti::Model::UnexpectedAttributesError => ex
|
32
|
+
ex.attributes.each do |attr_name|
|
33
|
+
errors[attr_name] << 'unexpected attribute'
|
115
34
|
end
|
116
|
-
end
|
117
35
|
|
118
|
-
|
119
|
-
|
36
|
+
ensure
|
37
|
+
raise_if_errors!
|
120
38
|
end
|
121
39
|
|
122
|
-
def
|
123
|
-
|
124
|
-
value.map { |v| serialize v }
|
125
|
-
elsif value.kind_of? Form
|
126
|
-
value.attributes
|
127
|
-
else
|
128
|
-
value
|
129
|
-
end
|
130
|
-
end
|
40
|
+
def validate_type_casting!
|
41
|
+
cast_attributes!
|
131
42
|
|
132
|
-
|
133
|
-
|
134
|
-
|
43
|
+
rescue Rasti::Types::CompoundError => ex
|
44
|
+
ex.errors.each do |key, messages|
|
45
|
+
errors[key] += messages
|
46
|
+
end
|
135
47
|
|
136
|
-
|
137
|
-
|
138
|
-
instance_variable_set "@#{name}", typed_value
|
48
|
+
ensure
|
49
|
+
raise_if_errors!
|
139
50
|
end
|
140
51
|
|
141
|
-
def
|
142
|
-
|
143
|
-
|
52
|
+
def assert_present(attr_name)
|
53
|
+
if !errors.key?(attr_name)
|
54
|
+
assert attr_name, assigned?(attr_name) && !public_send(attr_name).nil?, 'not present'
|
144
55
|
end
|
145
56
|
end
|
146
57
|
|
147
|
-
def
|
148
|
-
assert
|
149
|
-
end
|
150
|
-
|
151
|
-
def assert_not_present(attribute)
|
152
|
-
assert attribute, fetch(attribute).nil?, 'is present'
|
58
|
+
def assert_not_present(attr_name)
|
59
|
+
assert attr_name, !assigned?(attr_name) || public_send(attr_name).nil?, 'is present'
|
153
60
|
end
|
154
61
|
|
155
|
-
def assert_not_empty(
|
156
|
-
if assert_present
|
157
|
-
value =
|
158
|
-
assert
|
62
|
+
def assert_not_empty(attr_name)
|
63
|
+
if assert_present attr_name
|
64
|
+
value = public_send attr_name
|
65
|
+
assert attr_name, value.is_a?(String) ? !value.strip.empty? : !value.empty?, 'is empty'
|
159
66
|
end
|
160
67
|
end
|
161
68
|
|
162
|
-
def
|
163
|
-
|
69
|
+
def assert_included_in(attr_name, set)
|
70
|
+
if assert_present attr_name
|
71
|
+
assert attr_name, set.include?(public_send(attr_name)), "not included in #{set.map(&:inspect).join(', ')}"
|
72
|
+
end
|
164
73
|
end
|
165
74
|
|
166
|
-
def
|
167
|
-
if assert_present
|
168
|
-
assert
|
75
|
+
def assert_range(attr_name_from, attr_name_to)
|
76
|
+
if assert_present(attr_name_from) && assert_present(attr_name_to)
|
77
|
+
assert attr_name_from, public_send(attr_name_from) <= public_send(attr_name_to), 'invalid range'
|
169
78
|
end
|
170
79
|
end
|
171
80
|
|
data/rasti-form.gemspec
CHANGED
@@ -1,11 +1,6 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'rasti/form/version'
|
5
|
-
|
6
1
|
Gem::Specification.new do |spec|
|
7
2
|
spec.name = 'rasti-form'
|
8
|
-
spec.version =
|
3
|
+
spec.version = '6.0.0'
|
9
4
|
spec.authors = ['Gabriel Naiman']
|
10
5
|
spec.email = ['gabynaiman@gmail.com']
|
11
6
|
spec.summary = 'Forms validations and type casting'
|
@@ -19,6 +14,7 @@ Gem::Specification.new do |spec|
|
|
19
14
|
spec.require_paths = ['lib']
|
20
15
|
|
21
16
|
spec.add_runtime_dependency 'multi_require', '~> 1.0'
|
17
|
+
spec.add_runtime_dependency 'rasti-model', '~> 2.0'
|
22
18
|
|
23
19
|
spec.add_development_dependency 'rake', '~> 12.0'
|
24
20
|
spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
|
@@ -27,10 +23,4 @@ Gem::Specification.new do |spec|
|
|
27
23
|
spec.add_development_dependency 'simplecov', '~> 0.12'
|
28
24
|
spec.add_development_dependency 'coveralls', '~> 0.8'
|
29
25
|
spec.add_development_dependency 'pry-nav', '~> 0.2'
|
30
|
-
|
31
|
-
if RUBY_VERSION < '2'
|
32
|
-
spec.add_development_dependency 'term-ansicolor', '~> 1.3.0'
|
33
|
-
spec.add_development_dependency 'tins', '~> 1.6.0'
|
34
|
-
spec.add_development_dependency 'json', '~> 1.8'
|
35
|
-
end
|
36
26
|
end
|
data/spec/coverage_helper.rb
CHANGED