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 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