simplesync 0.0.2
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.
- data/LICENSE +16 -0
- data/README +40 -0
- data/Rakefile +78 -0
- data/doc/classes/Hash.html +153 -0
- data/doc/classes/Hash.src/M000001.html +28 -0
- data/doc/classes/SimpleSync.html +121 -0
- data/doc/classes/SimpleSync/Mappings.html +169 -0
- data/doc/classes/SimpleSync/Mappings.src/M000026.html +19 -0
- data/doc/classes/SimpleSync/Mappings.src/M000027.html +20 -0
- data/doc/classes/SimpleSync/Record.html +188 -0
- data/doc/classes/SimpleSync/Record.src/M000002.html +22 -0
- data/doc/classes/SimpleSync/Record.src/M000003.html +23 -0
- data/doc/classes/SimpleSync/Record.src/M000004.html +25 -0
- data/doc/classes/SimpleSync/RecordSet.html +235 -0
- data/doc/classes/SimpleSync/RecordSet.src/M000028.html +23 -0
- data/doc/classes/SimpleSync/RecordSet.src/M000029.html +18 -0
- data/doc/classes/SimpleSync/RecordSet.src/M000030.html +19 -0
- data/doc/classes/SimpleSync/RecordSet.src/M000031.html +20 -0
- data/doc/classes/SimpleSync/RecordSet.src/M000032.html +18 -0
- data/doc/classes/SimpleSync/Source.html +356 -0
- data/doc/classes/SimpleSync/Source.src/M000005.html +19 -0
- data/doc/classes/SimpleSync/Source.src/M000006.html +19 -0
- data/doc/classes/SimpleSync/Source.src/M000007.html +19 -0
- data/doc/classes/SimpleSync/Source.src/M000008.html +22 -0
- data/doc/classes/SimpleSync/Source.src/M000009.html +18 -0
- data/doc/classes/SimpleSync/Source.src/M000010.html +20 -0
- data/doc/classes/SimpleSync/Source.src/M000011.html +22 -0
- data/doc/classes/SimpleSync/Source.src/M000012.html +18 -0
- data/doc/classes/SimpleSync/Source.src/M000013.html +18 -0
- data/doc/classes/SimpleSync/Syncer.html +408 -0
- data/doc/classes/SimpleSync/Syncer.src/M000014.html +18 -0
- data/doc/classes/SimpleSync/Syncer.src/M000015.html +18 -0
- data/doc/classes/SimpleSync/Syncer.src/M000016.html +18 -0
- data/doc/classes/SimpleSync/Syncer.src/M000017.html +18 -0
- data/doc/classes/SimpleSync/Syncer.src/M000018.html +18 -0
- data/doc/classes/SimpleSync/Syncer.src/M000019.html +18 -0
- data/doc/classes/SimpleSync/Syncer.src/M000020.html +18 -0
- data/doc/classes/SimpleSync/Syncer.src/M000021.html +18 -0
- data/doc/classes/SimpleSync/Syncer.src/M000022.html +35 -0
- data/doc/classes/SimpleSync/Syncer.src/M000023.html +44 -0
- data/doc/classes/SimpleSync/Syncer.src/M000024.html +41 -0
- data/doc/classes/SimpleSync/Syncer.src/M000025.html +25 -0
- data/doc/created.rid +1 -0
- data/doc/files/LICENSE.html +129 -0
- data/doc/files/README.html +155 -0
- data/doc/files/lib/simple_sync_rb.html +107 -0
- data/doc/fr_class_index.html +33 -0
- data/doc/fr_file_index.html +29 -0
- data/doc/fr_method_index.html +58 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/lib/simple_sync.rb +387 -0
- metadata +115 -0
data/lib/simple_sync.rb
ADDED
@@ -0,0 +1,387 @@
|
|
1
|
+
# See README for usage.
|
2
|
+
module SimpleSync
|
3
|
+
# Syncer represents the sync setup: Add to this Sources via add_source, and use as a hub for the Mappings between them.
|
4
|
+
class Syncer
|
5
|
+
attr_reader :last_sync
|
6
|
+
|
7
|
+
# Call with an optional Time object, which will be initiated into the Syncer object as the last_sync time.
|
8
|
+
def initialize(last_sync)
|
9
|
+
@last_sync = last_sync
|
10
|
+
end
|
11
|
+
|
12
|
+
# Keeps a list of sources in a simple Array
|
13
|
+
def sources
|
14
|
+
@sources ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Initializes and holds on to the Mappings for this Syncer object.
|
18
|
+
def mappings
|
19
|
+
@mappings ||= Mappings.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Adds a Source to the Syncer's list of sources. If a block is supplied, it is sent to Source.new,
|
23
|
+
# where it is understood to be a block to be run on a newly-created record object of this source type.
|
24
|
+
def add_source(klass, identifier, finder_scope=nil, &block)
|
25
|
+
(block_given? ? (sources << Source.new(klass, identifier, mappings, finder_scope, &block)) : (sources << Source.new(klass, identifier, mappings, finder_scope)))[-1]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Lists the new_records from all sources put together. Generally only for `poking around' purposes, not used internally.
|
29
|
+
def new_records
|
30
|
+
sources.collect {|s| s.new_records.set}.flatten
|
31
|
+
end
|
32
|
+
# Lists the changed_records from all sources put together. Generally only for `poking around' purposes, not used internally.
|
33
|
+
def changed_records
|
34
|
+
sources.collect {|s| s.changed_records.set}.flatten
|
35
|
+
end
|
36
|
+
# Lists the deleted_records from all sources put together. Generally only for `poking around' purposes, not used internally.
|
37
|
+
def deleted_records
|
38
|
+
sources.collect {|s| s.deleted_records.set}.flatten
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect # :nodoc:
|
42
|
+
"#<SimpleSync::Syncer:#{object_id} sources: #{@sources.collect {|s| s.name}.join(', ')}>"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Takes a snapshot of every source, provided you've set a block to each source's new_records, changed_records, and deleted_records
|
46
|
+
# so it knows how to retrieve them.
|
47
|
+
def snapshot!
|
48
|
+
sources.each {|s| s.snapshot!}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Synchronizes only the deleted_records, from each source one at a time. With each source, first it takes a snapshot of just the
|
52
|
+
# deleted_records on just that source, then it loops through the other sources and deletes the record from each of them,
|
53
|
+
# after checking to make sure it exists before attempting to delete it. (This is rather inefficient, we should just 'delete' and
|
54
|
+
# if it doesn't exist it doesn't matter.)
|
55
|
+
def sync_deleted!
|
56
|
+
sources.each do |source|
|
57
|
+
source.deleted_records.snapshot!
|
58
|
+
puts "Deleting #{source.deleted_records.length} records that were deleted on #{source.name}..."
|
59
|
+
source.deleted_records.each do |sourcerec|
|
60
|
+
sources.except(source).each do |target|
|
61
|
+
# Some of these may already be deleted on the other sources. (Especially if we've synced within the last sync time already.)
|
62
|
+
# If the record exists on the source,
|
63
|
+
targetrec = sourcerec.find_on(target)
|
64
|
+
# delete it.
|
65
|
+
if targetrec
|
66
|
+
puts "\tDeleting #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
|
67
|
+
targetrec.delete
|
68
|
+
else
|
69
|
+
puts "\tSkipped #{sourcerec.identifier} on #{target.name}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Synchronizes only the new_records, from each source one at a time. With each source, first it takes a snapshot of just the
|
77
|
+
# new_records on just that source, then it loops through the other sources and creates the record on each of them. Since creating
|
78
|
+
# new records sometimes requires saving the associated-id back to the original record, the just-updated record is morphed back to
|
79
|
+
# see if there are any attributes that should be updated on the original record, and if so, it is also updated with the differences.
|
80
|
+
def sync_new!
|
81
|
+
sources.each do |source|
|
82
|
+
source.new_records.snapshot!
|
83
|
+
puts "Creating #{source.new_records.length} records that were created on #{source.name}..."
|
84
|
+
source.new_records.each do |sourcerec|
|
85
|
+
sources.except(source).each do |target|
|
86
|
+
# Need to weed out the ones that were already created
|
87
|
+
targetrec = sourcerec.find_on(target)
|
88
|
+
if targetrec
|
89
|
+
puts "\tSkipped #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
|
90
|
+
else
|
91
|
+
if (targetrec = sourcerec.morph_to(target)).save
|
92
|
+
puts "\tCreated #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
|
93
|
+
if sourcerec.morph_to(target).morph_to(source).to_hash != targetrec.morph_to(source).to_hash
|
94
|
+
sourcerec.data = targetrec.morph_to(source).to_hash.delete_if {|k,v| v.nil?}
|
95
|
+
if sourcerec.save
|
96
|
+
puts "\t -> Saved record relationship #{sourcerec.identifier} -> #{targetrec.identifier}."
|
97
|
+
else
|
98
|
+
puts "\t -> PROBLEM SAVING record relationship #{sourcerec.identifier} -> #{targetrec.identifier} !"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
else
|
102
|
+
puts "\tPROBLEM SAVING #{sourcerec.identifier} on #{target.name} !"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Synchronizes only the changed_records, from each source one at a time. With each source, first it takes a snapshot of just the
|
111
|
+
# changed_records on just that source, then it loops through the other sources and updates them, by grabbing the existing record,
|
112
|
+
# updating the attributes, and saving the record back.
|
113
|
+
def sync_changed!
|
114
|
+
sources.each do |source|
|
115
|
+
source.changed_records.snapshot!
|
116
|
+
puts "Updating #{source.changed_records.length} records that were updated on #{source.name}..."
|
117
|
+
source.changed_records.each do |sourcerec|
|
118
|
+
sources.except(source).each do |target|
|
119
|
+
# 1) Get the same record from the target
|
120
|
+
targetrec = sourcerec.find_on(target)
|
121
|
+
# 2) Update the attributes
|
122
|
+
target_update = sourcerec.morph_to(target).to_hash.reject {|k,v| !mappings[target => source].has_key?(k) }
|
123
|
+
target_attrs = targetrec.to_hash.reject {|k,v| !target_update.has_key?(k)}
|
124
|
+
if target_update != target_update # If any data was actually changed.
|
125
|
+
targetrec.update_data(target_update)
|
126
|
+
# 3) Save the target record
|
127
|
+
if targetrec.save
|
128
|
+
puts "\tUpdated #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
|
129
|
+
else
|
130
|
+
puts "\tPROBLEM SAVING #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"+(targetrec.respond_to?(:error) ? targetrec.error.to_s : '')
|
131
|
+
end
|
132
|
+
else
|
133
|
+
puts "\tNo changes mapped from #{sourcerec.identifier} -> #{targetrec.identifier} on #{target.name}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Synchronizes deleted_records, then new_records, then changed_records, simply by calling the sync! method for each of them in turn.
|
141
|
+
# By sync-ing deleted_records first and snapshot!-ing the new_records and changed_records when their turn comes, we can be sure
|
142
|
+
# those records deleted on one source won't show up in the new_records or changed_records on other sources.
|
143
|
+
def sync!
|
144
|
+
# To sync, we have all the tools, here comes the magic!
|
145
|
+
# 1) Sync DELETED.
|
146
|
+
sync_deleted!
|
147
|
+
# 2) Sync NEW.
|
148
|
+
sync_new!
|
149
|
+
# 3) Sync CHANGED.
|
150
|
+
sync_changed!
|
151
|
+
@last_sync = Time.now
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Mappings represents and encapsulates the mappings for a group of sync sources.
|
156
|
+
class Mappings
|
157
|
+
|
158
|
+
# The mappings hash is to be accessed not by a word-index but rather by a single key-value hash of {source => target_source}
|
159
|
+
# For example, mappings[@source_a => @source_b] should be set to a hash that represents the attribute mappings where
|
160
|
+
# @source_a's attributes are the keys and @source_b's attributes are the values.
|
161
|
+
def [](src_tgt)
|
162
|
+
source, target = src_tgt.keys[0], src_tgt.values[0]
|
163
|
+
mappings[source.name + '--' + target.name] || {}
|
164
|
+
end
|
165
|
+
|
166
|
+
# See above.
|
167
|
+
def []=(src_tgt, mapping)
|
168
|
+
source, target = src_tgt.keys[0], src_tgt.values[0]
|
169
|
+
raise TypeError, "mapping must be a hash" unless mapping.is_a?(Hash)
|
170
|
+
mappings[source.name + '--' + target.name] = mapping
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
def mappings
|
175
|
+
@mappings ||= {}
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Source represents each sync source. There may be more than two. A source must be tied to a Syncer
|
180
|
+
# (use Syncer#add_source to create sources), and any two sources must have mappings defined between them.
|
181
|
+
class Source
|
182
|
+
attr_accessor :klass, :identifier, :finder_scope, :targets, :mappings
|
183
|
+
def name # :nodoc:
|
184
|
+
"#{klass.name}#{finder_scope ? '('+finder_scope.inspect+')' : ''}"
|
185
|
+
end
|
186
|
+
|
187
|
+
# When called with a block, that block is stored for use when snapshot! is called, in order to retrieve the new_records
|
188
|
+
# from the source. Whether a block is given or not, a RecordSet object is returned that holds the current known list of
|
189
|
+
# new_records for this source.
|
190
|
+
def new_records(&block)
|
191
|
+
new_records.getter = block if block_given?
|
192
|
+
@new_records ||= RecordSet.new(self)
|
193
|
+
end
|
194
|
+
# When called with a block, that block is stored for use when snapshot! is called, in order to retrieve the changed_records
|
195
|
+
# from the source. Whether a block is given or not, a RecordSet object is returned that holds the current known list of
|
196
|
+
# changed_records for this source.
|
197
|
+
def changed_records(&block)
|
198
|
+
changed_records.getter = block if block_given?
|
199
|
+
@changed_records ||= RecordSet.new(self)
|
200
|
+
end
|
201
|
+
# When called with a block, that block is stored for use when snapshot! is called, in order to retrieve the deleted_records
|
202
|
+
# from the source. Whether a block is given or not, a RecordSet object is returned that holds the current known list of
|
203
|
+
# deleted_records for this source.
|
204
|
+
def deleted_records(&block)
|
205
|
+
deleted_records.getter = block if block_given?
|
206
|
+
@deleted_records ||= RecordSet.new(self)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Creates a new Source object. Use Syncer#add_source unless you know why you want to call this directly. If a block is
|
210
|
+
# supplied, it is understood to be a block to be run on a newly-created record object of this source type.
|
211
|
+
def initialize(klass, identifier, mappings, finder_scope, &block)
|
212
|
+
@klass = klass
|
213
|
+
@identifier = identifier
|
214
|
+
@mappings = mappings
|
215
|
+
@finder_scope = finder_scope
|
216
|
+
@initialize = block if block_given?
|
217
|
+
end
|
218
|
+
|
219
|
+
# Calls klass.get with the :finder_scope option(s) merged into the finder_options.
|
220
|
+
def get(finder_options={})
|
221
|
+
klass.get(finder_options.merge(@finder_scope || {}))
|
222
|
+
end
|
223
|
+
|
224
|
+
# Initializes a record retrieved by the SimpleSync gem. Simply runs the block supplied to the Source object creation, if one was given.
|
225
|
+
def initialize_record(record)
|
226
|
+
return record if @initialize.nil?
|
227
|
+
record.send(:eval, @initialize.to_ruby).call
|
228
|
+
record
|
229
|
+
end
|
230
|
+
|
231
|
+
def inspect # :nodoc:
|
232
|
+
"#<SimpleSync::Source #{name}: #{new_records.length} new, #{changed_records.length} changed, #{deleted_records.length} deleted >"
|
233
|
+
end
|
234
|
+
|
235
|
+
# Takes a snapshot of new_records, changed_records and deleted_records. Returns self (the Source object).
|
236
|
+
def snapshot!
|
237
|
+
raise RuntimeError, "Must send new_records, changed_records, and deleted_records each a block before this function can be run" unless @new_records_getter.is_a?(Proc) && @changed_records_getter.is_a?(Proc) && @deleted_records_getter.is_a?(Proc)
|
238
|
+
new_records.snapshot!
|
239
|
+
changed_records.snapshot!
|
240
|
+
deleted_records.snapshot!
|
241
|
+
self
|
242
|
+
end
|
243
|
+
|
244
|
+
# source.mapping_to(target) is synonymous to source.mappings[source => target].
|
245
|
+
def mapping_to(target)
|
246
|
+
mappings[self => target].merge(self.identifier.to_s => target.identifier.to_s)
|
247
|
+
end
|
248
|
+
# Returns true or false after checking to see if mappings are defined in source.mappings[source => target].
|
249
|
+
def can_map?(target)
|
250
|
+
!mappings[self => target].empty?
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# RecordSet is a class used internally to store collections of records, for new_records, changed_records, and deleted_records.
|
255
|
+
# Though not documented here, you can use several of the Array or Enumerable methods: [], <<, each, clear, length.
|
256
|
+
# RecordSets are created automatically, so the new method isn't documented either. The biggest thing it does is extend
|
257
|
+
# all record objects with the SimpleSync::Record module, which gives them a few essential methods.
|
258
|
+
# RecordSet is also where all the snapshot! methods actually do their work.
|
259
|
+
class RecordSet
|
260
|
+
attr_accessor :getter # :nodoc:
|
261
|
+
|
262
|
+
def initialize(source,getter_proc=nil) # :nodoc:
|
263
|
+
@source = source
|
264
|
+
@getter = getter_proc if getter_proc
|
265
|
+
end
|
266
|
+
|
267
|
+
# Takes a snapshot of whatever type of records this RecordSet holds, by using the block assigned to the RecordSet.
|
268
|
+
def snapshot!
|
269
|
+
if @getter.is_a?(Proc)
|
270
|
+
clear
|
271
|
+
self << @getter.call
|
272
|
+
else
|
273
|
+
nil
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Returns a simple Array of the collection - provided public mostly for debugging purposes. Typically you won't use this method
|
278
|
+
# publicly in production code.
|
279
|
+
def set
|
280
|
+
@set ||= []
|
281
|
+
end
|
282
|
+
|
283
|
+
def [](i) # :nodoc:
|
284
|
+
set[i]
|
285
|
+
end
|
286
|
+
|
287
|
+
def <<(values) # :nodoc:
|
288
|
+
[values].flatten.compact.each do |rec|
|
289
|
+
rec.extend Record
|
290
|
+
rec.source = @source
|
291
|
+
@set << rec
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Same as RecordSet#find (below), but removes the found record from this RecordSet.
|
296
|
+
def delete(record_or_identifier)
|
297
|
+
identifier = get_mapped_identifier(record_or_identifier)
|
298
|
+
!!(set.reject! {|e| e.identifier == identifier})
|
299
|
+
end
|
300
|
+
|
301
|
+
# Finds a in this RecordSet given a record from another RecordSet on another source, or returns nil on not found.
|
302
|
+
def find(record)
|
303
|
+
# ident_key = source.mapping_to(record.source)[source.identifier.to_s]
|
304
|
+
# ident = {source.identifier.to_s => record.send(ident_key)}
|
305
|
+
set.reject {|e| e.identifier != record.identifier}[0]
|
306
|
+
end
|
307
|
+
|
308
|
+
def each(&block) # :nodoc:
|
309
|
+
set.each(&block)
|
310
|
+
end
|
311
|
+
|
312
|
+
# Simply clears the collection.
|
313
|
+
def clear
|
314
|
+
set.clear
|
315
|
+
end
|
316
|
+
|
317
|
+
def length # :nodoc:
|
318
|
+
set.length
|
319
|
+
end
|
320
|
+
|
321
|
+
private
|
322
|
+
def get_mapped_identifier(record_or_identifier)
|
323
|
+
if record_or_identifier.respond_to?(:identifier)
|
324
|
+
(record_or_identifier.source == @source ? record_or_identifier : record_or_identifier.morph_to(@source)).identifier
|
325
|
+
else
|
326
|
+
record_or_identifier
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
# The Record module is used to extend record objects with a couple methods needed for sync-ing purposes.
|
332
|
+
module Record
|
333
|
+
def self.extended(base)
|
334
|
+
# Automatically adds 'identifier' method to the object if the object's class has an 'identifier'
|
335
|
+
# method that mentions the name of the identifier attribute.
|
336
|
+
def base.identifier
|
337
|
+
send(self.class.identifier.to_sym)
|
338
|
+
end if base.class.respond_to?(:identifier) && !base.respond_to?(:identifier)
|
339
|
+
end
|
340
|
+
attr_accessor :source
|
341
|
+
def morph_to(target)
|
342
|
+
raise ArgumentError, "can only morph by known mappings." unless source.can_map?(target)
|
343
|
+
new_attrs = to_hash.merge(self.class.identifier => identifier).transform_keys_by_mapping(source.mapping_to(target))
|
344
|
+
rec = target.initialize_record(target.klass.new(new_attrs))
|
345
|
+
rec.extend Record
|
346
|
+
rec.source = target
|
347
|
+
rec
|
348
|
+
end
|
349
|
+
# slave from master: {self => rec(external_id):map}
|
350
|
+
# slave from deleted: {self => rec(external_id):map}
|
351
|
+
# master from slave: {external_id => rec(self):map}
|
352
|
+
# master from deleted: {external_id => rec(self):map}
|
353
|
+
def find_on(target)
|
354
|
+
ident_key = target.mapping_to(source)[target.identifier.to_s]
|
355
|
+
ident = {target.identifier.to_s => send(ident_key)}
|
356
|
+
# puts "Looking for record #{source.identifier.to_s} => #{identifier} as record #{ident_key} => #{ident.inspect}"
|
357
|
+
found = ident.values[0].nil? ? nil : target.get(ident)
|
358
|
+
# puts "Found: #{found.inspect}"
|
359
|
+
found.extend Record
|
360
|
+
found.source = target
|
361
|
+
found
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# A little enhancement that we need...
|
367
|
+
class Hash
|
368
|
+
# Given a mapping (hash), this will transform the keys from the keys to the values in the mapping.
|
369
|
+
# For example:
|
370
|
+
# mapping = {:cat => :Cat, :dog => :Canine}
|
371
|
+
# hash = {:cat => 'Felix', :dog => 'Toby'}
|
372
|
+
# hash.transform_keys_by_mapping(mapping)
|
373
|
+
# => {:Cat => 'Felix', :Canine => 'Toby'}
|
374
|
+
def transform_keys_by_mapping(mapping)
|
375
|
+
ddup = {}
|
376
|
+
self.each do |k,v|
|
377
|
+
if mapping[k]
|
378
|
+
if mapping[k].is_a?(Proc)
|
379
|
+
ddup = mapping[k].call(ddup,v)
|
380
|
+
else
|
381
|
+
ddup[mapping[k]] = v
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
ddup
|
386
|
+
end
|
387
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simplesync
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Parker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-03-20 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: "QuickSync: an interface to synchronization logic."
|
17
|
+
email: gems@behindlogic.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- LICENSE
|
25
|
+
files:
|
26
|
+
- lib/simple_sync.rb
|
27
|
+
- LICENSE
|
28
|
+
- Rakefile
|
29
|
+
- README
|
30
|
+
- doc/classes
|
31
|
+
- doc/classes/Hash.html
|
32
|
+
- doc/classes/Hash.src
|
33
|
+
- doc/classes/Hash.src/M000001.html
|
34
|
+
- doc/classes/SimpleSync
|
35
|
+
- doc/classes/SimpleSync/Mappings.html
|
36
|
+
- doc/classes/SimpleSync/Mappings.src
|
37
|
+
- doc/classes/SimpleSync/Mappings.src/M000026.html
|
38
|
+
- doc/classes/SimpleSync/Mappings.src/M000027.html
|
39
|
+
- doc/classes/SimpleSync/Record.html
|
40
|
+
- doc/classes/SimpleSync/Record.src
|
41
|
+
- doc/classes/SimpleSync/Record.src/M000002.html
|
42
|
+
- doc/classes/SimpleSync/Record.src/M000003.html
|
43
|
+
- doc/classes/SimpleSync/Record.src/M000004.html
|
44
|
+
- doc/classes/SimpleSync/RecordSet.html
|
45
|
+
- doc/classes/SimpleSync/RecordSet.src
|
46
|
+
- doc/classes/SimpleSync/RecordSet.src/M000028.html
|
47
|
+
- doc/classes/SimpleSync/RecordSet.src/M000029.html
|
48
|
+
- doc/classes/SimpleSync/RecordSet.src/M000030.html
|
49
|
+
- doc/classes/SimpleSync/RecordSet.src/M000031.html
|
50
|
+
- doc/classes/SimpleSync/RecordSet.src/M000032.html
|
51
|
+
- doc/classes/SimpleSync/Source.html
|
52
|
+
- doc/classes/SimpleSync/Source.src
|
53
|
+
- doc/classes/SimpleSync/Source.src/M000005.html
|
54
|
+
- doc/classes/SimpleSync/Source.src/M000006.html
|
55
|
+
- doc/classes/SimpleSync/Source.src/M000007.html
|
56
|
+
- doc/classes/SimpleSync/Source.src/M000008.html
|
57
|
+
- doc/classes/SimpleSync/Source.src/M000009.html
|
58
|
+
- doc/classes/SimpleSync/Source.src/M000010.html
|
59
|
+
- doc/classes/SimpleSync/Source.src/M000011.html
|
60
|
+
- doc/classes/SimpleSync/Source.src/M000012.html
|
61
|
+
- doc/classes/SimpleSync/Source.src/M000013.html
|
62
|
+
- doc/classes/SimpleSync/Syncer.html
|
63
|
+
- doc/classes/SimpleSync/Syncer.src
|
64
|
+
- doc/classes/SimpleSync/Syncer.src/M000014.html
|
65
|
+
- doc/classes/SimpleSync/Syncer.src/M000015.html
|
66
|
+
- doc/classes/SimpleSync/Syncer.src/M000016.html
|
67
|
+
- doc/classes/SimpleSync/Syncer.src/M000017.html
|
68
|
+
- doc/classes/SimpleSync/Syncer.src/M000018.html
|
69
|
+
- doc/classes/SimpleSync/Syncer.src/M000019.html
|
70
|
+
- doc/classes/SimpleSync/Syncer.src/M000020.html
|
71
|
+
- doc/classes/SimpleSync/Syncer.src/M000021.html
|
72
|
+
- doc/classes/SimpleSync/Syncer.src/M000022.html
|
73
|
+
- doc/classes/SimpleSync/Syncer.src/M000023.html
|
74
|
+
- doc/classes/SimpleSync/Syncer.src/M000024.html
|
75
|
+
- doc/classes/SimpleSync/Syncer.src/M000025.html
|
76
|
+
- doc/classes/SimpleSync.html
|
77
|
+
- doc/created.rid
|
78
|
+
- doc/files
|
79
|
+
- doc/files/lib
|
80
|
+
- doc/files/lib/simple_sync_rb.html
|
81
|
+
- doc/files/LICENSE.html
|
82
|
+
- doc/files/README.html
|
83
|
+
- doc/fr_class_index.html
|
84
|
+
- doc/fr_file_index.html
|
85
|
+
- doc/fr_method_index.html
|
86
|
+
- doc/index.html
|
87
|
+
- doc/rdoc-style.css
|
88
|
+
has_rdoc: true
|
89
|
+
homepage: http://simplesync.rubyforge.org
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: "0"
|
100
|
+
version:
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: "0"
|
106
|
+
version:
|
107
|
+
requirements: []
|
108
|
+
|
109
|
+
rubyforge_project: simplesync
|
110
|
+
rubygems_version: 1.0.1
|
111
|
+
signing_key:
|
112
|
+
specification_version: 2
|
113
|
+
summary: "QuickSync: an interface to synchronization logic."
|
114
|
+
test_files: []
|
115
|
+
|