flor 0.16.1 → 0.16.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/CREDITS.md +1 -0
- data/Makefile +1 -1
- data/README.md +82 -6
- data/lib/flor.rb +1 -1
- data/lib/flor/conf.rb +19 -6
- data/lib/flor/core/executor.rb +45 -16
- data/lib/flor/core/node.rb +4 -4
- data/lib/flor/core/procedure.rb +40 -0
- data/lib/flor/djan.rb +5 -2
- data/lib/flor/flor.rb +92 -7
- data/lib/flor/id.rb +19 -0
- data/lib/flor/migrations/0001_tables.rb +6 -6
- data/lib/flor/migrations/0005_pointer_content.rb +20 -0
- data/lib/flor/pcore/_apply.rb +103 -57
- data/lib/flor/pcore/_att.rb +15 -1
- data/lib/flor/pcore/_ref.rb +2 -1
- data/lib/flor/pcore/arith.rb +46 -9
- data/lib/flor/pcore/break.rb +1 -1
- data/lib/flor/pcore/case.rb +41 -0
- data/lib/flor/pcore/collect.rb +1 -1
- data/lib/flor/pcore/cursor.rb +1 -1
- data/lib/flor/pcore/define.rb +32 -6
- data/lib/flor/pcore/iterator.rb +12 -0
- data/lib/flor/pcore/on_cancel.rb +1 -1
- data/lib/flor/pcore/set.rb +14 -4
- data/lib/flor/punit/{ccollect.rb → c_collect.rb} +2 -2
- data/lib/flor/punit/c_each.rb +11 -0
- data/lib/flor/punit/c_for_each.rb +41 -0
- data/lib/flor/punit/c_iterator.rb +160 -0
- data/lib/flor/punit/c_map.rb +43 -0
- data/lib/flor/punit/concurrence.rb +43 -200
- data/lib/flor/punit/graft.rb +3 -2
- data/lib/flor/punit/m_ram.rb +281 -0
- data/lib/flor/unit.rb +1 -0
- data/lib/flor/unit/caller.rb +6 -1
- data/lib/flor/unit/executor.rb +17 -4
- data/lib/flor/unit/ganger.rb +12 -1
- data/lib/flor/unit/hloader.rb +251 -0
- data/lib/flor/unit/hook.rb +74 -15
- data/lib/flor/unit/hooker.rb +9 -12
- data/lib/flor/unit/loader.rb +41 -17
- data/lib/flor/unit/models.rb +54 -18
- data/lib/flor/unit/models/execution.rb +15 -4
- data/lib/flor/unit/models/pointer.rb +11 -0
- data/lib/flor/unit/scheduler.rb +126 -30
- data/lib/flor/unit/spooler.rb +5 -3
- data/lib/flor/unit/storage.rb +40 -13
- data/lib/flor/unit/waiter.rb +165 -26
- data/lib/flor/unit/wlist.rb +98 -5
- metadata +10 -4
- data/lib/flor/punit/cmap.rb +0 -112
data/lib/flor/unit/spooler.rb
CHANGED
@@ -43,9 +43,11 @@ module Flor
|
|
43
43
|
|
44
44
|
def determine_spool_dir
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
r = @unit.conf['root']
|
47
|
+
return nil unless r
|
48
|
+
|
49
|
+
d = File.join(r, @unit.conf['spo_dir'] || 'var/spool')
|
50
|
+
return nil unless File.directory?(d)
|
49
51
|
|
50
52
|
d
|
51
53
|
end
|
data/lib/flor/unit/storage.rb
CHANGED
@@ -80,14 +80,25 @@ module Flor
|
|
80
80
|
opts[:current] ||= from if from.is_a?(Integer)
|
81
81
|
#
|
82
82
|
# defaults for the migration version table:
|
83
|
-
|
84
|
-
|
83
|
+
# { table: :schema_info,
|
84
|
+
# column: :version }
|
85
|
+
|
86
|
+
skip =
|
87
|
+
@unit.conf['db_sparse_migrations'] ||
|
88
|
+
@unit.conf['sto_sparse_migrations'] ||
|
89
|
+
opts[:sparse_migrations]
|
90
|
+
if skip && ! opts.has_key?(:allow_missing_migration_files)
|
91
|
+
opts[:allow_missing_migration_files] = true
|
92
|
+
end
|
85
93
|
|
86
94
|
dir =
|
87
95
|
@unit.conf['db_migrations'] ||
|
88
|
-
|
89
|
-
|
90
|
-
|
96
|
+
@unit.conf['db_migration_dir'] ||
|
97
|
+
@unit.conf['sto_migrations'] ||
|
98
|
+
@unit.conf['sto_migration_dir'] ||
|
99
|
+
opts[:migrations] ||
|
100
|
+
opts[:migration_dir] ||
|
101
|
+
Flor.migration_dir
|
91
102
|
|
92
103
|
synchronize do
|
93
104
|
|
@@ -664,7 +675,9 @@ module Flor
|
|
664
675
|
|
665
676
|
def update_pointers(exe, status, now)
|
666
677
|
|
667
|
-
#
|
678
|
+
# Q Should we archive old pointers?
|
679
|
+
# Well, it might be better to only archive the execution and leave
|
680
|
+
# in there enough information...
|
668
681
|
|
669
682
|
exid = exe['exid']
|
670
683
|
|
@@ -694,9 +707,12 @@ module Flor
|
|
694
707
|
|
695
708
|
vs = nid == '0' ? node['vars'] : nil
|
696
709
|
vs.each { |k, v|
|
697
|
-
case v; when
|
710
|
+
case v; when Numeric, String, TrueClass, FalseClass, NilClass
|
698
711
|
a << [ dom, exid, '0', 'var', k, v.to_s, now, u ]
|
699
|
-
when
|
712
|
+
when Array, Hash
|
713
|
+
s = '(array)'; s = '(object)' if v.is_a?(Hash)
|
714
|
+
a << [ dom, exid, '0', 'var', k, s, now, u ]
|
715
|
+
else
|
700
716
|
a << [ dom, exid, '0', 'var', k, nil, now, u ]
|
701
717
|
end } if vs
|
702
718
|
|
@@ -793,19 +809,30 @@ module Flor
|
|
793
809
|
@db.loggers << @db_logger
|
794
810
|
end
|
795
811
|
|
796
|
-
|
812
|
+
class << self
|
813
|
+
|
814
|
+
def to_blob(h)
|
797
815
|
|
798
|
-
|
816
|
+
h ? Sequel.blob(Zlib::Deflate.deflate(JSON.dump(h))) : nil
|
799
817
|
#rescue => e; pp h; raise e
|
800
|
-
|
818
|
+
end
|
801
819
|
|
802
|
-
|
820
|
+
def from_blob(content)
|
803
821
|
|
804
|
-
|
822
|
+
content ? JSON.parse(Zlib::Inflate.inflate(content)) : nil
|
823
|
+
end
|
805
824
|
end
|
806
825
|
|
807
826
|
def to_blob(h); self.class.to_blob(h); end
|
808
827
|
def from_blob(content); self.class.from_blob(content); end
|
809
828
|
end
|
829
|
+
|
830
|
+
# module Flor
|
831
|
+
|
832
|
+
class << self
|
833
|
+
|
834
|
+
def to_blob(h); ::Flor::Storage.to_blob(h); end
|
835
|
+
def from_blob(content); ::Flor::Storage.from_blob(content); end
|
836
|
+
end
|
810
837
|
end
|
811
838
|
|
data/lib/flor/unit/waiter.rb
CHANGED
@@ -5,11 +5,12 @@ module Flor
|
|
5
5
|
|
6
6
|
def initialize(exid, opts)
|
7
7
|
|
8
|
-
serie, timeout, on_timeout
|
8
|
+
serie, timeout, on_timeout =
|
9
9
|
expand_args(opts)
|
10
10
|
|
11
|
+
# TODO fail if the serie mixes msg_waiting with row_waiting...
|
12
|
+
|
11
13
|
@exid = exid
|
12
|
-
@original_serie = repeat ? Flor.dup(serie) : nil
|
13
14
|
@serie = serie
|
14
15
|
@timeout = timeout
|
15
16
|
@on_timeout = on_timeout
|
@@ -17,39 +18,81 @@ module Flor
|
|
17
18
|
@queue = []
|
18
19
|
@mutex = Mutex.new
|
19
20
|
@var = ConditionVariable.new
|
21
|
+
|
22
|
+
@executor = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
ROW_PSEUDO_POINTS = %w[ status tag tasker var variable ]
|
26
|
+
# "tasker", not "task", since "task" is already a message point
|
27
|
+
|
28
|
+
def row_waiter?
|
29
|
+
|
30
|
+
@serie.find { |_, points|
|
31
|
+
points.find { |po|
|
32
|
+
pos = po.split(':')
|
33
|
+
pos.length > 1 && ROW_PSEUDO_POINTS.include?(pos[0]) } }
|
34
|
+
end
|
35
|
+
|
36
|
+
def msg_waiter?
|
37
|
+
|
38
|
+
@serie.find { |_, points|
|
39
|
+
points.find { |po|
|
40
|
+
! ROW_PSEUDO_POINTS.include?(po.split(':').first) } }
|
20
41
|
end
|
21
42
|
|
22
43
|
def to_s
|
23
44
|
|
24
|
-
"#{super[0..-2]}#{
|
25
|
-
{ exid: @exid,
|
26
|
-
original_serie: @original_serie,
|
27
|
-
timeout: @timeout }.inspect
|
28
|
-
}>"
|
45
|
+
"#{super[0..-2]}#{{ exid: @exid, timeout: @timeout }.inspect}>"
|
29
46
|
end
|
30
47
|
|
31
48
|
def notify(executor, message)
|
32
49
|
|
50
|
+
@executor = executor
|
51
|
+
# could be handy
|
52
|
+
|
33
53
|
@mutex.synchronize do
|
34
54
|
|
35
55
|
return false unless match?(message)
|
36
56
|
|
37
57
|
@serie.shift
|
38
|
-
return false
|
58
|
+
return false if @serie.any?
|
39
59
|
|
40
60
|
@queue << [ executor, message ]
|
41
61
|
@var.signal
|
42
62
|
end
|
43
63
|
|
44
|
-
#
|
45
|
-
|
46
|
-
|
64
|
+
true # serie over, remove me
|
65
|
+
end
|
66
|
+
|
67
|
+
def check(unit, rs)
|
68
|
+
|
69
|
+
@mutex.synchronize do
|
47
70
|
|
48
|
-
|
71
|
+
row = nil
|
49
72
|
|
50
|
-
|
73
|
+
loop do
|
51
74
|
|
52
|
-
|
75
|
+
break if @serie.empty?
|
76
|
+
|
77
|
+
row = row_match?(unit, rs)
|
78
|
+
return false unless row
|
79
|
+
|
80
|
+
@serie.shift
|
81
|
+
end
|
82
|
+
|
83
|
+
@queue << [ unit, row ]
|
84
|
+
@var.signal
|
85
|
+
end
|
86
|
+
|
87
|
+
true # serie over, remove me
|
88
|
+
|
89
|
+
rescue => err
|
90
|
+
|
91
|
+
#puts "!" * 80; p err
|
92
|
+
unit.logger.warn(
|
93
|
+
"#{self.class}#check()", err, '(returning true, aka remove me)')
|
94
|
+
|
95
|
+
true # remove me
|
53
96
|
end
|
54
97
|
|
55
98
|
def wait
|
@@ -63,7 +106,8 @@ module Flor
|
|
63
106
|
|
64
107
|
if @queue.empty?
|
65
108
|
fail RuntimeError.new(
|
66
|
-
"timeout for #{self.to_s}"
|
109
|
+
"timeout for #{self.to_s}, " +
|
110
|
+
"msg_waiter? #{ !! msg_waiter?}, row_waiter? #{ !! row_waiter?}"
|
67
111
|
) if @on_timeout == 'fail'
|
68
112
|
return { 'exid' => @exid, 'timed_out' => @on_timeout }
|
69
113
|
end
|
@@ -73,6 +117,37 @@ module Flor
|
|
73
117
|
end
|
74
118
|
end
|
75
119
|
|
120
|
+
def to_query_hashes
|
121
|
+
|
122
|
+
@serie
|
123
|
+
.inject([ [], [] ]) { |a, (nid, points)|
|
124
|
+
|
125
|
+
points.each do |point|
|
126
|
+
|
127
|
+
ss = point.split(':')
|
128
|
+
|
129
|
+
h = {}
|
130
|
+
h[:exid] = @exid if @exid
|
131
|
+
h[:nid] = nid if nid
|
132
|
+
|
133
|
+
case ss[0]
|
134
|
+
when 'status'
|
135
|
+
h[:status] = ss[1]
|
136
|
+
a[0] << h
|
137
|
+
when 'tag', 'tasker', 'var', 'variable'
|
138
|
+
t = ss[0]; t = 'var' if t == 'variable'
|
139
|
+
h[:type] = t
|
140
|
+
h[:name] = ss[1]
|
141
|
+
h[:value] = ss[2] if ss[2]
|
142
|
+
a[1] << h
|
143
|
+
else
|
144
|
+
fail ArgumentError, "cannot turn to query_hash, #{self.inspect}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
a }
|
149
|
+
end
|
150
|
+
|
76
151
|
protected
|
77
152
|
|
78
153
|
def match?(message)
|
@@ -85,15 +160,70 @@ module Flor
|
|
85
160
|
mnid = message['nid']
|
86
161
|
|
87
162
|
return false if nid && mnid && nid != mnid
|
88
|
-
|
163
|
+
|
164
|
+
return false unless points.find { |point|
|
165
|
+
ps = point.split(':')
|
166
|
+
next false if ps[0] != mpoint
|
167
|
+
next false if ps[1] && ! message['tags'].include?(ps[1])
|
168
|
+
true }
|
89
169
|
|
90
170
|
true
|
91
171
|
end
|
92
172
|
|
173
|
+
def row_match?(unit, rs)
|
174
|
+
|
175
|
+
nid, points = @serie.first
|
176
|
+
|
177
|
+
row = nil
|
178
|
+
|
179
|
+
points.find { |point|
|
180
|
+
ps = point.split(':')
|
181
|
+
row = send("row_match_#{ps[0]}?", unit, rs, nid, ps[1..-1]) }
|
182
|
+
|
183
|
+
row
|
184
|
+
end
|
185
|
+
|
186
|
+
def row_match_status?(unit, rs, _, cdr)
|
187
|
+
|
188
|
+
rs[0].find { |exe|
|
189
|
+
(@exid == nil || exe.exid == @exid) &&
|
190
|
+
exe.status == cdr.first }
|
191
|
+
end
|
192
|
+
|
193
|
+
def row_match_tag?(unit, rs, nid, (name, value))
|
194
|
+
|
195
|
+
rs[1].find { |ptr|
|
196
|
+
ptr.type == 'tag' &&
|
197
|
+
(@exid == nil || ptr.exid == @exid) &&
|
198
|
+
(nid == nil || ptr.nid == nid) &&
|
199
|
+
(name == nil || ptr.name == name) &&
|
200
|
+
(value == nil || ptr.value == value) }
|
201
|
+
end
|
202
|
+
|
203
|
+
def row_match_var?(unit, rs, nid, (name, value))
|
204
|
+
|
205
|
+
rs[1].find { |ptr|
|
206
|
+
ptr.type == 'var' &&
|
207
|
+
(@exid == nil || ptr.exid == @exid) &&
|
208
|
+
(nid == nil || ptr.nid == nid) &&
|
209
|
+
(name == nil || ptr.name == name) &&
|
210
|
+
(value == nil || ptr.value == value.to_s) }
|
211
|
+
end
|
212
|
+
alias row_match_variable? row_match_var?
|
213
|
+
|
214
|
+
def row_match_tasker?(unit, rs, nid, (name, value))
|
215
|
+
|
216
|
+
rs[1].find { |ptr|
|
217
|
+
ptr.type == 'tasker' &&
|
218
|
+
(@exid == nil || ptr.exid == @exid) &&
|
219
|
+
(nid == nil || ptr.nid == nid) &&
|
220
|
+
(name == nil || ptr.name == name) &&
|
221
|
+
(value == nil || ptr.value == value) }
|
222
|
+
end
|
223
|
+
|
93
224
|
def expand_args(opts)
|
94
225
|
|
95
226
|
owait = opts[:wait]
|
96
|
-
orepeat = opts[:repeat] || false
|
97
227
|
otimeout = opts[:timeout]
|
98
228
|
oontimeout = opts[:on_timeout] || opts[:ontimeout] || 'fail'
|
99
229
|
|
@@ -101,33 +231,42 @@ module Flor
|
|
101
231
|
when nil, true
|
102
232
|
[ [ [ nil, %w[ failed terminated ] ] ], # serie
|
103
233
|
otimeout,
|
104
|
-
oontimeout
|
105
|
-
orepeat ]
|
234
|
+
oontimeout ]
|
106
235
|
when Numeric
|
107
236
|
[ [ [ nil, %w[ failed terminated ] ] ], # serie
|
108
237
|
owait, # timeout
|
109
|
-
oontimeout
|
110
|
-
orepeat ]
|
238
|
+
oontimeout ]
|
111
239
|
when String, Array
|
112
240
|
[ parse_serie(owait), # serie
|
113
241
|
otimeout,
|
114
|
-
oontimeout
|
115
|
-
orepeat ]
|
242
|
+
oontimeout ]
|
116
243
|
else
|
117
244
|
fail ArgumentError.new(
|
118
245
|
"don't know how to deal with #{owait.inspect} (#{owait.class})")
|
119
246
|
end
|
120
247
|
end
|
121
248
|
|
249
|
+
WAIT_REX =
|
250
|
+
%r{
|
251
|
+
\A
|
252
|
+
([0-9_\-]+)?[ ]*
|
253
|
+
(
|
254
|
+
[a-z]+(?::[^:|;,\s]+){0,2}
|
255
|
+
(?:[|, ][a-z]+(:[^:|;,\s]+){0,2})*
|
256
|
+
)\z
|
257
|
+
}x
|
258
|
+
|
122
259
|
def parse_serie(s)
|
123
260
|
|
124
261
|
return s if s.is_a?(Array) && s.collect(&:class).uniq == [ Array ]
|
125
262
|
|
126
263
|
(s.is_a?(String) ? s.split(';') : s)
|
127
264
|
.collect { |ss|
|
128
|
-
|
129
|
-
|
130
|
-
|
265
|
+
m = ss.strip.match(WAIT_REX)
|
266
|
+
fail ArgumentError.new(
|
267
|
+
"cannot parse #{ss.strip.inspect} wait directive") unless m
|
268
|
+
ni, pt = m[1, 2]
|
269
|
+
[ ni, pt.split(/[|,]/).collect(&:strip) ] }
|
131
270
|
end
|
132
271
|
end
|
133
272
|
end
|
data/lib/flor/unit/wlist.rb
CHANGED
@@ -7,6 +7,9 @@ module Flor
|
|
7
7
|
#
|
8
8
|
# `wtl_default_timeout`:
|
9
9
|
# when #launch ing or #wait ing, set the default timeout, in seconds
|
10
|
+
#
|
11
|
+
# `wtl_row_frequency`:
|
12
|
+
# sleep time between row waiter checks, defaults to 1
|
10
13
|
|
11
14
|
DEFAULT_TIMEOUT = Flor.env_i('FLOR_DEFAULT_TIMEOUT')
|
12
15
|
|
@@ -16,16 +19,25 @@ module Flor
|
|
16
19
|
@unit.hooker.add('wlist', self)
|
17
20
|
|
18
21
|
@mutex = Mutex.new
|
19
|
-
@
|
22
|
+
@msg_waiters = []
|
23
|
+
@row_waiters = []
|
20
24
|
|
21
25
|
@unit.instance_eval do
|
22
26
|
def wait(exid, opts=true, more=nil)
|
23
27
|
@hooker['wlist'].wait(exid, opts, more)
|
24
28
|
end
|
25
29
|
end
|
30
|
+
|
31
|
+
@row_thread = nil
|
32
|
+
@row_thread_status = nil
|
33
|
+
@row_frequency = @unit.conf['wtl_row_frequency'] || 1
|
26
34
|
end
|
27
35
|
|
28
36
|
def shutdown
|
37
|
+
|
38
|
+
@row_thread_status = :shutdown
|
39
|
+
|
40
|
+
nil
|
29
41
|
end
|
30
42
|
|
31
43
|
def notify(executor, message)
|
@@ -34,12 +46,12 @@ module Flor
|
|
34
46
|
|
35
47
|
to_remove = []
|
36
48
|
|
37
|
-
@
|
49
|
+
@msg_waiters.each do |w|
|
38
50
|
remove = w.notify(executor, message)
|
39
51
|
to_remove << w if remove
|
40
52
|
end
|
41
53
|
|
42
|
-
@
|
54
|
+
@msg_waiters -= to_remove
|
43
55
|
|
44
56
|
end if message['consumed']
|
45
57
|
|
@@ -49,7 +61,7 @@ module Flor
|
|
49
61
|
def wait(exid, opts, more)
|
50
62
|
|
51
63
|
exid, opts =
|
52
|
-
if opts == true && exid
|
64
|
+
if opts == true && ! Flor.is_exid?(exid)
|
53
65
|
[ nil, { wait: exid } ]
|
54
66
|
elsif opts == true || opts.is_a?(String)
|
55
67
|
[ exid, { wait: opts } ]
|
@@ -65,11 +77,92 @@ module Flor
|
|
65
77
|
|
66
78
|
@mutex.synchronize do
|
67
79
|
|
68
|
-
|
80
|
+
waiter = Waiter.new(exid, opts)
|
81
|
+
|
82
|
+
fail ArgumentError.new(
|
83
|
+
"unit is stopped, it cannot wait for #{[ exid, opts ].inspect}"
|
84
|
+
) if waiter.msg_waiter? && @unit.stopped?
|
85
|
+
|
86
|
+
waiters = [ @msg_waiters, @row_waiters ]
|
87
|
+
ts = [ 'msg', 'row' ]
|
88
|
+
if waiter.row_waiter?; waiters.reverse!; ts.reverse!; end
|
89
|
+
|
90
|
+
fail ArgumentError.new(
|
91
|
+
"cannot add a #{ts[0]} waiter, since there are already #{ts[1]} ones"
|
92
|
+
) if waiters[1].any?
|
93
|
+
|
94
|
+
waiters.first << waiter
|
95
|
+
|
96
|
+
start_row_thread if @row_waiters.any?
|
97
|
+
|
98
|
+
waiter
|
69
99
|
|
70
100
|
end.wait
|
71
101
|
# returns the response message
|
72
102
|
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
def start_row_thread
|
107
|
+
|
108
|
+
return if @row_thread_status == :shutdown
|
109
|
+
|
110
|
+
@row_thread = nil if @row_thread && ! @row_thread.alive?
|
111
|
+
@row_thread_status = :running
|
112
|
+
|
113
|
+
@row_thread ||=
|
114
|
+
Thread.new do
|
115
|
+
loop do
|
116
|
+
sleep(@row_frequency)
|
117
|
+
break if [ :stop, :shutdown ].include?(@row_thread_status)
|
118
|
+
break if @row_waiters.empty?
|
119
|
+
check
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def check
|
125
|
+
|
126
|
+
@mutex.synchronize do
|
127
|
+
|
128
|
+
to_remove = []
|
129
|
+
|
130
|
+
rs = row_query_all
|
131
|
+
|
132
|
+
@row_waiters.each do |w|
|
133
|
+
remove = w.check(@unit, rs)
|
134
|
+
to_remove << w if remove
|
135
|
+
end
|
136
|
+
|
137
|
+
@row_waiters -= to_remove
|
138
|
+
end
|
139
|
+
|
140
|
+
rescue => err
|
141
|
+
|
142
|
+
@unit.logger.warn("#{self.class}#check()", err)
|
143
|
+
end
|
144
|
+
|
145
|
+
def row_query_all
|
146
|
+
|
147
|
+
exes, ptrs =
|
148
|
+
@row_waiters.inject([ [], [] ]) { |a, w|
|
149
|
+
es, ps = w.to_query_hashes
|
150
|
+
a[0].concat(es)
|
151
|
+
a[1].concat(ps)
|
152
|
+
a }
|
153
|
+
|
154
|
+
[ row_query(:executions, exes), row_query(:pointers, ptrs) ]
|
155
|
+
end
|
156
|
+
|
157
|
+
def row_query(klass, hs)
|
158
|
+
|
159
|
+
return [] if hs.empty?
|
160
|
+
|
161
|
+
q = @unit.send(klass).where(hs.shift)
|
162
|
+
hs.each { |h| q = q.or(h) }
|
163
|
+
|
164
|
+
q.all
|
165
|
+
end
|
73
166
|
end
|
74
167
|
end
|
75
168
|
|