rubysync 0.0.1 → 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/bin/rubysync +3 -3
- data/examples/ims2/connectors/hr_db_connector.rb +3 -5
- data/examples/my_ims/connectors/my_csv_connector.rb +10 -0
- data/examples/my_ims/connectors/my_db_connector.rb +7 -0
- data/examples/my_ims/pipelines/{finance_pipeline.rb → my_pipeline.rb} +9 -9
- data/lib/net/ldif.rb +302 -0
- data/lib/ruby_sync/connectors/active_record_connector.rb +33 -32
- data/lib/ruby_sync/connectors/base_connector.rb +21 -10
- data/lib/ruby_sync/connectors/csv_file_connector.rb +17 -22
- data/lib/ruby_sync/connectors/file_connector.rb +11 -11
- data/lib/ruby_sync/connectors/ldap_connector.rb +206 -53
- data/lib/ruby_sync/connectors/memory_connector.rb +5 -8
- data/lib/ruby_sync/event.rb +11 -3
- data/lib/ruby_sync/operation.rb +1 -1
- data/lib/ruby_sync/pipelines/base_pipeline.rb +6 -0
- data/lib/ruby_sync/util/utilities.rb +22 -3
- data/lib/ruby_sync.rb +25 -2
- data/test/data/example1.ldif +20 -0
- data/test/data/example2.ldif +14 -0
- data/test/data/example3.ldif +13 -0
- data/test/data/example4.ldif +55 -0
- data/test/data/example5.ldif +12 -0
- data/test/data/example6.ldif +62 -0
- data/test/data/example7.ldif +8 -0
- data/test/test_active_record_vault.rb +3 -4
- data/test/test_base_pipeline.rb +58 -0
- data/test/test_csv_file_connector.rb +7 -7
- data/test/test_ldap_connector.rb +71 -13
- data/test/test_ldap_vault.rb +91 -0
- data/test/test_ldif.rb +122 -0
- data/test/test_utilities.rb +63 -0
- metadata +19 -7
- data/examples/my_ims/connectors/corp_directory_connector.rb +0 -12
- data/examples/my_ims/connectors/finance_connector.rb +0 -7
- data/examples/my_ims/connectors/hr_db_connector.rb +0 -7
- data/examples/my_ims/pipelines/hr_import_pipeline.rb +0 -29
data/bin/rubysync
CHANGED
@@ -224,14 +224,14 @@ puts <<"END"
|
|
224
224
|
connectors/my_db_connector.rb ;how to connect to your DB or Rails app.
|
225
225
|
|
226
226
|
And enter:
|
227
|
-
$ rubysync pipeline
|
227
|
+
$ rubysync pipeline my -C my_csv -V my_db
|
228
228
|
|
229
|
-
You would then edit the file
|
229
|
+
You would then edit the file pipelines/my_pipeline.rb to configure the
|
230
230
|
policy for synchronizing between the two connectors.
|
231
231
|
|
232
232
|
You may then execute the pipeline in one-shot mode (daemon mode is coming):
|
233
233
|
|
234
|
-
$ rubysync once
|
234
|
+
$ rubysync once my
|
235
235
|
END
|
236
236
|
end
|
237
237
|
|
@@ -1,8 +1,6 @@
|
|
1
1
|
class HrDbConnector < RubySync::Connectors::ActiveRecordConnector
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
:model=>:person
|
6
|
-
)
|
2
|
+
name "HR Database",
|
3
|
+
application "#{File.dirname(__FILE__)}/../../ar_webapp",
|
4
|
+
model :person
|
7
5
|
|
8
6
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
class
|
1
|
+
class MyPipeline < RubySync::Pipelines::BasePipeline
|
2
2
|
|
3
|
-
client :
|
3
|
+
client :my_csv
|
4
4
|
|
5
|
-
vault :
|
5
|
+
vault :my_db
|
6
6
|
|
7
7
|
# Remove any fields that you don't want to set in the client from the vault
|
8
|
-
allow_out :first_name
|
8
|
+
allow_out :first_name, :last_name
|
9
9
|
|
10
10
|
# Remove any fields that you don't want to set in the vault from the client
|
11
11
|
allow_in :first_name, :last_name
|
@@ -16,17 +16,17 @@ class FinancePipeline < RubySync::Pipelines::BasePipeline
|
|
16
16
|
# 'first name' => 'givenName'
|
17
17
|
# separate each mapping with a comma.
|
18
18
|
# The following fields were detected on the client:
|
19
|
-
# '
|
19
|
+
# 'id', 'given_name', 'surname'
|
20
20
|
map_vault_to_client(
|
21
|
-
'first_name' => '
|
22
|
-
|
21
|
+
'first_name' => 'given_name',
|
22
|
+
'last_name' => 'surname'
|
23
23
|
)
|
24
24
|
|
25
|
-
# in means going from client to vault
|
25
|
+
# "in" means going from client to vault
|
26
26
|
#in_transform do
|
27
27
|
#end
|
28
28
|
|
29
|
-
# out means going from vault to client
|
29
|
+
# "out" means going from vault to client
|
30
30
|
#out_transform do
|
31
31
|
#end
|
32
32
|
|
data/lib/net/ldif.rb
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
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
|
+
require 'base64'
|
17
|
+
|
18
|
+
module Net
|
19
|
+
|
20
|
+
class ParsingError < StandardError; end
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
# Represents a mod-spec structure from RFC2849
|
25
|
+
class LDIFModSpec # :nodoc:
|
26
|
+
attr_accessor :type, :attribute, :values
|
27
|
+
def initialize(type, attribute)
|
28
|
+
self.type = type
|
29
|
+
self.attribute = attribute
|
30
|
+
self.values = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_change name, value
|
34
|
+
if @values
|
35
|
+
if @values.kind_of? Array
|
36
|
+
@values << value
|
37
|
+
else
|
38
|
+
@values = [@values, value]
|
39
|
+
end
|
40
|
+
else
|
41
|
+
@values = value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def changes() [@type.to_sym, @attribute, @values] end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Represents an LDIF change record as defined in RFC2849.
|
49
|
+
# The RFC specifies that an LDIF file can contain either
|
50
|
+
# attrval records (which are just content) or change records
|
51
|
+
# which represent changes to the content.
|
52
|
+
# This parser puts both types into the change record structure
|
53
|
+
# Ldif attrval records simply become change records of type
|
54
|
+
# 'add'.
|
55
|
+
class ChangeRecord
|
56
|
+
attr_accessor :dn, :changetype, :data
|
57
|
+
|
58
|
+
def initialize(dn, changetype='add')
|
59
|
+
self.dn = dn
|
60
|
+
self.changetype = changetype
|
61
|
+
@mod_spec = nil
|
62
|
+
@data = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
"#{@dn}\n#{@changetype}\n" +
|
67
|
+
@data.inspect
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def add_value(name, value) # :nodoc:
|
72
|
+
# Changetype specified before any other fields
|
73
|
+
if name == 'changetype' and !@data
|
74
|
+
@changetype = value
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
if name == '-' and @changetype != 'modify'
|
79
|
+
raise ParsingError.new("'-' is only valid in LDIF change records when changetype is modify")
|
80
|
+
end
|
81
|
+
|
82
|
+
# Just an ordinary name value pair
|
83
|
+
case changetype
|
84
|
+
when 'add': add_content(name, value)
|
85
|
+
when 'delete':
|
86
|
+
raise ParsingError.new("Didn't expect content when changetype was 'delete', (#{name}:#{value})")
|
87
|
+
when 'modify': add_modification(name, value)
|
88
|
+
when 'modrdn': add_moddn_value(name,value)
|
89
|
+
when 'moddn': add_moddn_value(name, value)
|
90
|
+
else
|
91
|
+
raise ParsingError.new("Unknown changetype: '#{changetype}'")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def add_modification(name, value)
|
97
|
+
@data ||= []
|
98
|
+
if name == '-'
|
99
|
+
@mod_spec or
|
100
|
+
raise ParsingError.new("'-' in LDIF modify record before any actual changes")
|
101
|
+
@data << @mod_spec.changes
|
102
|
+
@mod_spec = nil
|
103
|
+
return
|
104
|
+
end
|
105
|
+
|
106
|
+
if @mod_spec
|
107
|
+
@mod_spec.add_change name,value
|
108
|
+
elsif %w{add delete replace}.include? name
|
109
|
+
@mod_spec = LDIFModSpec.new(name, value)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_moddn_value(name, value)
|
114
|
+
#TODO: implement
|
115
|
+
#raise Exception.new("Sorry, not yet implemented")
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_content(key, value)
|
119
|
+
@data ||= {}
|
120
|
+
if @data[key]
|
121
|
+
if @data[key].kind_of? Array
|
122
|
+
@data[key] << value
|
123
|
+
else
|
124
|
+
@data[key] = [@data[key], value]
|
125
|
+
end
|
126
|
+
else
|
127
|
+
@data[key] = value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end # of class ChangeRecord
|
132
|
+
|
133
|
+
|
134
|
+
class URLForValue < String # :nodoc:
|
135
|
+
def to_s
|
136
|
+
filename = self.sub(/^file:\/\//oi, '')
|
137
|
+
File.read(filename)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class Base64EncodedString < String # :nodoc:
|
142
|
+
def to_s() Base64.decode64 self; end
|
143
|
+
end
|
144
|
+
|
145
|
+
class LDIF
|
146
|
+
|
147
|
+
#FILL = '\s*'
|
148
|
+
# TODO: Attribute description should be more structured than this
|
149
|
+
ATTRIBUTE_DESCRIPTION = '[a-zA-Z0-9.;-]+'
|
150
|
+
SAFE_INIT_CHAR = '[\x01-\x09\x0b-\x0c\x0e-\x1f\x21-\x39\x3b\x3d-\x7f]'
|
151
|
+
SAFE_CHAR = '[\x01-\x09\x0b-\x0c\x0e-\x7f]'
|
152
|
+
SAFE_STRING = "#{SAFE_INIT_CHAR}#{SAFE_CHAR}*"
|
153
|
+
BASE64_STRING = '[\x2b\x2f\x30-\x39\x3d\x41-\x5a\x61-\x7a]*'
|
154
|
+
|
155
|
+
|
156
|
+
# Yields Net::ChangeRecord for each LDIF record in the file.
|
157
|
+
# If the file contains attr-val (content) records, they are
|
158
|
+
# yielded as Net::ChangeRecords of type 'add'.
|
159
|
+
# If no block is given, then returns an array of the
|
160
|
+
# Net::ChangeRecord objects.
|
161
|
+
def self.parse(stream)
|
162
|
+
return parse_to_array(stream) unless block_given?
|
163
|
+
type = nil
|
164
|
+
record_number = 0
|
165
|
+
record = nil
|
166
|
+
tokenize(stream) do |name, value|
|
167
|
+
|
168
|
+
# version-spec
|
169
|
+
if (name == 'version' and record_number == 0)
|
170
|
+
value == '1' or raise ParsingError.new("Don't know how to parse LDIF version '#{value}'")
|
171
|
+
next
|
172
|
+
end
|
173
|
+
|
174
|
+
# Blank line
|
175
|
+
# Unless I'm reading the spec wrong, blank lines don't seem to mean much
|
176
|
+
# Yet in all the examples, the records seem to be separated by blank lines.
|
177
|
+
# TODO: Check whether blank lines mean anything
|
178
|
+
next if (name == nil)
|
179
|
+
|
180
|
+
name.downcase!
|
181
|
+
# DN - start a new record
|
182
|
+
if name == 'dn'
|
183
|
+
# Process existing record
|
184
|
+
yield(record) if record
|
185
|
+
record = ChangeRecord.new(value)
|
186
|
+
next
|
187
|
+
end
|
188
|
+
|
189
|
+
record or raise ParsingException.new("Expecting a dn, got #{name}: #{value}")
|
190
|
+
record.add_value name, value
|
191
|
+
end # of tokens
|
192
|
+
yield(record) if record
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.parse_to_array(stream)
|
196
|
+
changes = []
|
197
|
+
parse(stream) do |change|
|
198
|
+
changes << change
|
199
|
+
end
|
200
|
+
changes
|
201
|
+
end
|
202
|
+
|
203
|
+
# Yields a series of pairs of the form name, value found in the
|
204
|
+
# given stream. Comments (lines starting with #) are removed,
|
205
|
+
# base64 values are decoded and folded lines are unfolded.
|
206
|
+
# Blank lines are yielded with a nil name, nil value pair.
|
207
|
+
# Lines containing only a hyphen are yielded as a name="-",
|
208
|
+
# value="-" pair.
|
209
|
+
# Values specified as file:// urls as described in RFC2849 are
|
210
|
+
# replaced with the contents of the specified file.
|
211
|
+
def self.tokenize(stream)
|
212
|
+
|
213
|
+
foldable = false
|
214
|
+
comment = false
|
215
|
+
name = nil
|
216
|
+
value = ""
|
217
|
+
line_number = 0
|
218
|
+
stream.each_line do |line|
|
219
|
+
line_number += 1
|
220
|
+
|
221
|
+
# Blank line
|
222
|
+
if line.strip.length == 0
|
223
|
+
yield(name, value.to_s) if name;name = nil;value = ""
|
224
|
+
yield nil,nil
|
225
|
+
foldable = false
|
226
|
+
comment = false
|
227
|
+
next
|
228
|
+
end
|
229
|
+
|
230
|
+
# Line extension
|
231
|
+
if foldable and line[0,1] == ' '
|
232
|
+
value << line.chop[1..-1] unless comment
|
233
|
+
next
|
234
|
+
end
|
235
|
+
|
236
|
+
# Comment
|
237
|
+
if line[0,1] == '#'
|
238
|
+
yield(name, value.to_s) if name;name = nil;value = ""
|
239
|
+
comment = true
|
240
|
+
foldable = true
|
241
|
+
next
|
242
|
+
end
|
243
|
+
|
244
|
+
# Base64 Encoded name:value pair
|
245
|
+
if line =~ /^(#{ATTRIBUTE_DESCRIPTION})::\s*(#{BASE64_STRING})/oi
|
246
|
+
yield(name, value.to_s) if name
|
247
|
+
name = $1
|
248
|
+
value = Base64EncodedString.new($2)
|
249
|
+
comment = false
|
250
|
+
foldable = false # It is but we've got a separate rule for it
|
251
|
+
next
|
252
|
+
end
|
253
|
+
|
254
|
+
# URL value
|
255
|
+
if line =~ /^(#{ATTRIBUTE_DESCRIPTION}):<\s*(#{SAFE_STRING})/oi
|
256
|
+
yield(name, value.to_s) if name
|
257
|
+
name = $1
|
258
|
+
value = URLForValue.new($2)
|
259
|
+
comment = false
|
260
|
+
foldable = true
|
261
|
+
next
|
262
|
+
end
|
263
|
+
|
264
|
+
# Name:Value pair
|
265
|
+
if line =~ /^(#{ATTRIBUTE_DESCRIPTION}):\s*(#{SAFE_STRING})/oi
|
266
|
+
yield(name, value.to_s) if name
|
267
|
+
name = $1; value = $2
|
268
|
+
foldable = true
|
269
|
+
comment = false
|
270
|
+
next
|
271
|
+
end
|
272
|
+
|
273
|
+
# Hyphen
|
274
|
+
if line =~ /^-/o
|
275
|
+
yield(name, value.to_s) if name;name = nil;value = ""
|
276
|
+
yield('-','-')
|
277
|
+
foldable = false
|
278
|
+
comment = false
|
279
|
+
next
|
280
|
+
end
|
281
|
+
|
282
|
+
# Continuation of Base64 Encoded value?
|
283
|
+
if value.kind_of?(Base64EncodedString) and line =~ /^ (#{BASE64_STRING})/oi
|
284
|
+
value << $1
|
285
|
+
next
|
286
|
+
end
|
287
|
+
|
288
|
+
raise ParsingError.new("Unexpected LDIF at line: #{line_number}")
|
289
|
+
end # of file
|
290
|
+
yield(name, value.to_s) if name
|
291
|
+
line_number
|
292
|
+
end
|
293
|
+
|
294
|
+
private
|
295
|
+
|
296
|
+
|
297
|
+
def process(record, type) # :nodoc:
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
end
|
@@ -27,27 +27,29 @@ module RubySync::Connectors
|
|
27
27
|
class ActiveRecordConnector < RubySync::Connectors::BaseConnector
|
28
28
|
|
29
29
|
|
30
|
-
|
31
|
-
|
30
|
+
option :ar_class, :model, :application, :rails_env, :db_type, :db_host, :db_name, :db_config
|
31
|
+
rails_env 'development'
|
32
|
+
db_type 'mysql'
|
33
|
+
db_host 'localhost'
|
34
|
+
db_name "rubysync_#{get_rails_env}"
|
35
|
+
# Default db_config in case we're not sucking the config out of a rails app
|
36
|
+
db_config(
|
37
|
+
:adapter=>get_db_type,
|
38
|
+
:host=>get_db_host,
|
39
|
+
:database=>get_db_name
|
40
|
+
)
|
41
|
+
model :user
|
42
|
+
|
43
|
+
|
32
44
|
def initialize options={}
|
33
45
|
super options
|
34
|
-
@rails_env ||= 'development'
|
35
|
-
@db_type ||= 'mysql'
|
36
|
-
@db_host ||= 'localhost'
|
37
|
-
@db_name ||= "rubysync_#{@rails_env}"
|
38
|
-
# Default db_config in case we're not sucking the config out of a rails app
|
39
|
-
@db_config = {
|
40
|
-
:adapter=>@db_type,
|
41
|
-
:host=>@db_host,
|
42
|
-
:database=>@db_name
|
43
|
-
}
|
44
46
|
|
45
47
|
# Rails app specified, use it to configure
|
46
|
-
if
|
48
|
+
if application
|
47
49
|
# Load the database configuration
|
48
|
-
rails_app_path = File.expand_path(
|
50
|
+
rails_app_path = File.expand_path(application, File.dirname(__FILE__))
|
49
51
|
db_config_filename = File.join(rails_app_path, 'config', 'database.yml')
|
50
|
-
|
52
|
+
db_config = YAML::load(ERB.new(IO.read(db_config_filename)).result)[rails_env]
|
51
53
|
# Require the models
|
52
54
|
log.debug "Loading the models for #{self.class.name}:"
|
53
55
|
Dir.chdir(File.join(rails_app_path,'app','models')) do
|
@@ -56,13 +58,12 @@ module RubySync::Connectors
|
|
56
58
|
require filename
|
57
59
|
class_name = filename[0..-4].camelize
|
58
60
|
klass = class_name.constantize
|
59
|
-
klass.establish_connection
|
61
|
+
klass.establish_connection db_config if defined? klass.establish_connection
|
60
62
|
end
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
|
-
|
65
|
-
@ar_class ||= @model.to_s.camelize.constantize
|
66
|
+
self.class.ar_class model.to_s.camelize.constantize
|
66
67
|
end
|
67
68
|
|
68
69
|
|
@@ -73,16 +74,16 @@ module RubySync::Connectors
|
|
73
74
|
|
74
75
|
def self.sample_config
|
75
76
|
return <<END
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
|
78
|
+
application '/path/to/a/rails/application'
|
79
|
+
model 'name_of_model_to_sync'
|
80
|
+
|
80
81
|
END
|
81
82
|
end
|
82
83
|
|
83
84
|
|
84
85
|
# Process each RubySyncEvent and then delete it from the db.
|
85
|
-
def
|
86
|
+
def each_change
|
86
87
|
::RubySyncEvent.find(:all).each do |rse|
|
87
88
|
event = RubySync::Event.new(rse.event_type, self, rse.trackable_id, nil, to_payload(rse))
|
88
89
|
yield event
|
@@ -102,7 +103,7 @@ END
|
|
102
103
|
# the id on creation.
|
103
104
|
def perform_add event
|
104
105
|
log.info "Adding '#{event.target_path}' to '#{name}'"
|
105
|
-
|
106
|
+
ar_class.new() do |record|
|
106
107
|
populate(record, perform_operations(event.payload))
|
107
108
|
log.info(record.inspect)
|
108
109
|
record.save!
|
@@ -119,21 +120,21 @@ END
|
|
119
120
|
|
120
121
|
|
121
122
|
def modify(path, operations)
|
122
|
-
|
123
|
+
ar_class.find(path) do |record|
|
123
124
|
populate(record, perform_operations(operations))
|
124
125
|
record.save
|
125
126
|
end
|
126
127
|
end
|
127
128
|
|
128
129
|
def delete(path)
|
129
|
-
|
130
|
+
ar_class.destroy path
|
130
131
|
end
|
131
132
|
|
132
133
|
# Implement vault functionality
|
133
134
|
|
134
135
|
def associate association, path
|
135
136
|
log.debug "Associating '#{association}' with '#{path}'"
|
136
|
-
ruby_sync_association.create :synchronizable_id=>path, :synchronizable_type
|
137
|
+
ruby_sync_association.create :synchronizable_id=>path, :synchronizable_type=>ar_class.name,
|
137
138
|
:context=>association.context, :key=>association.key
|
138
139
|
end
|
139
140
|
|
@@ -147,12 +148,12 @@ END
|
|
147
148
|
end
|
148
149
|
|
149
150
|
def association_key_for context, path
|
150
|
-
record = ruby_sync_association.find_by_synchronizable_id_and_synchronizable_type_and_context path,
|
151
|
+
record = ruby_sync_association.find_by_synchronizable_id_and_synchronizable_type_and_context path, model.to_s, context
|
151
152
|
record and record.key
|
152
153
|
end
|
153
154
|
|
154
155
|
def associations_for(path)
|
155
|
-
ruby_sync_association.find_by_synchronizable_id_and_synchronizable_type(path,
|
156
|
+
ruby_sync_association.find_by_synchronizable_id_and_synchronizable_type(path, model.to_s)
|
156
157
|
rescue ActiveRecord::RecordNotFound
|
157
158
|
return nil
|
158
159
|
end
|
@@ -165,7 +166,7 @@ END
|
|
165
166
|
|
166
167
|
|
167
168
|
def [](path)
|
168
|
-
|
169
|
+
ar_class.find(path)
|
169
170
|
rescue ActiveRecord::RecordNotFound
|
170
171
|
return nil
|
171
172
|
end
|
@@ -175,13 +176,13 @@ private
|
|
175
176
|
def ruby_sync_association
|
176
177
|
unless @ruby_sync_association
|
177
178
|
@ruby_sync_association = ::RubySyncAssociation
|
178
|
-
::RubySyncAssociation.establish_connection(
|
179
|
+
::RubySyncAssociation.establish_connection(db_config)
|
179
180
|
end
|
180
181
|
@ruby_sync_association
|
181
182
|
end
|
182
183
|
|
183
184
|
def populate record, content
|
184
|
-
|
185
|
+
ar_class.content_columns.each do |c|
|
185
186
|
record[c.name] = content[c.name][0] if content[c.name]
|
186
187
|
end
|
187
188
|
end
|
@@ -23,7 +23,6 @@ module RubySync::Connectors
|
|
23
23
|
|
24
24
|
attr_accessor :once_only, :name, :is_vault
|
25
25
|
|
26
|
-
|
27
26
|
def initialize options={}
|
28
27
|
options = self.class.default_options.merge(options)
|
29
28
|
once_only = false
|
@@ -52,18 +51,24 @@ module RubySync::Connectors
|
|
52
51
|
def started; end
|
53
52
|
|
54
53
|
# Subclasses must override this to
|
55
|
-
# interface with the external system and generate events
|
54
|
+
# interface with the external system and generate events for every
|
55
|
+
# entry in the scope.
|
56
56
|
# These events are yielded to the passed in block to process.
|
57
57
|
# This method will be called repeatedly until the connector is
|
58
58
|
# stopped.
|
59
|
-
def
|
59
|
+
def each_entry; end
|
60
|
+
|
61
|
+
# Subclasses must override this to interface with the external system
|
62
|
+
# and generate an event for every change that affects items within
|
63
|
+
# the scope of this connector.
|
64
|
+
def each_change; end
|
60
65
|
|
61
66
|
# Override this to perform actions that must be performed when
|
62
67
|
# the connector exits (eg closing network conections).
|
63
68
|
def stopped; end
|
64
69
|
|
65
70
|
|
66
|
-
# Call
|
71
|
+
# Call each_change repeatedly (or once if in once_only mode)
|
67
72
|
# to generate events.
|
68
73
|
# Should generally only be called by the pipeline to which it is attached.
|
69
74
|
def start
|
@@ -71,7 +76,7 @@ module RubySync::Connectors
|
|
71
76
|
@running = true
|
72
77
|
started()
|
73
78
|
while @running
|
74
|
-
|
79
|
+
each_change do |event|
|
75
80
|
if is_delete_echo?(event) || is_echo?(event)
|
76
81
|
log.debug "Ignoring echoed event"
|
77
82
|
else
|
@@ -156,9 +161,7 @@ module RubySync::Connectors
|
|
156
161
|
defined? associations_for
|
157
162
|
end
|
158
163
|
|
159
|
-
#
|
160
|
-
# we can distinguish between foreign keys for the same record but different
|
161
|
-
# connectors/pipelines.
|
164
|
+
# Implement these if you want your connector to be able to act as a vault
|
162
165
|
|
163
166
|
# def associate association, path
|
164
167
|
# end
|
@@ -166,14 +169,22 @@ module RubySync::Connectors
|
|
166
169
|
# def path_for_association association
|
167
170
|
# end
|
168
171
|
#
|
169
|
-
# def association_key_for context, path
|
170
|
-
# end
|
171
172
|
#
|
172
173
|
# def associations_for path
|
173
174
|
# end
|
174
175
|
#
|
175
176
|
# def remove_association association
|
176
177
|
# end
|
178
|
+
|
179
|
+
|
180
|
+
def association_key_for context, path
|
181
|
+
raise "#{name} is not a vault." unless is_vault?
|
182
|
+
associations_for(path).each do |assoc|
|
183
|
+
(c, key) = assoc.split(RubySync::Association.delimiter, 2)
|
184
|
+
return key if c == context
|
185
|
+
end
|
186
|
+
return nil
|
187
|
+
end
|
177
188
|
|
178
189
|
# Return the association object given the association context and path.
|
179
190
|
# This should only be called on the vault.
|