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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +36 -0
  3. data/README.md +6 -61
  4. data/lib/rasti/form/errors.rb +6 -52
  5. data/lib/rasti/form/validable.rb +10 -7
  6. data/lib/rasti/form.rb +40 -131
  7. data/rasti-form.gemspec +2 -12
  8. data/spec/coverage_helper.rb +1 -3
  9. data/spec/minitest_helper.rb +1 -7
  10. data/spec/validations_spec.rb +333 -0
  11. metadata +19 -48
  12. data/.travis.yml +0 -20
  13. data/lib/rasti/form/castable.rb +0 -25
  14. data/lib/rasti/form/types/array.rb +0 -54
  15. data/lib/rasti/form/types/boolean.rb +0 -42
  16. data/lib/rasti/form/types/enum.rb +0 -48
  17. data/lib/rasti/form/types/float.rb +0 -33
  18. data/lib/rasti/form/types/form.rb +0 -40
  19. data/lib/rasti/form/types/hash.rb +0 -39
  20. data/lib/rasti/form/types/integer.rb +0 -21
  21. data/lib/rasti/form/types/io.rb +0 -23
  22. data/lib/rasti/form/types/regexp.rb +0 -23
  23. data/lib/rasti/form/types/string.rb +0 -40
  24. data/lib/rasti/form/types/symbol.rb +0 -23
  25. data/lib/rasti/form/types/time.rb +0 -40
  26. data/lib/rasti/form/types/uuid.rb +0 -7
  27. data/lib/rasti/form/version.rb +0 -5
  28. data/spec/form_spec.rb +0 -411
  29. data/spec/types/array_spec.rb +0 -54
  30. data/spec/types/boolean_spec.rb +0 -24
  31. data/spec/types/enum_spec.rb +0 -28
  32. data/spec/types/float_spec.rb +0 -18
  33. data/spec/types/form_spec.rb +0 -41
  34. data/spec/types/hash_spec.rb +0 -20
  35. data/spec/types/integer_spec.rb +0 -18
  36. data/spec/types/io_spec.rb +0 -18
  37. data/spec/types/regexp_spec.rb +0 -18
  38. data/spec/types/string_formatted_spec.rb +0 -20
  39. data/spec/types/string_spec.rb +0 -16
  40. data/spec/types/symbol_spec.rb +0 -16
  41. data/spec/types/time_spec.rb +0 -25
  42. data/spec/types/uuid_spec.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d63a6c2410b6e7bbb259a0212442d2aad83fa53c4191856baf04466d3e9c73d0
4
- data.tar.gz: 1605263448bb732f3095b5fba45829ece27cecf159c2dbe39d1729dc3dc7981d
3
+ metadata.gz: efb9cfdea85fcce07f977a6bf54a183aface5cce17bfa3f9720f0b60684f48c3
4
+ data.tar.gz: f1aa1973dad9c2f363bd8f9f030ca48fd158a9897821439ab9e0d93c334a79de
5
5
  SHA512:
6
- metadata.gz: e5df6a4b5dc7e8b1181a72ef0a1c9ba51b13b643c25c5cb6b8fef47369b181ee0c3598da1d4a22884dee6b5d4b9515f5638a52ae7a15b7cb9dd6c91c92863a76
7
- data.tar.gz: da10879fdf48628f72e4d353ee22b628e7520c2e2201918712c63401bb81cd6a1550916f2d1c93bf68249b03ed98c938266842713c32f9bc3d380b7f3e8834fa
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
- [![Build Status](https://travis-ci.org/gabynaiman/rasti-form.svg?branch=master)](https://travis-ci.org/gabynaiman/rasti-form)
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 and type casting
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.attributes # => {x: 1, y: 2}
37
+ form.to_h # => {x: 1, y: 2}
57
38
 
58
- PointForm.new x: true # => Validation error: {"x":["Invalid cast: true -> Rasti::Form::Types::Integer"]}
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.
@@ -1,65 +1,19 @@
1
1
  module Rasti
2
2
  class Form
3
-
4
- class CastError < StandardError
5
3
 
6
- attr_reader :type, :value
4
+ class ValidationError < Rasti::Types::CompoundError
7
5
 
8
- def initialize(type, value)
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
- @errors = errors
10
+ super errors
53
11
  end
54
12
 
55
- def message
56
- lines = ['Validation errors:']
57
-
58
- errors.each do |key, value|
59
- lines << "- #{key}: #{value}"
60
- end
13
+ private
61
14
 
62
- lines.join("\n")
15
+ def message_title
16
+ 'Validation errors:'
63
17
  end
64
18
 
65
19
  end
@@ -10,24 +10,27 @@ module Rasti
10
10
 
11
11
  def validate!
12
12
  validate
13
- raise ValidationError.new(self, errors) unless errors.empty?
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
- return true if condition
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 => error
30
- assert key, false, error.message
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 'json'
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
- class << self
13
+ alias_method :__initialize__, :initialize
14
+ private :__initialize__
15
15
 
16
- def [](attributes)
17
- Class.new(self) do
18
- attributes.each do |name, type, options={}|
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 to_s
55
- "#<#{self.class.name || self.class.superclass.name}[#{to_h.map { |n,v| "#{n}: #{v.inspect}" }.join(', ')}]>"
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(attrs={})
89
- attrs.each do |name, value|
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
- def set_defaults
110
- (self.class.attribute_names - attributes.keys).each do |name|
111
- if self.class.attributes[name].key? :default
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
- def assigned_attribute_names
119
- self.class.attribute_names & instance_variables.map { |v| v.to_s[1..-1].to_sym }
36
+ ensure
37
+ raise_if_errors!
120
38
  end
121
39
 
122
- def serialize(value)
123
- if value.kind_of? Array
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
- def read_attribute(name)
133
- instance_variable_get "@#{name}"
134
- end
43
+ rescue Rasti::Types::CompoundError => ex
44
+ ex.errors.each do |key, messages|
45
+ errors[key] += messages
46
+ end
135
47
 
136
- def write_attribute(name, value)
137
- typed_value = value.nil? ? nil : self.class.attributes[name][:type].cast(value)
138
- instance_variable_set "@#{name}", typed_value
48
+ ensure
49
+ raise_if_errors!
139
50
  end
140
51
 
141
- def fetch(attribute)
142
- attribute.to_s.split('.').inject(self) do |target, attr_name|
143
- target.nil? ? nil : target.public_send(attr_name)
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 assert_present(attribute)
148
- assert attribute, !fetch(attribute).nil?, 'not present' unless errors.key? attribute
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(attribute)
156
- if assert_present attribute
157
- value = fetch attribute
158
- assert attribute, value.is_a?(String) ? !value.strip.empty? : !value.empty?, 'is empty'
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 assert_time_range(attribute_from, attribute_to)
163
- assert attribute_from, public_send(attribute_from) <= public_send(attribute_to), 'invalid time range'
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 assert_included_in(attribute, set)
167
- if assert_present attribute
168
- assert attribute, set.include?(fetch(attribute)), "not included in #{set.map { |e| e.is_a?(::String) ? "'#{e}'" : e.inspect }.join(', ')}"
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 = Rasti::Form::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
@@ -2,6 +2,4 @@ require 'simplecov'
2
2
  require 'coveralls'
3
3
 
4
4
  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]
5
- SimpleCov.start do
6
- add_group 'Types', 'lib/rasti/form/types'
7
- end
5
+ SimpleCov.start
@@ -4,10 +4,4 @@ require 'minitest/colorin'
4
4
  require 'pry-nav'
5
5
  require 'rasti-form'
6
6
 
7
- module Minitest
8
- class Test
9
- def as_string(value)
10
- value.is_a?(::String) ? "'#{value}'" : value.inspect
11
- end
12
- end
13
- end
7
+ T = Rasti::Types