attribute-trackable 0.0.2.pre.rc1
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.
- checksums.yaml +7 -0
- data/lib/trackable.rb +246 -0
- data/lib/trackable/errors.rb +12 -0
- data/lib/trackable/helpers/circular_dependency.rb +64 -0
- data/lib/trackable/helpers/verify_options.rb +69 -0
- data/lib/trackable/version.rb +3 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ec7576bd556386c9eb3e9e21a3f6f1872dd36240cbd0f9625d7478e26dade44c
|
4
|
+
data.tar.gz: 1e88eb34d0624aa3394c80743b4ed4700831cd237a4f7bedfe668a7f1ecf0b4a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c1ad9d08eb731530e2ebbe3adc257d397d82b1dd2ad4cccf2ef0948a645f8700e211abc407caa95fbc46201d2b5eb4886b8122bd65c7fe8a9dc11e7473e1d51a
|
7
|
+
data.tar.gz: 6c239fbd602b9ffd2b6688f660f5a51b8c49cefb115ee4f118ce88fac8102abade074e783e16aa7dab7ea40d0e4188c177ccf07df41e8da78510e444f46f44ae
|
data/lib/trackable.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Defines tracking logic to an internal field and/or to an external resource using a hash DSL
|
4
|
+
#
|
5
|
+
# Interface: `object.trackable_update(params.merge(unlink_fields: [:teeth]))`
|
6
|
+
# Reserved params:
|
7
|
+
# unlink_fields: Array - array of symbolized field names to unlink from tracked their fields
|
8
|
+
# unlink_child_fields: Array - array of symbolized field names to unlink child fields
|
9
|
+
#
|
10
|
+
# The modified external resources can be found in: `object.trackable_dependents`
|
11
|
+
#
|
12
|
+
# DSL Structure:
|
13
|
+
# {
|
14
|
+
# species: {
|
15
|
+
# linked_attributes: {
|
16
|
+
# coat: {
|
17
|
+
# if: ->(animal) { animal.tracking_coat },
|
18
|
+
# tracking_bool: :tracking_coat,
|
19
|
+
# track: ->(animal) { TrackableAnimal.track_coat_to_species(animal) }
|
20
|
+
# },
|
21
|
+
# teeth: {
|
22
|
+
# tracking_bool: :tracking_teeth,
|
23
|
+
# track: ->(a) { TrackableAnimal.track_teeth_to_species(animal) }
|
24
|
+
# }
|
25
|
+
# },
|
26
|
+
# linked_child_attributes: {
|
27
|
+
# species: {
|
28
|
+
# children: :children,
|
29
|
+
# tracking_bool: :tracking_species,
|
30
|
+
# track: ->(animal, child) { TrackableAnimal.track_species_to_parent(animal, child) }
|
31
|
+
# }
|
32
|
+
# }
|
33
|
+
# },
|
34
|
+
# teeth: {
|
35
|
+
# linked_attributes: {
|
36
|
+
# diet: {
|
37
|
+
# tracking_bool: :tracking_diet,
|
38
|
+
# track: ->(animal) { TrackableAnimal.track_diet_to_teeth(animal) }
|
39
|
+
# }
|
40
|
+
# }
|
41
|
+
# }
|
42
|
+
# }
|
43
|
+
#
|
44
|
+
# DSL Params:
|
45
|
+
# 1. tracking_bool: boolean value used to determine whether or not a field is linked
|
46
|
+
# 2. if: proc used to determine whether or not to track the linked field
|
47
|
+
# 3. children: symbol to send to object to obtain it's children
|
48
|
+
# 4. track: proc used to translate tracked field into its linked dependent
|
49
|
+
# - when attributes are linked to base object, param passed to proc is (object)
|
50
|
+
# - when attributes are linked to parent object, params passed to proc are (object, child)
|
51
|
+
module Trackable
|
52
|
+
require 'trackable/errors'
|
53
|
+
require 'trackable/helpers/circular_dependency'
|
54
|
+
require 'trackable/helpers/verify_options'
|
55
|
+
require 'active_support/concern'
|
56
|
+
require 'active_support/core_ext/class/attribute'
|
57
|
+
require 'active_support/core_ext/object'
|
58
|
+
extend ::ActiveSupport::Concern
|
59
|
+
|
60
|
+
included do
|
61
|
+
class_attribute :trackable_data, :trackable_defaults
|
62
|
+
|
63
|
+
attr_accessor :trackable_dependents
|
64
|
+
end
|
65
|
+
|
66
|
+
module ClassMethods
|
67
|
+
def tracking(structure)
|
68
|
+
self.trackable_data = structure
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_trackable_defaults(children: nil, linked_bool: nil)
|
72
|
+
self.trackable_defaults = {
|
73
|
+
linked_bool: linked_bool,
|
74
|
+
children: children
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def propagate_changes(opts)
|
79
|
+
add_trackable_dependency(opts)
|
80
|
+
end
|
81
|
+
|
82
|
+
def propagate_changes_to_children(opts)
|
83
|
+
add_trackable_dependency(opts.merge(child: true))
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_trackable_dependency(opts)
|
87
|
+
self.trackable_data ||= {}
|
88
|
+
self_or_child_attributes = opts[:child] ? :linked_child_attributes : :linked_attributes
|
89
|
+
new_option = { track: opts[:use] }
|
90
|
+
new_option[:if] = opts[:if] if opts[:if]
|
91
|
+
new_option[:tracking_bool] = opts[:linked_bool] if opts[:linked_bool]
|
92
|
+
new_option[:skip_assignment] = opts[:skip_assignment] if opts[:skip_assignment]
|
93
|
+
new_option[:children] = opts[:children] if opts[:children] && opts[:child]
|
94
|
+
linked_attrs = trackable_data.dig(opts[:from], self_or_child_attributes) || {}
|
95
|
+
trackable_data[opts[:from]] = (trackable_data.dig(opts[:from]) || {})
|
96
|
+
.merge(self_or_child_attributes => linked_attrs
|
97
|
+
.merge(opts[:to] => new_option))
|
98
|
+
end
|
99
|
+
|
100
|
+
def explain_trackable_attribute(attribute)
|
101
|
+
linked_to = []
|
102
|
+
linked_to_parent = []
|
103
|
+
|
104
|
+
trackable_data.each do |key, value|
|
105
|
+
linked_to << key if value[:linked_attributes] && attribute.in?(value[:linked_attributes])
|
106
|
+
if value[:linked_child_attributes] && attribute.in?(value[:linked_child_attributes])
|
107
|
+
linked_to_parent << key
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
{
|
112
|
+
inherits: linked_to,
|
113
|
+
inherits_from_parent: linked_to_parent,
|
114
|
+
inherited_by: trackable_data[attribute][:linked_child_attributes],
|
115
|
+
inherited_by_to_children: trackable_data[attribute][:linked_child_attributes]
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def verify_trackable_options!
|
120
|
+
Trackable::Helpers::VerifyOptions.new(trackable_data, trackable_defaults).verify!
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.prepended(base)
|
125
|
+
class << base
|
126
|
+
prepend ClassMethods
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
define_method(:trackable_update) do |attrs|
|
131
|
+
unlink_fields = Array(attrs.delete(:unlink_fields))
|
132
|
+
unlink_child_fields = Array(attrs.delete(:unlink_child_fields))
|
133
|
+
|
134
|
+
attrs.each do |key, value|
|
135
|
+
# set the value using the default setter method
|
136
|
+
public_send("#{key.to_sym}=", value)
|
137
|
+
next unless trackable_data.key? key
|
138
|
+
|
139
|
+
changes_to_propagate = trackable_data[key][:linked_attributes]
|
140
|
+
changes_to_propagate_to_children = trackable_data[key][:linked_child_attributes]
|
141
|
+
|
142
|
+
changes_to_propagate&.each do |attribute, options|
|
143
|
+
next unless should_track?(linked_bool(attribute, options), options[:if])
|
144
|
+
|
145
|
+
new_value = options[:track].is_a?(Symbol) ? public_send(options[:track]) : options[:track].call(self)
|
146
|
+
trackable_update("#{attribute}": new_value) unless options[:skip_assignment]
|
147
|
+
end
|
148
|
+
|
149
|
+
self.trackable_dependents ||= {} # build hash of mutated child objects keyed by :id
|
150
|
+
changes_to_propagate_to_children&.each do |attribute, options|
|
151
|
+
unlink_child = attribute.in? unlink_child_fields
|
152
|
+
update_dependents(attribute, options, unlink_child)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Make the dependency tree order agnostic by overriding attributes that are explicitly set in params
|
157
|
+
overriding_attrs = attrs.keys & all_linked_attributes
|
158
|
+
override(attrs.slice(*overriding_attrs), unlink_fields)
|
159
|
+
end
|
160
|
+
|
161
|
+
define_method(:trackable_link_child) do |child|
|
162
|
+
self.trackable_dependents ||= {}
|
163
|
+
all_linked_attributes(:linked_child_attributes).each do |attribute|
|
164
|
+
update_dependents(
|
165
|
+
attribute,
|
166
|
+
trackable_data.dig(attribute, :linked_child_attributes, attribute),
|
167
|
+
false,
|
168
|
+
child
|
169
|
+
)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
define_method(:trackable_unlink) do |attribute|
|
174
|
+
unlinked = trackable_data.each_value.map do |options|
|
175
|
+
opts = options.dig(:linked_attributes, attribute) || options.dig(:linked_child_attributes, attribute)
|
176
|
+
linked_bool(attribute, opts) if opts
|
177
|
+
end.compact
|
178
|
+
|
179
|
+
if unlinked.empty?
|
180
|
+
raise Trackable::Errors::InvalidUnlink, "Must include tracking_bool for #{attribute} to override"
|
181
|
+
end
|
182
|
+
|
183
|
+
unlinked.each { |tracking_bool| public_send("#{tracking_bool}=", false) }
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def linked_bool(attribute, options)
|
189
|
+
return false unless options
|
190
|
+
|
191
|
+
defaults = trackable_defaults&.dig(:linked_bool)
|
192
|
+
if options[:tracking_bool].is_a?(Symbol)
|
193
|
+
options[:tracking_bool]
|
194
|
+
elsif defaults && options[:tracking_bool]
|
195
|
+
[defaults[:prefix], attribute, defaults[:sufix]]
|
196
|
+
.compact.join('_').to_sym
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def should_track?(bool, func, child = nil)
|
201
|
+
bool_result = bool.nil? || (child || self).send(bool.to_sym)
|
202
|
+
proc_params = func&.parameters&.map(&:last) || []
|
203
|
+
parameters = { root: self, child: child }
|
204
|
+
|
205
|
+
func.present? ? bool_result && func.call(**parameters.slice(*proc_params)) : bool_result
|
206
|
+
end
|
207
|
+
|
208
|
+
# Overrides an attribute when it is passed as a parameter to trackable_update
|
209
|
+
def override(attrs, unlink_fields)
|
210
|
+
attrs.each do |attribute, value|
|
211
|
+
unlink_field = attribute.in? unlink_fields
|
212
|
+
public_send("#{attribute}=", value)
|
213
|
+
|
214
|
+
if trackable_data.dig(attribute, :linked_child_attributes)
|
215
|
+
update_dependents(
|
216
|
+
attribute,
|
217
|
+
trackable_data.dig(attribute, :linked_child_attributes, attribute),
|
218
|
+
unlink_field
|
219
|
+
)
|
220
|
+
end
|
221
|
+
|
222
|
+
next unless unlink_field
|
223
|
+
|
224
|
+
trackable_unlink(attribute)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def update_dependents(attribute, options, unlink_field, override_child = nil)
|
229
|
+
dependents = Array(override_child || public_send((options[:children] || trackable_defaults[:children])))
|
230
|
+
dependents.each do |dependent|
|
231
|
+
dependent.trackable_unlink(attribute) if unlink_field
|
232
|
+
next unless options && should_track?(linked_bool(attribute, options), options[:if], dependent)
|
233
|
+
|
234
|
+
current_dep = trackable_dependents[dependent.id] || dependent
|
235
|
+
|
236
|
+
new_value = options[:track].is_a?(Symbol) ? public_send(options[:track]) : options[:track].call(self, current_dep)
|
237
|
+
current_dep.trackable_update("#{attribute}": new_value) unless options[:skip_assignment]
|
238
|
+
|
239
|
+
trackable_dependents[current_dep.id] = current_dep
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def all_linked_attributes(from = :linked_attributes)
|
244
|
+
trackable_data.map { |_k, v| v[from]&.keys }.flatten.compact
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trackable
|
4
|
+
module Errors
|
5
|
+
class TrackableError < StandardError; end
|
6
|
+
class CyclicDependency < TrackableError; end
|
7
|
+
class InvalidOptions < TrackableError; end
|
8
|
+
|
9
|
+
class TrackableRuntimeError < RuntimeError; end
|
10
|
+
class InvalidUnlink < TrackableRuntimeError; end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Example graph
|
4
|
+
# {
|
5
|
+
# 'a': ['b', 'd'],
|
6
|
+
# 'b': ['c', 'e'],
|
7
|
+
# 'c': ['d', 'a']
|
8
|
+
# }
|
9
|
+
# TODO: refactor to use sets
|
10
|
+
module Trackable
|
11
|
+
module Helpers
|
12
|
+
class CircularDependency
|
13
|
+
attr_accessor :cyclic_dependencies, :graph, :traversed_verticies,
|
14
|
+
:verticies, :work_graph
|
15
|
+
|
16
|
+
def initialize(graph = {})
|
17
|
+
@graph = graph
|
18
|
+
@work_graph = @graph.dup
|
19
|
+
@verticies = @graph.keys
|
20
|
+
@traversed_verticies = []
|
21
|
+
@cyclic_dependencies = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def check_graph
|
25
|
+
verticies.each do |vertex|
|
26
|
+
check_node(vertex) if work_graph[vertex]
|
27
|
+
end
|
28
|
+
cyclic_dependencies
|
29
|
+
end
|
30
|
+
|
31
|
+
def check_node(node)
|
32
|
+
traversed_verticies << node
|
33
|
+
|
34
|
+
work_graph[node].each do |child|
|
35
|
+
# There is no point in further investigating this node
|
36
|
+
# since it has been removed
|
37
|
+
if work_graph[child].nil?
|
38
|
+
# We have followed this tree to its end
|
39
|
+
self.traversed_verticies = [] if graph[child].nil?
|
40
|
+
next
|
41
|
+
end
|
42
|
+
|
43
|
+
repeated_index = repeated_index(child)
|
44
|
+
if repeated_index # Found a cycle so bust the stack and continue on
|
45
|
+
new_dep = (traversed_verticies[repeated_index..-1] +
|
46
|
+
[child, traversed_verticies[repeated_index]])
|
47
|
+
self.traversed_verticies = []
|
48
|
+
cyclic_dependencies << new_dep
|
49
|
+
next
|
50
|
+
end
|
51
|
+
|
52
|
+
check_node(child)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Finished checking all cycles for this node so don't repeat it
|
56
|
+
work_graph.delete(node)
|
57
|
+
end
|
58
|
+
|
59
|
+
def repeated_index(child)
|
60
|
+
traversed_verticies.index { |v| v.in?(work_graph[child]) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trackable
|
4
|
+
module Helpers
|
5
|
+
class VerifyOptions
|
6
|
+
attr_accessor :options, :defaults
|
7
|
+
|
8
|
+
def initialize(options, defaults)
|
9
|
+
self.options = options
|
10
|
+
self.defaults = defaults.to_h
|
11
|
+
end
|
12
|
+
|
13
|
+
def verify!
|
14
|
+
verify_trackable_opts(options)
|
15
|
+
verify_cyclical_dependencies(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def verify_trackable_opts(options)
|
21
|
+
options.each_value do |opts|
|
22
|
+
if no_attribute_dependencies?(opts)
|
23
|
+
raise Trackable::Errors::InvalidOptions,
|
24
|
+
'Missing tracking attributes'
|
25
|
+
end
|
26
|
+
|
27
|
+
verify_attributes(opts)
|
28
|
+
verify_attributes(opts, true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def verify_attributes(opts, child = false)
|
33
|
+
opt_key = child ? :linked_attributes : :linked_child_attributes
|
34
|
+
opts[opt_key]&.each_value do |opt_value|
|
35
|
+
verify_track_attribute(opt_value, child)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def verify_track_attribute(opt_value, child = false)
|
40
|
+
if child && opt_value[:children].blank? && defaults[:children]&.blank?
|
41
|
+
raise Trackable::Errors::InvalidOptions, 'Missing children attribute'
|
42
|
+
end
|
43
|
+
|
44
|
+
return unless opt_value[:track].blank?
|
45
|
+
|
46
|
+
raise Trackable::Errors::InvalidOptions, 'Missing track attribute'
|
47
|
+
end
|
48
|
+
|
49
|
+
def verify_cyclical_dependencies(options)
|
50
|
+
graph = build_graph(options)
|
51
|
+
cycles = Trackable::Helpers::CircularDependency.new(graph).check_graph
|
52
|
+
return if cycles.empty?
|
53
|
+
|
54
|
+
raise Trackable::Errors::CyclicDependency,
|
55
|
+
"Cyclic dependency found: #{cycles.map { |c| c.join(' -> ') }}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_graph(options)
|
59
|
+
options.each_with_object({}) do |(k, v), h|
|
60
|
+
h[k] = v[:linked_attributes]&.keys
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def no_attribute_dependencies?(opts)
|
65
|
+
opts[:linked_attributes].nil? && opts[:linked_child_attributes].nil?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attribute-trackable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2.pre.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam David
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-nav
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activesupport
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- adam.david@bugcrowd.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- lib/trackable.rb
|
119
|
+
- lib/trackable/errors.rb
|
120
|
+
- lib/trackable/helpers/circular_dependency.rb
|
121
|
+
- lib/trackable/helpers/verify_options.rb
|
122
|
+
- lib/trackable/version.rb
|
123
|
+
homepage: https://github.com/adamrdavid/trackable
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">"
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: 1.3.1
|
141
|
+
requirements: []
|
142
|
+
rubygems_version: 3.0.3
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: A flowdown wrapper for attribute dependencies
|
146
|
+
test_files: []
|