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,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Concerns
5
+ module ProtectFromMutableInstanceMethods
6
+ MUTABLE_INSTANCE_METHODS = %i[
7
+ touch
8
+ update
9
+ update!
10
+ update_all
11
+ update_attribute
12
+ update_attributes
13
+ update_column
14
+ update_columns
15
+ ].freeze
16
+
17
+ RESTRICTED_INSTANCE_METHODS = (MUTABLE_INSTANCE_METHODS + %i[delete destroy destroy_all]).freeze
18
+
19
+ def restricted_instance_methods
20
+ RESTRICTED_INSTANCE_METHODS
21
+ end
22
+
23
+ # By design all records must be immutable. I do not recommend to avoid
24
+ # this restriction
25
+
26
+ MUTABLE_INSTANCE_METHODS.each do |name|
27
+ define_method(name) { |*| raise Errors::ImmutableError }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ class Manifest < ActiveRecord::Base
5
+ prepend ActiveValidation::Concerns::ProtectFromMutableInstanceMethods
6
+
7
+ self.table_name = :active_validation_manifests
8
+
9
+ attribute :version, :version
10
+
11
+ has_many :checks
12
+ accepts_nested_attributes_for :checks, allow_destroy: false
13
+
14
+ def to_internal_manifest
15
+ json_options = { include: { checks: { methods: %i[method_name] } }, root: false }
16
+ ActiveValidation::Internal::Models::Manifest.new as_json(json_options).to_options!
17
+ end
18
+ end
19
+ end
20
+
21
+ ::ActiveRecord::Type.register(:version, ActiveValidation::Type::Version)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Type
5
+ class Version < ActiveModel::Type::Integer
6
+ def serialize(value)
7
+ value.to_i
8
+ end
9
+
10
+ def deserialize(value)
11
+ return unless value
12
+
13
+ ActiveValidation::Values::Version.new value
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ class Registry
5
+ include Enumerable
6
+
7
+ attr_reader :name
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ @items = Concurrent::Map.new
12
+ end
13
+
14
+ def clear
15
+ @items.clear
16
+ end
17
+
18
+ def each(&block)
19
+ @items.values.uniq.each(&block)
20
+ end
21
+
22
+ def find(item_name)
23
+ @items.fetch(normalize_name(item_name))
24
+ rescue KeyError => e
25
+ raise key_error_with_custom_message(e, item_name)
26
+ end
27
+
28
+ alias [] find
29
+
30
+ def register(name, item)
31
+ @items[normalize_name(name)] = item
32
+ end
33
+
34
+ def registered?(name)
35
+ @items.key?(normalize_name(name))
36
+ end
37
+
38
+ def delete(name)
39
+ @items.delete(normalize_name(name))
40
+ end
41
+
42
+ private
43
+
44
+ def key_error_with_custom_message(key_error, item_name)
45
+ message = key_error.message.sub("key not found", %(#{@name} not registered: "#{item_name}"))
46
+ error = KeyError.new(message)
47
+ error.set_backtrace(key_error.backtrace)
48
+ error
49
+ end
50
+
51
+ def normalize_name(key)
52
+ key.respond_to?(:to_sym) ? key.to_sym : key.to_s.to_sym
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Values
5
+ class Base
6
+ BAD_VALUES = [nil, true, false].freeze
7
+
8
+ attr_reader :value
9
+
10
+ def initialize(value)
11
+ raise ArgumentError, "Value of #{self.class} can not be #{value.inspect}" if BAD_VALUES.include? value
12
+
13
+ @value = value.freeze
14
+ end
15
+
16
+ def <=>(other)
17
+ raise ArgumentError, "Inconcictent classes #{self.class} and #{other.class}" unless self.class == other.class
18
+
19
+ value <=> other.value
20
+ end
21
+
22
+ def ==(other)
23
+ value == (other.class == self.class ? other.value : other)
24
+ end
25
+
26
+ def to_s
27
+ value.to_s
28
+ end
29
+
30
+ def to_sym
31
+ value.to_sym
32
+ end
33
+
34
+ def as_json(*)
35
+ value
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Values
5
+ class MethodName < Base
6
+ # @param value [#to_i, #to_s]
7
+ def initialize(value)
8
+ unless allowed_methods.include? value.to_s
9
+ raise ActiveValidation::Errors::UnsupportedMethodError.new value: value, allowed_methods: allowed_methods
10
+ end
11
+
12
+ super value.to_s
13
+ end
14
+
15
+ private
16
+
17
+ def allowed_methods
18
+ %w[validates validates_with validate].freeze
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ module Values
5
+ class Version < Base
6
+ # @param value [#to_i, #to_s]
7
+ def initialize(value)
8
+ super
9
+ @value = value.respond_to?(:to_int) ? value.to_i : Integer(value.to_s.sub(/\AV/, ""))
10
+ end
11
+
12
+ def to_i
13
+ value
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ class Verifier
5
+ class << self
6
+ # Shortcut for the global verifiers registry instance
7
+ # @return [Registry]
8
+ def registry
9
+ ActiveValidation.config.verifiers_registry
10
+ end
11
+
12
+ delegate :find_or_build, to: :registry
13
+ end
14
+ delegate :config, to: :ActiveValidation
15
+
16
+ # Name of the folder, where all validations method should be scoped.
17
+ # Inside, in corresponding sub-folder with version name shall be
18
+ # stored validation related methods
19
+ attr_accessor :validations_module_name
20
+
21
+ # @note The corresponding model class
22
+ attr_reader :base_klass
23
+
24
+ # @note Orm adapter for exactly this verifier instance
25
+ attr_accessor :orm_adapter
26
+
27
+ # @note Custom name formatter for Manifests
28
+ attr_accessor :manifest_name_formatter
29
+
30
+ # Stick manifest to specific version
31
+ # @return [Internal::Manifest]
32
+ attr_accessor :manifest
33
+
34
+ # Contains Internal::Observers::Manifest, which store everything about used
35
+ # manifests
36
+ #
37
+ # @return [Internal::Observers::Manifest]
38
+ attr_reader :observer
39
+
40
+ # Main switch for this class. After switch, all installed validators
41
+ # remain in place, while new installations will be turned off.
42
+ #
43
+ # @return [TrueClass, FalseClass]
44
+ attr_accessor :enabled
45
+ def enabled?; !!enabled; end
46
+
47
+ # If something go wrong or there is no any Manifests for selected class yet,
48
+ # to prevent DB flooding we just until the situation resolves.
49
+ # Here we can specify the period between the attempts
50
+ #
51
+ attr_accessor :failed_attempt_retry_time
52
+
53
+ delegate :install, to: :observer
54
+
55
+ def initialize(base_klass)
56
+ config.verifier_defaults.call self
57
+ @base_klass = base_klass.to_s
58
+ @orm_adapter ||= config.orm_adapter
59
+ @manifest_name_formatter ||= config.manifest_name_formatter
60
+ @validations_module_name ||= "Validations"
61
+ @enabled ||= true
62
+ @failed_attempt_retry_time ||= 1.day
63
+
64
+ yield self if block_given?
65
+
66
+ @observer = Internal::Observers::Manifest.new self
67
+ self.class.registry.register base_klass, self
68
+ end
69
+
70
+ # @return [Array<Value::Version>] Sorted list of versions.
71
+ def versions
72
+ base_class.const_get(validations_module_name)
73
+ .constants.map { |k| ActiveValidation::Values::Version.new(k) }.sort
74
+ rescue NameError
75
+ []
76
+ end
77
+
78
+ # @!group Manual version lock
79
+ def version
80
+ @version ||= versions.last
81
+ end
82
+
83
+ def version=(other)
84
+ @version = versions.detect { |a| a == ActiveValidation::Values::Version.new(other) } or
85
+ raise ArgumentError, "Version #{other} not found"
86
+ end
87
+
88
+ # @!endgroup
89
+
90
+ # @return [Internal::Manifest]
91
+ def current_manifest
92
+ manifest or find_manifests.first
93
+ end
94
+
95
+ # @return [Class]
96
+ def base_class
97
+ base_klass.is_a?(Class) ? base_klass : base_klass.constantize
98
+ end
99
+
100
+ # Return the list af parents with active_validation
101
+ # @return [Array]
102
+ def descendants_with_active_validation
103
+ [].tap do |descendants|
104
+ k = base_class.superclass
105
+ while k.respond_to?(:active_validation) && k.instance_methods.include?(:manifest)
106
+ descendants << k
107
+ k = k.superclass
108
+ end
109
+ end
110
+ end
111
+
112
+ # Install parent models and self
113
+ #
114
+ # Return Symbol Installation status
115
+ def install!(*args)
116
+ descendants_with_active_validation.each { |klass| klass.active_validation.install }
117
+ install(*args)
118
+ end
119
+
120
+ # Forward the normalized request to ORM mapper
121
+ # For the name field we calculate default value
122
+ #
123
+ # param [Hash]
124
+ # @return [Internal::Manifest, Array<Internal::Manifest>]
125
+
126
+ def add_manifest(**hash)
127
+ add_defaults_for_orm_adapter(hash) do |**h|
128
+ h[:name] ||= manifest_name_formatter.call(base_klass)
129
+ orm_adapter.public_send :add_manifest, h
130
+ end
131
+ end
132
+
133
+ # Forward the normalized request to ORM mapper
134
+ #
135
+ # param [Hash]
136
+ # @return [Internal::Manifest, Array<Internal::Manifest>]
137
+
138
+ %i[find_manifest find_manifests].each do |m|
139
+ define_method(m) { |**hash| add_defaults_for_orm_adapter(hash) { |**h| orm_adapter.public_send m, h } }
140
+ end
141
+
142
+ private
143
+
144
+ def add_defaults_for_orm_adapter(**hash)
145
+ hash[:base_klass] ||= base_klass
146
+ hash[:version] ||= version if version
147
+ block_given? ? yield(hash) : hash
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveValidation
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveValidation::BaseAdapter do
4
+ context "global configuration" do
5
+ subject { ActiveValidation.config }
6
+
7
+ it "is in the registry" do
8
+ expect(subject.orm_adapters_registry).not_to be_registered(:base)
9
+ end
10
+ end
11
+
12
+ it "has 'base' plugin name" do
13
+ expect(described_class.plugin_name).to eq "base"
14
+ end
15
+
16
+ it "has 'base' adapter name" do
17
+ expect(described_class.adapter_name).to eq "base"
18
+ end
19
+
20
+ it "contains '(abstract)' suffix" do
21
+ expect(described_class.to_s).to match(/\(abstract\)\z/)
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveValidation::Configuration do
4
+ it "has verifiers registry" do
5
+ expect(subject.verifiers_registry).to be_a ActiveValidation::Registry
6
+ end
7
+
8
+ it "has ORM adapters registry" do
9
+ expect(subject.orm_adapters_registry).to be_a ActiveValidation::Registry
10
+ end
11
+
12
+ it "has method name values registry" do
13
+ expect(subject.method_name_values_registry).to be_a ActiveValidation::Registry
14
+ end
15
+
16
+ context "manifest name formatter" do
17
+ it "has value" do
18
+ expect(subject.manifest_name_formatter).to eq ActiveValidation::Formatters::ManifestNameFormatter
19
+ end
20
+
21
+ it "has value" do
22
+ expect(subject.manifest_name_formatter).to respond_to(:call)
23
+ end
24
+ end
25
+
26
+ context "orm_adapter=" do
27
+ let(:config) { ActiveValidation.config }
28
+
29
+ context "with fake" do
30
+ around do |tst|
31
+ adapter = config.orm_adapter
32
+ module ActiveValidation::OrmPlugins::FakePlugin; class Adapter < ActiveValidation::BaseAdapter; end; end
33
+ tst.call
34
+ ActiveValidation::OrmPlugins.send :remove_const, :FakePlugin
35
+ config.orm_adapters_registry.delete "fake"
36
+ config.orm_adapter = adapter
37
+ end
38
+
39
+ after do
40
+ end
41
+
42
+ it "setup correctly loaded adapter" do
43
+ config.orm_adapter = "fake"
44
+ expect(config.orm_adapter.to_s).to eq "fake_plugin"
45
+ end
46
+ end
47
+
48
+ it "raise if plugin not found" do
49
+ expect { config.orm_adapter = "not_exist" }.to raise_error LoadError
50
+ end
51
+ end
52
+ end