rasti-form 3.1.2 → 6.0.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.
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