dry-validation-rails 0.1.0 → 0.1.1
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 +4 -4
- data/README.md +246 -1
- data/lib/dry/validation/rails/configuration.rb +19 -0
- data/lib/dry/validation/rails/helpers.rb +99 -0
- data/lib/dry/validation/rails/validatable.rb +13 -0
- data/lib/dry/validation/rails/validator.rb +70 -0
- data/lib/dry/validation/rails/version.rb +1 -1
- data/lib/dry/validation/rails.rb +26 -2
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe5b7374edb7ccf542e436b1f90fd2caca2cae5b0a320a72b292cf053600f5a1
|
4
|
+
data.tar.gz: fb19d418b26fe96e37d1e3aa550e915389214d6fdd3fe40edf9cd9ceb0315ea0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d88af909bf6625d14def732f9bd0b309a511446d724f87ebace41268908c35c59a869610180a63b5d5bcfc9a6f657129dac25ef03183203733e47346ae1ee651
|
7
|
+
data.tar.gz: 212c57c2d78d2befd7841436efc1a859ec21b7c72ae5c9e94b93588ed7e3770ba1887e7bed1f6d8ebe9a6cbcf8eb72e9ff90f63d6f22f23ccc548dbb76d091cf
|
data/README.md
CHANGED
@@ -24,9 +24,254 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
24
24
|
$ gem install dry-validation-rails
|
25
25
|
```
|
26
26
|
|
27
|
+
## Configuration
|
28
|
+
```ruby
|
29
|
+
Dry::Validation::Rails.configure do |config|
|
30
|
+
config.default_schema_prefix = 'ApplicationContract::'
|
31
|
+
config.default_schema_suffix = 'Schema'
|
32
|
+
end
|
33
|
+
|
34
|
+
Dry::Validation::Rails.configuration.default_schema_prefix = 'ApplicationContract::'
|
35
|
+
Dry::Validation::Rails.configuration.default_schema_suffix = 'Contract'
|
36
|
+
```
|
37
|
+
|
27
38
|
## Usage
|
28
39
|
|
29
|
-
|
40
|
+
Simply drop in followability to a model:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
class User < ActiveRecord::Base
|
44
|
+
validates_with_dry
|
45
|
+
end
|
46
|
+
|
47
|
+
class UserSchema < Dry::Validation::Contract
|
48
|
+
params do
|
49
|
+
required(:name).filled(:string)
|
50
|
+
required(:email).filled(:string)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
user = User.new(name: '', email: '')
|
55
|
+
user.valid? # => false
|
56
|
+
user.errors.full_messages # => ["Name is missing", "Name must be a string", "Email is missing", "Email must be a string"]
|
57
|
+
```
|
58
|
+
|
59
|
+
### With DRY Schema
|
60
|
+
You can use [dry-schema](https://dry-rb.org/gems/dry-schema/) for defining your schema and use it in your model.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
class User < ActiveRecord::Base
|
64
|
+
validates_with_dry
|
65
|
+
end
|
66
|
+
|
67
|
+
UserSchema = Dry::Schema.Params do
|
68
|
+
required(:name).filled(:string)
|
69
|
+
required(:email).filled(:string)
|
70
|
+
end
|
71
|
+
|
72
|
+
user = User.new(name: '', email: '')
|
73
|
+
user.valid? # => false
|
74
|
+
user.errors.full_messages # => ["Name is missing", "Name must be a string", "Email is missing", "Email must be a string"]
|
75
|
+
```
|
76
|
+
|
77
|
+
### With DRY Validation Contract
|
78
|
+
You can use [dry-validation](https://dry-rb.org/gems/dry-validation/) for defining your schema and use it in your model.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class User < ActiveRecord::Base
|
82
|
+
validates_with_dry
|
83
|
+
end
|
84
|
+
|
85
|
+
class UserSchema < Dry::Validation::Contract
|
86
|
+
params do
|
87
|
+
required(:name).filled(:string)
|
88
|
+
required(:email).filled(:string)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
user = User.new(name: '', email: '')
|
93
|
+
user.valid? # => false
|
94
|
+
user.errors.full_messages # => ["Name is missing", "Name must be a string", "Email is missing", "Email must be a string"]
|
95
|
+
```
|
96
|
+
|
97
|
+
### Passing options
|
98
|
+
|
99
|
+
You can pass options to the schema by passing a hash to the `validates_with_dry` method.
|
100
|
+
|
101
|
+
|
102
|
+
#### Prefix and Suffix
|
103
|
+
```ruby
|
104
|
+
class User < ActiveRecord::Base
|
105
|
+
# You can pass custom prefix and suffix for validator class
|
106
|
+
validates_with_dry schema_prefix: 'ApplicationContract::', schema_suffix: 'Contract'
|
107
|
+
end
|
108
|
+
|
109
|
+
class Application
|
110
|
+
class UserContract < Dry::Validation::Contract
|
111
|
+
params do
|
112
|
+
required(:name).filled(:string)
|
113
|
+
required(:email).filled(:string)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
user = User.new(name: '', email: '')
|
119
|
+
user.valid? # => false
|
120
|
+
user.errors.full_messages # => ["Name is missing", "Name must be a string", "Email is missing", "Email must be a string"]
|
121
|
+
```
|
122
|
+
|
123
|
+
#### Custom Schema
|
124
|
+
```ruby
|
125
|
+
class User < ActiveRecord::Base
|
126
|
+
validates_with_dry schema: UserCustomSchema # custom schema
|
127
|
+
end
|
128
|
+
|
129
|
+
class UserCustomSchema < Dry::Validation::Contract
|
130
|
+
params do
|
131
|
+
required(:name).filled(:string)
|
132
|
+
required(:email).filled(:string)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
user = User.new(name: '', email: '')
|
137
|
+
user.valid? # => false
|
138
|
+
user.errors.full_messages # => ["Name is missing", "Name must be a string", "Email is missing", "Email must be a string"]
|
139
|
+
```
|
140
|
+
|
141
|
+
#### Pass record to contract
|
142
|
+
We can define it as an external dependency that will be injected to the contract's constructor for Dry Validation Contract. You can pass record to the contract like this:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
class User < ActiveRecord::Base
|
146
|
+
validates_with_dry schema: UserSchema, pass_record_to_contract: true # default key is :record
|
147
|
+
end
|
148
|
+
|
149
|
+
class UserSchema < Dry::Validation::Contract
|
150
|
+
option :record # this is the record that will be passed to the contract as an option
|
151
|
+
|
152
|
+
params do
|
153
|
+
required(:name).filled(:string)
|
154
|
+
required(:email).filled(:string)
|
155
|
+
required(:age).filled(:integer).value(gteq?: 18)
|
156
|
+
end
|
157
|
+
|
158
|
+
rule(:age) do
|
159
|
+
key.failure('must be greater than 20') if record.role.admin? && value < 20
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
user = User.new(name: '', email: '')
|
164
|
+
user.valid? # => false
|
165
|
+
user.errors.full_messages # => ["Name is missing", "Name must be a string", "Email is missing", "Email must be a string"]
|
166
|
+
```
|
167
|
+
|
168
|
+
**Custom key for record**
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class User < ActiveRecord::Base
|
172
|
+
validates_with_dry schema: UserSchema, pass_record_to_contract: { as: :user }
|
173
|
+
end
|
174
|
+
|
175
|
+
class UserSchema < Dry::Validation::Contract
|
176
|
+
option :user
|
177
|
+
|
178
|
+
params do
|
179
|
+
required(:name).filled(:string)
|
180
|
+
required(:email).filled(:string)
|
181
|
+
required(:age).filled(:integer).value(gteq?: 18)
|
182
|
+
end
|
183
|
+
|
184
|
+
rule(:age) do
|
185
|
+
key.failure('must be greater than 20') if user.role.admin? && value < 20
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
user = User.new(name: '', email: '')
|
190
|
+
user.valid? # => false
|
191
|
+
user.errors.full_messages # => ["Name is missing", "Name must be a string", "Email is missing", "Email must be a string"]
|
192
|
+
```
|
193
|
+
|
194
|
+
#### Run validation on update or create
|
195
|
+
Default is `:all` which means it will run on both create and update. You can pass `:create` or `:update` to run validation only on create or update.
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
class User < ActiveRecord::Base
|
199
|
+
validates_with_dry schema: UserSchema, on: :create
|
200
|
+
end
|
201
|
+
|
202
|
+
class UserSchema < Dry::Validation::Contract
|
203
|
+
params do
|
204
|
+
required(:name).filled(:string)
|
205
|
+
required(:email).filled(:string)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
user = User.first
|
210
|
+
user.name = ''
|
211
|
+
user.valid? # => true
|
212
|
+
user.errors.full_messages # => []
|
213
|
+
```
|
214
|
+
|
215
|
+
### Run validation with if or unless
|
216
|
+
You can pass `if` or `unless` option as a symbol or a proc to run validation only if the condition is true.
|
217
|
+
|
218
|
+
***if***
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
class User < ActiveRecord::Base
|
222
|
+
validates_with_dry schema: AdminSchema, if: Proc.new { |user| user.admin? }
|
223
|
+
validates_with_dry schema: UserSchema, if: :normal_user?
|
224
|
+
|
225
|
+
def admin?
|
226
|
+
role.admin?
|
227
|
+
end
|
228
|
+
|
229
|
+
def normal_user?
|
230
|
+
!admin?
|
231
|
+
end
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
***unless***
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
class User < ActiveRecord::Base
|
239
|
+
validates_with_dry schema: AdminSchema, unless: :normal_user?
|
240
|
+
validates_with_dry schema: UserSchema, unless: Proc.new { |user| user.admin? }
|
241
|
+
|
242
|
+
def admin?
|
243
|
+
role.admin?
|
244
|
+
end
|
245
|
+
|
246
|
+
def normal_user?
|
247
|
+
!admin?
|
248
|
+
end
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
## JSON / JSONB Attributes
|
253
|
+
You can validate json/jsonb columns with like this:
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
class User < ActiveRecord::Base
|
257
|
+
validates_with_dry schema: UserSchema
|
258
|
+
end
|
259
|
+
|
260
|
+
class UserSchema < Dry::Validation::Contract
|
261
|
+
params do
|
262
|
+
required(:name).filled(:string)
|
263
|
+
required(:email).filled(:string)
|
264
|
+
required(:preferences).hash do
|
265
|
+
required(:color).filled(:string)
|
266
|
+
required(:font).filled(:string)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
user = User.new(name: '', email: '', preferences: { color: '', font: '' })
|
272
|
+
user.valid? # => false
|
273
|
+
user.errors.full_messages # => ["Name is missing", "Name must be a string", "Email is missing", "Email must be a string", "Preferences is missing", "Preferences must be a hash", "Preferences.color is missing", "Preferences.color must be a string", "Preferences.font is missing", "Preferences.font must be a string"]
|
274
|
+
```
|
30
275
|
|
31
276
|
## Development
|
32
277
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Validation
|
5
|
+
module Rails
|
6
|
+
class Configuration
|
7
|
+
DEFAULT_SCHEMA_PREFIX = ''
|
8
|
+
DEFAULT_SCHEMA_SUFFIX = 'Schema'
|
9
|
+
|
10
|
+
attr_accessor :default_schema_prefix, :default_schema_suffix
|
11
|
+
|
12
|
+
def initialize(default_schema_prefix: DEFAULT_SCHEMA_PREFIX, default_schema_suffix: DEFAULT_SCHEMA_SUFFIX)
|
13
|
+
@default_schema_prefix = default_schema_prefix
|
14
|
+
@default_schema_suffix = default_schema_suffix
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Validation
|
5
|
+
module Rails
|
6
|
+
module Helpers
|
7
|
+
SUPPORTED_VALIDATORS = [
|
8
|
+
Dry::Validation::Contract,
|
9
|
+
Dry::Schema.Params,
|
10
|
+
Dry::Schema::JSON
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
def default_schema_for(record, options = {})
|
14
|
+
prefix = options.fetch(:schema_prefix, Dry::Validation::Rails.configuration.default_schema_prefix).to_s
|
15
|
+
suffix = options.fetch(:schema_suffix, Dry::Validation::Rails.configuration.default_schema_suffix).to_s
|
16
|
+
|
17
|
+
begin
|
18
|
+
"#{prefix}#{record.class.name}#{suffix}".classify.constantize
|
19
|
+
rescue StandardError
|
20
|
+
raise Dry::Validation::Rails::SchemaNotFound, "Schema not found for #{record.class.name}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# rubocop:disable Layout/LineLength
|
25
|
+
def valid_schema?(schema)
|
26
|
+
if schema.respond_to?(:ancestors)
|
27
|
+
return schema.ancestors.any? do |ancestor|
|
28
|
+
SUPPORTED_VALIDATORS.include?(ancestor)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
schema.is_a?(Dry::Validation::Contract) || schema.is_a?(Dry::Schema::Params) || schema.is_a?(Dry::Schema::JSON)
|
33
|
+
end
|
34
|
+
# rubocop:enable Layout/LineLength
|
35
|
+
|
36
|
+
def need_to_validate?(record, options)
|
37
|
+
return true if options[:on].blank? || options[:on] == :all
|
38
|
+
|
39
|
+
record.new_record? if options[:on] == :create
|
40
|
+
|
41
|
+
record.persisted? if options[:on] == :update
|
42
|
+
|
43
|
+
raise ArgumentError, "Invalid value for :on option: #{options[:on]}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# rubocop:disable Naming/PredicateName
|
47
|
+
def is_dry_schema?(schema)
|
48
|
+
schema.is_a?(Dry::Schema::Params) || schema.is_a?(Dry::Schema::JSON)
|
49
|
+
end
|
50
|
+
|
51
|
+
def is_dry_validation_contract?(schema)
|
52
|
+
schema.respond_to?(:ancestors) && schema.ancestors.include?(Dry::Validation::Contract)
|
53
|
+
end
|
54
|
+
# rubocop:enable Naming/PredicateName
|
55
|
+
|
56
|
+
def pass_record_to_contract?(options)
|
57
|
+
value = options[:pass_record_to_contract]
|
58
|
+
|
59
|
+
return true if boolean?(value) || value.is_a?(Hash)
|
60
|
+
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
def record_key_for_passing_to_contract(options)
|
65
|
+
return :record if boolean?(options[:pass_record_to_contract])
|
66
|
+
|
67
|
+
options.fetch(:pass_record_to_contract, {}).fetch(:as, :record)
|
68
|
+
end
|
69
|
+
|
70
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
71
|
+
def skip_validation?(options, record)
|
72
|
+
if_val = options[:if]
|
73
|
+
unless_val = options[:unless]
|
74
|
+
|
75
|
+
if if_val.present?
|
76
|
+
return false unless if_val.is_a?(Symbol) || if_val.is_a?(Proc)
|
77
|
+
return false if if_val.is_a?(Symbol) && record.respond_to?(if_val) && record.send(if_val)
|
78
|
+
return false if if_val.is_a?(Proc) && if_val.call(record)
|
79
|
+
end
|
80
|
+
|
81
|
+
if unless_val.present?
|
82
|
+
return false unless unless_val.is_a?(Symbol) || unless_val.is_a?(Proc)
|
83
|
+
return false if unless_val.is_a?(Symbol) && record.respond_to?(unless_val) && !record.send(unless_val)
|
84
|
+
return false if unless_val.is_a?(Proc) && !unless_val.call(record)
|
85
|
+
end
|
86
|
+
|
87
|
+
false
|
88
|
+
end
|
89
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def boolean?(value)
|
94
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helpers'
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Validation
|
7
|
+
module Rails
|
8
|
+
class Validator < ActiveModel::Validator
|
9
|
+
include Dry::Validation::Rails::Helpers
|
10
|
+
|
11
|
+
def validate(record)
|
12
|
+
return if skip_validation?(options, record)
|
13
|
+
|
14
|
+
schema = options.fetch(:schema, default_schema_for(record, options))
|
15
|
+
|
16
|
+
unless valid_schema?(schema)
|
17
|
+
raise ArgumentError,
|
18
|
+
'Schema must be a Dry::Schema::Params or Dry::Schema::JSON or Dry::Validation::Contract'
|
19
|
+
end
|
20
|
+
|
21
|
+
validate_schema(record, schema, options) if need_to_validate?(record, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_schema(record, schema, options)
|
25
|
+
if is_dry_validation_contract?(schema)
|
26
|
+
validate_dry_validation_contract(record, schema, options)
|
27
|
+
elsif is_dry_schema?(schema)
|
28
|
+
validate_dry_schema(record, schema, options)
|
29
|
+
else
|
30
|
+
raise ArgumentError,
|
31
|
+
'Schema must be a Dry::Schema::Params or Dry::Schema::JSON or Dry::Validation::Contract'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def validate_dry_validation_contract(record, schema, options)
|
38
|
+
validator_schema = if pass_record_to_contract?(options)
|
39
|
+
key = record_key_for_passing_to_contract(options)
|
40
|
+
schema.new(key => record)
|
41
|
+
else
|
42
|
+
schema.new
|
43
|
+
end
|
44
|
+
|
45
|
+
result = validator_schema.call(record.attributes)
|
46
|
+
|
47
|
+
return if result.success?
|
48
|
+
|
49
|
+
validate_each(record, result.errors.to_h)
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_dry_schema(record, schema, _options)
|
53
|
+
result = schema.call(record.attributes)
|
54
|
+
|
55
|
+
return if result.success?
|
56
|
+
|
57
|
+
validate_each(record, result.errors.to_h)
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_each(record, errors)
|
61
|
+
errors.each do |attribute, all_errors|
|
62
|
+
all_errors.each do |error|
|
63
|
+
record.errors.add(attribute, error) if record.respond_to?(attribute.to_sym)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/dry/validation/rails.rb
CHANGED
@@ -1,14 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'rails/version'
|
4
|
+
require_relative 'rails/configuration'
|
4
5
|
require 'rails'
|
5
6
|
require 'dry-validation'
|
7
|
+
require 'active_support'
|
8
|
+
require 'active_record'
|
9
|
+
require 'active_model'
|
6
10
|
|
7
11
|
module Dry
|
8
12
|
module Validation
|
9
13
|
module Rails
|
10
|
-
|
11
|
-
|
14
|
+
extend ActiveSupport::Autoload
|
15
|
+
|
16
|
+
autoload :Validatable
|
17
|
+
autoload :Validator
|
18
|
+
autoload :Errors
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def configuration
|
22
|
+
@configuration ||= Configuration.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def configure
|
26
|
+
yield configuration
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class BaseError < StandardError; end
|
31
|
+
class SchemaNotFound < BaseError; end
|
12
32
|
end
|
13
33
|
end
|
14
34
|
end
|
35
|
+
|
36
|
+
ActiveSupport.on_load(:active_record) do
|
37
|
+
extend Dry::Validation::Rails::Validatable
|
38
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-validation-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- nejdetkadir
|
@@ -56,6 +56,10 @@ files:
|
|
56
56
|
- Rakefile
|
57
57
|
- dry-validation-rails.gemspec
|
58
58
|
- lib/dry/validation/rails.rb
|
59
|
+
- lib/dry/validation/rails/configuration.rb
|
60
|
+
- lib/dry/validation/rails/helpers.rb
|
61
|
+
- lib/dry/validation/rails/validatable.rb
|
62
|
+
- lib/dry/validation/rails/validator.rb
|
59
63
|
- lib/dry/validation/rails/version.rb
|
60
64
|
- sig/dry/validation/rails.rbs
|
61
65
|
homepage: https://github.com/nejdetkadir/dry-validation-rails
|