arcopy 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.
- checksums.yaml +7 -0
- data/COPYING +18 -0
- data/HACKING +61 -0
- data/README.md +285 -0
- data/Rakefile +16 -0
- data/bin/replicate +100 -0
- data/lib/replicate.rb +24 -0
- data/lib/replicate/active_record.rb +347 -0
- data/lib/replicate/dumper.rb +142 -0
- data/lib/replicate/emitter.rb +54 -0
- data/lib/replicate/loader.rb +157 -0
- data/lib/replicate/object.rb +57 -0
- data/lib/replicate/status.rb +54 -0
- data/test/active_record_test.rb +589 -0
- data/test/dumper_test.rb +108 -0
- data/test/dumpscript.rb +1 -0
- data/test/linked_dumpscript.rb +1 -0
- data/test/loader_test.rb +93 -0
- data/test/replicate_test.rb +10 -0
- metadata +135 -0
data/lib/replicate.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Replicate
|
2
|
+
autoload :Emitter, 'replicate/emitter'
|
3
|
+
autoload :Dumper, 'replicate/dumper'
|
4
|
+
autoload :Loader, 'replicate/loader'
|
5
|
+
autoload :Object, 'replicate/object'
|
6
|
+
autoload :Status, 'replicate/status'
|
7
|
+
autoload :AR, 'replicate/active_record'
|
8
|
+
|
9
|
+
# Determine if this is a production looking environment. Used in bin/replicate
|
10
|
+
# to safeguard against loading in production.
|
11
|
+
def self.production_environment?
|
12
|
+
if defined?(Rails) && Rails.respond_to?(:env)
|
13
|
+
Rails.env.to_s == 'production'
|
14
|
+
elsif defined?(RAILS_ENV)
|
15
|
+
RAILS_ENV == 'production'
|
16
|
+
elsif ENV['RAILS_ENV']
|
17
|
+
ENV['RAILS_ENV'] == 'production'
|
18
|
+
elsif ENV['RACK_ENV']
|
19
|
+
ENV['RACK_ENV'] == 'production'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
AR if defined?(::ActiveRecord::Base)
|
24
|
+
end
|
@@ -0,0 +1,347 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Replicate
|
4
|
+
# ActiveRecord::Base instance methods used to dump replicant objects for the
|
5
|
+
# record and all 1:1 associations. This module implements the replicant_id
|
6
|
+
# and dump_replicant methods using AR's reflection API to determine
|
7
|
+
# relationships with other objects.
|
8
|
+
module AR
|
9
|
+
# Mixin for the ActiveRecord instance.
|
10
|
+
module InstanceMethods
|
11
|
+
# Replicate::Dumper calls this method on objects to trigger dumping a
|
12
|
+
# replicant object tuple. The default implementation dumps all belongs_to
|
13
|
+
# associations, then self, then all has_one associations, then any
|
14
|
+
# has_many or has_and_belongs_to_many associations declared with the
|
15
|
+
# replicate_associations macro.
|
16
|
+
#
|
17
|
+
# dumper - Dumper object whose #write method must be called with the
|
18
|
+
# type, id, and attributes hash.
|
19
|
+
#
|
20
|
+
# Returns nothing.
|
21
|
+
def dump_replicant(dumper, opts={})
|
22
|
+
@replicate_opts = opts
|
23
|
+
@replicate_opts[:associations] ||= []
|
24
|
+
@replicate_opts[:omit] ||= []
|
25
|
+
dump_all_association_replicants dumper, :belongs_to
|
26
|
+
dumper.write self.class.to_s, id, replicant_attributes, self
|
27
|
+
dump_all_association_replicants dumper, :has_one
|
28
|
+
included_associations.each do |association|
|
29
|
+
dump_association_replicants dumper, association
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# List of associations to explicitly include when dumping this object.
|
34
|
+
def included_associations
|
35
|
+
(self.class.replicate_associations + @replicate_opts[:associations]).uniq
|
36
|
+
end
|
37
|
+
|
38
|
+
# List of attributes and associations to omit when dumping this object.
|
39
|
+
def omitted_attributes
|
40
|
+
(self.class.replicate_omit_attributes + @replicate_opts[:omit]).uniq
|
41
|
+
end
|
42
|
+
|
43
|
+
# Attributes hash used to persist this object. This consists of simply
|
44
|
+
# typed values (no complex types or objects) with the exception of special
|
45
|
+
# foreign key values. When an attribute value is [:id, "SomeClass:1234"],
|
46
|
+
# the loader will handle translating the id value to the local system's
|
47
|
+
# version of the same object.
|
48
|
+
def replicant_attributes
|
49
|
+
attributes = self.attributes.dup
|
50
|
+
|
51
|
+
omitted_attributes.each { |omit| attributes.delete(omit.to_s) }
|
52
|
+
self.class.reflect_on_all_associations(:belongs_to).each do |reflection|
|
53
|
+
next if omitted_attributes.include?(reflection.name)
|
54
|
+
if info = replicate_reflection_info(reflection)
|
55
|
+
if replicant_id = info[:replicant_id]
|
56
|
+
foreign_key = info[:foreign_key].to_s
|
57
|
+
attributes[foreign_key] = [:id, *replicant_id]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
attributes
|
63
|
+
end
|
64
|
+
|
65
|
+
# Retrieve information on a reflection's associated class and various
|
66
|
+
# keys.
|
67
|
+
#
|
68
|
+
# Returns an info hash with these keys:
|
69
|
+
# :class - The class object the association points to.
|
70
|
+
# :primary_key - The string primary key column name.
|
71
|
+
# :foreign_key - The string foreign key column name.
|
72
|
+
# :replicant_id - The [classname, id] tuple identifying the record.
|
73
|
+
#
|
74
|
+
# Returns nil when the reflection can not be linked to a model.
|
75
|
+
def replicate_reflection_info(reflection)
|
76
|
+
options = reflection.options
|
77
|
+
if options[:polymorphic]
|
78
|
+
reference_class = attributes[reflection.foreign_type]
|
79
|
+
return if reference_class.nil?
|
80
|
+
|
81
|
+
klass = reference_class.constantize
|
82
|
+
primary_key = klass.primary_key
|
83
|
+
foreign_key = "#{reflection.name}_id"
|
84
|
+
else
|
85
|
+
klass = reflection.klass
|
86
|
+
primary_key = (options[:primary_key] || klass.primary_key).to_s
|
87
|
+
foreign_key = (options[:foreign_key] || "#{reflection.name}_id").to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
info = {
|
91
|
+
:class => klass,
|
92
|
+
:primary_key => primary_key,
|
93
|
+
:foreign_key => foreign_key
|
94
|
+
}
|
95
|
+
|
96
|
+
if primary_key == klass.primary_key
|
97
|
+
if id = attributes[foreign_key]
|
98
|
+
info[:replicant_id] = [klass.to_s, id]
|
99
|
+
else
|
100
|
+
# nil value in association reference
|
101
|
+
end
|
102
|
+
else
|
103
|
+
# association uses non-primary-key foreign key. no special key
|
104
|
+
# conversion needed.
|
105
|
+
end
|
106
|
+
|
107
|
+
info
|
108
|
+
end
|
109
|
+
|
110
|
+
# The replicant id is a two tuple containing the class and object id. This
|
111
|
+
# is used by Replicant::Dumper to determine if the object has already been
|
112
|
+
# dumped or not.
|
113
|
+
def replicant_id
|
114
|
+
[self.class.name, id]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Dump all associations of a given type.
|
118
|
+
#
|
119
|
+
# dumper - The Dumper object used to dump additional objects.
|
120
|
+
# association_type - :has_one, :belongs_to, :has_many
|
121
|
+
#
|
122
|
+
# Returns nothing.
|
123
|
+
def dump_all_association_replicants(dumper, association_type)
|
124
|
+
self.class.reflect_on_all_associations(association_type).each do |reflection|
|
125
|
+
next if omitted_attributes.include?(reflection.name)
|
126
|
+
|
127
|
+
# bail when this object has already been dumped
|
128
|
+
next if (info = replicate_reflection_info(reflection)) &&
|
129
|
+
(replicant_id = info[:replicant_id]) &&
|
130
|
+
dumper.dumped?(replicant_id)
|
131
|
+
|
132
|
+
next if (dependent = __send__(reflection.name)).nil?
|
133
|
+
|
134
|
+
case dependent
|
135
|
+
when ActiveRecord::Base, Array
|
136
|
+
dumper.dump(dependent)
|
137
|
+
|
138
|
+
# clear reference to allow GC
|
139
|
+
if respond_to?(:association)
|
140
|
+
association(reflection.name).reset
|
141
|
+
elsif respond_to?(:association_instance_set, true)
|
142
|
+
association_instance_set(reflection.name, nil)
|
143
|
+
end
|
144
|
+
else
|
145
|
+
warn "warn: #{self.class}##{reflection.name} #{association_type} association " \
|
146
|
+
"unexpectedly returned a #{dependent.class}. skipping."
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Dump objects associated with an AR object through an association name.
|
152
|
+
#
|
153
|
+
# object - AR object instance.
|
154
|
+
# association - Name of the association whose objects should be dumped.
|
155
|
+
#
|
156
|
+
# Returns nothing.
|
157
|
+
def dump_association_replicants(dumper, association)
|
158
|
+
if reflection = self.class.reflect_on_association(association)
|
159
|
+
objects = __send__(reflection.name)
|
160
|
+
dumper.dump(objects)
|
161
|
+
if reflection.macro == :has_and_belongs_to_many
|
162
|
+
dump_has_and_belongs_to_many_replicant(dumper, reflection)
|
163
|
+
end
|
164
|
+
object = __send__(reflection.name) # clear to allow GC
|
165
|
+
if object.respond_to?(:reset)
|
166
|
+
object.reset
|
167
|
+
end
|
168
|
+
else
|
169
|
+
warn "error: #{self.class}##{association} is invalid"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Dump the special Habtm object used to establish many-to-many
|
174
|
+
# relationships between objects that have already been dumped. Note that
|
175
|
+
# this object and all objects referenced must have already been dumped
|
176
|
+
# before calling this method.
|
177
|
+
def dump_has_and_belongs_to_many_replicant(dumper, reflection)
|
178
|
+
dumper.dump Habtm.new(self, reflection)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Mixin for the ActiveRecord class.
|
183
|
+
module ClassMethods
|
184
|
+
# Set and retrieve list of association names that should be dumped when
|
185
|
+
# objects of this class are dumped. This method may be called multiple
|
186
|
+
# times to add associations.
|
187
|
+
def replicate_associations(*names)
|
188
|
+
self.replicate_associations += names if names.any?
|
189
|
+
@replicate_associations || superclass.replicate_associations
|
190
|
+
end
|
191
|
+
|
192
|
+
# Set the list of association names to dump to the specific set of values.
|
193
|
+
def replicate_associations=(names)
|
194
|
+
@replicate_associations = names.uniq.map { |name| name.to_sym }
|
195
|
+
end
|
196
|
+
|
197
|
+
# Compound key used during load to locate existing objects for update.
|
198
|
+
# When no natural key is defined, objects are created new.
|
199
|
+
#
|
200
|
+
# attribute_names - Macro style setter.
|
201
|
+
def replicate_natural_key(*attribute_names)
|
202
|
+
self.replicate_natural_key = attribute_names if attribute_names.any?
|
203
|
+
@replicate_natural_key || superclass.replicate_natural_key
|
204
|
+
end
|
205
|
+
|
206
|
+
# Set the compound key used to locate existing objects for update when
|
207
|
+
# loading. When not set, loading will always create new records.
|
208
|
+
#
|
209
|
+
# attribute_names - Array of attribute name symbols
|
210
|
+
def replicate_natural_key=(attribute_names)
|
211
|
+
@replicate_natural_key = attribute_names
|
212
|
+
end
|
213
|
+
|
214
|
+
# Set or retrieve whether replicated object should keep its original id.
|
215
|
+
# When not set, replicated objects will be created with new id.
|
216
|
+
def replicate_id(boolean=nil)
|
217
|
+
self.replicate_id = boolean unless boolean.nil?
|
218
|
+
@replicate_id.nil? ? superclass.replicate_id : @replicate_id
|
219
|
+
end
|
220
|
+
|
221
|
+
# Set flag for replicating original id.
|
222
|
+
def replicate_id=(boolean)
|
223
|
+
self.replicate_natural_key = [self.primary_key.to_sym] if boolean
|
224
|
+
@replicate_id = boolean
|
225
|
+
end
|
226
|
+
|
227
|
+
# Set which, if any, attributes should not be dumped. Also works for
|
228
|
+
# associations.
|
229
|
+
#
|
230
|
+
# attribute_names - Macro style setter.
|
231
|
+
def replicate_omit_attributes(*attribute_names)
|
232
|
+
self.replicate_omit_attributes = attribute_names if attribute_names.any?
|
233
|
+
@replicate_omit_attributes || superclass.replicate_omit_attributes
|
234
|
+
end
|
235
|
+
|
236
|
+
# Set which, if any, attributes should not be dumped. Also works for
|
237
|
+
# associations.
|
238
|
+
#
|
239
|
+
# attribute_names - Array of attribute name symbols
|
240
|
+
def replicate_omit_attributes=(attribute_names)
|
241
|
+
@replicate_omit_attributes = attribute_names
|
242
|
+
end
|
243
|
+
|
244
|
+
# Load an individual record into the database. If the models defines a
|
245
|
+
# replicate_natural_key then an existing record will be updated if found
|
246
|
+
# instead of a new record being created.
|
247
|
+
#
|
248
|
+
# type - Model class name as a String.
|
249
|
+
# id - Primary key id of the record on the dump system. This must be
|
250
|
+
# translated to the local system and stored in the keymap.
|
251
|
+
# attrs - Hash of attributes to set on the new record.
|
252
|
+
#
|
253
|
+
# Returns the ActiveRecord object instance for the new record.
|
254
|
+
def load_replicant(type, id, attributes)
|
255
|
+
instance = replicate_find_existing_record(attributes) || new
|
256
|
+
create_or_update_replicant instance, attributes
|
257
|
+
end
|
258
|
+
|
259
|
+
# Locate an existing record using the replicate_natural_key attribute
|
260
|
+
# values.
|
261
|
+
#
|
262
|
+
# Returns the existing record if found, nil otherwise.
|
263
|
+
def replicate_find_existing_record(attributes)
|
264
|
+
return if replicate_natural_key.empty?
|
265
|
+
conditions = {}
|
266
|
+
replicate_natural_key.each do |attribute_name|
|
267
|
+
conditions[attribute_name] = attributes[attribute_name.to_s]
|
268
|
+
end
|
269
|
+
where(conditions).first
|
270
|
+
end
|
271
|
+
|
272
|
+
# Update an AR object's attributes and persist to the database without
|
273
|
+
# running validations or callbacks.
|
274
|
+
#
|
275
|
+
# Returns the [id, object] tuple for the newly replicated objected.
|
276
|
+
def create_or_update_replicant(instance, attributes)
|
277
|
+
# write replicated attributes to the instance
|
278
|
+
attributes.each do |key, value|
|
279
|
+
next if key == primary_key and not replicate_id
|
280
|
+
instance.send :write_attribute, key, value
|
281
|
+
end
|
282
|
+
|
283
|
+
# save the instance bypassing all callbacks and validations
|
284
|
+
replicate_disable_callbacks instance
|
285
|
+
instance.save :validate => false
|
286
|
+
|
287
|
+
[instance.send(instance.class.primary_key), instance]
|
288
|
+
end
|
289
|
+
|
290
|
+
# Disable all callbacks on an ActiveRecord::Base instance. Only the
|
291
|
+
# instance is effected. There is no way to re-enable callbacks once
|
292
|
+
# they've been disabled on an object.
|
293
|
+
def replicate_disable_callbacks(instance)
|
294
|
+
def instance.run_callbacks(*args)
|
295
|
+
yield if block_given?
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
# Special object used to dump the list of associated ids for a
|
302
|
+
# has_and_belongs_to_many association. The object includes attributes for
|
303
|
+
# locating the source object and writing the list of ids to the appropriate
|
304
|
+
# association method.
|
305
|
+
class Habtm
|
306
|
+
def initialize(object, reflection)
|
307
|
+
@object = object
|
308
|
+
@reflection = reflection
|
309
|
+
end
|
310
|
+
|
311
|
+
def id
|
312
|
+
end
|
313
|
+
|
314
|
+
def attributes
|
315
|
+
ids = @object.__send__("#{@reflection.name.to_s.singularize}_ids")
|
316
|
+
{
|
317
|
+
'id' => [:id, @object.class.to_s, @object.id],
|
318
|
+
'class' => @object.class.to_s,
|
319
|
+
'ref_class' => @reflection.klass.to_s,
|
320
|
+
'ref_name' => @reflection.name.to_s,
|
321
|
+
'collection' => [:id, @reflection.klass.to_s, ids]
|
322
|
+
}
|
323
|
+
end
|
324
|
+
|
325
|
+
def dump_replicant(dumper, opts={})
|
326
|
+
type = self.class.name
|
327
|
+
id = "#{@object.class.to_s}:#{@reflection.name}:#{@object.id}"
|
328
|
+
dumper.write type, id, attributes, self
|
329
|
+
end
|
330
|
+
|
331
|
+
def self.load_replicant(type, id, attrs)
|
332
|
+
object = attrs['class'].constantize.find(attrs['id'])
|
333
|
+
ids = attrs['collection']
|
334
|
+
object.__send__("#{attrs['ref_name'].to_s.singularize}_ids=", ids)
|
335
|
+
[id, new(object, nil)]
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Load active record and install the extension methods.
|
340
|
+
::ActiveRecord::Base.send :include, InstanceMethods
|
341
|
+
::ActiveRecord::Base.send :extend, ClassMethods
|
342
|
+
::ActiveRecord::Base.replicate_associations = []
|
343
|
+
::ActiveRecord::Base.replicate_natural_key = []
|
344
|
+
::ActiveRecord::Base.replicate_omit_attributes = []
|
345
|
+
::ActiveRecord::Base.replicate_id = false
|
346
|
+
end
|
347
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Replicate
|
2
|
+
# Dump replicants in a streaming fashion.
|
3
|
+
#
|
4
|
+
# The Dumper takes objects and generates one or more replicant objects. A
|
5
|
+
# replicant has the form [type, id, attributes] and describes exactly one
|
6
|
+
# addressable record in a datastore. The type and id identify the model
|
7
|
+
# class name and model primary key id. The attributes Hash is a set of attribute
|
8
|
+
# name to primitively typed object value mappings.
|
9
|
+
#
|
10
|
+
# Example dump session:
|
11
|
+
#
|
12
|
+
# >> Replicate::Dumper.new do |dumper|
|
13
|
+
# >> dumper.marshal_to $stdout
|
14
|
+
# >> dumper.log_to $stderr
|
15
|
+
# >> dumper.dump User.find(1234)
|
16
|
+
# >> end
|
17
|
+
#
|
18
|
+
class Dumper < Emitter
|
19
|
+
# Create a new Dumper.
|
20
|
+
#
|
21
|
+
# io - IO object to write marshalled replicant objects to.
|
22
|
+
# block - Dump context block. If given, the end of the block's execution
|
23
|
+
# is assumed to be the end of the dump stream.
|
24
|
+
def initialize(io=nil)
|
25
|
+
@memo = Hash.new { |hash,k| hash[k] = {} }
|
26
|
+
super() do
|
27
|
+
marshal_to io if io
|
28
|
+
yield self if block_given?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Register a filter to write marshalled data to the given IO object.
|
33
|
+
def marshal_to(io)
|
34
|
+
listen { |type, id, attrs, obj| Marshal.dump([type, id, attrs], io) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Register a filter to write status information to the given stream. By
|
38
|
+
# default, a single line is used to report object counts while the dump is
|
39
|
+
# in progress; dump counts for each class are written when complete. The
|
40
|
+
# verbose and quiet options can be used to increase or decrease
|
41
|
+
# verbosity.
|
42
|
+
#
|
43
|
+
# out - An IO object to write to, like stderr.
|
44
|
+
# verbose - Whether verbose output should be enabled.
|
45
|
+
# quiet - Whether quiet output should be enabled.
|
46
|
+
#
|
47
|
+
# Returns the Replicate::Status object.
|
48
|
+
def log_to(out=$stderr, verbose=false, quiet=false)
|
49
|
+
use Replicate::Status, 'dump', out, verbose, quiet
|
50
|
+
end
|
51
|
+
|
52
|
+
# Load a dump script. This evals the source of the file in the context
|
53
|
+
# of a special object with a #dump method that forwards to this instance.
|
54
|
+
# Dump scripts are useful when you want to dump a lot of stuff. Call the
|
55
|
+
# dump method as many times as necessary to dump all objects.
|
56
|
+
def load_script(path)
|
57
|
+
dumper = self
|
58
|
+
object = ::Object.new
|
59
|
+
meta = (class<<object;self;end)
|
60
|
+
[:dump, :load_script].each do |method|
|
61
|
+
meta.send(:define_method, method) { |*args| dumper.send(method, *args) }
|
62
|
+
end
|
63
|
+
file = find_file(path)
|
64
|
+
object.instance_eval File.read(file), file, 0
|
65
|
+
end
|
66
|
+
|
67
|
+
# Dump one or more objects to the internal array or provided dump
|
68
|
+
# stream. This method guarantees that the same object will not be dumped
|
69
|
+
# more than once.
|
70
|
+
#
|
71
|
+
# objects - ActiveRecord object instances.
|
72
|
+
#
|
73
|
+
# Returns nothing.
|
74
|
+
def dump(*objects)
|
75
|
+
opts = if objects.last.is_a? Hash
|
76
|
+
objects.pop
|
77
|
+
else
|
78
|
+
{}
|
79
|
+
end
|
80
|
+
objects = objects[0] if objects.size == 1 && objects[0].respond_to?(:to_ary)
|
81
|
+
objects.each do |object|
|
82
|
+
next if object.nil? || dumped?(object)
|
83
|
+
if object.respond_to?(:dump_replicant)
|
84
|
+
args = [self]
|
85
|
+
args << opts unless object.method(:dump_replicant).arity == 1
|
86
|
+
object.dump_replicant(*args)
|
87
|
+
else
|
88
|
+
raise NoMethodError, "#{object.class} must respond to #dump_replicant"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Check if object has been written yet.
|
94
|
+
def dumped?(object)
|
95
|
+
if object.respond_to?(:replicant_id)
|
96
|
+
type, id = object.replicant_id
|
97
|
+
elsif object.is_a?(Array)
|
98
|
+
type, id = object
|
99
|
+
else
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
@memo[type.to_s][id]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Called exactly once per unique type and id. Emits to all listeners.
|
106
|
+
#
|
107
|
+
# type - The model class name as a String.
|
108
|
+
# id - The record's id. Usually an integer.
|
109
|
+
# attributes - All model attributes.
|
110
|
+
# object - The object this dump is generated for.
|
111
|
+
#
|
112
|
+
# Returns the object.
|
113
|
+
def write(type, id, attributes, object)
|
114
|
+
type = type.to_s
|
115
|
+
return if dumped?([type, id])
|
116
|
+
@memo[type][id] = true
|
117
|
+
|
118
|
+
emit type, id, attributes, object
|
119
|
+
end
|
120
|
+
|
121
|
+
# Retrieve dumped object counts for all classes.
|
122
|
+
#
|
123
|
+
# Returns a Hash of { class_name => count } where count is the number of
|
124
|
+
# objects dumped with a class of class_name.
|
125
|
+
def stats
|
126
|
+
stats = {}
|
127
|
+
@memo.each { |class_name, items| stats[class_name] = items.size }
|
128
|
+
stats
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
def find_file(path)
|
133
|
+
path = "#{path}.rb" unless path =~ /\.rb$/
|
134
|
+
return path if File.exists? path
|
135
|
+
$LOAD_PATH.each do |prefix|
|
136
|
+
full_path = File.expand_path(path, prefix)
|
137
|
+
return full_path if File.exists? full_path
|
138
|
+
end
|
139
|
+
false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|