activemodel-interdependence 0.0.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +97 -0
  3. data/.rspec +4 -0
  4. data/.rubocop.yml +208 -0
  5. data/.rubocop_todo.yml +34 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +83 -0
  8. data/Gemfile.lock +336 -0
  9. data/Guardfile +68 -0
  10. data/LICENSE +21 -0
  11. data/README.md +76 -0
  12. data/Rakefile +95 -0
  13. data/activemodel-interdependence.gemspec +34 -0
  14. data/circle.yml +7 -0
  15. data/config/devtools.yml +2 -0
  16. data/config/flay.yml +3 -0
  17. data/config/flog.yml +2 -0
  18. data/config/mutant.yml +9 -0
  19. data/config/reek.yml +120 -0
  20. data/config/rubocop.yml +1 -0
  21. data/config/yardstick.yml +2 -0
  22. data/lib/activemodel/interdependence.rb +12 -0
  23. data/lib/activemodel/model/interdependence.rb +97 -0
  24. data/lib/activemodel/validator/interdependence.rb +107 -0
  25. data/lib/interdependence.rb +87 -0
  26. data/lib/interdependence/activemodel/class_methods.rb +50 -0
  27. data/lib/interdependence/activemodel/validates_with.rb +128 -0
  28. data/lib/interdependence/common_mixin.rb +84 -0
  29. data/lib/interdependence/dependency/base.rb +177 -0
  30. data/lib/interdependence/dependency/model.rb +61 -0
  31. data/lib/interdependence/dependency/validator.rb +43 -0
  32. data/lib/interdependence/dependency_resolver/base.rb +114 -0
  33. data/lib/interdependence/dependency_resolver/model.rb +76 -0
  34. data/lib/interdependence/dependency_resolver/validator.rb +34 -0
  35. data/lib/interdependence/dependency_set.rb +15 -0
  36. data/lib/interdependence/dependency_set_graph.rb +66 -0
  37. data/lib/interdependence/graph.rb +103 -0
  38. data/lib/interdependence/model.rb +70 -0
  39. data/lib/interdependence/model/validator.rb +99 -0
  40. data/lib/interdependence/observable_dependency_set_graph.rb +23 -0
  41. data/lib/interdependence/types.rb +199 -0
  42. data/lib/interdependence/validator.rb +67 -0
  43. data/lib/interdependence/validator/validator.rb +105 -0
  44. data/lib/interdependence/version.rb +3 -0
  45. metadata +213 -0
@@ -0,0 +1,23 @@
1
+ module Interdependence
2
+ # {DependencySetGraph} mixed with the Observable module
3
+ #
4
+ class ObservableDependencySetGraph < DependencySetGraph
5
+ include Observable
6
+
7
+ # Merge dependency set graph and notify observers
8
+ #
9
+ # @return [ObservableDependencySetGraph] merged self
10
+ #
11
+ # @see DependencySetGraph#merge!
12
+ # @see Graph#merge!
13
+ #
14
+ # @api private
15
+ #
16
+ def merge!(*)
17
+ super.tap do
18
+ changed
19
+ notify_observers(self)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,199 @@
1
+ module Interdependence
2
+ module Types
3
+ # Unset type abstraction
4
+ #
5
+ class UnsetType
6
+ # @abstract Unset type class
7
+ #
8
+ # Always return true for unset? on an unset type
9
+ #
10
+ # @return [TrueClass]
11
+ #
12
+ # @api private
13
+ #
14
+ def unset?
15
+ true
16
+ end
17
+
18
+ # Check if other class is the same unset type and is also unset
19
+ #
20
+ # @param other [AbstractUnset] [descendant of AbstractUnset]
21
+ #
22
+ # @return [TrueClass,FalseClass] outcome
23
+ #
24
+ # @api private
25
+ #
26
+ def ==(other)
27
+ other.instance_of?(self.class) && other.unset?
28
+ end
29
+ alias_method :eql?, :==
30
+ end
31
+
32
+ # Unset field abstraction for handling cases where fields are not specified
33
+ #
34
+ class UnsetField < UnsetType
35
+ # Return name of unset field (nil)
36
+ #
37
+ # @return [nil]
38
+ #
39
+ # @api private
40
+ #
41
+ def name; end
42
+ end
43
+
44
+ # Unset options abstraction for handling cases where options are not specified
45
+ #
46
+ class UnsetOptions < UnsetType
47
+ end
48
+
49
+ # Wrapper for validator fields
50
+ #
51
+ # Useful for handling and resolving cases where fields are not yet specified
52
+ #
53
+ class Field
54
+ # name of field
55
+ # @return [Symbol]
56
+ #
57
+ # @api private
58
+ #
59
+ attr_reader :name
60
+
61
+ # Create a new field
62
+ #
63
+ # @param name [Symbol] [name of field]
64
+ #
65
+ # @return [Void]
66
+ #
67
+ # @api private
68
+ #
69
+ def initialize(name)
70
+ @name = name
71
+ end
72
+
73
+ # Always return false. For comparing with {UnsetField}
74
+ #
75
+ # @return [FalseClass]
76
+ #
77
+ # @api private
78
+ #
79
+ def unset?
80
+ false
81
+ end
82
+
83
+ # Compare with other field
84
+ #
85
+ # @param other [Field,UnsetField] Other field
86
+ #
87
+ # @return [TrueClass,FalseClass]
88
+ #
89
+ # @api private
90
+ #
91
+ def ==(other)
92
+ name.equal?(other.name)
93
+ end
94
+
95
+ alias_method :eql?, :==
96
+ end
97
+
98
+ # Coerce arguments into fields
99
+ #
100
+ class FieldCoercer
101
+ # Coerce different field values
102
+ #
103
+ # * Coercions:
104
+ # - Symbol -> Field
105
+ # - Field -> clone
106
+ # - nil -> UnsetField
107
+ #
108
+ # @param value [input value]
109
+ #
110
+ # @return [Field,UnsetField] [Field or UnsetField]
111
+ #
112
+ # @api private
113
+ #
114
+ def self.call(value)
115
+ case value
116
+ when Symbol then Field.new(value)
117
+ when Field then value.clone
118
+ when nil then UnsetField.new
119
+ end
120
+ end
121
+
122
+ # Was the field successfully coerced into a field or UnsetField?
123
+ #
124
+ # @param primitive [Class] [Field primitive]
125
+ # @param value [coerced value]
126
+ #
127
+ # @return [TrueClass, FalseClass] [outcome]
128
+ #
129
+ # @api private
130
+ #
131
+ def self.success?(primitive, value)
132
+ value.instance_of?(primitive) || value.instance_of?(UnsetField)
133
+ end
134
+ end
135
+
136
+ # Coerce arguments into either a Hash or UnsetOptions
137
+ #
138
+ class OptionsCoercer
139
+ # Coerce different option values
140
+ #
141
+ # * Coercions:
142
+ # - true -> {}
143
+ # - nil -> UnsetOptions
144
+ # - any hash -> no change
145
+ #
146
+ # @param value [input value]
147
+ #
148
+ # @return [Hash,UnsetOptions] [either a hash or unset options]
149
+ #
150
+ # @api private
151
+ #
152
+ def self.call(value)
153
+ case value
154
+ when TrueClass then {}
155
+ when Hash then value
156
+ when nil then UnsetOptions.new
157
+ end
158
+ end
159
+
160
+ # Determine if value is a hash or unset
161
+ #
162
+ # @param value [coerced value]
163
+ #
164
+ # @return [TrueClass,FalseClass] [outcome]
165
+ #
166
+ # @api private
167
+ #
168
+ def self.success?(_, value)
169
+ value.instance_of?(Hash) || value.unset?
170
+ end
171
+ end
172
+
173
+ # Coerce and validate that an argument is an ActiveModel::Validator
174
+ #
175
+ class ValidatorClassCoercer
176
+ # returns value given so that success can test it
177
+ # @return [value passed in]
178
+ #
179
+ # @api private
180
+ #
181
+ def self.call(value)
182
+ value
183
+ end
184
+
185
+ # Determine whether a validator class is valid
186
+ #
187
+ # @param primitive [Class] [primitive class expected]
188
+ # @param value [coerced value]
189
+ #
190
+ # @return [TrueClass,FalseClass] [outcome]
191
+ #
192
+ # @api private
193
+ #
194
+ def self.success?(primitive, value)
195
+ value.instance_of?(primitive) && value.ancestors.include?(::ActiveModel::Validator)
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,67 @@
1
+ module Interdependence
2
+ module Validator
3
+ extend CommonMixin
4
+
5
+ # Class methods mixed into ActiveModel::Validator
6
+ module ClassMethods
7
+ # Transfer parent dependencies to child upon inheritance
8
+ #
9
+ # @param base [Class] [parent class]
10
+ #
11
+ # @return [undefined]
12
+ #
13
+ # @api private
14
+ #
15
+ def inherited(base)
16
+ super
17
+
18
+ base.inherit_dependencies(as_dependency)
19
+ end
20
+
21
+ # Delete another key and inherit its dependencies
22
+ #
23
+ # @param old_owner [Dependency::Base] [key in dependencies]
24
+ #
25
+ # @return [undefined]
26
+ #
27
+ # @api private
28
+ #
29
+ def inherit_dependencies(old_owner)
30
+ dependencies[as_dependency] = dependencies.delete(old_owner)
31
+ end
32
+ # Current validator wrapped in the {Dependency::Validator} class
33
+ #
34
+ # @return [Dependency::Validator] [proxy validator dependency]
35
+ #
36
+ # @api private
37
+ #
38
+ def as_dependency
39
+ @as_dependency ||= Dependency::Validator.new(validator_class: self)
40
+ end
41
+
42
+ # Merges dependencies of validators the current class depends on
43
+ #
44
+ # @return [Void]
45
+ #
46
+ # @api private
47
+ #
48
+ def sync_dependencies(*)
49
+ dependencies.keys.each do |key|
50
+ validator = key.validator_class
51
+ dependencies[key] = validator.dependencies[validator.as_dependency]
52
+ end
53
+ end
54
+
55
+ # Registers self as a dependency owner on dependencies
56
+ #
57
+ # @return [Void]
58
+ #
59
+ # @api private
60
+ #
61
+ def clear_dependencies!
62
+ super
63
+ dependencies.register(as_dependency)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,105 @@
1
+ module Interdependence
2
+ module Validator
3
+ # Interdependence validator wrapper
4
+ #
5
+ class Validator
6
+ include Virtus.model
7
+
8
+ # Validator that owns {#validator_class}
9
+ #
10
+ # @return [Class] validator dependency
11
+ #
12
+ # @api private
13
+ #
14
+ attribute :validator, Class
15
+
16
+ # Field that {#validator_class} will validate
17
+ #
18
+ # @return [Symbol] field name
19
+ #
20
+ # @api private
21
+ #
22
+ attribute :field, Symbol
23
+
24
+ # ActiveModel::Validator class that is owned by {#validator}
25
+ #
26
+ # @return [Class] class guaranteed to be a descendant of ActiveModel::Validator
27
+ #
28
+ # @api private
29
+ #
30
+ attribute :validator_class, Class, coercer: Types::ValidatorClassCoercer, strict: true
31
+
32
+ # Validation options for {#validator_class}
33
+ #
34
+ # @api private
35
+ #
36
+ attribute :options, Hash, coercer: Types::OptionsCoercer, default: {}
37
+
38
+ # Generic setter to unify with {Model::Validator}
39
+ #
40
+ # @return [owner]
41
+ #
42
+ # @api private
43
+ #
44
+ alias_method :owner=, :validator=
45
+
46
+ # @!method new_dependency
47
+ #
48
+ # Delegated method to {#resolver} for creating new {Dependency::Validator}
49
+ #
50
+ # @return [Dependency::Validator]
51
+ #
52
+ # @api private
53
+ #
54
+ delegate :dependencies, :new_dependency, to: :resolver
55
+
56
+ # @!method validator_dependencies
57
+ #
58
+ # Delegated method to {#validator} for accessing dependency graph
59
+ #
60
+ # @return [ObservableDependencySetGraph]
61
+ #
62
+ # @api private
63
+ #
64
+ delegate :dependencies, to: :validator, prefix: true
65
+
66
+ # Observe new dependency and merge its dependencies into {#validator}'s
67
+ #
68
+ # @return [ObservableDependencySetGraph] [updated dependencies]
69
+ #
70
+ # @api private
71
+ #
72
+ def save
73
+ validator_dependencies[validator.as_dependency] << new_observed_dependency
74
+ validator_dependencies.merge!(dependencies)
75
+ end
76
+
77
+ # Memoized resolver
78
+ #
79
+ # @return [DependencyResolver::Validator]
80
+ #
81
+ # @api private
82
+ #
83
+ def resolver
84
+ @resolver ||= DependencyResolver::Validator.new(self)
85
+ end
86
+
87
+ private
88
+
89
+ # Clone {#new_dependency} and {#validator} as an observer
90
+ #
91
+ # @see Validator::ClassMethods#sync_dependencies
92
+ # @see Dependency::Validator#add_validator_observer
93
+ #
94
+ # @return [Dependency::Validator] [new validator]
95
+ #
96
+ # @api private
97
+ #
98
+ def new_observed_dependency
99
+ new_dependency.clone.tap do |dependency|
100
+ dependency.add_validator_observer(validator, :sync_dependencies)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,3 @@
1
+ module Interdependence
2
+ VERSION = '0.0.1'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activemodel-interdependence
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - John Backus
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-07-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: virtus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: adamantium
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-core
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-its
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.2'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: bundler
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.7'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.7'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '10.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '10.0'
139
+ description: Interdependent model validations
140
+ email:
141
+ - john@blockscore.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".rspec"
148
+ - ".rubocop.yml"
149
+ - ".rubocop_todo.yml"
150
+ - ".yardopts"
151
+ - Gemfile
152
+ - Gemfile.lock
153
+ - Guardfile
154
+ - LICENSE
155
+ - README.md
156
+ - Rakefile
157
+ - activemodel-interdependence.gemspec
158
+ - circle.yml
159
+ - config/devtools.yml
160
+ - config/flay.yml
161
+ - config/flog.yml
162
+ - config/mutant.yml
163
+ - config/reek.yml
164
+ - config/rubocop.yml
165
+ - config/yardstick.yml
166
+ - lib/activemodel/interdependence.rb
167
+ - lib/activemodel/model/interdependence.rb
168
+ - lib/activemodel/validator/interdependence.rb
169
+ - lib/interdependence.rb
170
+ - lib/interdependence/activemodel/class_methods.rb
171
+ - lib/interdependence/activemodel/validates_with.rb
172
+ - lib/interdependence/common_mixin.rb
173
+ - lib/interdependence/dependency/base.rb
174
+ - lib/interdependence/dependency/model.rb
175
+ - lib/interdependence/dependency/validator.rb
176
+ - lib/interdependence/dependency_resolver/base.rb
177
+ - lib/interdependence/dependency_resolver/model.rb
178
+ - lib/interdependence/dependency_resolver/validator.rb
179
+ - lib/interdependence/dependency_set.rb
180
+ - lib/interdependence/dependency_set_graph.rb
181
+ - lib/interdependence/graph.rb
182
+ - lib/interdependence/model.rb
183
+ - lib/interdependence/model/validator.rb
184
+ - lib/interdependence/observable_dependency_set_graph.rb
185
+ - lib/interdependence/types.rb
186
+ - lib/interdependence/validator.rb
187
+ - lib/interdependence/validator/validator.rb
188
+ - lib/interdependence/version.rb
189
+ homepage: https://github.com/blockscore/activemodel-interdependence
190
+ licenses: []
191
+ metadata: {}
192
+ post_install_message:
193
+ rdoc_options: []
194
+ require_paths:
195
+ - lib
196
+ required_ruby_version: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: 2.2.0
201
+ required_rubygems_version: !ruby/object:Gem::Requirement
202
+ requirements:
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ requirements: []
207
+ rubyforge_project:
208
+ rubygems_version: 2.4.6
209
+ signing_key:
210
+ specification_version: 4
211
+ summary: Use validators interdependently
212
+ test_files: []
213
+ has_rdoc: