skynet 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/History.txt +4 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +65 -0
  4. data/README.txt +100 -0
  5. data/Rakefile +4 -0
  6. data/app_generators/skynet_install/USAGE +5 -0
  7. data/app_generators/skynet_install/skynet_install_generator.rb +84 -0
  8. data/app_generators/skynet_install/templates/migration.rb +60 -0
  9. data/app_generators/skynet_install/templates/skynet +33 -0
  10. data/app_generators/skynet_install/templates/skynet_console +16 -0
  11. data/bin/skynet +20 -0
  12. data/bin/skynet_console +9 -0
  13. data/bin/skynet_install +12 -0
  14. data/bin/skynet_tuplespace_server +53 -0
  15. data/config/hoe.rb +74 -0
  16. data/config/requirements.rb +17 -0
  17. data/lib/skynet.rb +34 -0
  18. data/lib/skynet/mapreduce_test.rb +25 -0
  19. data/lib/skynet/message_queue_adapters/message_queue_adapter.rb +70 -0
  20. data/lib/skynet/message_queue_adapters/mysql.rb +573 -0
  21. data/lib/skynet/message_queue_adapters/tuple_space.rb +327 -0
  22. data/lib/skynet/skynet_active_record_extensions.rb +237 -0
  23. data/lib/skynet/skynet_config.rb +59 -0
  24. data/lib/skynet/skynet_console.rb +34 -0
  25. data/lib/skynet/skynet_console_helper.rb +59 -0
  26. data/lib/skynet/skynet_debugger.rb +84 -0
  27. data/lib/skynet/skynet_guid_generator.rb +68 -0
  28. data/lib/skynet/skynet_job.rb +607 -0
  29. data/lib/skynet/skynet_launcher.rb +10 -0
  30. data/lib/skynet/skynet_logger.rb +52 -0
  31. data/lib/skynet/skynet_manager.rb +486 -0
  32. data/lib/skynet/skynet_message.rb +366 -0
  33. data/lib/skynet/skynet_message_queue.rb +100 -0
  34. data/lib/skynet/skynet_ruby_extensions.rb +36 -0
  35. data/lib/skynet/skynet_task.rb +76 -0
  36. data/lib/skynet/skynet_tuplespace_server.rb +82 -0
  37. data/lib/skynet/skynet_worker.rb +395 -0
  38. data/lib/skynet/version.rb +9 -0
  39. data/log/debug.log +0 -0
  40. data/log/skynet.log +29 -0
  41. data/log/skynet_tuplespace_server.log +7 -0
  42. data/log/skynet_worker.pid +1 -0
  43. data/script/destroy +14 -0
  44. data/script/generate +14 -0
  45. data/script/txt2html +74 -0
  46. data/setup.rb +1585 -0
  47. data/sometest.rb +23 -0
  48. data/tasks/deployment.rake +34 -0
  49. data/tasks/environment.rake +7 -0
  50. data/tasks/website.rake +17 -0
  51. data/test/all_models_test.rb +139 -0
  52. data/test/mysql_message_queue_adaptor_test.rb +199 -0
  53. data/test/skynet_manager_test.rb +107 -0
  54. data/test/skynet_message_test.rb +42 -0
  55. data/test/test_generator_helper.rb +20 -0
  56. data/test/test_helper.rb +2 -0
  57. data/test/test_skynet.rb +11 -0
  58. data/test/test_skynet_install_generator.rb +53 -0
  59. data/test/tuplespace_message_queue_test.rb +179 -0
  60. data/tmtags +1242 -0
  61. data/website/index.html +93 -0
  62. data/website/index.txt +39 -0
  63. data/website/javascripts/rounded_corners_lite.inc.js +285 -0
  64. data/website/stylesheets/screen.css +138 -0
  65. data/website/template.rhtml +48 -0
  66. metadata +129 -0
@@ -0,0 +1,327 @@
1
+ require 'rinda/tuplespace'
2
+ class Rinda::TupleSpaceProxy
3
+ def take(tuple, sec=nil, &block)
4
+ port = []
5
+ port.push @ts.move(nil, tuple, sec, &block)
6
+ port[0]
7
+ end
8
+ end
9
+
10
+ class Skynet
11
+ class Error < StandardError
12
+ end
13
+
14
+ class RequestExpiredError < Skynet::Error
15
+ end
16
+
17
+ class InvalidMessage < Skynet::Error
18
+ end
19
+
20
+ class MessageQueueAdapter
21
+
22
+ class TupleSpace < Skynet::MessageQueueAdapter
23
+
24
+ include SkynetDebugger
25
+
26
+ USE_FALLBACK_TASKS = true
27
+
28
+ @@ts = nil
29
+ @@curhostidx = 0
30
+
31
+ def self.debug_class_desc
32
+ "TUPLESPACE"
33
+ end
34
+
35
+ def self.adapter
36
+ :tuplespace
37
+ end
38
+
39
+ def initialize(ts=nil)
40
+ if not ts
41
+ ts = self.class.get_tuple_space
42
+ end
43
+ @ts = ts
44
+ end
45
+
46
+ def take_next_task(curver,timeout=nil,payload_type=nil)
47
+ message = Skynet::Message.new(take(Skynet::Message.next_task_template(curver,payload_type),timeout))
48
+ write_fallback_task(message)
49
+ message
50
+ end
51
+
52
+ def write_message(message,timeout=nil)
53
+ write(message,timeout)
54
+ end
55
+
56
+ def write_result(message,result=[],timeout=nil)
57
+ result_message = message.result_message(result).to_a
58
+ write(result_message,timeout)
59
+ take_fallback_message(message)
60
+ result_message
61
+ end
62
+
63
+ def take_result(job_id,timeout=nil)
64
+ Skynet::Message.new(take(Skynet::Message.result_template(job_id),timeout))
65
+ end
66
+
67
+ def write_error(message,error='',timeout=nil)
68
+ write(message.error_message(error),timeout)
69
+ take_fallback_message(message)
70
+ end
71
+
72
+
73
+ def write_worker_status(task, timeout=nil)
74
+ begin
75
+ take_worker_status(task,0.00001)
76
+ rescue Skynet::RequestExpiredError
77
+ end
78
+ write(Skynet::WorkerStatusMessage.new(task), timeout)
79
+ end
80
+
81
+ def take_worker_status(task, timeout=nil)
82
+ Skynet::WorkerStatusMessage.new(take(Skynet::WorkerStatusMessage.worker_status_template(task), timeout))
83
+ end
84
+
85
+ def read_all_worker_statuses(hostname=nil)
86
+ ws = Skynet::WorkerStatusMessage.all_workers_template(hostname)
87
+ workers = read_all(ws).collect{ |w| Skynet::WorkerStatusMessage.new(w) }#.sort{ |a,b| a.process_id <=> b.process_id }
88
+ end
89
+
90
+ def clear_worker_status(hostname=nil)
91
+ cnt = 0
92
+ begin
93
+ loop do
94
+ take(Skynet::WorkerStatusMessage.new([:status, :worker, hostname, nil, nil]),0.01)
95
+ cnt += 1
96
+ end
97
+ rescue Skynet::RequestExpiredError
98
+ end
99
+ cnt
100
+ end
101
+
102
+ def list_tasks(iteration=nil)
103
+ read_all(Skynet::Message.outstanding_tasks_template(iteration))
104
+ end
105
+
106
+ def list_results
107
+ read_all(Skynet::Message.outstanding_results_template)
108
+ end
109
+
110
+ def get_worker_version
111
+ begin
112
+ message = Skynet::WorkerVersionMessage.new(read(Skynet::WorkerVersionMessage.template,0.00001))
113
+ if message
114
+ curver = message.version
115
+ else
116
+ curver=0
117
+ end
118
+ rescue Skynet::RequestExpiredError => e
119
+ curver = 0
120
+ end
121
+ curver
122
+ end
123
+
124
+ def set_worker_version(ver=nil)
125
+ begin
126
+ messages = read_all(Skynet::WorkerVersionMessage.template).collect {|ret| Skynet::WorkerVersionMessage.new(ret)}
127
+ curver = 0
128
+ messages.each do |message|
129
+ curver = message.version
130
+ debug "CURRENT WORKER VERSION #{curver}"
131
+ curvmessage = Skynet::WorkerVersionMessage.new(take(message.template,0.00001))
132
+ if curvmessage
133
+ curver = curvmessage.version
134
+ else
135
+ curver=0
136
+ end
137
+ end
138
+ rescue Skynet::RequestExpiredError => e
139
+ curver = 0
140
+ end
141
+
142
+ newver = ver ? ver : curver + 1
143
+ debug "WRITING CURRENT WORKER REV #{newver}"
144
+ write(Skynet::WorkerVersionMessage.new(:version=>newver))
145
+ newver
146
+ end
147
+
148
+ def clear_outstanding_tasks
149
+ begin
150
+ tasks = read_all(Skynet::Message.outstanding_tasks_template)
151
+ rescue DRb::DRbConnError, Errno::ECONNREFUSED => e
152
+ error "ERROR #{e.inspect}", caller
153
+ end
154
+
155
+ tasks.size.times do |ii|
156
+ take(Skynet::Message.outstanding_tasks_template,0.00001)
157
+ end
158
+
159
+ results = read_all(Skynet::Message.outstanding_results_template)
160
+ results.size.times do |ii|
161
+ take(Skynet::Message.outstanding_results_template,0.00001)
162
+ end
163
+
164
+ task_tuples = read_all(Skynet::Message.outstanding_tasks_template)
165
+ result_tuples = read_all(Skynet::Message.outstanding_results_template)
166
+ return task_tuples + result_tuples
167
+ end
168
+
169
+ def stats
170
+ t1 = Time.now
171
+ tasks = list_tasks
172
+ results = list_results
173
+ t2 = Time.now - t1
174
+ p_tasks = tasks.partition {|task| task[9] == 0}
175
+ {:taken_tasks => p_tasks[1].size, :untaken_tasks => p_tasks[0].size, :results => list_results.size, :time => t2.to_f}
176
+ end
177
+
178
+ private
179
+
180
+ attr_accessor :ts
181
+
182
+ def write(tuple,timeout=nil)
183
+ ts_command(:write,tuple,timeout)
184
+ end
185
+
186
+ def take(template,timeout=nil)
187
+ ts_command(:take,template,timeout)
188
+ end
189
+
190
+ def read(template,timeout=nil)
191
+ ts_command(:read,template,timeout)
192
+ end
193
+
194
+ def read_all(template)
195
+ ts_command(:read_all,template)
196
+ end
197
+
198
+ ###### FALLBACK METHODS
199
+ def write_fallback_task(message)
200
+ return unless USE_FALLBACK_TASKS
201
+ debug "4 WRITING BACKUP TASK #{message.task_id}", @fallback_worker_message
202
+ ftm = message.fallback_task_message
203
+ debug "WRITE FALLBACK TASK", ftm.to_a
204
+ timeout = message.expiry * 8
205
+ write(ftm,timeout)
206
+ ftm
207
+ end
208
+
209
+ def take_fallback_message(message,timeout=0.01)
210
+ return unless USE_FALLBACK_TASKS
211
+ begin
212
+ # debug "LOOKING FOR FALLBACK TEMPLATE", message.fallback_template
213
+ fb_message = Skynet::Message.new(take(message.fallback_template,timeout))
214
+ # debug "TOOK FALLBACK MESSAGE for TASKID: #{fb_message.task_id}"
215
+ rescue Skynet::RequestExpiredError => e
216
+ error "Couldn't find expected FALLBACK MESSAGE"
217
+ end
218
+ end
219
+ ## END FALLBACK METHODS
220
+
221
+ def ts_command(command,message,timeout=nil)
222
+ # tries = 0
223
+ # until(tries > 3)
224
+ if message.is_a?(Skynet::Message)
225
+ tuple = message.to_a
226
+ elsif message.is_a?(Array)
227
+ tuple = message
228
+ else
229
+ raise InvalidMessage.new("You must provide a valid Skynet::Message object when calling #{command}. You passed #{message.inspect}.")
230
+ end
231
+
232
+ begin
233
+ if command==:read_all
234
+ return ts.send(command,tuple)
235
+ else
236
+ return ts.send(command,tuple,timeout)
237
+ end
238
+
239
+ rescue Rinda::RequestExpiredError
240
+ raise Skynet::RequestExpiredError.new
241
+ rescue DRb::DRbConnError => e
242
+ begin
243
+ log.error "Couldnt run command [#{command}] on tuplespace."
244
+ @ts = self.class.get_tuple_space
245
+ raise Skynet::ConnectionError.new("Can't find ring finger. #{e.inspect}")
246
+ # tries += 1
247
+ # next
248
+ rescue Skynet::ConnectionError => e
249
+ raise Skynet::ConnectionError.new("Can't find ring finger. #{e.inspect}")
250
+ # rescue RuntimeError => e
251
+ # raise Skynet::ConnectionError.new("Can't find ring finger. #{}")
252
+ rescue DRb::DRbConnError, Errno::ECONNREFUSED => e
253
+ raise Skynet::ConnectionError.new("There was a problem conected to the #{self.class} #{e.class} #{e.message}")
254
+ end
255
+ end
256
+ # end
257
+ end
258
+
259
+ ####################################
260
+ ######## CLASS METHODS #############
261
+ ####################################
262
+
263
+ ### XXX ACCEPT MULTIPLE TUPLE SPACES and a flag whether to use replication or failover.
264
+
265
+ def self.get_tuple_space
266
+ return @@ts if is_valid_tuplespace?(@@ts)
267
+ loop do
268
+ begin
269
+ DRb.start_service
270
+ if Skynet::CONFIG[:USE_RINGSERVER]
271
+ Skynet::CONFIG[:SERVER_HOSTS][@@curhostidx] =~ /(.+):(\d+)/
272
+ host = $1
273
+ port = $2.to_i
274
+ @@ts = connect_to_tuple_space(host,port)
275
+ else
276
+ drburi = Skynet::CONFIG[:TUPLESPACE_DRBURIS].first
277
+ drburi = "druby://#{drburi}" unless drburi =~ %r{druby://}
278
+ @@ts = get_tuple_space_from_drburi(drburi)
279
+ log.info "#{self} CONNECTED TO #{drburi}"
280
+ end
281
+ return @@ts
282
+ rescue RuntimeError => e
283
+ if Skynet::CONFIG[:SERVER_HOSTS][@@curhostidx + 1]
284
+ log.error "#{self} Couldn't connect to #{Skynet::CONFIG[:SERVER_HOSTS][@@curhostidx]} trying #{Skynet::CONFIG[:SERVER_HOSTS][@@curhostidx+1]}"
285
+ @@curhostidx += 1
286
+ next
287
+ else
288
+ raise Skynet::ConnectionError.new("Can't find ring finger @ #{Skynet::CONFIG[:SERVER_HOSTS][@@curhostidx]}. #{e.class} #{e.message}")
289
+ end
290
+ rescue Exception => e
291
+ raise Skynet::ConnectionError.new("Error getting tuplespace @ #{Skynet::CONFIG[:SERVER_HOSTS][@@curhostidx]}. #{e.class} #{e.message}")
292
+ end
293
+ end
294
+ return @@ts
295
+ end
296
+
297
+ def self.connect_to_tuple_space(host,port)
298
+ log.info "#{self} trying to connect to #{host}:#{port}"
299
+ if Skynet::CONFIG[:USE_RINGSERVER]
300
+ ring_finger = Rinda::RingFinger.new(host,port)
301
+ ring_server = ring_finger.lookup_ring_any(0.5)
302
+
303
+ ringts = ring_server.read([:name, :TupleSpace, nil, nil],0.00005)[2]
304
+ ts = Rinda::TupleSpaceProxy.new(ringts)
305
+ else
306
+ ts = get_tuple_space_from_drburi("druby://#{host}:#{port}")
307
+ end
308
+ log.info "#{self} CONNECTED TO #{host}:#{port}"
309
+ ts
310
+ end
311
+
312
+ def self.get_tuple_space_from_drburi(drburi)
313
+ DRbObject.new(nil, drburi)
314
+ end
315
+
316
+ def self.is_valid_tuplespace?(ts)
317
+ return false unless ts
318
+ begin
319
+ ts.read_all([:valid])
320
+ return true
321
+ rescue DRb::DRbConnError, RuntimeError, Errno::ECONNREFUSED => e
322
+ return false
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,237 @@
1
+ class ActiveRecord::Base
2
+ def send_later(method,opts=nil,save=nil)
3
+ raise NoMethodError.new("Method: #{method} doesn't exist in #{self.class}") unless self.respond_to?(method)
4
+ data = {
5
+ :model_class => self.class.to_s,
6
+ :model_id => self.id,
7
+ :method => method,
8
+ }
9
+ data[:save] = 1 if save
10
+ data[:opts] = opts.to_yaml if opts
11
+
12
+ jobopts = {
13
+ :single => true,
14
+ :map_tasks => 1,
15
+ :map_data => [data],
16
+ :name => "send_later #{self.class}##{method}",
17
+ :map_name => "",
18
+ :map_timeout => 1.hour,
19
+ :reduce_timeout => 1.hour,
20
+ :master_timeout => 8.hours,
21
+ :master_result_timeout => 1.minute,
22
+ :map_reduce_class => Skynet::ActiveRecordAsync
23
+ }
24
+ job = Skynet::AsyncJob.new(jobopts)
25
+ job.run_master
26
+ end
27
+ end
28
+
29
+ class <<ActiveRecord::Base
30
+ def distributed_find(*args)
31
+ all = ActiveRecord::Mapreduce.find(*args)
32
+ all.model_class = self
33
+ all
34
+ end
35
+ end
36
+
37
+ class Skynet::ActiveRecordAsync
38
+ include SkynetDebugger
39
+
40
+ def self.map(datas)
41
+ datas.each do |data|
42
+ begin
43
+ model = data[:model_class].constantize.find(data[:model_id])
44
+ if data[:opts]
45
+ model.send(data[:method].to_sym, YAML.load(data[:opts]))
46
+ else
47
+ model.send(data[:method].to_sym)
48
+ end
49
+ model.save if data[:save]
50
+ rescue Exception => e
51
+ error "Error in #{self} #{e.inspect}"
52
+ end
53
+ end
54
+ return
55
+ end
56
+ end
57
+
58
+ module ActiveRecord
59
+
60
+
61
+ class Mapreduce
62
+ BATCH_SIZE=1000 unless defined?(BATCH_SIZE)
63
+
64
+ attr_accessor :find_args, :batch_size
65
+ attr_reader :model_class
66
+
67
+ def initialize(options = {})
68
+ @find_args = options[:find_args]
69
+ @batch_size = options[:batch_size] || BATCH_SIZE
70
+ @model_class = options[:model_class]
71
+ end
72
+
73
+ def model_class=(model_c)
74
+ @model_class = model_c.to_s
75
+ end
76
+
77
+ def self.find(*args)
78
+ if not args.first.is_a?(Hash)
79
+ args.shift
80
+ end
81
+ if args.nil? or args.empty?
82
+ args = {}
83
+ else
84
+ args = *args
85
+ end
86
+ new(:find_args => args, :batch_size => args.delete(:batch_size), :model_class => args.delete(:model_class))
87
+ end
88
+
89
+ def each_range(opts={})
90
+ opts = opts.clone
91
+ opts[:id] || opts[:id] = 0
92
+ rows = chunk_query(opts)
93
+
94
+ ii = 0
95
+ if rows.empty?
96
+ rows = [{"first" => 0, "last" => nil, "cnt" => ii}]
97
+ end
98
+ last_row = nil
99
+ while rows.any?
100
+ rows.each do |record|
101
+ last_row = record
102
+ yield record, ii
103
+ end
104
+ ii +=1
105
+ return if last_row["last"].nil?
106
+ rows = chunk_query(opts.merge(:id => rows.last["last"]))
107
+ end
108
+
109
+ if last_row["last"] and (last_row["last"].to_i - last_row["first"].to_i) >= batch_size
110
+ catchall_row = {"first" => last_row["last"].to_i+1, "last" => nil, "cnt" => ii}
111
+ yield catchall_row, ii
112
+ end
113
+ end
114
+
115
+ def chunk_query(opts={})
116
+ mc = model_class.constantize
117
+ table_name = mc.table_name
118
+
119
+ conditions = "#{table_name}.id > #{opts[:id]} AND ((@t1:=(@t1+1) % #{batch_size})=0)"
120
+ opts = opts.clone
121
+ if opts[:conditions].nil? or opts[:conditions].empty?
122
+ opts[:conditions] = conditions
123
+ else
124
+ opts[:conditions] += " AND " unless opts[:conditions].empty?
125
+ opts[:conditions] += conditions
126
+ end
127
+ limit = opts[:limit] ? "LIMIT #{opts[:limit]}" : nil
128
+ # select @t2:=(@t2+1), @t3:=@t4, @t4:=id from profiles where ( ((@t1:=(@t1+1) % 1000)=0) or (((@t1+1) % 1000)=0) ) order by id LIMIT 100;
129
+
130
+ # BEST
131
+ # select @t1:=0, @t2:=0, @t3:=0, @t4:=0;
132
+ # select @t2:=(@t2+1) as cnt, ((@t3:=@t4)+1) as first, @t4:=id as last from profiles where ((@t1:=(@t1+1) % 1000)=0) order by id LIMIT 100;
133
+ # select (@t2:=(@t2+1) % 2) as evenodd, ((@t3:=@t4)+1) as first, @t4:=id as last from profiles where ((@t1:=(@t1+1) % 1000)=0) order by id LIMIT 100;
134
+
135
+ mc.connection.execute('select @t1:=0, @t2:=-1, @t3:=0, @t4:=0')
136
+ mc.connection.select_all("select @t2:=(@t2+1) as cnt, ((@t3:=@t4)+1) as first, @t4:=#{table_name}.id as last from #{table_name} #{opts[:joins]} where #{opts[:conditions]} ORDER BY #{table_name}.id #{limit}")
137
+
138
+ # mc.connection.select_values(mc.send(:construct_finder_sql, :select => "#{mc.table_name}.id", :joins => opts[:joins], :conditions => conditions, :limit => opts[:limit], :order => :id))
139
+ end
140
+
141
+
142
+ def each(klass_or_method=nil,&block)
143
+ klass_or_method ||= model_class
144
+ batches = []
145
+ each_range(find_args) do |ids,ii|
146
+ batch_count = ids["cnt"].to_i
147
+ batches[batch_count] = [
148
+ ids['first'].to_i,
149
+ ids['last'].to_i,
150
+ find_args.clone,
151
+ model_class
152
+ ]
153
+ if block_given?
154
+ batches[batch_count][4] = block
155
+ else
156
+ batches[batch_count][4] = "#{klass_or_method}"
157
+ end
158
+ end
159
+
160
+ job = nil
161
+ jobopts = {
162
+ :map_tasks => 20000,
163
+ :map_data => batches,
164
+ :name => "each #{model_class} MASTER",
165
+ :map_name => "each #{model_class} MAP",
166
+ :map_timeout => 1.hour,
167
+ :reduce_timeout => 1.hour,
168
+ :master_timeout => 8.hours,
169
+ :master_result_timeout => 1.minute
170
+
171
+ }
172
+ if block_given?
173
+ job = Skynet::Job.new(jobopts.merge(:map_reduce_class => "#{self.class}"))
174
+ else
175
+ job = Skynet::AsyncJob.new(jobopts.merge(:map_reduce_class => "#{self.class}"))
176
+ end
177
+ job.run
178
+ end
179
+
180
+ alias_method :mapreduce, :each
181
+
182
+ def model_class
183
+ @model_class || self.class.model_class
184
+ end
185
+
186
+ def self.model_class(model_class)
187
+ (class << self; self; end).module_eval do
188
+ define_method(:model_class) {model_class}
189
+ end
190
+ end
191
+
192
+ def self.log
193
+ Skynet::Logger.get
194
+ end
195
+
196
+ def self.map(datas)
197
+ datas.each do |data|
198
+ model_class = data[3].constantize
199
+ table_name = model_class.table_name
200
+ conditions = "#{table_name}.id >= #{data[0]}"
201
+ conditions += " AND #{table_name}.id <= #{data[1]}" if data[1] > data[0]
202
+ conditions = "(#{conditions})"
203
+ # conditions = "ID BETWEEN #{data[0]} and #{data[1]}"
204
+ if data[2].empty? or data[2][:conditions].empty?
205
+ data[2] = {:conditions => conditions}
206
+ else
207
+ data[2][:conditions] += " AND #{conditions}"
208
+ end
209
+ data[2][:select] = "#{table_name}.*"
210
+ model_class.find(:all, data[2]).each do |ar_object|
211
+ begin
212
+ if data[4].kind_of?(String)
213
+ begin
214
+ data[4].constantize.each(ar_object)
215
+ rescue NameError
216
+ if ar_object.respond_to?(data[4].to_sym)
217
+ ar_object.send(data[4])
218
+ else
219
+ raise NameError.new("#{data[4]} is not a class or an instance method in #{model_class}")
220
+ end
221
+ end
222
+ else
223
+ data[4].call(ar_object)
224
+ end
225
+ rescue Exception => e
226
+ if data[4].kind_of?(String)
227
+ log.error("Error in #{data[4]} #{e.inspect} #{e.backtrace.join("\n")}")
228
+ else
229
+ log.error("Error in #{self} with given block #{e.inspect} #{e.backtrace.join("\n")}")
230
+ end
231
+ end
232
+ end
233
+ end
234
+ nil
235
+ end
236
+ end
237
+ end