rubysync 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.
Files changed (161) hide show
  1. data/bin/rubysync +312 -0
  2. data/examples/ar_client_webapp/README +182 -0
  3. data/examples/ar_client_webapp/Rakefile +10 -0
  4. data/examples/ar_client_webapp/app/controllers/application.rb +7 -0
  5. data/examples/ar_client_webapp/app/controllers/user_controller.rb +5 -0
  6. data/examples/ar_client_webapp/app/helpers/application_helper.rb +3 -0
  7. data/examples/ar_client_webapp/app/helpers/user_helper.rb +2 -0
  8. data/examples/ar_client_webapp/app/models/user.rb +2 -0
  9. data/examples/ar_client_webapp/config/boot.rb +45 -0
  10. data/examples/ar_client_webapp/config/database.yml +36 -0
  11. data/examples/ar_client_webapp/config/environment.rb +60 -0
  12. data/examples/ar_client_webapp/config/environments/development.rb +21 -0
  13. data/examples/ar_client_webapp/config/environments/production.rb +18 -0
  14. data/examples/ar_client_webapp/config/environments/test.rb +19 -0
  15. data/examples/ar_client_webapp/config/routes.rb +23 -0
  16. data/examples/ar_client_webapp/db/migrate/001_create_users.rb +13 -0
  17. data/examples/ar_client_webapp/db/schema.rb +13 -0
  18. data/examples/ar_client_webapp/doc/README_FOR_APP +2 -0
  19. data/examples/ar_client_webapp/public/404.html +30 -0
  20. data/examples/ar_client_webapp/public/500.html +30 -0
  21. data/examples/ar_client_webapp/public/dispatch.cgi +10 -0
  22. data/examples/ar_client_webapp/public/dispatch.fcgi +24 -0
  23. data/examples/ar_client_webapp/public/dispatch.rb +10 -0
  24. data/examples/ar_client_webapp/public/favicon.ico +0 -0
  25. data/examples/ar_client_webapp/public/images/rails.png +0 -0
  26. data/examples/ar_client_webapp/public/index.html +277 -0
  27. data/examples/ar_client_webapp/public/javascripts/application.js +2 -0
  28. data/examples/ar_client_webapp/public/javascripts/controls.js +833 -0
  29. data/examples/ar_client_webapp/public/javascripts/dragdrop.js +942 -0
  30. data/examples/ar_client_webapp/public/javascripts/effects.js +1088 -0
  31. data/examples/ar_client_webapp/public/javascripts/prototype.js +2515 -0
  32. data/examples/ar_client_webapp/public/robots.txt +1 -0
  33. data/examples/ar_client_webapp/script/about +3 -0
  34. data/examples/ar_client_webapp/script/breakpointer +3 -0
  35. data/examples/ar_client_webapp/script/console +3 -0
  36. data/examples/ar_client_webapp/script/destroy +3 -0
  37. data/examples/ar_client_webapp/script/generate +3 -0
  38. data/examples/ar_client_webapp/script/performance/benchmarker +3 -0
  39. data/examples/ar_client_webapp/script/performance/profiler +3 -0
  40. data/examples/ar_client_webapp/script/plugin +3 -0
  41. data/examples/ar_client_webapp/script/process/inspector +3 -0
  42. data/examples/ar_client_webapp/script/process/reaper +3 -0
  43. data/examples/ar_client_webapp/script/process/spawner +3 -0
  44. data/examples/ar_client_webapp/script/runner +3 -0
  45. data/examples/ar_client_webapp/script/server +3 -0
  46. data/examples/ar_client_webapp/test/fixtures/users.yml +5 -0
  47. data/examples/ar_client_webapp/test/functional/user_controller_test.rb +18 -0
  48. data/examples/ar_client_webapp/test/test_helper.rb +28 -0
  49. data/examples/ar_client_webapp/test/unit/user_test.rb +10 -0
  50. data/examples/ar_webapp/README +1 -0
  51. data/examples/ar_webapp/Rakefile +10 -0
  52. data/examples/ar_webapp/app/controllers/application.rb +7 -0
  53. data/examples/ar_webapp/app/controllers/hobbies_controller.rb +10 -0
  54. data/examples/ar_webapp/app/controllers/interests_controller.rb +9 -0
  55. data/examples/ar_webapp/app/controllers/people_controller.rb +14 -0
  56. data/examples/ar_webapp/app/controllers/ruby_sync_associations_controller.rb +10 -0
  57. data/examples/ar_webapp/app/helpers/application_helper.rb +3 -0
  58. data/examples/ar_webapp/app/models/hobby.rb +5 -0
  59. data/examples/ar_webapp/app/models/interest.rb +6 -0
  60. data/examples/ar_webapp/app/models/person.rb +9 -0
  61. data/examples/ar_webapp/app/models/ruby_sync_association.rb +5 -0
  62. data/examples/ar_webapp/app/models/ruby_sync_event.rb +9 -0
  63. data/examples/ar_webapp/app/models/ruby_sync_observer.rb +28 -0
  64. data/examples/ar_webapp/app/models/ruby_sync_operation.rb +20 -0
  65. data/examples/ar_webapp/app/models/ruby_sync_state.rb +2 -0
  66. data/examples/ar_webapp/app/models/ruby_sync_value.rb +7 -0
  67. data/examples/ar_webapp/app/views/layouts/application.rhtml +19 -0
  68. data/examples/ar_webapp/app/views/people/show.rhtml +18 -0
  69. data/examples/ar_webapp/config/boot.rb +45 -0
  70. data/examples/ar_webapp/config/database.yml +36 -0
  71. data/examples/ar_webapp/config/environment.rb +61 -0
  72. data/examples/ar_webapp/config/environments/development.rb +21 -0
  73. data/examples/ar_webapp/config/environments/production.rb +18 -0
  74. data/examples/ar_webapp/config/environments/test.rb +19 -0
  75. data/examples/ar_webapp/config/routes.rb +23 -0
  76. data/examples/ar_webapp/db/migrate/001_create_people.rb +12 -0
  77. data/examples/ar_webapp/db/migrate/002_create_interests.rb +12 -0
  78. data/examples/ar_webapp/db/migrate/003_create_hobbies.rb +11 -0
  79. data/examples/ar_webapp/db/migrate/004_create_ruby_sync_associations.rb +18 -0
  80. data/examples/ar_webapp/db/migrate/005_create_ruby_sync_events.rb +16 -0
  81. data/examples/ar_webapp/db/migrate/006_create_ruby_sync_operations.rb +15 -0
  82. data/examples/ar_webapp/db/migrate/007_create_ruby_sync_values.rb +12 -0
  83. data/examples/ar_webapp/db/migrate/008_ruby_sync_tracking.rb +16 -0
  84. data/examples/ar_webapp/db/migrate/009_create_ruby_sync_states.rb +10 -0
  85. data/examples/ar_webapp/db/schema.rb +56 -0
  86. data/examples/ar_webapp/doc/README_FOR_APP +2 -0
  87. data/examples/ar_webapp/public/404.html +30 -0
  88. data/examples/ar_webapp/public/500.html +30 -0
  89. data/examples/ar_webapp/public/dispatch.cgi +10 -0
  90. data/examples/ar_webapp/public/dispatch.fcgi +24 -0
  91. data/examples/ar_webapp/public/dispatch.rb +10 -0
  92. data/examples/ar_webapp/public/favicon.ico +0 -0
  93. data/examples/ar_webapp/public/images/rails.png +0 -0
  94. data/examples/ar_webapp/public/index.html +277 -0
  95. data/examples/ar_webapp/public/javascripts/application.js +2 -0
  96. data/examples/ar_webapp/public/javascripts/controls.js +833 -0
  97. data/examples/ar_webapp/public/javascripts/dragdrop.js +942 -0
  98. data/examples/ar_webapp/public/javascripts/effects.js +1088 -0
  99. data/examples/ar_webapp/public/javascripts/prototype.js +2515 -0
  100. data/examples/ar_webapp/public/robots.txt +1 -0
  101. data/examples/ar_webapp/script/about +3 -0
  102. data/examples/ar_webapp/script/breakpointer +3 -0
  103. data/examples/ar_webapp/script/console +3 -0
  104. data/examples/ar_webapp/script/destroy +3 -0
  105. data/examples/ar_webapp/script/generate +3 -0
  106. data/examples/ar_webapp/script/performance/benchmarker +3 -0
  107. data/examples/ar_webapp/script/performance/profiler +3 -0
  108. data/examples/ar_webapp/script/plugin +3 -0
  109. data/examples/ar_webapp/script/process/inspector +3 -0
  110. data/examples/ar_webapp/script/process/reaper +3 -0
  111. data/examples/ar_webapp/script/process/spawner +3 -0
  112. data/examples/ar_webapp/script/runner +3 -0
  113. data/examples/ar_webapp/script/server +3 -0
  114. data/examples/ar_webapp/test/fixtures/association_keys.yml +5 -0
  115. data/examples/ar_webapp/test/fixtures/hobbies.yml +5 -0
  116. data/examples/ar_webapp/test/fixtures/interests.yml +5 -0
  117. data/examples/ar_webapp/test/fixtures/people.yml +9 -0
  118. data/examples/ar_webapp/test/fixtures/ruby_sync_events.yml +5 -0
  119. data/examples/ar_webapp/test/fixtures/ruby_sync_operations.yml +5 -0
  120. data/examples/ar_webapp/test/fixtures/ruby_sync_states.yml +5 -0
  121. data/examples/ar_webapp/test/fixtures/ruby_sync_values.yml +5 -0
  122. data/examples/ar_webapp/test/test_helper.rb +28 -0
  123. data/examples/ar_webapp/test/unit/association_key_test.rb +8 -0
  124. data/examples/ar_webapp/test/unit/hobby_test.rb +10 -0
  125. data/examples/ar_webapp/test/unit/interest_test.rb +10 -0
  126. data/examples/ar_webapp/test/unit/person_test.rb +10 -0
  127. data/examples/ar_webapp/test/unit/ruby_sync_event_test.rb +12 -0
  128. data/examples/ar_webapp/test/unit/ruby_sync_observer_test.rb +57 -0
  129. data/examples/ar_webapp/test/unit/ruby_sync_operation_test.rb +10 -0
  130. data/examples/ar_webapp/test/unit/ruby_sync_state_test.rb +10 -0
  131. data/examples/ar_webapp/test/unit/ruby_sync_value_test.rb +10 -0
  132. data/examples/ims2/connectors/hr_db_connector.rb +8 -0
  133. data/examples/ims2/connectors/my_csv_connector.rb +12 -0
  134. data/examples/ims2/pipelines/hr_import_pipeline.rb +33 -0
  135. data/examples/my_ims/connectors/corp_directory_connector.rb +12 -0
  136. data/examples/my_ims/connectors/finance_connector.rb +7 -0
  137. data/examples/my_ims/connectors/hr_db_connector.rb +7 -0
  138. data/examples/my_ims/pipelines/finance_pipeline.rb +33 -0
  139. data/examples/my_ims/pipelines/hr_import_pipeline.rb +29 -0
  140. data/lib/ruby_sync/connectors/active_record_connector.rb +198 -0
  141. data/lib/ruby_sync/connectors/base_connector.rb +317 -0
  142. data/lib/ruby_sync/connectors/connector_event_processing.rb +78 -0
  143. data/lib/ruby_sync/connectors/csv_file_connector.rb +95 -0
  144. data/lib/ruby_sync/connectors/file_connector.rb +74 -0
  145. data/lib/ruby_sync/connectors/ldap_connector.rb +192 -0
  146. data/lib/ruby_sync/connectors/memory_connector.rb +185 -0
  147. data/lib/ruby_sync/event.rb +220 -0
  148. data/lib/ruby_sync/operation.rb +82 -0
  149. data/lib/ruby_sync/pipelines/base_pipeline.rb +360 -0
  150. data/lib/ruby_sync/util/metaid.rb +24 -0
  151. data/lib/ruby_sync/util/utilities.rb +115 -0
  152. data/lib/ruby_sync.rb +81 -0
  153. data/test/hashlike_tests.rb +111 -0
  154. data/test/ruby_sync_test.rb +48 -0
  155. data/test/test_active_record_vault.rb +113 -0
  156. data/test/test_csv_file_connector.rb +75 -0
  157. data/test/test_event.rb +40 -0
  158. data/test/test_ldap_connector.rb +89 -0
  159. data/test/test_memory_connectors.rb +40 -0
  160. data/test/ts_rubysync.rb +20 -0
  161. metadata +316 -0
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2007 Ritchie Young. All rights reserved.
4
+ #
5
+ # This file is part of RubySync.
6
+ #
7
+ # RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
9
+ #
10
+ # RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
11
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
14
+ # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15
+
16
+ lib_path = File.dirname(__FILE__) + '/../..'
17
+ $:.unshift lib_path unless $:.include?(lib_path) || $:.include?(File.expand_path(lib_path))
18
+
19
+ require 'active_support'
20
+ require 'ruby_sync/util/metaid'
21
+ require 'yaml'
22
+
23
+
24
+ module RubySync
25
+ module Pipelines
26
+
27
+ # This pipeline is the base for those that synchronize content bi-directionally between
28
+ # two datastores. These are commonly used for synchronizing identity information between
29
+ # directories.
30
+ #
31
+ # One of the data-stores is called the identity-vault. This is generally the central repository for
32
+ # identity information (typically, an LDAP server or relational database).
33
+ # We'll call the other data-store the client for want of a better term. This is could be anything
34
+ # that an EndPoint has been written for (an LDAP server, a text file, an application, etc).
35
+ #
36
+ # We refer to the flow of events from the client to the identity-vault as incoming and those from
37
+ # the identity vault to the client as out-going. Methods in this class prefixed with 'in_' or 'out_'
38
+ # work on the incoming or outgoing flows respectively.
39
+ class BasePipeline
40
+
41
+ include RubySync::Utilities
42
+
43
+ def initialize
44
+ end
45
+
46
+ def name
47
+ self.class.name
48
+ end
49
+
50
+ def self.client(connector_name, options={})
51
+ class_name = RubySync::Connectors::BaseConnector.class_name_for(connector_name)
52
+ options[:name] ||= "#{self.name}(client)"
53
+ options[:is_vault] = false
54
+ class_def 'client' do
55
+ @client ||= eval("::#{class_name}").new(options)
56
+ end
57
+ end
58
+
59
+ def self.vault(connector_name, options={})
60
+ class_name = RubySync::Connectors::BaseConnector.class_name_for(connector_name)
61
+ options[:name] ||= "#{self.name}(vault)"
62
+ options[:is_vault] = true
63
+ class_def 'vault' do
64
+ @vault ||= eval("::" + class_name).new(options)
65
+ end
66
+ end
67
+
68
+ def self.map_client_to_vault mappings
69
+ remove_method :client_to_vault_map if method_defined? :client_to_vault_map
70
+ class_def 'client_to_vault_map' do
71
+ unless @client_to_vault_map
72
+ @client_to_vault_map = {}
73
+ mappings.each {|k,v| @client_to_vault_map[k.to_s] = v.to_s}
74
+ end
75
+ @client_to_vault_map
76
+ end
77
+ unless method_defined? :vault_to_client_map
78
+ class_def 'vault_to_client_map' do
79
+ @vault_to_client_map ||= client_to_vault_map.invert
80
+ end
81
+ end
82
+ end
83
+
84
+ def self.map_vault_to_client mappings
85
+ remove_method :vault_to_client_map if method_defined? :vault_to_client_map
86
+ class_def 'vault_to_client_map' do
87
+ unless @vault_to_client_map
88
+ @vault_to_client_map = {}
89
+ mappings.each {|k,v| @vault_to_client_map[k.to_s] = v.to_s}
90
+ end
91
+ @vault_to_client_map
92
+ end
93
+ unless method_defined? :client_to_vault_map
94
+ class_def 'client_to_vault_map' do
95
+ @client_to_vault_map ||= vault_to_client_map.invert
96
+ end
97
+ end
98
+ end
99
+
100
+ def self.out_transform &blk
101
+ define_method :out_transform do |event|
102
+ event.meta_def :transform, &blk
103
+ event.transform
104
+ end
105
+ end
106
+
107
+
108
+
109
+ # Called by the identity-vault connector in the 'out' thread to process events generated
110
+ # by the identity vault.
111
+ def out_handler(event)
112
+
113
+ event.retrieve_association(association_context)
114
+ event.convert_to_modify if event.associated? and event.type == :add
115
+
116
+ hint = " (#{vault.name} => #{client.name})"
117
+ log.info "Processing out-going #{event.type} event #{hint}"
118
+ log.info YAML.dump(event)
119
+ return unless out_event_filter event
120
+
121
+ # Remove unwanted attributes
122
+ perform_transform :out_filter, event
123
+
124
+ unless event.associated?
125
+ if [:delete, :remove_association].include? event.type
126
+ log.info "#{name}: No action for #{event.type} of unassociated entry"
127
+ log.info YAML.dump(event)
128
+ return
129
+ end
130
+ end
131
+
132
+ if event.type == :modify
133
+ unless event.associated? and client.has_entry_for_key?(event.association.key)
134
+ event.convert_to_add
135
+ end
136
+ end
137
+
138
+ if event.type == :add
139
+ match = out_match(event)
140
+ log.info "Attempting to match"
141
+ if match # exactly one event record on the client matched
142
+ log.info "Match found, merging"
143
+ event.merge(match)
144
+ association = Association.new(self.association_context, match.src_path)
145
+ vault.associate asssociation, event.source_path
146
+ return
147
+ end
148
+ log.info "No match found, creating"
149
+ return unless out_create(event)
150
+ perform_transform :out_place, event
151
+ end
152
+
153
+ perform_transform :out_map_schema, event
154
+ perform_transform :out_transform, event
155
+ association_key = nil
156
+ with_rescue("#{client.name}: Processing command") do
157
+ association_key = client.process(event)
158
+ end
159
+ if association_key
160
+ association = Association.new(association_context, association_key)
161
+ with_rescue("#{client.name}: Storing association #{association} in vault") do
162
+ vault.associate(association, event.source_path)
163
+ end
164
+ end
165
+ end
166
+
167
+ # Override to map schema from vault namespace to client namespace
168
+ # def out_map_schema event
169
+ # end
170
+
171
+ # Override to implement some kind of matching
172
+ def out_match event
173
+ log.debug "Default matching rule - source path exists on client?"
174
+ client.respond_to?(:'[]') and client[event.source_path]
175
+ false
176
+ end
177
+
178
+ # Override to restrict creation on the client
179
+ def out_create event
180
+ log.debug "Create allowed through default rule"
181
+ true
182
+ end
183
+
184
+ # Override to restrict creation on the vault
185
+ def in_create event
186
+ log.debug "Create allowed through default rule"
187
+ true
188
+ end
189
+
190
+ # Override to modify the target path for creation on the client
191
+ def out_place(event)
192
+ log.debug "Default placement rule target_path = source_path"
193
+ event.target_path = event.source_path
194
+ end
195
+
196
+ # Override to modify the target path for creation in the vault
197
+ def in_place(event)
198
+ log.debug "Default placement rule target_path = source_path"
199
+ event.target_path = event.source_path
200
+ end
201
+
202
+ def perform_transform name, event, hint=""
203
+ call_if_exists name, event, hint
204
+ event.commit_changes
205
+ log_progress name, event, hint
206
+ end
207
+
208
+ # Transform the out-going event before the client receives it
209
+ # def out_transform(event)
210
+ # end
211
+
212
+ # Execute the pipeline once then return.
213
+ # TODO Consider making this run in and out simultaneously
214
+ def run_once
215
+ log.info "Running #{name} pipeline once"
216
+ run_in_once
217
+ run_out_once
218
+ end
219
+
220
+ # Execute the in pipe once and then return
221
+ def run_in_once
222
+ log.info "Running #{name} 'in' pipeline once"
223
+ client.once_only = true
224
+ client.start {|event| in_handler(event)}
225
+ end
226
+
227
+ # Execute the out pipe once and then return
228
+ def run_out_once
229
+ log.info "Running #{name} 'out' pipeline once"
230
+ vault.once_only = true
231
+ vault.start {|event| out_handler(event)}
232
+ end
233
+
234
+
235
+ # Override to process the event generated by the publisher before any other processing is done.
236
+ # Return false to veto the event.
237
+ def out_event_filter(event); true; end
238
+
239
+ # Called by the 'in' connector in the 'in' thread to process events generated by the client.
240
+ def in_handler(event)
241
+ event.retrieve_association(association_context)
242
+
243
+ hint = " (#{client.name} => #{vault.name})"
244
+ log.info "Processing incoming #{event.type} event"+hint
245
+ log.info YAML.dump(event)
246
+ perform_transform :in_map_schema, event, hint
247
+ perform_transform :in_transform, event, hint
248
+ perform_transform :in_filter, event, hint
249
+
250
+ # The client can't really know whether its an add or a modify because it doesn't store
251
+ # the association.
252
+ if event.type == :modify
253
+ event.convert_to_add unless event.associated? and vault.find_associated(event.association)
254
+ elsif event.type == :add and event.associated? and vault.find_associated(event.association)
255
+ event.convert_to_modify
256
+ end
257
+
258
+ if event.type == :add
259
+ match = in_match(event) # exactly one event record in the vault matched
260
+ if match
261
+ event.merge(match)
262
+ return
263
+ end
264
+
265
+ if in_create(event)
266
+ perform_transform :in_place, event, hint
267
+ else
268
+ return
269
+ end
270
+ end
271
+
272
+ with_rescue("#{vault.name}: Processing command") {vault.process(event)}
273
+
274
+ end
275
+
276
+ # The context for all association keys used by this pipeline.
277
+ # By default, defer to the client
278
+ def association_context
279
+ @client.association_context
280
+ end
281
+
282
+ def in_match event
283
+ log.debug "Default match rule - source path exists in vault"
284
+ vault.respond_to?(:'[]') and vault[event.source_path]
285
+ end
286
+
287
+ # If client_to_vault_map is defined (usually by map_client_to_vault)
288
+ # then fix up the contents of the payload to refer to the fields by
289
+ # the names in the vault namespace
290
+ def in_map_schema event
291
+ map_schema event, client_to_vault_map if respond_to? :client_to_vault_map
292
+ end
293
+
294
+ def out_map_schema event
295
+ map_schema event, vault_to_client_map if respond_to? :vault_to_client_map
296
+ end
297
+
298
+ def map_schema event, map
299
+ return unless map and event.payload
300
+ event.payload.each do |op|
301
+ op.subject = map[op.subject] || op.subject if op.subject
302
+ end
303
+ end
304
+
305
+
306
+
307
+
308
+ # Override to perform whatever transformation on the event is required
309
+ #def in_transform(event); event; end
310
+
311
+ # Convert fields in the incoming event to those used by the identity vault
312
+ #def in_map_schema(event); end
313
+
314
+ # Specify which fields will be allowed through the incoming filter
315
+ # If nil (the default), all fields are allowed.
316
+ def self.allow_in *fields
317
+ class_def 'allowed_in' do
318
+ fields.map {|f| f.to_s}
319
+ end
320
+ end
321
+
322
+ # default allowed_in in case allow_in doesn't get called
323
+ def allowed_in; nil; end
324
+
325
+ # Default method for allowed_in. Override by calling allow_in
326
+ #def allowed_in; false; end
327
+ def in_filter(event)
328
+ if allowed_in
329
+ event.drop_all_but_changes_to allowed_in
330
+ else
331
+ event
332
+ end
333
+ end
334
+
335
+
336
+ # Specify which fields will be allowed through the incoming filter
337
+ # If nil (the default), all fields are allowed.
338
+ def self.allow_out *fields
339
+ class_def 'allowed_out' do
340
+ fields.map {|f| f.to_s }
341
+ end
342
+ end
343
+
344
+ # default allowed_out in case allow_out doesn't get called
345
+ def allowed_out; nil; end
346
+
347
+ # Default method for allowed_out. Override by calling allow_in
348
+ #def allowed_out; false; end
349
+ def out_filter(event)
350
+ if allowed_out
351
+ event.drop_all_but_changes_to allowed_out
352
+ else
353
+ event
354
+ end
355
+ end
356
+
357
+
358
+ end
359
+ end
360
+ end
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This file is extracted from an article published by "why the lucky stiff" at
4
+ # http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
5
+ #
6
+ # Hopefully Why will publish a meta-programming gem later and RubySync can
7
+ # just make that a dependency.
8
+ #
9
+
10
+ class Object
11
+ # The hidden singleton lurks behind everyone
12
+ def metaclass; class << self; self; end; end
13
+ def meta_eval(&blk); metaclass.instance_eval(&blk); end
14
+
15
+ # Adds methods to a metaclass
16
+ def meta_def name, &blk
17
+ meta_eval { define_method name, &blk }
18
+ end
19
+
20
+ # Defines an instance method within a class
21
+ def class_def name, &blk
22
+ class_eval { define_method name, &blk }
23
+ end
24
+ end
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2007 Ritchie Young. All rights reserved.
4
+ #
5
+ # This file is part of RubySync.
6
+ #
7
+ # RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
9
+ #
10
+ # RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
11
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
14
+ # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15
+
16
+
17
+ require 'fileutils'
18
+ require 'irb'
19
+
20
+
21
+
22
+ # Generally useful methods
23
+ module RubySync
24
+ module Utilities
25
+
26
+
27
+ # Perform an action and rescue any exceptions thrown, display the exception with the specified text
28
+ def with_rescue text
29
+ begin
30
+ yield
31
+ rescue Exception => exception
32
+ log.warn "#{text}: #{exception.message}"
33
+ log.debug exception.backtrace.join("\n")
34
+ end
35
+ end
36
+
37
+ def call_if_exists(method, event, hint="")
38
+ result = nil
39
+ if respond_to? method
40
+ with_rescue("#{method} #{hint}") {result = send method, event}
41
+ else
42
+ log.debug "No #{method}(event) method, continuing #{hint}"
43
+ end
44
+ return result
45
+ end
46
+
47
+ def log_progress last_action, event, hint=""
48
+ log.info "Result of #{last_action}: #{hint}\n" + YAML.dump(event)
49
+ end
50
+
51
+
52
+ # Ensure that a given path exists as a directory
53
+ def ensure_dir_exists path
54
+ raise Exception.new("Can't create nil directory") unless path
55
+ if File.exist? path
56
+ unless File.directory? path
57
+ raise Exception.new("'#{path}' exists but is not a directory")
58
+ end
59
+ else
60
+ log.info "Creating directory '#{path}'"
61
+ FileUtils.mkpath path
62
+ end
63
+ end
64
+
65
+ def pipeline_called name
66
+ something_called name, "pipeline"
67
+ end
68
+
69
+
70
+ def connector_called name
71
+ something_called name, "connector"
72
+ end
73
+
74
+ # Locates and returns an instance of a class for
75
+ # the given name.
76
+ def something_called name, extension
77
+ filename = "#{name.to_s}_#{extension}"
78
+ $".include?(filename) or require filename or return nil
79
+ eval(filename.camelize).new
80
+ end
81
+
82
+ # Ensure that path is in the search path
83
+ # prepends it if it's not
84
+ def include_in_search_path path
85
+ path = File.expand_path(path)
86
+ $:.unshift path unless $:.include?(path)
87
+ end
88
+
89
+ # Return the base_path
90
+ def base_path
91
+ @base_path = find_base_path unless defined? @base_path
92
+ @base_path
93
+ end
94
+
95
+ # Locate a configuration directory by checking the current directory and
96
+ # all of it's ancestors until it finds one that looks like a rubysync configuration
97
+ # directory.
98
+ # Returns false if no suitable directory was found
99
+ def find_base_path
100
+ base_path = File.expand_path(".")
101
+ last = nil
102
+ # Keep going up until we start repeating ourselves
103
+ while File.directory?(base_path) && base_path != last && base_path != "/"
104
+ return base_path if File.directory?("#{base_path}/pipelines") &&
105
+ File.directory?("#{base_path}/connectors")
106
+ last = base_path
107
+ base_path = File.expand_path("#{base_path}/..")
108
+ end
109
+ return false
110
+ end
111
+
112
+
113
+
114
+ end
115
+ end
data/lib/ruby_sync.rb ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2007 Ritchie Young. All rights reserved.
4
+ #
5
+ # This file is part of RubySync.
6
+ #
7
+ # RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
9
+ #
10
+ # RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
11
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
14
+ # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15
+
16
+ lib_path = File.dirname(__FILE__)
17
+ $:.unshift lib_path unless $:.include?(lib_path) || $:.include?(File.expand_path(lib_path))
18
+
19
+ require 'rubygems'
20
+ require 'active_support'
21
+ require 'ruby_sync/util/utilities'
22
+ #require 'ruby_sync/connectors/base_connector'
23
+ #require 'ruby_sync/pipelines/base_pipeline'
24
+ require 'ruby_sync/operation'
25
+ require 'ruby_sync/event'
26
+
27
+ # Make the log method globally available
28
+ class Object
29
+
30
+ def log
31
+ unless defined? @@log
32
+ @@log = Logger.new(STDOUT)
33
+ #@@log.level = Logger::DEBUG
34
+ @@log.datetime_format = "%H:%M:%S"
35
+ end
36
+ @@log
37
+ end
38
+ end
39
+
40
+ class Configuration
41
+
42
+ include RubySync::Utilities
43
+
44
+ def initialize
45
+ include_in_search_path "#{base_path}/pipelines"
46
+ include_in_search_path "#{base_path}/connectors"
47
+
48
+ lib_path = File.dirname(__FILE__)
49
+ require_all_in_dir "#{lib_path}/ruby_sync/connectors", "*_connector.rb"
50
+ require_all_in_dir "#{lib_path}/ruby_sync/pipelines", "*_pipeline.rb"
51
+ end
52
+
53
+ # We find the first directory in the search path that is a parent of the specified
54
+ # directory and do our requires relative to that in order to increase the likelihood
55
+ # that duplicate requires will be recognised.
56
+ def require_all_in_dir(dir, glob="*.rb")
57
+ expanded = File.expand_path dir
58
+ base = $:.detect do |path_dir|
59
+ expanded_pd = File.expand_path(path_dir)
60
+ expanded[0, expanded_pd.length] == expanded_pd
61
+ end
62
+ prefix = (base)? expanded[File.expand_path(base).length+1, expanded.length]+"/" : ""
63
+
64
+ # puts $:.join "\n"
65
+ # puts "expanded = '#{expanded}'"
66
+ # puts "base = '#{base}'"
67
+ # puts "prefix = '#{prefix}'"
68
+
69
+ Dir.chdir dir do |cwd|
70
+ Dir.glob(glob) do |filename|
71
+ require prefix + filename
72
+ end
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ Configuration.new
79
+
80
+
81
+
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2007 Ritchie Young. All rights reserved.
4
+ #
5
+ # This file is part of RubySync.
6
+ #
7
+ # RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
9
+ #
10
+ # RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
11
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
14
+ # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15
+
16
+
17
+ # Tests that assume that the connectors can be treated like hashes. That is, they can be accessed
18
+ # using the [] and []= operators.
19
+ module HashlikeTests
20
+
21
+ # Override this if :bob isn't a good path for your directory
22
+ # def path
23
+ # :bob
24
+ # end
25
+
26
+ def client_path
27
+ 'bob'
28
+ end
29
+
30
+ def vault_path
31
+ 'bob'
32
+ end
33
+
34
+ def test_client_to_vault
35
+ banner "test_client_to_vault"
36
+ assoc_key = @client.add client_path, @client.create_operations_for(@bob_details)
37
+ assert_not_nil @client.entry_for_own_association_key(assoc_key)
38
+ assert_nil @vault[vault_path], "Vault already contains bob"
39
+ @pipeline.run_once
40
+ assert_not_nil @vault[vault_path], "#{vault_path} wasn't created on the vault"
41
+ assert_equal @bob_details, @vault[vault_path].reject {|k,v| ['modifier',:association].include? k}
42
+ if @client.respond_to? :delete
43
+ @client.delete client_path
44
+ assert_equal @bob_details, @vault[vault_path].reject {|k,v| ['modifier',:association].include? k}
45
+ assert_nil @client[client_path], "Bob wasn't deleted from the client"
46
+ @pipeline.run_once
47
+ assert_nil @client[client_path], "Bob reappeared on the client"
48
+ assert_nil @vault[vault_path], "Bob wasn't deleted from the vault"
49
+ @pipeline.run_once # run again in case of echoes
50
+ assert_nil @client[client_path], "Bob reappeared on the client"
51
+ assert_nil @vault[vault_path], "Bob reappeared in the vault. He may have been created by an echoed add event"
52
+ end
53
+ end
54
+
55
+
56
+ def test_vault_to_client
57
+ banner "test_vault_to_client"
58
+ @vault.add vault_path, @vault.create_operations_for(@bob_details)
59
+ assert_nil @client[client_path], "Client already contains bob"
60
+ @pipeline.run_once
61
+ assert_not_nil @client[client_path], "#{client_path} wasn't created on the client"
62
+ assert_equal normalise(@bob_details), normalise(@client[client_path])
63
+ @vault.delete vault_path
64
+ assert_equal normalise(@bob_details), normalise(@client[client_path])
65
+ assert_nil @vault[vault_path], "Bob wasn't deleted from the vault"
66
+ assert_not_nil @client[client_path], "Bob was deleted from the client before we ran the pipe"
67
+ @pipeline.run_once
68
+ assert_nil @client[client_path], "Bob wasn't deleted from the client"
69
+ @pipeline.run_once # run again in case there were unhandled echos
70
+ assert_nil @client[client_path], "Bob is back in client, he may have been recreated by an echoed add event"
71
+ end
72
+
73
+ # Names of attributes that can't be synched
74
+ def unsynchable
75
+ [:modifier]
76
+ end
77
+
78
+ def normalise details
79
+ normal = {}
80
+ @unsynchable ||= unsynchable.map{|u| u.to_s.downcase}
81
+ details.each_pair do |k,v|
82
+ key = k.to_s.downcase
83
+ unless @unsynchable.include?(key)
84
+ normal[key] = v
85
+ # puts "[#{@unsynchable.join ','}] doesn't include '#{key}'"
86
+ end
87
+ end
88
+ normal
89
+ end
90
+
91
+
92
+ def test_vault
93
+ banner :test_vault
94
+ assert @vault.can_act_as_vault?
95
+ assert @vault.is_vault?
96
+ @vault.add vault_path, @vault.create_operations_for(@bob_details)
97
+ association = RubySync::Association.new(@pipeline.association_context, 'blah')
98
+ @vault.associate association, vault_path
99
+ assert_equal vault_path, @vault.path_for_association(association)
100
+ assert_equal 'blah', @vault.association_key_for(@pipeline.association_context, vault_path)
101
+ end
102
+
103
+ def test_perform_operations
104
+ banner :test_perform_operations
105
+ result = @vault.perform_operations [
106
+ RubySync::Operation.new(:add, :name, 'Fred'),
107
+ RubySync::Operation.new(:add, :email, 'fred@test.com')
108
+ ]
109
+ assert_equal({'name'=>['Fred'], 'email'=>['fred@test.com']}, result)
110
+ end
111
+ end