activevalidation 0.1.0 → 1.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +32 -9
  3. data/.overcommit.yml +93 -0
  4. data/.rspec +1 -1
  5. data/.rubocop.yml +80 -0
  6. data/.rubocop_todo.yml +7 -0
  7. data/.travis.yml +42 -2
  8. data/.yardops +9 -0
  9. data/Appraisals +27 -0
  10. data/Gemfile +24 -1
  11. data/MIT-LICENSE +20 -0
  12. data/README.md +142 -20
  13. data/Rakefile +21 -3
  14. data/activevalidation.gemspec +35 -25
  15. data/bin/console +4 -7
  16. data/gemfiles/am_5.0.gemfile +17 -0
  17. data/gemfiles/am_5.0.gemfile.lock +159 -0
  18. data/gemfiles/am_5.1.gemfile +17 -0
  19. data/gemfiles/am_5.1.gemfile.lock +159 -0
  20. data/gemfiles/am_5.2.gemfile +17 -0
  21. data/gemfiles/am_5.2.gemfile.lock +159 -0
  22. data/gemfiles/am_6.0.gemfile +18 -0
  23. data/gemfiles/am_6.0.gemfile.lock +158 -0
  24. data/lib/active_validation.rb +27 -0
  25. data/lib/active_validation/base_adapter.rb +103 -0
  26. data/lib/active_validation/configuration.rb +52 -0
  27. data/lib/active_validation/decorator.rb +27 -0
  28. data/lib/active_validation/decorators/consistent_registry.rb +36 -0
  29. data/lib/active_validation/decorators/disallows_duplicates_registry.rb +17 -0
  30. data/lib/active_validation/errors.rb +28 -0
  31. data/lib/active_validation/ext/add_active_validation_context_check.rb +21 -0
  32. data/lib/active_validation/formatters/manifest_name_formatter.rb +13 -0
  33. data/lib/active_validation/formatters/validation_context_formatter.rb +28 -0
  34. data/lib/active_validation/frameworks/rspec.rb +10 -0
  35. data/lib/active_validation/frameworks/rspec/helpers.rb +15 -0
  36. data/lib/active_validation/internal/models/check.rb +51 -0
  37. data/lib/active_validation/internal/models/concerns/to_internal.rb +27 -0
  38. data/lib/active_validation/internal/models/manifest.rb +122 -0
  39. data/lib/active_validation/internal/models/manifest/installer.rb +86 -0
  40. data/lib/active_validation/internal/observers/manifest.rb +114 -0
  41. data/lib/active_validation/model_extension_base.rb +33 -0
  42. data/lib/active_validation/orm_plugins/active_record_plugin/adapter.rb +59 -0
  43. data/lib/active_validation/orm_plugins/active_record_plugin/internals/active_validation/internal_model_extensions/check.rb +11 -0
  44. data/lib/active_validation/orm_plugins/active_record_plugin/model_extension/active_validation/active_record_model_extension.rb +25 -0
  45. data/lib/active_validation/orm_plugins/active_record_plugin/models/active_validation/check.rb +31 -0
  46. data/lib/active_validation/orm_plugins/active_record_plugin/models/active_validation/check/concerns/method_must_be_allowed.rb +38 -0
  47. data/lib/active_validation/orm_plugins/active_record_plugin/models/active_validation/check/validate_method.rb +9 -0
  48. data/lib/active_validation/orm_plugins/active_record_plugin/models/active_validation/check/validates_method.rb +9 -0
  49. data/lib/active_validation/orm_plugins/active_record_plugin/models/active_validation/check/validates_with_method.rb +19 -0
  50. data/lib/active_validation/orm_plugins/active_record_plugin/models/active_validation/concerns/protect_from_mutable_instance_methods.rb +31 -0
  51. data/lib/active_validation/orm_plugins/active_record_plugin/models/active_validation/manifest.rb +21 -0
  52. data/lib/active_validation/orm_plugins/active_record_plugin/types/active_validation/type/version.rb +17 -0
  53. data/lib/active_validation/registry.rb +55 -0
  54. data/lib/active_validation/values/base.rb +39 -0
  55. data/lib/active_validation/values/method_name.rb +22 -0
  56. data/lib/active_validation/values/version.rb +17 -0
  57. data/lib/active_validation/verifier.rb +150 -0
  58. data/lib/active_validation/version.rb +5 -0
  59. data/spec/active_validation/base_adapter_spec.rb +23 -0
  60. data/spec/active_validation/configuration_spec.rb +52 -0
  61. data/spec/active_validation/decorators/consistent_registry_spec.rb +117 -0
  62. data/spec/active_validation/decorators/disallows_duplicates_registry_spec.rb +21 -0
  63. data/spec/active_validation/formatters/manifest_name_formatter_spec.rb +7 -0
  64. data/spec/active_validation/formatters/validation_context_formatter_spec.rb +39 -0
  65. data/spec/active_validation/internal/models/check_spec.rb +67 -0
  66. data/spec/active_validation/internal/models/manifest/installer_spec.rb +177 -0
  67. data/spec/active_validation/internal/models/manifest_spec.rb +136 -0
  68. data/spec/active_validation/internal/observers/manifest_spec.rb +201 -0
  69. data/spec/active_validation/model_extension_base_spec.rb +71 -0
  70. data/spec/active_validation/orm_plugins/active_record_plugin/adapter_spec.rb +31 -0
  71. data/spec/active_validation/orm_plugins/active_record_plugin/adapter_spec_orm_specific_spec.rb +84 -0
  72. data/spec/active_validation/orm_plugins/active_record_plugin/models/active_validation/check/validate_method_spec.rb +26 -0
  73. data/spec/active_validation/orm_plugins/active_record_plugin/models/active_validation/check/validates_method_spec.rb +26 -0
  74. data/spec/active_validation/orm_plugins/active_record_plugin/models/active_validation/check/validates_with_method_spec.rb +34 -0
  75. data/spec/active_validation/orm_plugins/active_record_plugin/models/active_validation/check_spec.rb +48 -0
  76. data/spec/active_validation/orm_plugins/active_record_plugin/models/active_validation/manifest_spec.rb +61 -0
  77. data/spec/active_validation/orm_plugins/active_record_plugin/readme_spec.rb +89 -0
  78. data/spec/active_validation/registry_spec.rb +76 -0
  79. data/spec/active_validation/values/base_spec.rb +61 -0
  80. data/spec/active_validation/values/method_name_spec.rb +16 -0
  81. data/spec/active_validation/values/version_spec.rb +36 -0
  82. data/spec/active_validation/verifier_spec.rb +214 -0
  83. data/spec/active_validation_spec.rb +19 -0
  84. data/spec/factories/internal/internal_check.rb +43 -0
  85. data/spec/features/active_record/child_record.feature +32 -0
  86. data/spec/features/active_record/new_record.feature +22 -0
  87. data/spec/features/no_orm/install.feature +19 -0
  88. data/spec/features/no_orm/validate.feature +27 -0
  89. data/spec/features/no_orm/validate_with_multiple_manifests.feature +29 -0
  90. data/spec/features/no_orm/validate_with_multiple_versions.feature +42 -0
  91. data/spec/features/placeholders/be_matcher.rb +7 -0
  92. data/spec/features/placeholders/klass.rb +5 -0
  93. data/spec/features/placeholders/version.rb +11 -0
  94. data/spec/features/placeholders/whether_to.rb +11 -0
  95. data/spec/features/step_definitions/active_record_steps.rb +7 -0
  96. data/spec/features/step_definitions/steps.rb +85 -0
  97. data/spec/orm/active_record/db_adapters/database.mysql.yml +12 -0
  98. data/spec/orm/active_record/db_adapters/database.postgres.yml +11 -0
  99. data/spec/orm/active_record/db_adapters/database.sqlite.yml +8 -0
  100. data/spec/orm/active_record/factories/check/check_validate.rb +8 -0
  101. data/spec/orm/active_record/factories/check/check_validates.rb +8 -0
  102. data/spec/orm/active_record/factories/check/check_validates_with.rb +19 -0
  103. data/spec/orm/active_record/factories/manifest.rb +29 -0
  104. data/spec/orm/active_record/prepare_db.rb +89 -0
  105. data/spec/orm/active_record/setup.rb +11 -0
  106. data/spec/orm/mongoid/setup.rb +15 -0
  107. data/spec/spec_helper.rb +38 -0
  108. data/spec/support/database_cleaner.rb +16 -0
  109. data/spec/support/deferred_garbage_collection.rb +31 -0
  110. data/spec/support/define_constant_macros.rb +17 -0
  111. data/spec/support/factory_bot.rb +12 -0
  112. data/spec/support/helpers.rb +3 -0
  113. data/spec/support/helpers/only_with_active_record.rb +15 -0
  114. data/spec/support/matchers/delegate.rb +50 -0
  115. data/spec/support/matchers/have_attr.rb +58 -0
  116. data/spec/support/mongoid.yml +6 -0
  117. data/spec/support/shared_examples/check_attributes.rb +9 -0
  118. data/spec/support/shared_examples/verifiers_registry.rb +10 -0
  119. data/spec/support/simplecov.rb +11 -0
  120. data/spec/turnip_helper.rb +4 -0
  121. metadata +304 -20
  122. data/lib/activevalidation.rb +0 -6
  123. data/lib/activevalidation/version.rb +0 -3
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ class Configuration
5
+ attr_reader :verifiers_registry, :orm_adapters_registry, :method_name_values_registry
6
+
7
+ def initialize
8
+ @orm_adapters_registry = Registry.new("Orm adapters")
9
+ @verifiers_registry = strict_registry name: "Verifiers", klass: Verifier
10
+ @method_name_values_registry = strict_registry name: "Validation methods", klass: Values::MethodName
11
+
12
+ @manifest_name_formatter = Formatters::ManifestNameFormatter
13
+ @validation_context_formatter = Formatters::ValidationContextFormatter
14
+ end
15
+
16
+ attr_accessor :manifest_name_formatter, :validation_context_formatter
17
+
18
+ attr_reader :orm_adapter
19
+
20
+ def orm_adapter=(name, retry_attempt: 0)
21
+ retry_attempt += 1
22
+ @orm_adapter = case name
23
+ when BaseAdapter
24
+ name
25
+ when Class
26
+ name.new
27
+ when String, Symbol
28
+ "ActiveValidation::OrmPlugins::#{name.to_s.classify}Plugin::Adapter".constantize.new
29
+ else
30
+ raise ArgumentError "ORM plugin not found"
31
+ end
32
+ rescue NameError
33
+ raise if retry_attempt > 1
34
+
35
+ require_relative "orm_plugins/#{name}_plugin/adapter"
36
+ retry
37
+ end
38
+
39
+ def verifier_defaults(&block)
40
+ @verifier_defaults ||= ->(_config) {}
41
+ return @verifier_defaults unless block_given?
42
+
43
+ @verifier_defaults = block
44
+ end
45
+
46
+ private
47
+
48
+ def strict_registry(name:, klass:)
49
+ Decorators::ConsistentRegistry.new(klass, Decorators::DisallowsDuplicatesRegistry.new(Registry.new(name)))
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ class Decorator < BasicObject
5
+ undef_method :==
6
+
7
+ def initialize(component)
8
+ @component = component
9
+ end
10
+
11
+ def method_missing(name, *args, &block) # rubocop:disable Style/MethodMissingSuper
12
+ @component.send(name, *args, &block)
13
+ end
14
+
15
+ def respond_to_missing?(name, include_private = false)
16
+ @component.respond_to?(name, true) || super
17
+ end
18
+
19
+ def send(symbol, *args, &block)
20
+ __send__(symbol, *args, &block)
21
+ end
22
+
23
+ def self.const_missing(name)
24
+ ::Object.const_get(name)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Decorators
5
+ class ConsistentRegistry < Decorator
6
+ # All items in the registry must be ancestors of this Class
7
+ # @return [Class]
8
+ attr_reader :klass
9
+
10
+ def initialize(klass, registry)
11
+ super registry
12
+
13
+ @klass = klass.is_a?(Class) ? klass : klass.to_s.constantize
14
+ end
15
+
16
+ def find_or_build(name, &block)
17
+ return klass.new(name, &block) unless registered?(name)
18
+
19
+ found = find(name)
20
+ found.instance_exec(found, &block) if block
21
+ found
22
+ end
23
+
24
+ def find_or_add(name, &block)
25
+ record = find_or_build(name, &block)
26
+ registered?(name) ? record : register(name, record)
27
+ end
28
+
29
+ def register(name, item)
30
+ raise Errors::InconsistentRegistryError unless item.is_a? klass
31
+
32
+ super
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Decorators
5
+ class DisallowsDuplicatesRegistry < Decorator
6
+ def register(name, item)
7
+ return @component.register(name, item) unless registered?(name)
8
+
9
+ raise Errors::DuplicateRegistryEntryError, "#{@component.name} already registered: #{name}"
10
+ end
11
+
12
+ def ==(other)
13
+ object_id == other.object_id
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Errors
5
+ class ImmutableError < StandardError
6
+ def initialize(msg = "Object can not be changed in lifetime")
7
+ super
8
+ end
9
+ end
10
+
11
+ class DuplicateRegistryEntryError < RuntimeError; end
12
+
13
+ class InconsistentRegistryError < ArgumentError; end
14
+
15
+ class UnsupportedMethodError < ArgumentError
16
+ attr_reader :value, :allowed_methods
17
+ def initialize(value:, allowed_methods:)
18
+ super
19
+ @value = value
20
+ @allowed_methods = allowed_methods
21
+ end
22
+
23
+ def to_s
24
+ "You provided #{value.inspect} while supported are only #{allowed_methods.inspect}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Ext
5
+ module AddActiveValidationContextCheck
6
+ def valid?(*_args)
7
+ result = super
8
+ return result unless ActiveValidation.config.verifiers_registry.registered?(self.class)
9
+
10
+ av_context = try(:manifest).try(:context) ||
11
+ ActiveValidation.config.verifiers_registry[self.class].current_manifest.try(:context)
12
+ return result unless av_context
13
+
14
+ self.validation_context = av_context
15
+ run_validations!
16
+ end
17
+
18
+ alias validate valid?
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Formatters
5
+ class ManifestNameFormatter
6
+ class << self
7
+ def call(base_klass)
8
+ "Manifest for #{base_klass}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Formatters
5
+ class ValidationContextFormatter
6
+ class << self
7
+ def call(*args)
8
+ new(*args).call
9
+ end
10
+ end
11
+
12
+ def initialize(manifest)
13
+ @manifest = manifest
14
+ end
15
+
16
+ attr_reader :manifest
17
+
18
+ delegate :options, :id, :name, to: :manifest
19
+
20
+ def call
21
+ ["active_validation", "id#{id}"].tap do |base|
22
+ base << "on_#{options[:on]}" if options[:on]
23
+ base << name.to_s.underscore unless name.blank?
24
+ end.join("_")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/core"
4
+ require "rspec/matchers"
5
+ require "active_validation/frameworks/rspec/helpers"
6
+
7
+ RSpec.configure do |config|
8
+ config.include ::ActiveValidation::RSpec::Helpers::InstanceMethods
9
+ config.extend ::ActiveValidation::RSpec::Helpers::ClassMethods
10
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module RSpec
5
+ module Helpers
6
+ # Included in the RSpec configuration in `frameworks/rspec.rb`
7
+ module InstanceMethods
8
+ end
9
+
10
+ # Extended by the RSpec configuration in `frameworks/rspec.rb`
11
+ module ClassMethods
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Internal
5
+ module Models
6
+ class Check
7
+ attr_reader :method_name, :argument, :options, :created_at
8
+ def initialize(method_name:, argument:, options: {}, created_at: nil, **_other)
9
+ @method_name = registry.find_or_add method_name
10
+ @argument = argument
11
+ @options = options.to_h.deep_symbolize_keys
12
+ @created_at = created_at
13
+ end
14
+
15
+ def ==(other)
16
+ method_name == other.method_name &&
17
+ argument == other.argument &&
18
+ options == other.options
19
+ end
20
+
21
+ def to_hash
22
+ as_json
23
+ end
24
+
25
+ def as_json(only: %i[method_name argument options], **_options)
26
+ only.each_with_object({}) { |el, acc| acc[el.to_sym] = public_send(el).as_json }
27
+ end
28
+
29
+ def to_internal_check
30
+ self
31
+ end
32
+
33
+ def normalized_argument
34
+ method_name.to_sym == :validates_with ? argument.to_s.constantize : argument.to_sym
35
+ end
36
+
37
+ def to_validation_arguments(context: nil)
38
+ [method_name.to_sym,
39
+ normalized_argument,
40
+ options.merge(on: context) { |_, old, new| Array(new) + Array(old) }]
41
+ end
42
+
43
+ private
44
+
45
+ def registry
46
+ ActiveValidation.config.method_name_values_registry
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Internal
5
+ module Models
6
+ module Concerns
7
+ module ToInternal
8
+ module Check
9
+ refine Hash do
10
+ def to_internal_check
11
+ ActiveValidation::Internal::Models::Check.new to_options!
12
+ end
13
+ end
14
+ end
15
+
16
+ module Manifest
17
+ refine Hash do
18
+ def to_internal_manifest
19
+ ActiveValidation::Internal::Models::Manifest.new to_options!
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ using ActiveValidation::Internal::Models::Concerns::ToInternal::Check
4
+
5
+ module ActiveValidation
6
+ module Internal
7
+ module Models
8
+ class Manifest
9
+ attr_reader :version, :base_klass, :created_at, :checks, :options, :other, :id, :name
10
+
11
+ delegate :installed_callbacks, to: :installer
12
+
13
+ # @param [Hash] Manifest options hash
14
+ # @option manifest_hash [String] :name Human readable name, by default build with selected
15
+ # formatter for current manifest
16
+ # @option manifest_hash [String, #to_s] :base_klass (base_klass) Base klass for the Manifest
17
+ # @option manifest_hash [String, Symbol, Integer, Values::Version] :version (:current) Version
18
+ # of the current manifest
19
+ # @option manifest_hash [Internal::Check] :checks ([])
20
+ #
21
+ # @example Add new manifest:
22
+ # new({ name: 'Cool Manifest',
23
+ # version: 42,
24
+ # base_klass: 'Bar',
25
+ # checks: [
26
+ # { method_name: "validates", argument: "name", options: { presence: true } }
27
+ # ]})
28
+ def initialize(version:, base_klass:, checks: [], options: {}, **other)
29
+ @version = ActiveValidation::Values::Version.new version
30
+ @base_klass = base_klass.to_s
31
+ @checks = Array(checks).map(&:to_internal_check)
32
+ @other = ActiveSupport::OrderedOptions.new other
33
+
34
+ @id = other[:id]
35
+ @name = other[:name]
36
+ @created_at = other[:created_at]
37
+ @options = options.to_h.to_options!
38
+ end
39
+
40
+ def ==(other)
41
+ return true if id == other.id
42
+
43
+ version == other.version &&
44
+ base_klass == other.base_klass &&
45
+ options == other.options &&
46
+ checks == other.checks
47
+ end
48
+
49
+ def base_class
50
+ base_klass.constantize
51
+ end
52
+
53
+ def to_hash
54
+ as_json
55
+ end
56
+
57
+ # rubocop:disable Naming/MemoizedInstanceVariableName
58
+ def install
59
+ @installed ||= installer.install
60
+ end
61
+ # rubocop:enable Naming/MemoizedInstanceVariableName
62
+
63
+ def uninstall
64
+ return false unless installed?
65
+
66
+ @installed = installer.uninstall
67
+ end
68
+
69
+ # Are the callbacks installed to the `base_class`?
70
+ #
71
+ # @return [TrueClass, FalseClass]
72
+ def installed?
73
+ !!@installed
74
+ end
75
+
76
+ # ActiveSupport#as_json interface
77
+ # Supported options:
78
+ # :only [Array<Symbol>, Symbol] select only listed elements
79
+ #
80
+ # Nested elements accept options:
81
+ # :only [Array<Symbol>, Symbol] select only listed elements
82
+ # :as [Symbol, String] in place rename for the column
83
+ #
84
+ # @example
85
+ # as_json(only: :version, checks: { as: :checks_attributes,
86
+ # only: [:argument] })
87
+ #
88
+ # @return [Hash]
89
+ def as_json(only: %i[version base_klass checks name id], **options)
90
+ only = Array(only)
91
+ {}.tap do |acc|
92
+ options.each_pair do |k, v|
93
+ only.delete(k)
94
+ as = v.delete(:as)
95
+ key_name = (as || k).to_sym
96
+ acc[key_name] = public_send(k).as_json(v)
97
+ end
98
+ only.each { |el| acc[el.to_sym] = public_send(el).as_json }
99
+ end
100
+ end
101
+
102
+ def to_internal_manifest
103
+ self
104
+ end
105
+
106
+ # Formatted context, as it will be stored on `on` option with the
107
+ # validations
108
+ #
109
+ # @return [String]
110
+ def context
111
+ @context ||= ActiveValidation.config.validation_context_formatter.call self
112
+ end
113
+
114
+ private
115
+
116
+ def installer
117
+ @installer ||= Installer.new(base_class: base_class, checks: checks, context: context)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end