Class: Updater::Update

Inherits:
Object
  • Object
show all
Defined in:
lib/updater/update.rb

Overview

The basic class that drives Updater. See Readme for usage information.

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(orm_inst) ⇒ Update

orm_inst must be set to an instacne of the class Update.orm



70
71
72
73
74
75
76
# File 'lib/updater/update.rb', line 70

def initialize(orm_inst)
  if orm_inst.nil? || !orm_inst.kind_of?(self.class.orm)
    raise ArgumentError, 
      "Update has been set to use %s but recieved a %s  (%s:%s)\n  recieved %s." % [self.class.orm.inspect, orm_inst.class.inspect, __FILE__,__LINE__-1,orm_inst.inspect]
  end
  @orm = orm_inst
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

see if this method was intended for the underlying ORM layer.



46
47
48
# File 'lib/updater/update.rb', line 46

def method_missing(method, *args)
  @orm.send(method,*args)
end

Class Attribute Details

.config_fileObject

The name of the file to look for information if we loose the server



414
415
416
# File 'lib/updater/update.rb', line 414

def config_file
  @config_file
end

.finder_idObject

This is the application level default method to call on an instance type target. It should return a value to be passed to the #finder_method (above) inorder to retrieve the instance from the datastore. (eg. id) In most circumstances the ORM layer defines an appropriate default and this does not need to be explcitly set.

MongoDB is one significant exception to this rule. The Updater Mongo ORM layer uses the 10gen MongoDB dirver directly without an ORM such as Mongoid or Mongo_Mapper. If the application uses ond of thes ORMs #finder_method and #finder_id should be explicitly set.



197
198
199
# File 'lib/updater/update.rb', line 197

def finder_id
  @finder_id
end

.finder_methodObject

This is the application level default method to call on a class in order to find/create a target instance. (e.g find, get, find_one, etc…). In most circumstances the ORM layer defines an appropriate default and this does not need to be explcitly set.

MongoDB is one significant exception to this rule. The Updater Mongo ORM layer uses the 10gen MongoDB dirver directly without an ORM such as Mongoid or Mongo_Mapper. If the application uses one of thes ORMs #finder_method and #finder_id should be explicitly set.



187
188
189
# File 'lib/updater/update.rb', line 187

def finder_method
  @finder_method
end

.loggerObject

Returns the logger instance. If it has not been set, a new Logger will be created pointing to STDOUT



214
215
216
# File 'lib/updater/update.rb', line 214

def logger
  @logger ||= Logger.new(STDOUT)
end

.ormObject

This attribute must be set to some ORM that will persist the data. The value is normally set using one of the methods in Updater::Setup.



178
179
180
# File 'lib/updater/update.rb', line 178

def orm
  @orm
end

.socketObject

This is an open IO socket that will be writen to when a job is scheduled. If it is unset then @pid is signaled instead.



208
209
210
# File 'lib/updater/update.rb', line 208

def socket
  @socket
end

Instance Attribute Details

#errorObject (readonly)

Contains the Error class after an error is caught in run. Not stored to the database



8
9
10
# File 'lib/updater/update.rb', line 8

def error
  @error
end

#ormObject (readonly)

Contains the underlying ORM instance (eg. ORM::Datamapper or ORM Mongo)



11
12
13
# File 'lib/updater/update.rb', line 11

def orm
  @orm
end

#paramsObject

In order to reduce the proliferation of chained jobs in the queue, jobs chain request are allowed a params value that will pass specific values to a chained method. When a chained instance is created, the job processor will set this value. It will then be sent to the target method in plance of ‘__param__’. See #sub_args



18
19
20
# File 'lib/updater/update.rb', line 18

def params
  @params
end

Class Method Details

.at(t, target, method = nil, args = [], options = {}) ⇒ Object

Request that the target be sent the method with args at the given time.

Parameters

time <Integer | Object responding to to_i>, by default the number of seconds sence the epoch.

What ‘time’ references can be set by sending the a substitute class to the time= method.

target <Class | instance> . If target is a class then ‘method’ will be sent to that class (unless the finder option is used. Otherwise, the target will be assumed to be the result of (target.class).get(target.id). (note: The ORM can/should override #get and #id with the proper methods for it’s storage model.) The finder method (:get by default) and the finder_args (target.id by default) can be set in the options. A ORM (eg DataMapper) instance passed as the target will “just work.” Any object can be found in this mannor is known as a ‘conforming instance’. TODO: make ORM finder and id constants overridable for times when one ORM is used for Updater and another is used by the model classes.

method <Symbol>. The method that will be sent to the calculated target.

args <Array> a list of arguments to be sent to with the method call. Note: ‘args’ must be seirialiable with Marshal.dump. The special values ‘__job__’, ‘__params__’, and ‘__self__’ are replaced they are found in this list. Defaults to []. (note: the #to_s method will be called on all args before variable substitution any arg that responds with one of the special values will be replaced as noted above. E.g :__job__ . If something is silly enough to respond to to_s with a non-pure method you will have problems. NoMethodError is caught and handled gracefully)

options <Hash> Addational options that will be used to configure the request. see Options section below.

Options

:finder <Symbol> This method will be sent to the stored target class (either target or target.class) inorder to extract the instance on which to preform the request. By default :get is used. For example to use on an ActiveRecord class

:finder=>:find

:finder_args <Array> | <Object>. This is passed to the finder function. By default it is target.id. Note that by setting :finder_args you will force Updater to calculate in instance as the computed target even if you pass a Class as the target.

:name <String> A string sent by the requesting class to identify the request. ‘name’ must be unique for a given computed target. Names cannot be used effectivally when a Class has non- conforming instances as there is no way predict the results of a finder call. ‘name’ can be used in conjunction with the for method to manipulate requests effecting an object or class after they are set. See for for examples

:failure, :success,:ensure <Updater::Update instance> an other request to be run when the request compleste. Usually these valuses will be created with the chained method.

As an alternative a Hash (OrderedHash in ruby 1.8) with keys of Updater::Update instances and values of Hash may be used. The hash will be substituted for the ‘__param__’ argument if/when the chained method is called.

:persistant <true|false> if true the object will not be destroyed after the completion of its run. By default this is false except when time is nil.

Note:

Unless finder_args is passed, a non-class target will be asked for its ID value using #finder_id or if that is not set, then the default value defined in the ORM layer. Particularly for MongoDB it is important that #finder_id be set to an appropriate value sence the Updater ORM layer uses the low level MongoDB driver instead of a more feature complete ORM like Mongoid.

Examples

Updater.at(Chronic.parse('tomorrow'),Foo,:bar,[]) # will run Foo.bar() tomorrow at midnight

f = Foo.create
u = Updater.at(Chronic.parse('2 hours form now'),f,:bar,[]) # will run Foo.get(f.id).bar in 2 hours

See Also

in, immidiate and chain which share the same arguments and options but treat time differently



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/updater/update.rb', line 305

def at(t,target,method = nil,args=[],options={})
  hash = Hash.new
  hash[:time] = t.to_i unless t.nil?
  
  hash[:target],hash[:finder],hash[:finder_args] = target_for(target, options)
  
  hash[:method] = method || :perform
  hash[:method_args] = args
  
  [:name,:failure,:success,:ensure].each do |opt|
    hash[opt] = options[opt] if options[opt]
  end
  
  hash[:persistant] = options[:persistant] || t.nil? ? true : false
  
  schedule(hash)
end

.chain(*args) ⇒ Object

like at but without a time to run. This is used to create requests that run in responce to the failure of other requests. See at for details



347
348
349
# File 'lib/updater/update.rb', line 347

def chain(*args)
  at(nil,*args)
end

.clear_allObject

Remove all scheduled jobs. Mostly intended for testing, but may also be useful in cases of crashes or system corruption. removes all pending jobs.



409
410
411
# File 'lib/updater/update.rb', line 409

def clear_all
  @orm.clear_all
end

.clear_locks(worker) ⇒ Object

Ensure that a worker no longer holds any locks.



235
# File 'lib/updater/update.rb', line 235

def clear_locks(worker); @orm.clear_locks(worker); end

.currentObject

A filter for all requests that are ready to run, that is they requested to be run before or at time.now and ar not being processed by another worker



386
387
388
# File 'lib/updater/update.rb', line 386

def current
  @orm.current
end

.delayedObject

A count of how many jobs are scheduled but not yet run



396
397
398
# File 'lib/updater/update.rb', line 396

def delayed
  @orm.delayed
end

.for(target, name = nil) ⇒ Object

Retrieves all updates for a conforming target possibly limiting the results to the named request.

Parameters

target <Class | Object> a class or conforming object that postentially is the calculated target of a request.

name(optional) <String> If a name is sent, the first request with fot this target with this name will be returned.

Returns

<Array> unless name is given then only a single [Updater] instance.



365
366
367
368
369
# File 'lib/updater/update.rb', line 365

def for(target,name=nil)
  target,finder,args = target_for(target)
  ret = @orm.for(target,finder,args,name).map {|i| new(i)}
  name ? ret.first : ret
end

.future(start, finish = nil) ⇒ Object

How many jobs will happen at least ‘start’ seconds from now, but not more then finish seconds from now. If the second parameter is nil then it is the number of jobbs between now and the first parameter.



402
403
404
405
# File 'lib/updater/update.rb', line 402

def future(start,finish = nil)
  start, finish = [0, start] unless finish 
  @orm.future(start,finish)
end

.immidiate(*args) ⇒ Object

like at but with time as time.now. Generally this will be used to run a long running operation in asyncronously in a differen process. See at for details



341
342
343
# File 'lib/updater/update.rb', line 341

def immidiate(*args)
  at(time.now,*args)
end

.in(t, *args) ⇒ Object

Run this job in ‘time’ seconds from now. See at for details on expected args.



324
325
326
# File 'lib/updater/update.rb', line 324

def in(t,*args)
  at(time.now+t,*args)
end

.loadObject

The number of jobs currently backloged in the system



391
392
393
# File 'lib/updater/update.rb', line 391

def load
  @orm.current_load
end

.pidObject

The PID of the worker process



436
437
438
# File 'lib/updater/update.rb', line 436

def pid
  @pid
end

.pid=(p) ⇒ Object

Sets the process id of the worker process if known. If this is set then an attempt will be made to signal the worker any time a new update is made.

The PID will not be signaled if @socket is availible, but should be set as a back-up

If pid is not set, or is set to nil then the scheduleing program is responcible for waking-up a potentially sleeping worker process in another way.



425
426
427
428
429
430
431
432
433
# File 'lib/updater/update.rb', line 425

def pid=(p)
  return @pid = nil unless p #tricky assignment in return
  @pid = Integer("#{p}") #safety check that prevents a curupted PID file from crashing the system
  Process::kill 0, @pid #check that the process exists
  @pid
rescue Errno::ESRCH, ArgumentError
  @pid = nil
  raise ArgumentError, "PID was invalid"
end

.schedule(hash) ⇒ Object

Advanced: This method allows values to be passed directly to the ORM layer’s create method. use at and friends for everyday use cases.



330
331
332
333
334
335
336
337
# File 'lib/updater/update.rb', line 330

def schedule(hash)
  r = new(@orm.create(hash))
  signal_worker
  r
rescue NoMethodError
  raise ArgumentError, "ORM not initialized!" if @orm.nil?
  raise
end

.timeObject

The time class used by Updater. See time=



372
373
374
# File 'lib/updater/update.rb', line 372

def time
  @time ||= Time
end

.time=(klass) ⇒ Object

By default Updater will use the system time (Time class) to get the current time. The application that Updater was developed for used a game clock that could be paused or restarted. This method allows us to substitute a custom class for Time. This class must respond with in interger or Time to the #now method.



380
381
382
# File 'lib/updater/update.rb', line 380

def time=(klass)
  @time = klass
end

.work_off(worker) ⇒ Object

Gets a single job form the queue, locks and runs it. it returns the number of second Until the next job is scheduled, or 0 is there are more current jobs, or nil if there are no jobs scheduled.



221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/updater/update.rb', line 221

def work_off(worker)
  inst = @orm.lock_next(worker)
  if inst
    worker.logger.debug "  running job #{inst.id}" 
    new(inst).run
  else
    worker.logger.debug "  could not find a ready job in the datastore" 
  end
  @orm.queue_time
ensure
  clear_locks(worker)
end

Instance Method Details

#==(other) ⇒ Object



93
94
95
# File 'lib/updater/update.rb', line 93

def ==(other)
  other.kind_of?(self.class) && id == other.id 
end

#idObject

This is the appropriate value to use for a chanable field value



89
90
91
# File 'lib/updater/update.rb', line 89

def id
  @orm.id
end

#inspectObject



102
103
104
105
106
# File 'lib/updater/update.rb', line 102

def inspect
  "#<Updater::Update target=#{target.inspect} time=#{orm.time}>"
rescue TargetMissingError
  "#<Updater::Update target=<missing> time=#{orm.time}>"
end

#methodObject



50
51
52
# File 'lib/updater/update.rb', line 50

def method
  @orm.method
end

#nameObject

Jobs may be named to make them easier to find



84
85
86
# File 'lib/updater/update.rb', line 84

def name
  @orm.name
end

#name=(n) ⇒ Object

Jobs may be named to make them easier to find



79
80
81
# File 'lib/updater/update.rb', line 79

def name=(n)
  @orm.name=n
end

#persistant?Boolean

If this is true, the job will NOT be removed after it is run. This is usually true for chained Jobs.

Returns:

  • (Boolean)


98
99
100
# File 'lib/updater/update.rb', line 98

def persistant?
  @orm.persistant
end

#run(job = nil) ⇒ Object

Run the action on this traget compleating any chained actions



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/updater/update.rb', line 21

def run(job=nil)
  ret = true #put return in scope
  begin
    t = target 
    final_args = sub_args(job,@orm.method_args)
    t.send(@orm.method.to_sym,*final_args)
  rescue => e
    @error = e
    run_chain :failure
    ret = false
  ensure
    run_chain :success if ret
    run_chain :ensure
    begin
      @orm.destroy unless @orm.persistant
    rescue StandardError => e
      raise e unless e.class.to_s =~ /Connection/
      sleep 0.1
      retry
    end
  end
  ret
end

#targetObject

Determins and if necessary find/creates the target for this instance.

Warning: This value is intentionally NOT memoized. For instance type targets, it will result in a call to the datastore (or the recreation of an object) on EACH invocation. Methods that need to refer to the target more then once should take care to store this value locally after initial retreavel.



59
60
61
62
63
64
65
66
67
# File 'lib/updater/update.rb', line 59

def target
  begin
    target = @orm.finder.nil? ? @orm.target : @orm.target.send(@orm.finder,*@orm.finder_args)
    raise TargetMissingError unless target
  rescue
    raise TargetMissingError, "Target missing --Class:'#{@orm.target}' Finder:'#{@orm.finder}', Args:'#{@orm.finder_args.inspect}'"
  end
  target
end