ruote-ar 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/ruote-ar.rb +1 -0
- data/lib/ruote/ar.rb +1 -0
- data/lib/ruote/ar/storage.rb +422 -0
- data/lib/ruote/ar/version.rb +5 -0
- metadata +92 -0
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
|
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: []
|