attribute-trackable 0.0.2.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|