ruote-ar 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 74ff893d6570dea5e548db182d76ab6f9f4cf669
4
+ data.tar.gz: b0340869ce9c611fbf280cf27f4561745a6b31fe
5
+ SHA512:
6
+ metadata.gz: ffdca4a2483653789724db5cec12e398ee319415440008e204087d17854fd787c8c6fb634502c98c18944269b47cad162bc65a9ff139f865315712e51503a145
7
+ data.tar.gz: 8606fc9ceb173ead835b6f951a95867faca673629721b45d0c6f43916e6efa79f1ea0e457590e821b125943b3ad9da3310eee2ff1fc1905b0b1765982133f362
data/lib/ruote-ar.rb ADDED
@@ -0,0 +1 @@
1
+ require 'ruote/ar/storage'
data/lib/ruote/ar.rb ADDED
@@ -0,0 +1 @@
1
+ require 'ruote/ar/storage'
@@ -0,0 +1,422 @@
1
+ # encoding: utf-8
2
+
3
+ require 'active_record'
4
+ require 'ruote/storage/base'
5
+
6
+ Rufus::Json.backend = :active_support
7
+
8
+ module Ruote
9
+ module ActiveRecord
10
+ class Storage
11
+ include Ruote::StorageBase
12
+
13
+ def initialize(options = {})
14
+
15
+ @table_name = options['table_name'] || ('documents').to_sym
16
+
17
+ replace_engine_configuration(options)
18
+ end
19
+
20
+
21
+ def put_msg(action, options)
22
+
23
+ # put_msg is a unique action, no need for all the complexity of put
24
+ do_insert(prepare_msg_doc(action, options), 1)
25
+
26
+ nil
27
+ end
28
+
29
+ # Used to reserve 'msgs' and 'schedules'. Simply update and
30
+ # return true if the update was affected more than one line.
31
+ #
32
+ def reserve(doc)
33
+ um = Arel::UpdateManager.new Arel::Table.engine
34
+ um.table table
35
+ um.where table[:typ].eq(doc['type'].to_s).and(table[:ide].eq(doc['_id'].to_s).and(table[:rev].eq(1).and(table[:worker].eq(nil))))
36
+ um.set [
37
+ [table[:worker], worker]
38
+ ]
39
+ connection.update(um.to_sql) > 0
40
+ end
41
+
42
+ # removing doc after success (or fail) success.
43
+ # It's important to not leave any message.
44
+ def done(doc)
45
+ dm = Arel::DeleteManager.new Arel::Table.engine
46
+ dm.from table
47
+ dm.where table[:typ].eq(doc['type']).and(table[:ide].eq(doc['_id']).and(table[:rev].eq(1).and(table[:worker].eq(worker))))
48
+ connection.delete(dm)
49
+ end
50
+
51
+ def put_schedule(flavour, owner_fei, s, msg)
52
+
53
+ # put_schedule is a unique action, no need for all the complexity of put
54
+
55
+ doc = prepare_schedule_doc(flavour, owner_fei, s, msg)
56
+
57
+ return nil unless doc
58
+
59
+ do_insert(doc, 1)
60
+
61
+ doc['_id']
62
+ end
63
+
64
+ def put(doc, opts={})
65
+
66
+ cache_clear(doc)
67
+
68
+ if doc['_rev']
69
+
70
+ d = get(doc['type'], doc['_id'])
71
+
72
+ return true unless d
73
+ return d if d['_rev'] != doc['_rev']
74
+ # failures
75
+ end
76
+
77
+ nrev = doc['_rev'].to_i + 1
78
+
79
+ begin
80
+
81
+ do_insert(doc, nrev, opts[:update_rev])
82
+
83
+ rescue Exception => de
84
+ puts "Error putting: #{de.message}: #{doc.inspect}"
85
+ return (get(doc['type'], doc['_id']) || true)
86
+ # failure
87
+ end
88
+
89
+ dm = Arel::DeleteManager.new Arel::Table.engine
90
+ dm.from table
91
+ dm.where table[:typ].eq(doc['type']).and(table[:ide].eq(doc['_id']).and(table[:rev].lt(nrev)))
92
+ connection.delete(dm)
93
+
94
+ nil
95
+ # success
96
+ end
97
+
98
+ def get(type, key)
99
+ cache_get(type, key) || do_get(type, key)
100
+ end
101
+
102
+ def delete(doc)
103
+ raise true if doc.nil?
104
+
105
+ raise ArgumentError.new('no _rev for doc') unless doc['_rev']
106
+
107
+ cache_clear(doc)
108
+ # usually not necessary, adding it not to forget it later on
109
+
110
+ dm = Arel::DeleteManager.new Arel::Table.engine
111
+ dm.from table
112
+ dm.where table[:typ].eq(doc['type']).and(table[:ide].eq(doc['_id']).and(table[:rev].eq(doc['_rev'].to_i)))
113
+ count = connection.delete(dm)
114
+
115
+ return (get(doc['type'], doc['_id']) || true) if count < 1
116
+ # failure
117
+
118
+ nil
119
+ # success
120
+ end
121
+
122
+ def get_many(type, key=nil, opts={})
123
+
124
+ ###
125
+
126
+ cached = cache_get_many(type, key, opts)
127
+ return cached if cached
128
+
129
+ ds = table[:typ].eq(type)
130
+
131
+ keys = key ? Array(key) : nil
132
+ ds = ds.and(table[:wfid].in(keys)) if keys && keys.first.is_a?(String)
133
+
134
+ ds = table.where(ds)
135
+
136
+ return connection.select_value(ds.project(table[:wfid].count)) if opts[:count]
137
+
138
+ if opts[:descending].is_a?(Array) && opts[:descending].first.class != String
139
+ opts[:descending] = opts[:descending].collect {|s| s.inspect.gsub(':','').gsub('.', ' ')}
140
+ end
141
+
142
+ if opts[:descending]
143
+ ds = ds.order(table[:ide].desc, table[:rev].desc)
144
+ else
145
+ ds = ds.order(table[:ide].asc, table[:rev].asc)
146
+ end
147
+
148
+ ds = ds.take(opts[:limit]).skip(opts[:skip]||opts[:offset])
149
+
150
+ docs = connection.select_all(ds.project('*'))
151
+ docs = select_last_revs(docs)
152
+ docs = docs.collect { |d| Rufus::Json.decode(d['doc']) }
153
+
154
+ if keys && keys.first.is_a?(Regexp)
155
+ docs.select { |doc| keys.find { |k| k.match(doc['_id']) } }
156
+ else
157
+ docs
158
+ end
159
+
160
+ # (pass on the dataset.filter(:wfid => /regexp/) for now
161
+ # since we have potentially multiple keys)
162
+ end
163
+
164
+ # Returns all the ids of the documents of a given type.
165
+ #
166
+ def ids(type)
167
+ connection.select_values(table.where(table[:typ].eq(type)).project('distinct ide').order(table[:ide]))
168
+ end
169
+
170
+ # Nukes all the documents in this storage.
171
+ #
172
+ def purge!
173
+ # just for test
174
+ end
175
+
176
+ # Returns connection to pool
177
+ def shutdown
178
+ ::ActiveRecord::Base.clear_active_connections!
179
+ ::ActiveRecord::Base.connection.close
180
+ end
181
+
182
+ # Grrr... I should sort the mess between close and shutdown...
183
+ # Tests vs production :-(
184
+ #
185
+ def close
186
+ shutdown
187
+ end
188
+
189
+ # Mainly used by ruote's test/unit/ut_17_storage.rb
190
+ #
191
+ def add_type(type)
192
+ # does nothing, types are differentiated by the 'typ' column
193
+ end
194
+
195
+ # Nukes a db type and reputs it (losing all the documents that were in it).
196
+ #
197
+ def purge_type!(type)
198
+ # just for test
199
+ end
200
+
201
+ # A provision made for workitems, allow to query them directly by
202
+ # participant name.
203
+ #
204
+ def by_participant(type, participant_name, opts={})
205
+
206
+ raise NotImplementedError if type != 'workitems'
207
+
208
+ docs = table.where(table[:typ].eq(type).and(table[:participant_name].eq(participant_name)))
209
+
210
+ return connection.select_value(docs.project('count(*)')) if opts[:count]
211
+
212
+ docs = connection.select_all(docs.project('*').order(table[:ide].asc, table[:rev].desc).take(opts[:limit]).skip(opts[:offset] || opts[:skip]))
213
+
214
+ select_last_revs(docs).collect { |d| Ruote::Workitem.from_json(d['doc']) }
215
+ end
216
+
217
+ # Querying workitems by field (warning, goes deep into the JSON structure)
218
+ #
219
+ def by_field(type, field, value, opts={})
220
+
221
+ raise NotImplementedError if type != 'workitems'
222
+
223
+ lk = [ '%"', field, '":' ]
224
+ lk.push(Rufus::Json.encode(value)) if value
225
+ lk.push('%')
226
+
227
+ docs = table.where(table[:typ].eq(type).and(table[:doc].matches(lk.join)))
228
+
229
+ return connection.select_value(docs.project('count(*)')) if opts[:count]
230
+
231
+ docs = connection.select_all(docs.project('*').order(table[:ide].asc, table[:rev].desc).take(opts[:limit]).skip(opts[:offset] || opts[:skip]))
232
+ select_last_revs(docs).collect { |d| Ruote::Workitem.from_json(d['doc']) }
233
+ end
234
+
235
+ def query_workitems(criteria)
236
+
237
+ ds = table[:typ].eq('workitems')
238
+
239
+ wfid = criteria.delete('wfid')
240
+ ds = ds.and(table[:ide].matches("%!#{wfid}")) if wfid
241
+
242
+ pname = criteria.delete('participant_name') || criteria.delete('participant')
243
+ ds = ds.and(table[:participant_name].eq(pname)) if pname
244
+
245
+ count = criteria.delete('count')
246
+ limit = criteria.delete('limit')
247
+ offset = criteria.delete('offset') || criteria.delete('skip')
248
+
249
+ criteria.collect do |k, v|
250
+ ds = ds.and(table[:doc].matches("%\"#{k}\":#{Rufus::Json.encode(v)}%"))
251
+ end
252
+
253
+ ds = table.where(ds).take(limit).skip(offset)
254
+
255
+ return connection.select_one(ds.project(table[:wfid].count)).first if count
256
+
257
+ select_last_revs(connection.select_all(ds.project('*'))).collect { |d| Ruote::Workitem.from_json(d['doc']) }
258
+ end
259
+
260
+ def begin_step
261
+
262
+ prepare_cache
263
+ end
264
+
265
+ protected
266
+
267
+ def decode_doc(doc)
268
+
269
+ return nil if doc.nil?
270
+
271
+ doc = doc['doc']
272
+
273
+ Rufus::Json.decode(doc)
274
+ end
275
+
276
+
277
+ def do_insert(doc, rev, update_rev=false)
278
+
279
+ doc = doc.send(
280
+ update_rev ? :merge! : :merge,
281
+ {'_rev' => rev, 'put_at' => Ruote.now_to_utc_s}
282
+ )
283
+
284
+ m = Arel::InsertManager.new(Arel::Table.engine)
285
+ m.into table
286
+ m.insert [
287
+ [table[:ide], (doc['_id'] || '')],
288
+ [table[:rev], (rev || '')],
289
+ [table[:typ], (doc['type'] || '')],
290
+ [table[:doc], (Rufus::Json.encode(doc) || '')],
291
+ [table[:wfid], (extract_wfid(doc) || '')],
292
+ [table[:participant_name], (doc['participant_name'] || '')]]
293
+
294
+ connection.insert(m)
295
+ end
296
+
297
+
298
+ def extract_wfid(doc)
299
+ doc['wfid'] || (doc['fei'] ? doc['fei']['wfid'] : nil)
300
+ end
301
+
302
+ def do_get(type, key)
303
+ decode_doc connection.select_one(table.project('*').
304
+ where(table[:typ].eq(type).and(table[:ide].eq(key))).
305
+ order(table[:rev].desc))
306
+ end
307
+
308
+ # Don't put configuration if it's already in
309
+ #
310
+ # (avoid storages from trashing configuration...)
311
+ #
312
+ # def put_configuration
313
+ #
314
+ # return if get('configurations', 'engine')
315
+ #
316
+ # conf = { '_id' => 'engine', 'type' => 'configurations' }.merge(@options)
317
+ # put(conf)
318
+ # end
319
+
320
+ # Weed out older docs (same ide, smaller rev).
321
+ #
322
+ # This could all have been done via SQL, but those inconsistencies
323
+ # are rare, the cost of the pumped SQL is not constant :-(
324
+ #
325
+ def select_last_revs(docs)
326
+ docs.each_with_object([]) { |doc,a|
327
+ a << doc if a.last.nil? || doc['ide'] != a.last['ide']
328
+ }
329
+ end
330
+
331
+ #--
332
+ # worker step cache
333
+ #
334
+ # in order to cut down the number of selects, do one select with
335
+ # all the information the worker needs for one step of work
336
+ #++
337
+
338
+
339
+ CACHED_TYPES = %w[ msgs schedules configurations variables ]
340
+
341
+ # One select to grab in all the info necessary for a worker step
342
+ # (expressions excepted).
343
+ #
344
+ def prepare_cache
345
+
346
+ CACHED_TYPES.each { |t| cache[t] = {} }
347
+
348
+ ds = table.where(table[:typ].in(CACHED_TYPES)).
349
+ project(table[:ide], table[:typ], table[:doc]).
350
+ order(table[:ide].asc, table[:rev].desc)
351
+
352
+ connection.select_all(ds).each do |d|
353
+ (cache[d['typ']] ||= {})[d['ide']] ||= decode_doc(d)
354
+ end
355
+
356
+ cache['variables']['trackers'] ||=
357
+ { '_id' => 'trackers', 'type' => 'variables', 'trackers' => {} }
358
+ end
359
+
360
+ # Ask the cache for a doc. Returns nil if it's not cached.
361
+ #
362
+ def cache_get(type, key)
363
+
364
+ (cache[type] || {})[key]
365
+ end
366
+
367
+ # Ask the cache for a set of documents. Returns nil if it's not cached
368
+ # or caching is not OK.
369
+ #
370
+ def cache_get_many(type, keys, options)
371
+
372
+ if !options[:batch] && CACHED_TYPES.include?(type) && cache[type]
373
+ cache[type].values
374
+ else
375
+ nil
376
+ end
377
+ end
378
+
379
+ # Removes a document from the cache.
380
+ #
381
+ def cache_clear(doc)
382
+
383
+ (cache[doc['type']] || {}).delete(doc['_id'])
384
+ end
385
+
386
+ # Returns the cache for the given thread. Returns {} if there is no
387
+ # cache available.
388
+ #
389
+ def cache
390
+
391
+ worker = Thread.current['ruote_worker']
392
+
393
+ return {} unless worker
394
+
395
+ (Thread.current["cache_#{worker.name}"] ||= {})
396
+ end
397
+
398
+
399
+ # def do_delete(doc)
400
+ # Document.delete_all(
401
+ # :ide => doc['_id'], :typ => doc['type'], :rev => doc['_rev'].to_i
402
+ # )
403
+ # end
404
+
405
+ private
406
+ def table
407
+ @table ||= ::Arel::Table.new @table_name
408
+ end
409
+
410
+ def connection
411
+ ::ActiveRecord::Base.connection
412
+ end
413
+
414
+ def worker
415
+ worker = Thread.current['ruote_worker']
416
+ if worker
417
+ worker.name
418
+ end || 'worker'
419
+ end
420
+ end
421
+ end
422
+ end
@@ -0,0 +1,5 @@
1
+ module Ruote
2
+ module ActiveRecord
3
+ VERSION = '0.0.5'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruote-ar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - pedroteixeira
8
+ - jiangchaofan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-07-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '3.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rails
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '3.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '3.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: ruote
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '2.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '2.0'
56
+ description: ruote storage
57
+ email:
58
+ - pedro@intelie.com.br
59
+ - jiangchaofan@gmail.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - lib/ruote-ar.rb
65
+ - lib/ruote/ar.rb
66
+ - lib/ruote/ar/storage.rb
67
+ - lib/ruote/ar/version.rb
68
+ homepage: https://github.com/intelie/ruote-ar
69
+ licenses:
70
+ - apache
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.2.2
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: ruote storage
92
+ test_files: []