dry-validation-rails 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 500d12bc6fad48330593308a23c315510fc282a787c7cdeb41503b30dd518c78
4
- data.tar.gz: dd435b9261fb3dfbe81ac3200f9df3b3f86244b8e704378ff591bfc7cc76668b
3
+ metadata.gz: fe5b7374edb7ccf542e436b1f90fd2caca2cae5b0a320a72b292cf053600f5a1
4
+ data.tar.gz: fb19d418b26fe96e37d1e3aa550e915389214d6fdd3fe40edf9cd9ceb0315ea0
5
5
  SHA512:
6
- metadata.gz: f5c6a9ef66888dcfb94a9516bab3f73dd1e4ac622737f5eb92abb0a56b4668b4a7becbbf4c2c2387178242d9ed61e1bed26f72028fb2f3f0674b7db84d5e8cc1
7
- data.tar.gz: 56dda1dfcd16135f5c9a0da9373c7955e2c391b1679dd132109d00763b4b76106aa823bd672f6b48489d48cba460e737106e2d708acb5f132b5eb281ca8ab34d
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
- TODO: Write usage instructions here
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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Validation
5
+ module Rails
6
+ module Validatable
7
+ def validates_with_dry(**options)
8
+ validates_with Dry::Validation::Rails::Validator, **options
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -3,7 +3,7 @@
3
3
  module Dry
4
4
  module Validation
5
5
  module Rails
6
- VERSION = '0.1.0'
6
+ VERSION = '0.1.1'
7
7
  end
8
8
  end
9
9
  end
@@ -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
- class Error < StandardError; end
11
- # Your code goes here...
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.0
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