activemodel-interdependence 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: