tram-validators 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aca5b01edeaa35458d5ba907676b8bed73a60f1e
4
+ data.tar.gz: 9c65185129ee67af6848516c0623405676176383
5
+ SHA512:
6
+ metadata.gz: dcf7914965ea2df8011562c7dc85b4f0f5fd22fe388421558394fdbcb6e39ec6cc5fbb885f79dc0a024a7bc4016159770791c855db8ba51a44a8135af0bcaaf0
7
+ data.tar.gz: 61dd66c80636c75067a5c2eb438d682a3fae75a818113f905e5d806296a741e1243f516a544059ddae681d2206a2ca16e1460dbb34f472d4a4f64f33d0def8b6
@@ -0,0 +1,18 @@
1
+ ---
2
+ engines:
3
+ bundler-audit:
4
+ enabled: true
5
+ duplication:
6
+ enabled: true
7
+ config:
8
+ languages:
9
+ - ruby
10
+ fixme:
11
+ enabled: true
12
+ rubocop:
13
+ enabled: true
14
+ ratings:
15
+ paths:
16
+ - "**.rb"
17
+ exclude_paths:
18
+ - spec/
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
@@ -0,0 +1,27 @@
1
+ ---
2
+ AllCops:
3
+ DisplayCopNames: true
4
+ DisplayStyleGuide: true
5
+ StyleGuideCopsOnly: true
6
+ TargetRubyVersion: 2.3
7
+
8
+ Style/Alias:
9
+ Enabled: false
10
+
11
+ Style/ClassAndModuleChildren:
12
+ EnforcedStyle: compact
13
+
14
+ Style/FileName:
15
+ Enabled: false
16
+
17
+ Style/FrozenStringLiteralComment:
18
+ Enabled: false
19
+
20
+ Style/ModuleFunction:
21
+ Enabled: false
22
+
23
+ Style/StringLiterals:
24
+ EnforcedStyle: double_quotes
25
+
26
+ Style/StringLiteralsInInterpolation:
27
+ EnforcedStyle: double_quotes
@@ -0,0 +1,14 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ script:
6
+ - bundle exec rspec
7
+ - bundle exec rubocop
8
+ rvm:
9
+ - 2.3.0
10
+ - ruby-head
11
+ before_install: gem install bundler
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: ruby-head
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in tram-form.gemspec
4
+ gemspec
5
+
6
+ group :test, :development do
7
+ gem "pry"
8
+ gem "pry-byebug"
9
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Andrew Kozin (nepalez), Evil Martians
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,134 @@
1
+ # Tram::Validators
2
+
3
+ Collection of ActiveModel validators for rails projects with focus on composition of standalone policy objects.
4
+
5
+ <a href="https://evilmartians.com/">
6
+ <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
7
+
8
+ [![Gem Version][gem-badger]][gem]
9
+ [![Build Status][travis-badger]][travis]
10
+ [![Dependency Status][gemnasium-badger]][gemnasium]
11
+ [![Code Climate][codeclimate-badger]][codeclimate]
12
+
13
+ ## Installation
14
+
15
+ ```ruby
16
+ gem "tram-validators"
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Below is a short review of available validators. Read the [specs][specs] for more details:
22
+
23
+ ### Contract Validator
24
+
25
+ Checks that a value satisfies a contract, represented by a standalone validator (policy object).
26
+ It applies policy validator, and collects its messages under corresponding keys.
27
+
28
+ ```ruby
29
+ class PolicyObject < SimpleDelegator
30
+ include ActiveModel::Validations
31
+ validates :bar, presence: true
32
+ end
33
+
34
+ # PolicyObject.new(record.foo).valid? == true
35
+ # collects original error messages from policy under the key `foo`
36
+ validates :foo, contract: { policy: PolicyObject }
37
+
38
+ # collects the same messages from policy under their original keys (`bar`)
39
+ validates :foo, contract: { policy: PolicyObject, original_keys: true }
40
+
41
+ # collects the same messages from policy under nested keys (`foo[bar]`)
42
+ validates :foo, contract: { policy: PolicyObject, nested_keys: true }
43
+ ```
44
+
45
+ ### Validity Validator
46
+
47
+ Checks that an attribute is valid per se.
48
+ It collects original error messages under corresponding keys.
49
+
50
+ ```ruby
51
+ # record.foo.valid? == true
52
+ # collects original error messages under the key `foo`
53
+ validates :foo, validity: true
54
+
55
+ # collects the same messages from policy under their original keys (`bar`)
56
+ validates :foo, validity: { original_keys: true }
57
+
58
+ # collects the same messages from policy under nested keys (`foo[bar]`)
59
+ validates :foo, validity: { nested_keys: true }
60
+ ```
61
+
62
+ ### Each Validator
63
+
64
+ Applies validation rule to every element of the collection (that responds to `to_a`).
65
+
66
+ ```ruby
67
+ # Checks that every element of record.list is present
68
+ # collects original errors under the key `list[i]` (i for index of invalid item)
69
+ validates :list, each: { presence: true }
70
+ ```
71
+
72
+ ### Outcome Validator
73
+
74
+ Validates value by checking another method depending on it.
75
+
76
+ ```ruby
77
+ # Validates `user_id` by checking that `user.role` (depending on user_id) is set
78
+ # adds error `user_role_presence` to the original attribute `user_id`
79
+ validates :user_id, outcome: { value: "user.role", presence: true }
80
+ ```
81
+
82
+ This technics is useful in form objects where you should attach errors to the original fields accessible to the user.
83
+
84
+ ### Consistency Validator
85
+
86
+ Compares a value of some attribute to a value of another attribute or method chain.
87
+ Supports all keys from the standard rails [numericality validator][numericality].
88
+
89
+ ```ruby
90
+ # record.foo < record.bar.baz
91
+ # adds error named `less_than_bar_baz` under the key `foo`
92
+ validates :foo, consistency: { less_than: "bar.baz" }
93
+ ```
94
+
95
+ ### Size Validator
96
+
97
+ Compares size of array to given value or another attribute.
98
+ Supports all keys from the standard rails [numericality validator][numericality].
99
+
100
+ ```ruby
101
+ # record.names.size < 6
102
+ # adds error named `size_less_than` under the key `names`
103
+ validates :names, size: { less_than: 6 }
104
+
105
+ # record.values.size == record.parent&.names&.size
106
+ # adds error named `size_equal_to_parent_names_size` under the key `names`
107
+ validates :values, size: { equal_to: "parent.names.size" }
108
+ ```
109
+
110
+ ### Reference Validator
111
+
112
+ This is an AR-dependent validator, that checks an instance can be extracted from database by given key.
113
+
114
+ ```ruby
115
+ # Checks that User.find_by(record.user_key).present?
116
+ validates :user_key, reference: { model: User, find_by: :key }
117
+ ```
118
+
119
+ Like the outcome validator above, it can be useful for validation of form objects.
120
+
121
+ ## License
122
+
123
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
124
+
125
+ [specs]: ./spec/tram-validators
126
+ [numericality]: http://guides.rubyonrails.org/active_record_validations.html#numericality
127
+ [codeclimate-badger]: https://img.shields.io/codeclimate/github/tram-rb/tram-validators.svg?style=flat
128
+ [codeclimate]: https://codeclimate.com/github/tram-rb/tram-validators
129
+ [gem-badger]: https://img.shields.io/gem/v/tram-validators.svg?style=flat
130
+ [gem]: https://rubygems.org/gems/tram-validators
131
+ [gemnasium-badger]: https://img.shields.io/gemnasium/tram-rb/tram-validators.svg?style=flat
132
+ [gemnasium]: https://gemnasium.com/tram-rb/tram-validators
133
+ [travis-badger]: https://img.shields.io/travis/tram-rb/tram-validators/master.svg?style=flat
134
+ [travis]: https://travis-ci.org/tram-rb/tram-validators
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1 @@
1
+ require_relative "tram/validators"
@@ -0,0 +1,64 @@
1
+ require "active_model"
2
+ require "active_support/inflector"
3
+
4
+ module Tram
5
+ module Validators
6
+ # Standard conditions to check
7
+ CONDITIONS = {
8
+ equal_to: ->(value, limit) { value == limit },
9
+ other_than: ->(value, limit) { value != limit },
10
+ less_than: ->(value, limit) { value < limit },
11
+ less_than_or_equal_to: ->(value, limit) { value <= limit },
12
+ greater_than: ->(value, limit) { value > limit },
13
+ greater_than_or_equal_to: ->(value, limit) { value >= limit }
14
+ }.freeze
15
+
16
+ class << self
17
+ # Gets value of chained attribute
18
+ def chained_value(record, chain)
19
+ chain.to_s.split(".").inject(record) { |obj, name| obj&.send(name) }
20
+ end
21
+
22
+ # Provides standard key for error collected by standalone validators
23
+ def error_key(source, target, nested_keys: nil, original_keys: nil, **)
24
+ return source if original_keys
25
+ return target unless nested_keys
26
+ source.to_s
27
+ .split(/\[|\]/)
28
+ .compact
29
+ .inject(target) { |obj, key| "#{obj}[#{key}]" }
30
+ .to_sym
31
+ end
32
+
33
+ # Extracts nested validators from options
34
+ def validators(attr, options, *blacklist)
35
+ options.map.with_object({}) do |(key, opts), obj|
36
+ name = key.to_s
37
+
38
+ next if blacklist.map(&:to_s).include? name
39
+ next if %w(allow_nil if unless on message).include? name
40
+
41
+ opts = {} unless opts.is_a? Hash
42
+ obj[key] = find_validator_by_name(name).new(attributes: attr, **opts)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def find_validator_by_name(name)
49
+ klass_name = "#{name.camelize}Validator"
50
+ klass_name.constantize
51
+ rescue
52
+ ActiveModel::Validations.const_get(klass_name)
53
+ end
54
+ end
55
+
56
+ require_relative "validators/consistency_validator"
57
+ require_relative "validators/contract_validator"
58
+ require_relative "validators/each_validator"
59
+ require_relative "validators/outcome_validator"
60
+ require_relative "validators/reference_validator"
61
+ require_relative "validators/size_validator"
62
+ require_relative "validators/validity_validator"
63
+ end
64
+ end
@@ -0,0 +1,26 @@
1
+ # Compares value of the attribute to value of another attribute
2
+ #
3
+ # @example Compares price to source.price
4
+ # validates :price, consistency: { greater_than: 'source.price' }
5
+ # # I18n error key 'price.greater_than_source.price'
6
+ #
7
+ class ConsistencyValidator < ActiveModel::EachValidator
8
+ def validate_each(record, attribute, value)
9
+ Tram::Validators::CONDITIONS.each do |key, block|
10
+ check(key, record, attribute, value, &block)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def check(condition, record, attribute, value)
17
+ chain = options[condition]
18
+ return if chain.blank?
19
+
20
+ other_value = Tram::Validators.chained_value(record, chain)
21
+ return if value && other_value && yield(value, other_value)
22
+
23
+ error_name = [condition, chain.to_s.split(".")].flatten.join("_").to_sym
24
+ record.errors.add attribute, error_name, value: value, other: other_value
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ # Validates attribute by calling given type with the attribute's value
2
+ #
3
+ # @example Checks that AdminPolicy.new(user).valid?
4
+ # validates :user, contract: { policy: AdminPolicy, nested_keys: true }
5
+ #
6
+ class ContractValidator < ActiveModel::EachValidator
7
+ def validate_each(record, attribute, value)
8
+ return unless options[:policy]
9
+ options[:policy].new(value).tap(&:valid?).errors.messages.each do |key, msg|
10
+ error_key = Tram::Validators.error_key(key, attribute, options)
11
+ msg.each { |message| record.errors.add error_key, message }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ # Applies validator to every element of the collection
2
+ class EachValidator < ActiveModel::EachValidator
3
+ def validate_each(record, attribute, values)
4
+ return unless values.is_a? Enumerable
5
+ values.each_with_index do |value, index|
6
+ item = record.dup.tap { |rec| rec.errors.clear }
7
+
8
+ call_validations(item, attribute, value)
9
+
10
+ copy_errors(item, record, attribute, index)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def call_validations(item, attribute, value)
17
+ Tram::Validators.validators(@attributes, options).each do |_, validator|
18
+ validator.validate_each item, attribute, value
19
+ end
20
+ end
21
+
22
+ def copy_errors(item, record, attribute, index)
23
+ item.errors.messages.each do |original_key, messages|
24
+ messages.each do |message|
25
+ key = original_key.to_s.sub /\A#{attribute}/, "#{attribute}[#{index}]"
26
+ record.errors.add key.to_sym, message
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+ # Validates attribute by applying validation rule not to the attribute itself,
2
+ # but to another parameter, whose value depends on the attribute.
3
+ #
4
+ # The resulting error is collected under the attribute's key, which
5
+ # is necessary to correctly bind the error to the field, that causes
6
+ # the problem.
7
+ #
8
+ # @example
9
+ # let(:user) { User.find_by(id: user_id) }
10
+ # validates :user_id, outcome: { value: 'user.name', presence: true }
11
+ # # user_id.user_name_presence
12
+ #
13
+ class OutcomeValidator < ActiveModel::EachValidator
14
+ def validate_each(record, attribute, value)
15
+ dependency = options[:value].to_s.gsub(".", "_")
16
+ dependent = Tram::Validators.chained_value(record, options[:value])
17
+ validators = Tram::Validators.validators(@attributes, options, :value)
18
+
19
+ sandbox = record.dup
20
+ validators.each do |condition, validator|
21
+ next if valid_in_sandbox(sandbox, attribute, dependent, validator)
22
+
23
+ key = message_key(dependency, condition)
24
+ text = message(record, attribute, value, dependent, key)
25
+ record.errors.add attribute, text
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def valid_in_sandbox(sandbox, attribute, dependent, validator)
32
+ sandbox.errors.clear
33
+ validator.validate_each(sandbox, attribute, dependent)
34
+ sandbox.errors.empty?
35
+ end
36
+
37
+ def message_key(dependency, condition)
38
+ [dependency, condition.to_s].compact.join("_").to_sym
39
+ end
40
+
41
+ def message(record, attribute, value, dependent, message_key)
42
+ model = record.class.name.underscore
43
+ scope = %W(active_model errors models #{model} attributes #{attribute})
44
+ I18n.t message_key, record: record,
45
+ attribute: attribute,
46
+ value: value,
47
+ delependent: dependent,
48
+ scope: scope
49
+ end
50
+ end