deferrer 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 455d09ae4793d235d895e80dff0d81f90bbef7ef
4
- data.tar.gz: ba33ec69ad5f8d7144c3235585e436141e0fe21d
3
+ metadata.gz: 1bc1f7b9f8b06d8c607e75b51a267f77b017e493
4
+ data.tar.gz: b5ace0f90bf10f7ab04e8bfefa63fdd22fbef02c
5
5
  SHA512:
6
- metadata.gz: a05628c41c9700e8330db18872f32c9d42c53843407aa8c8dd92997c67115b5e66766640992c20998e81ea3276cc9f189a47b04340eb1bd0f986f334b3187e69
7
- data.tar.gz: 16906be529bd41cd0e5ec9304f5749a004f20d55fa5078a464a9bd91f9c382c5620a39a590077aed05ff630dc2e951bd0d0117421456b09b6a4fa9b42b6fac8a
6
+ metadata.gz: 424bb4d6787044ae36c91f1e05da72dc7c25700a0a9862e87a0fd493a617593046d4202cc129ba8781f5122e57664ace03e1a1ef829e88839dcc782422a25bdb
7
+ data.tar.gz: 978dbb2dceed7aa36ffe56a9906bdaba02d998b3cc168874a70fcbfb4dfeb382c6f5f0d39f6050e58ad736c418186294314608c0bdc230036e49f5cd51325f06
data/README.md CHANGED
@@ -35,19 +35,21 @@ Deferrer.logger = Logger.new(STDOUT)
35
35
  ```
36
36
 
37
37
 
38
- Define deferrer worker that must respond to call method
38
+ Define Deferrer worker (must respond to peform method):
39
39
 
40
40
  ```ruby
41
- Deferrer.worker = lambda do |klass, *args|
42
- # do some work
43
- # Resque.enqueue(klass, *args)
41
+ class WorkDeferrer
42
+ include Deferrer::Worker
43
+
44
+ def perform(*args)
45
+ end
44
46
  end
45
47
  ```
46
48
 
47
- Deferrer is usually used in combination with background processing tools like sidekiq and resque. If that's the case, Deferrer.worker can be light-weight and responsible only for pushing work to a background job.
49
+ Deferrer is usually used in combination with background processing tools like sidekiq and resque where the Deferrer worker is a light-weight worker that just pushed jobs on the queue.
48
50
 
49
51
 
50
- Start a worker process.
52
+ Start the runner.
51
53
 
52
54
  ```ruby
53
55
  Deferrer.run(options = {})
@@ -58,20 +60,19 @@ Deferrer.run(options = {})
58
60
  # ignore_time - don't wait for time period to expire, useful for testing
59
61
  ```
60
62
 
61
-
62
63
  Defer some executions:
63
64
 
64
65
  ```ruby
65
- Deferrer.defer_in(5, 'identifier', Worker, 'update 1')
66
- Deferrer.defer_in(6, 'identifier', Worker, 'update 2')
67
- Deferrer.defer_in(9, 'identifier', Worker, 'update 3')
66
+ WorkDeferrer.perform_in(5, 'identifier', 'update 1')
67
+ WorkDeferrer.perform_in(6, 'identifier', 'update 2')
68
+ WorkDeferrer.perform_in(9, 'identifier', 'update 3')
68
69
  ```
69
70
 
70
71
 
71
- It will stack all defered executions per identifier until first timeout expires (5 seconds) and then it will only process the last update for the expired identifier, calling the deferrer worker:
72
+ It will stack all defered work units per identifier until first timeout expires (5 seconds) and then it will only process the last update for the expired identifier, calling the deferrer worker:
72
73
 
73
74
  ```ruby
74
- Deferrer.worker.call('Worker', 'update 3')
75
+ WorkDeferrer.new.perform('update 3')
75
76
  ```
76
77
 
77
78
 
@@ -2,5 +2,5 @@ require 'deferrer'
2
2
  require_relative './common'
3
3
 
4
4
  1.upto(5) do |i|
5
- Deferrer.defer_in(i + 3, 'identifier', "update #{i}")
5
+ WorkDeferrer.perform_in(i + 3, 'identifier', "update #{i}")
6
6
  end
@@ -1,2 +1,10 @@
1
1
  # setup redis
2
2
  Deferrer.redis_config = { :host => "localhost", :port => 6379, db: 15 }
3
+
4
+ class WorkDeferrer
5
+ include Deferrer::Worker
6
+
7
+ def perform(update)
8
+ p update
9
+ end
10
+ end
@@ -6,9 +6,6 @@ puts 'Runner started'
6
6
 
7
7
  Deferrer.redis_config = { :host => "localhost", :port => 6379, :db => 15 }
8
8
  Deferrer.logger = Logger.new(STDOUT)
9
- Deferrer.worker = lambda do |*args|
10
- p args
11
- end
12
9
  Deferrer.run({
13
10
  :loop_frequency => 0.5,
14
11
  })
@@ -3,20 +3,12 @@ require "deferrer/version"
3
3
 
4
4
  module Deferrer
5
5
  autoload :Configuration, 'deferrer/configuration'
6
- autoload :JsonEncoding, 'deferrer/json_encoding'
7
6
  autoload :Runner, 'deferrer/runner'
8
- autoload :Job, 'deferrer/job'
9
-
10
- LIST_KEY = 'deferred_list'
11
- ITEM_KEY_PREFIX = 'deferred'
7
+ autoload :Worker, 'deferrer/worker'
8
+ autoload :Item, 'deferrer/item'
9
+ autoload :Queue, 'deferrer/queue'
10
+ autoload :Processor, 'deferrer/processor'
12
11
 
13
12
  extend Configuration
14
- extend JsonEncoding
15
13
  extend Runner
16
-
17
- class WorkerNotConfigured < NotImplementedError
18
- def initialize
19
- super("Deferrer worker not configured")
20
- end
21
- end
22
14
  end
@@ -3,12 +3,15 @@ module Deferrer
3
3
 
4
4
  attr_reader :redis
5
5
  attr_accessor :logger
6
- attr_accessor :worker
7
6
  attr_accessor :inline
8
7
 
9
8
  # Deferrer.redis_config = { :host => "localhost", :port => 6379 }
10
9
  def redis_config=(config)
11
10
  @redis = Redis.new(config)
12
11
  end
12
+
13
+ def log(type, message)
14
+ logger.send(type, message) if logger
15
+ end
13
16
  end
14
17
  end
@@ -0,0 +1,32 @@
1
+ require 'multi_json'
2
+
3
+ module Deferrer
4
+ class Item
5
+
6
+ attr_reader :id, :class_name, :args
7
+
8
+ def self.from_json(json)
9
+ item = MultiJson.load(json)
10
+ new(item['id'], item['class'], item['args'])
11
+ end
12
+
13
+ def initialize(id, class_name, args)
14
+ @id = id
15
+ @class_name = class_name
16
+ @args = args
17
+ end
18
+
19
+ def to_json
20
+ MultiJson.dump(to_hash)
21
+ end
22
+
23
+ def to_hash
24
+ { 'id' => id, 'class' => class_name, 'args' => args }
25
+ end
26
+
27
+ def ==(object)
28
+ object.id == self.id
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,23 @@
1
+ module Deferrer
2
+ class Processor
3
+
4
+ def initialize(item)
5
+ @item = item
6
+ end
7
+
8
+ def process
9
+ Deferrer.log(:info, "Processing: #{@item.id}")
10
+ klass = constantize(@item.class_name)
11
+ klass.new.send(:perform, *@item.args)
12
+ end
13
+
14
+ private
15
+ def constantize(klass_string)
16
+ klass_string.split('::').inject(Object) do |object, name|
17
+ object = object.const_get(name)
18
+ object
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,73 @@
1
+ module Deferrer
2
+ class Queue
3
+ LIST_KEY = 'deferred_list'
4
+ ITEM_KEY_PREFIX = 'deferred'
5
+
6
+ class << self
7
+ def push(item, timestamp)
8
+ key = item_key(item.id)
9
+ count = redis.rpush(key, item.to_json)
10
+
11
+ # set score only on first update
12
+ if count == 1
13
+ score = calculate_score(timestamp)
14
+ redis.zadd(LIST_KEY, score, key)
15
+ end
16
+ end
17
+
18
+ def pop(ignore_time = false)
19
+ find(next_key(ignore_time))
20
+ end
21
+
22
+ def find_by_id(id)
23
+ return nil unless id
24
+ find(item_key(id))
25
+ end
26
+
27
+ def find(key)
28
+ return nil unless key
29
+
30
+ item = nil
31
+
32
+ if json = redis.rpop(key)
33
+ item = Item.from_json(json)
34
+ end
35
+
36
+ remove(key)
37
+
38
+ item
39
+ end
40
+
41
+ private
42
+ def next_key(ignore_time)
43
+ if ignore_time
44
+ redis.zrange(LIST_KEY, 0, 1).first
45
+ else
46
+ score = calculate_score(Time.now)
47
+ redis.zrangebyscore(LIST_KEY, '-inf', score, :limit => [0, 1]).first
48
+ end
49
+ end
50
+
51
+ def remove(key)
52
+ redis.watch(key)
53
+ redis.multi do
54
+ redis.del(key)
55
+ redis.zrem(LIST_KEY, key)
56
+ end
57
+ redis.unwatch
58
+ end
59
+
60
+ def calculate_score(timestamp)
61
+ timestamp.to_f
62
+ end
63
+
64
+ def item_key(id)
65
+ "#{ITEM_KEY_PREFIX}:#{id}"
66
+ end
67
+
68
+ def redis
69
+ Deferrer.redis
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,17 +1,15 @@
1
1
  module Deferrer
2
2
  module Runner
3
+
3
4
  def run(options = {})
4
5
  loop_frequency = options.fetch(:loop_frequency, 0.1)
5
6
  single_run = options.fetch(:single_run, false)
6
- @ignore_time = options.fetch(:ignore_time, false)
7
- @inline = options.fetch(:inline, false)
8
-
9
- raise WorkerNotConfigured unless worker
7
+ ignore_time = options.fetch(:ignore_time, false)
10
8
 
11
9
  loop do
12
10
  begin
13
- while item = next_item
14
- process_item(item)
11
+ while item = Deferrer::Queue.pop(ignore_time)
12
+ Processor.new(item).process
15
13
  end
16
14
  rescue StandardError => e
17
15
  log(:error, "Error: #{e.class}: #{e.message}")
@@ -24,82 +22,5 @@ module Deferrer
24
22
  sleep loop_frequency
25
23
  end
26
24
  end
27
-
28
- def next_item(key = next_key)
29
- return nil unless key
30
-
31
- item = nil
32
- decoded_item = nil
33
-
34
- item = redis.rpop(key)
35
- if item
36
- decoded_item = decode(item)
37
- decoded_item['key'] = key
38
- end
39
-
40
- remove(key)
41
-
42
- decoded_item
43
- end
44
-
45
- def defer_in(number_of_seconds_from_now, identifier, *args)
46
- timestamp = Time.now + number_of_seconds_from_now
47
- defer_at(timestamp, identifier, *args)
48
- end
49
-
50
- def defer_at(timestamp, identifier, *args)
51
- key = item_key(identifier)
52
- item = { 'args' => args }
53
-
54
- push_item(key, item, timestamp)
55
-
56
- process_item(next_item(key)) if @inline
57
- end
58
-
59
- private
60
- def process_item(item)
61
- log(:info, "Processing: #{item['key']}")
62
- worker.call(*item['args'])
63
- end
64
-
65
- def item_key(identifier)
66
- "#{ITEM_KEY_PREFIX}:#{identifier}"
67
- end
68
-
69
- def push_item(key, item, timestamp)
70
- count = redis.rpush(key, encode(item))
71
-
72
- # set score only on first update
73
- if count == 1
74
- score = calculate_score(timestamp)
75
- redis.zadd(LIST_KEY, score, key)
76
- end
77
- end
78
-
79
- def next_key
80
- if @ignore_time
81
- redis.zrange(LIST_KEY, 0, 1).first
82
- else
83
- score = calculate_score(Time.now)
84
- redis.zrangebyscore(LIST_KEY, '-inf', score, :limit => [0, 1]).first
85
- end
86
- end
87
-
88
- def calculate_score(timestamp)
89
- timestamp.to_f
90
- end
91
-
92
- def remove(key)
93
- redis.watch(key)
94
- redis.multi do
95
- redis.del(key)
96
- redis.zrem(LIST_KEY, key)
97
- end
98
- redis.unwatch
99
- end
100
-
101
- def log(type, message)
102
- logger.send(type, message) if logger
103
- end
104
25
  end
105
26
  end
@@ -1,3 +1,3 @@
1
1
  module Deferrer
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,25 @@
1
+ module Deferrer
2
+ module Worker
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def perform_in(number_of_seconds_from_now, id, *args)
10
+ timestamp = Time.now + number_of_seconds_from_now
11
+ perform_at(timestamp, id, *args)
12
+ end
13
+
14
+ def perform_at(timestamp, id, *args)
15
+ item = Item.new(id, name, args)
16
+ Deferrer::Queue.push(item, timestamp)
17
+
18
+ if Deferrer.inline
19
+ item = Deferrer::Queue.find_by_id(item.id)
20
+ Processor.new(item).process
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deferrer::Item do
4
+
5
+ let(:item_hash) { {'id' => 1, 'class' => 'TestWorker', 'args' => [1, 2]} }
6
+ let(:item_json) { MultiJson.dump(item_hash) }
7
+
8
+ it "can create a new" do
9
+ item = Deferrer::Item.new(1, 'TestWorker', [1, 2])
10
+ expect(item.id).to eq(1)
11
+ expect(item.class_name).to eq('TestWorker')
12
+ expect(item.args).to eq([1, 2])
13
+ end
14
+
15
+ it "can create new item from json" do
16
+ item = Deferrer::Item.from_json(item_json)
17
+
18
+ expect(item.id).to eq(1)
19
+ expect(item.class_name).to eq('TestWorker')
20
+ expect(item.args).to eq([1, 2])
21
+ end
22
+
23
+ it "can test if two objects are equal" do
24
+ item1 = Deferrer::Item.new(1, 'TestWorker', [1, 2])
25
+ item2 = Deferrer::Item.from_json(item_json)
26
+
27
+ expect(item1).to eq(item2)
28
+ end
29
+
30
+ it "can convert to hash" do
31
+ item = Deferrer::Item.new(1, 'TestWorker', [1, 2])
32
+ expect(item.to_hash).to eq(item_hash)
33
+ end
34
+
35
+ it "can convert to json" do
36
+ item = Deferrer::Item.new(1, 'TestWorker', [1, 2])
37
+ expect(item.to_json).to eq(item_json)
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deferrer::Processor do
4
+ it "can process item" do
5
+ expect_any_instance_of(TestWorker).to receive(:perform).with(1, 2)
6
+ item = Deferrer::Item.new(1, 'TestWorker', [1, 2])
7
+ Deferrer::Processor.new(item).process
8
+ end
9
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deferrer::Queue do
4
+ let(:queue) { Deferrer::Queue }
5
+ let(:id) { 'some_id' }
6
+ let(:redis) { Deferrer.redis }
7
+ let(:list_key) { Deferrer::Queue::LIST_KEY }
8
+
9
+ describe ".push" do
10
+ it "can push items" do
11
+ item1 = Deferrer::Item.new('1', 'TestWorker', [1])
12
+ item2 = Deferrer::Item.new('2', 'TestWorker', [2])
13
+
14
+ queue.push(item1, Time.now - 1)
15
+ queue.push(item2, Time.now - 2)
16
+
17
+ expect(queue.pop).to eq(item2)
18
+ expect(queue.pop).to eq(item1)
19
+ end
20
+ end
21
+
22
+ describe ".pop" do
23
+ it "returns the next item" do
24
+ TestWorker.perform_at(Time.now, id, 'test')
25
+
26
+ item = queue.pop
27
+
28
+ expect(item.class_name).to eq('TestWorker')
29
+ expect(item.args).to eq(['test'])
30
+ end
31
+
32
+ it "returns last update of an item" do
33
+ TestWorker.perform_at(Time.now - 3, id, 'update1')
34
+ TestWorker.perform_at(Time.now - 2, id, 'update2')
35
+
36
+ item = queue.pop
37
+
38
+ expect(item.class_name).to eq('TestWorker')
39
+ expect(item.args).to eq(['update2'])
40
+ end
41
+
42
+ it "returns nil when no next item" do
43
+ expect(queue.pop).to be_nil
44
+ end
45
+
46
+ it "removes values from redis" do
47
+ TestWorker.perform_at(Time.now, id, 'test')
48
+
49
+ item = queue.pop
50
+
51
+ expect(redis.zrangebyscore(list_key, '-inf', Time.now.to_f, :limit => [0, 1]).first).to be_nil
52
+ expect(redis.exists(item_key(id))).to be_falsey
53
+ expect(queue.pop).to be_nil
54
+ end
55
+
56
+ it "doesn't block on empty lists" do
57
+ TestWorker.perform_in(-1, id, 'test')
58
+ redis.del(item_key(id))
59
+
60
+ expect(queue.pop).to be_nil
61
+ expect(redis.zrangebyscore(list_key, '-inf', 'inf', :limit => [0, 1]).first).to be_nil
62
+ end
63
+ end
64
+
65
+ describe ".find_by_id" do
66
+ it "can find by id" do
67
+ item1 = Deferrer::Item.new('1', 'TestWorker', [1])
68
+ item2 = Deferrer::Item.new('2', 'TestWorker', [2])
69
+
70
+ queue.push(item1, Time.now)
71
+ queue.push(item2, Time.now)
72
+
73
+ expect(queue.find_by_id(item1.id)).to eq(item1)
74
+ expect(queue.find_by_id(item2.id)).to eq(item2)
75
+ end
76
+ end
77
+
78
+ describe ".find" do
79
+ it "can find by key" do
80
+ item1 = Deferrer::Item.new('1', 'TestWorker', [1])
81
+ item2 = Deferrer::Item.new('2', 'TestWorker', [2])
82
+
83
+ queue.push(item1, Time.now)
84
+ queue.push(item2, Time.now)
85
+
86
+ expect(queue.find(item_key(item1.id))).to eq(item1)
87
+ expect(queue.find(item_key(item2.id))).to eq(item2)
88
+ end
89
+ end
90
+ end
@@ -2,153 +2,78 @@ require 'spec_helper'
2
2
  require 'logger'
3
3
 
4
4
  describe Deferrer::Runner do
5
- let(:identifier) { 'some_identifier' }
6
- let(:redis) { Deferrer.redis }
7
- let(:list_key) { Deferrer::LIST_KEY }
5
+ let(:id) { 'some_id' }
8
6
  let(:logger) { Logger.new(STDOUT) }
9
7
 
10
8
  describe ".run" do
11
9
  it "processes jobs" do
12
- expect(Deferrer.worker).to receive(:call).with('TestWorker', 'test')
10
+ expect_any_instance_of(TestWorker).to receive(:perform).with('test')
13
11
 
14
- Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
12
+ TestWorker.perform_in(-1, id, 'test')
15
13
  Deferrer.run(single_run: true)
16
14
  end
17
15
 
18
16
  it "correctly sets arguments and converts symbols to strings for hashes" do
19
- expect(Deferrer.worker).to receive(:call).with('TestWorker', 1, 'arg1', { "a" => "b"})
17
+ expect_any_instance_of(TestWorker).to receive(:perform).with(1, 'arg1', { "a" => "b"})
20
18
 
21
- Deferrer.defer_in(-1, identifier, 'TestWorker', 1, 'arg1', { a: :b })
19
+ TestWorker.perform_in(-1, id, 1, 'arg1', { a: :b })
22
20
  Deferrer.run(single_run: true)
23
21
  end
24
22
 
25
23
  it "rescues standard errors" do
26
- allow(Deferrer).to receive(:next_item) { raise RuntimeError.new('error') }
24
+ expect(logger).to receive(:error).with("Error: RuntimeError: error")
25
+ expect(Deferrer::Queue).to receive(:pop) { raise RuntimeError.new('error') }
27
26
 
28
- Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
27
+ TestWorker.perform_in(-1, id, 'test')
28
+ Deferrer.logger = logger
29
29
  Deferrer.run(single_run: true)
30
30
  end
31
31
 
32
32
  it "rescues exceptions and logs error messages" do
33
33
  expect(logger).to receive(:error).with("Error: Exception: error")
34
- allow(Deferrer).to receive(:next_item) { raise Exception.new('error') }
34
+ expect(Deferrer::Queue).to receive(:pop) { raise Exception.new('error') }
35
35
 
36
+ TestWorker.perform_in(-1, id, 'test')
36
37
  Deferrer.logger = logger
37
- Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
38
38
  expect { Deferrer.run(single_run: true) }.to raise_error(Exception)
39
39
  end
40
40
 
41
- it "raises error if worker not configured" do
42
- Deferrer.worker = nil
43
- Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
44
-
45
- expect { Deferrer.run(single_run: true) }.to raise_error(Deferrer::WorkerNotConfigured)
46
- end
47
-
48
41
  it "ignores time to wait and performs jobs" do
49
- expect(Deferrer.worker).to receive(:call).with('Worker', { "c" => "d"})
42
+ expect_any_instance_of(TestWorker).to receive(:perform).with({ "c" => "d"})
50
43
 
51
- Deferrer.defer_in(100, identifier, 'Worker', { a: :b })
52
- Deferrer.defer_in(100, identifier, 'Worker', { c: :d })
44
+ TestWorker.perform_in(100, id, { a: :b })
45
+ TestWorker.perform_in(100, id, { c: :d })
53
46
 
54
47
  Deferrer.run(single_run: true, ignore_time: true)
55
48
  end
56
49
  end
57
50
 
58
- describe ".defer_at" do
59
- it "deferrs at given time" do
60
- Deferrer.defer_at(Time.now, identifier, 'TestWorker', 'test')
61
-
62
- expect(redis.zrangebyscore(list_key, '-inf', Time.now.to_f, :limit => [0, 1]).first).not_to be_nil
63
- expect(redis.exists(item_key(identifier))).to be_truthy
64
- end
65
- end
66
-
67
- describe ".defer_in" do
68
- it "defers in given interval" do
69
- Deferrer.defer_in(1, identifier, 'TestWorker', 'test')
70
-
71
- expect(redis.zrangebyscore(list_key, '-inf', (Time.now + 1).to_f, :limit => [0, 1]).first).not_to be_nil
72
- expect(redis.exists(item_key(identifier))).to be_truthy
73
- end
74
- end
75
-
76
- describe ".next_item" do
77
- it "returns the next item" do
78
- Deferrer.defer_at(Time.now, identifier, 'TestWorker', 'test')
79
-
80
- item = Deferrer.next_item
81
-
82
- expect(item['args']).to eq(['TestWorker', 'test'])
83
- end
84
-
85
- it "returns last update of an item" do
86
- Deferrer.defer_at(Time.now - 3, identifier, 'TestWorker', 'update1')
87
- Deferrer.defer_at(Time.now - 2, identifier, 'TestWorker', 'update2')
88
-
89
- item = Deferrer.next_item
90
-
91
- expect(item['args']).to eq(['TestWorker', 'update2'])
92
- end
93
-
94
- it "returns nil when no next item" do
95
- expect(Deferrer.next_item).to be_nil
96
- end
97
-
98
- it "removes values from redis" do
99
- Deferrer.defer_at(Time.now, identifier, 'TestWorker', 'test')
100
-
101
- item = Deferrer.next_item
102
-
103
- expect(redis.zrangebyscore(list_key, '-inf', Time.now.to_f, :limit => [0, 1]).first).to be_nil
104
- expect(redis.exists(item_key(identifier))).to be_falsey
105
- expect(Deferrer.next_item).to be_nil
106
- end
107
-
108
- it "doesn't block on empty lists" do
109
- Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
110
- redis.del(item_key(identifier))
111
-
112
- expect(Deferrer.next_item).to be_nil
113
- expect(redis.zrangebyscore(list_key, '-inf', 'inf', :limit => [0, 1]).first).to be_nil
114
- end
115
- end
116
-
117
51
  describe ".logger" do
118
52
  before :each do
119
53
  Deferrer.logger = logger
120
54
  end
121
55
 
122
56
  it "logs info messages" do
123
- expect(logger).to receive(:info).with("Processing: deferred:#{identifier}")
57
+ expect(logger).to receive(:info).with("Processing: #{id}")
124
58
 
125
- Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
59
+ TestWorker.perform_in(-1, id, 'test')
126
60
  Deferrer.run(single_run: true)
127
61
  end
128
62
 
129
63
  it "logs error messages" do
130
64
  expect(logger).to receive(:error).with("Error: RuntimeError: error")
131
- allow(Deferrer).to receive(:next_item) { raise RuntimeError.new('error') }
65
+ allow(Deferrer::Queue).to receive(:pop) { raise RuntimeError.new('error') }
132
66
 
133
- Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
67
+ TestWorker.perform_in(-1, id, 'test')
134
68
  Deferrer.run(single_run: true)
135
69
  end
136
70
 
137
71
  it "logs error messages on exceptions" do
138
72
  expect(logger).to receive(:error).with("Error: Exception: error")
139
- allow(Deferrer).to receive(:next_item) { raise Exception.new('error') }
73
+ allow(Deferrer::Queue).to receive(:pop) { raise Exception.new('error') }
140
74
 
141
- Deferrer.defer_in(-1, identifier, 'TestWorker', 'test')
75
+ TestWorker.perform_in(-1, id, 'test')
142
76
  expect { Deferrer.run(single_run: true) }.to raise_error
143
77
  end
144
78
  end
145
-
146
- describe ".inline" do
147
- it "ignores time to wait and performs jobs" do
148
- Deferrer.inline = true
149
- expect(Deferrer.worker).to receive(:call).with('Worker', { "c" => "d"})
150
-
151
- Deferrer.defer_in(100, identifier, 'Worker', { c: :d })
152
- end
153
- end
154
79
  end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deferrer::Worker do
4
+ let(:id) { 'some_id' }
5
+ let(:redis) { Deferrer.redis }
6
+ let(:list_key) { Deferrer::Queue::LIST_KEY }
7
+
8
+ describe ".perform_at" do
9
+ it "deferrs at given time" do
10
+ TestWorker.perform_at(Time.now, id, 'test')
11
+
12
+ expect(redis.zrangebyscore(list_key, '-inf', Time.now.to_f, :limit => [0, 1]).first).not_to be_nil
13
+ expect(redis.exists(item_key(id))).to be_truthy
14
+ end
15
+ end
16
+
17
+ describe ".perform_in" do
18
+ it "defers in given interval" do
19
+ TestWorker.perform_in(1, id, 'test')
20
+
21
+ expect(redis.zrangebyscore(list_key, '-inf', (Time.now + 1).to_f, :limit => [0, 1]).first).not_to be_nil
22
+ expect(redis.exists(item_key(id))).to be_truthy
23
+ end
24
+ end
25
+
26
+ describe ".inline" do
27
+ it "ignores time to wait and performs jobs" do
28
+ Deferrer.inline = true
29
+ expect_any_instance_of(TestWorker).to receive(:perform).with({ "c" => "d"})
30
+
31
+ TestWorker.perform_in(100, id, { c: :d })
32
+ end
33
+ end
34
+ end
@@ -16,7 +16,6 @@ RSpec.configure do |config|
16
16
  Deferrer.redis.flushdb
17
17
  Deferrer.logger = nil
18
18
  Deferrer.inline = false
19
- Deferrer.worker = lambda { |klass, *args| }
20
19
  end
21
20
  end
22
21
 
@@ -1,5 +1,12 @@
1
1
  module Helpers
2
2
  def item_key(identifier)
3
- "#{Deferrer::ITEM_KEY_PREFIX}:#{identifier}"
3
+ "#{Deferrer::Queue::ITEM_KEY_PREFIX}:#{identifier}"
4
+ end
5
+ end
6
+
7
+ class TestWorker
8
+ include Deferrer::Worker
9
+
10
+ def perform(*args)
4
11
  end
5
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deferrer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dalibor Nasevic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-28 00:00:00.000000000 Z
11
+ date: 2014-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -101,10 +101,17 @@ files:
101
101
  - example/runner.rb
102
102
  - lib/deferrer.rb
103
103
  - lib/deferrer/configuration.rb
104
- - lib/deferrer/json_encoding.rb
104
+ - lib/deferrer/item.rb
105
+ - lib/deferrer/processor.rb
106
+ - lib/deferrer/queue.rb
105
107
  - lib/deferrer/runner.rb
106
108
  - lib/deferrer/version.rb
109
+ - lib/deferrer/worker.rb
110
+ - spec/deferrer/item_spec.rb
111
+ - spec/deferrer/processor_spec.rb
112
+ - spec/deferrer/queue_spec.rb
107
113
  - spec/deferrer/runner_spec.rb
114
+ - spec/deferrer/worker_spec.rb
108
115
  - spec/spec_helper.rb
109
116
  - spec/support/helpers.rb
110
117
  homepage: ''
@@ -132,7 +139,11 @@ signing_key:
132
139
  specification_version: 4
133
140
  summary: Defer work units and process only the last work unit when time comes
134
141
  test_files:
142
+ - spec/deferrer/item_spec.rb
143
+ - spec/deferrer/processor_spec.rb
144
+ - spec/deferrer/queue_spec.rb
135
145
  - spec/deferrer/runner_spec.rb
146
+ - spec/deferrer/worker_spec.rb
136
147
  - spec/spec_helper.rb
137
148
  - spec/support/helpers.rb
138
149
  has_rdoc:
@@ -1,14 +0,0 @@
1
- require 'multi_json'
2
-
3
- module Deferrer
4
- module JsonEncoding
5
-
6
- def encode(item)
7
- MultiJson.dump(item)
8
- end
9
-
10
- def decode(item)
11
- MultiJson.load(item)
12
- end
13
- end
14
- end