urge 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZjYwNzQxMzM3ZmQyNGJlNzBhOWY2YTJmZTFlMzk0ODFlODA1NjExYw==
5
+ data.tar.gz: !binary |-
6
+ M2NmZGU5ZWY0OGVkZmI0MzU5NDYyMzc0NThhOTU2Yjc2NDA0ZGFkNw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MzA2NDVlMWQ1OWJkNzY1OGJjNDhkNzllMjI0NzlmODg3YzczOTQyYjUxMjZh
10
+ MzMyNjZmYzRlZmUwYmI1OTFlNGZlZDNhZmIyZjEwYThjZmM4Zjg4ZGQyZDY2
11
+ OTYxYjljZTQzYjNlYjdlYTcwYmNkYTZmNmFlNGRkYjlmMzU2NGM=
12
+ data.tar.gz: !binary |-
13
+ NmY5M2QwNjNkZWY0N2EyOTA5MWNiMzA1NjY1ZWVmZjgwOTA3YjQzNjg3NTk4
14
+ YjhmZjhmYWFmZTc4ODEyZDNkNzJhODZjZTUyZmJhZTNhY2EzMWNmNzhjYjg3
15
+ ZTgzZjRlYTUwNGFiM2FkMDc3MDAwMmVkYzNmYWQzYWE3MjMzZmM=
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in urge.gemspec
4
+ gemspec
5
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nick Adams
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Urge
2
+
3
+ Urges action on a timed basis. Works with persistent and transient objects.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'urge'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install urge
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,62 @@
1
+ module Urge
2
+ module Persistence
3
+ module ActiveRecordPersistence
4
+
5
+ def self.included( base )
6
+ base.extend Urge::Persistence::Base::ClassMethods
7
+ base.extend Urge::Persistence::ActiveRecordPersistence::ClassMethods
8
+ base.send( :include, Urge::Persistence::ActiveRecordPersistence::InstanceMethods )
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ # AR finder
14
+ def ready_to_run( name, at = DateTime.now )
15
+ options = schedules[name]
16
+ raise 'Unknown schedule' unless options
17
+ where( "#{attr_name(name)} < ?", at )
18
+ end
19
+
20
+
21
+ # def self.retailer( criteria )
22
+ # matches = joins( :retailer )
23
+ # # Can specify name and account code explicitly
24
+ # [:name, :account_code].each do |attribute|
25
+ # matches = matches.where( Retailer.arel_table[attribute].matches("%#{criteria[attribute]}%" ) ) unless criteria[attribute].blank?
26
+ # end
27
+ # # Can specify general criteria
28
+ # if !criteria[:retailer].blank?
29
+ # restriction = nil
30
+ # [:name, :account_code].each do |attribute|
31
+ # predicate = Retailer.arel_table[attribute].matches( "%#{criteria[:retailer]}%" )
32
+ # restriction = restriction ? restriction.or( predicate ) : predicate
33
+ # end
34
+ # matches = matches.where( restriction )
35
+ # end
36
+ # matches
37
+ # end
38
+
39
+ # Same as Scheduled::run_all but saves the rescheduled task
40
+ def run_all!( name )
41
+ now = DateTime.now
42
+ logger.debug "run_all! Time now: #{now}"
43
+ ready_to_run( now ).each do |task|
44
+ logger.debug "Task of class #{task.class.name} is about to be run! (with a bang)"
45
+ task.run!( name )
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ module InstanceMethods
52
+
53
+ def run!( name, options = {} )
54
+ run( name, options )
55
+ save!
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,74 @@
1
+ module Urge
2
+ module Persistence
3
+ module Base
4
+
5
+ module ClassMethods
6
+
7
+ # # Maps to the aasm_column in the database. Defaults to "aasm_state". You can write
8
+ # # (example provided here for ActiveRecord, but it's true for Mongoid as well):
9
+ # #
10
+ # # create_table :foos do |t|
11
+ # # t.string :name
12
+ # # t.string :aasm_state
13
+ # # end
14
+ # #
15
+ # # class Foo < ActiveRecord::Base
16
+ # # include AASM
17
+ # # end
18
+ # #
19
+ # # OR:
20
+ # #
21
+ # # create_table :foos do |t|
22
+ # # t.string :name
23
+ # # t.string :status
24
+ # # end
25
+ # #
26
+ # # class Foo < ActiveRecord::Base
27
+ # # include AASM
28
+ # # aasm_column :status
29
+ # # end
30
+ # #
31
+ # # This method is both a getter and a setter
32
+ # def aasm_column(column_name=nil)
33
+ # if column_name
34
+ # AASM::StateMachine[self].config.column = column_name.to_sym
35
+ # # @aasm_column = column_name.to_sym
36
+ # else
37
+ # AASM::StateMachine[self].config.column ||= :aasm_state
38
+ # # @aasm_column ||= :aasm_state
39
+ # end
40
+ # # @aasm_column
41
+ # AASM::StateMachine[self].config.column
42
+ # end
43
+
44
+ def persist_class_method
45
+ "This is Base::persist_class_method"
46
+ end
47
+ end
48
+
49
+ end # Base
50
+ end # Persistence
51
+
52
+ class Base
53
+ # def state_with_scope(name, *args)
54
+ # state_without_scope(name, *args)
55
+ # unless @clazz.respond_to?(name)
56
+ # if @clazz.ancestors.map {|klass| klass.to_s}.include?("ActiveRecord::Base")
57
+ # scope_options = {:conditions => { "#{@clazz.table_name}.#{@clazz.aasm_column}" => name.to_s}}
58
+ # scope_method = ActiveRecord::VERSION::MAJOR >= 3 ? :scope : :named_scope
59
+ # @clazz.send(scope_method, name, scope_options)
60
+ # elsif @clazz.ancestors.map {|klass| klass.to_s}.include?("Mongoid::Document")
61
+ # scope_options = lambda { @clazz.send(:where, {@clazz.aasm_column.to_sym => name.to_s}) }
62
+ # @clazz.send(:scope, name, scope_options)
63
+ # end
64
+ # end
65
+ # end
66
+ # alias_method :state_without_scope, :state
67
+ # alias_method :state, :state_with_scope
68
+
69
+ def method_in_persistence_base
70
+ "This is method_in_persistence_base"
71
+ end
72
+ end # Base
73
+
74
+ end # Urge
@@ -0,0 +1,20 @@
1
+ module Urge
2
+ module Persistence
3
+
4
+ def self.set_persistence(base)
5
+
6
+ # Use a fancier auto-loading thingy, perhaps. When there are more persistence engines.
7
+ hierarchy = base.ancestors.map {|klass| klass.to_s}
8
+
9
+ dir_name = File.join( File.dirname(__FILE__), 'persistence' )
10
+ require File.join( dir_name, 'base' )
11
+ # require File.join(File.dirname(__FILE__), 'persistence', 'read_state')
12
+ if hierarchy.include?( "ActiveRecord::Base" )
13
+ require File.join( dir_name, 'active_record_persistence' )
14
+ base.send( :include, Urge::Persistence::ActiveRecordPersistence )
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,95 @@
1
+ require 'logging'
2
+
3
+ module Urge
4
+ module Scheduled
5
+ module ClassMethods
6
+
7
+ def run_all( name )
8
+ now = DateTime.now
9
+ logger.debug "run_all. Time now: #{now}"
10
+ ready_to_run( name, now ).each do |task|
11
+ logger.debug "Task of class #{task.class.name} named: #{name} is about to be run"
12
+ task.run( name )
13
+ end
14
+ end
15
+
16
+ def inspect_all( name )
17
+ now = DateTime.now
18
+ tasks = ready_to_run( name, now )
19
+ logger.info "inspect_queue. Time now: #{now}. The following #{tasks.size} tasks are ready to run"
20
+ tasks.each { |task| logger.info task.inspect }
21
+ tasks
22
+ end
23
+
24
+ def logger
25
+ @@logger ||= Logging.logger[self]
26
+ end
27
+
28
+ def urge_schedule( name, options = {} )
29
+ logger.info "Defining schedule: #{name}. Options: #{options}. Class: #{self.name}"
30
+ raise 'Cannot have two schedules with the same name' if schedules[name]
31
+ schedules[name] = options
32
+ end
33
+
34
+ def schedules
35
+ @@per_class_schedules ||= {}
36
+ @@per_class_schedules[self.name] ||= {}
37
+ end
38
+
39
+ def attr_name( name )
40
+ raise 'Non existent schedule name' if schedules[name].blank?
41
+ schedules[name][:scheduled_for] || "scheduled_for_#{name}"
42
+ end
43
+
44
+ def all_schedules
45
+ @@per_class_schedules
46
+ end
47
+
48
+ end
49
+
50
+ module InstanceMethods
51
+
52
+ def ready_to_run?( name )
53
+ ts = self.send( attr_name( name ) )
54
+ ts ? (DateTime.now >= ts) : false
55
+ end
56
+
57
+ def reschedule( name, _when )
58
+ logger.debug "ScheduledTask#reschedule about to reschedule for #{_when}"
59
+ self.send( "#{attr_name(name)}=", _when )
60
+ end
61
+
62
+ # Takes action and reschedules itself. That is all, and that is enough!
63
+ def run( name, options = {} )
64
+
65
+ return false unless ready_to_run?( name )
66
+
67
+ logger.debug "About to call take_action_#{name}"
68
+ interval = internal_take_action( name, options )
69
+
70
+ logger.debug "Action taken. Calculated run interval: #{interval ? interval : 'none - task will *not* be rescheduled'}"
71
+ reschedule( name, interval ? interval.from_now : nil )
72
+
73
+ true
74
+
75
+ end
76
+
77
+ private
78
+
79
+ def internal_take_action( name, options )
80
+ self.send( self.class.schedules[name][:action] || "take_action_#{name}".to_sym, options )
81
+ end
82
+
83
+ def attr_name( name )
84
+ self.class.attr_name( name )
85
+ end
86
+
87
+ end
88
+
89
+ def self.included(base)
90
+ base.extend ClassMethods
91
+ base.send :include, InstanceMethods
92
+ Urge::Persistence.set_persistence(base)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,3 @@
1
+ module Urge
2
+ VERSION = "0.0.1"
3
+ end
data/lib/urge.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'urge/version'
2
+ require 'urge/scheduled'
3
+ require 'urge/persistence'
4
+
5
+ # require 'urge/credit_control'
6
+
7
+ require 'logging'
8
+
9
+ module Urge
10
+ end
Binary file
data/spec/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: spec/aasm.sqlite3.db
@@ -0,0 +1,24 @@
1
+ FactoryGirl.define do
2
+
3
+ factory :client do
4
+ name "Cash It In Limited"
5
+ code "9999"
6
+ end
7
+
8
+ factory :guest do
9
+ sequence :joined_at do |n|
10
+ n.weeks.ago
11
+ end
12
+
13
+ first_name "Freddy"
14
+ sequence :last_name do |n|
15
+ "Starr#{n}"
16
+ end
17
+
18
+ sequence :email do |n|
19
+ "fs#{n}@example.com"
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,131 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ load_schema
4
+
5
+ Logging.logger['simple'].tap {|logger|
6
+ logger.add_appenders 'colourful_stdout'
7
+ logger.level = :debug
8
+ }
9
+
10
+
11
+
12
+ logger = Logging.logger['simple']
13
+
14
+
15
+ class DualActionGuest < ActiveRecord::Base
16
+
17
+ include Urge::Scheduled
18
+
19
+ urge_schedule( :status_check, :scheduled_for => :status_check_at, :action => :check_status )
20
+ urge_schedule( :insurance_check, :scheduled_for => :insurance_check_at, :action => :check_insurance )
21
+
22
+ def status_checked?
23
+ @status_checked
24
+ end
25
+
26
+ def insurance_checked?
27
+ @insurance_checked
28
+ end
29
+
30
+ private
31
+
32
+ def check_status( options )
33
+ logger.debug "In check_status"
34
+ @status_checked = true
35
+ nil
36
+ end
37
+
38
+ def check_insurance( options )
39
+ logger.debug "In check_insurance"
40
+ @insurance_checked = true
41
+ nil
42
+ end
43
+
44
+ end
45
+
46
+ Logging.logger[DualActionGuest].tap {|logger|
47
+ logger.add_appenders 'colourful_stdout'
48
+ logger.level = :debug
49
+ }
50
+
51
+ FactoryGirl.define do
52
+
53
+ factory :dual_action_guest do
54
+ sequence :joined_at do |n|
55
+ n.weeks.ago
56
+ end
57
+
58
+ first_name "Freddy"
59
+ sequence :last_name do |n|
60
+ "Starr#{n}"
61
+ end
62
+
63
+ sequence :email do |n|
64
+ "fs#{n}@example.com"
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
71
+ describe 'AR model with more than one schedule' do
72
+
73
+ context "when created" do
74
+
75
+ before(:each) do
76
+ @guest = FactoryGirl.create :dual_action_guest, :status_check_at => 1.hour.ago, :insurance_check_at => 1.day.ago
77
+ @guest.should_not be_nil
78
+ end
79
+
80
+ context "both checks" do
81
+
82
+ before(:each) do
83
+ @checks = [:status_check, :insurance_check]
84
+ end
85
+
86
+ it "both checks should be ready to run" do
87
+ @checks.each { |c| @guest.should be_ready_to_run( c ) }
88
+ end
89
+
90
+ context "when run" do
91
+ before(:each) do
92
+ @checks.each { |c| @guest.run( c )}
93
+ end
94
+
95
+ it "should result in the guest having his status and his insurance checked" do
96
+ @guest.should be_status_checked
97
+ @guest.should be_insurance_checked
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+
108
+
109
+ describe "AR finders" do
110
+
111
+ before(:each) do
112
+ DualActionGuest.delete_all
113
+ @count = 10
114
+ @count.times do |index|
115
+ FactoryGirl.create :dual_action_guest, :status_check_at => 1.hour.ago, :insurance_check_at => 1.day.ago
116
+ end
117
+ end
118
+
119
+ it "should return the correct number guests ready to run" do
120
+
121
+ logger.info "All schedules: #{DualActionGuest.all_schedules}"
122
+
123
+ expect {
124
+ DualActionGuest.ready_to_run( :default )
125
+ }.to raise_error( RuntimeError )
126
+
127
+ DualActionGuest.ready_to_run( :status_check ).should have(@count).items
128
+ DualActionGuest.ready_to_run( :insurance_check ).should have(@count).items
129
+ end
130
+
131
+ end
@@ -0,0 +1,93 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ load_schema
4
+
5
+ class SimpleGuest < ActiveRecord::Base
6
+
7
+ include Urge::Scheduled
8
+
9
+ urge_schedule( :default, :scheduled_for => :scheduled_for, :action => :take_action )
10
+
11
+ private
12
+
13
+ def take_action( options )
14
+ logger.debug "In take_action. About to return empty set"
15
+ nil
16
+ end
17
+
18
+ end
19
+
20
+ FactoryGirl.define do
21
+
22
+ factory :simple_guest do
23
+ sequence :joined_at do |n|
24
+ n.weeks.ago
25
+ end
26
+
27
+ first_name "Freddy"
28
+ sequence :last_name do |n|
29
+ "Starr#{n}"
30
+ end
31
+
32
+ sequence :email do |n|
33
+ "fs#{n}@example.com"
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ describe 'Simplest active record model' do
41
+
42
+ before(:each) do
43
+ @guest = FactoryGirl.create :simple_guest, :scheduled_for => 1.day.ago
44
+ end
45
+
46
+ it "can be created" do
47
+ @guest.should_not be_nil
48
+ end
49
+
50
+ it "can be scheduled" do
51
+ @guest.scheduled_for.should_not be_nil
52
+ end
53
+
54
+ it "should be ready to run" do
55
+ @guest.should be_ready_to_run( :default )
56
+ end
57
+
58
+ it "should raise an exception if an attempt is made to access a schedule that doesn't exist" do
59
+ expect {
60
+ SimpleGuest.attr_name( :non_existent ).should be_nil
61
+ }.to raise_error
62
+ end
63
+
64
+ context "when run" do
65
+ before(:each) do
66
+ @guest.run :default
67
+ end
68
+
69
+ it "should not be rescheduled" do
70
+ @guest.scheduled_for.should be_nil
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ describe "AR finders" do
77
+
78
+ before(:each) do
79
+
80
+ SimpleGuest.delete_all
81
+
82
+ @count = 10
83
+ @count.times do |index|
84
+ FactoryGirl.create :simple_guest, :scheduled_for => 1.day.ago
85
+ end
86
+ end
87
+
88
+ it "should return the correct number guests ready to run" do
89
+ SimpleGuest.ready_to_run( :default ).should have(@count).items
90
+ end
91
+
92
+ end
93
+
@@ -0,0 +1,107 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ #
4
+ #
5
+ #
6
+ class TwoTask
7
+
8
+ include Urge::Scheduled
9
+
10
+ attr_accessor :scheduled_for_one, :scheduled_for_two
11
+
12
+ attr_reader :logger
13
+
14
+ def initialize( attrs, options )
15
+ @scheduled_for_one = attrs[:scheduled_for_one]
16
+ @scheduled_for_two = attrs[:scheduled_for_two]
17
+
18
+ @logger = options[:logger] || Logging.logger['scheduled_test']
19
+
20
+ @actions = options[:actions]
21
+ end
22
+
23
+ urge_schedule( :one, :scheduled_for => :scheduled_for_one, :action => :take_one )
24
+ urge_schedule( :two, :scheduled_for => :scheduled_for_two, :action => :take_two )
25
+
26
+ def take_one( options )
27
+ @actions << :action_one
28
+ 1.hour
29
+ end
30
+
31
+ def take_two( options )
32
+ @actions << :action_two
33
+ 2.hours
34
+ end
35
+
36
+ end
37
+
38
+ describe Urge::Scheduled do
39
+
40
+ context "when applied to an in memory object requiring two separate actions, that object" do
41
+
42
+ before(:each) do
43
+ @actions = []
44
+ @attrs = {
45
+ :scheduled_for_one => 1.second.ago,
46
+ :scheduled_for_two => 2.seconds.ago
47
+ }
48
+ @object = TwoTask.new @attrs, :actions => @actions
49
+ end
50
+
51
+ it "should exist with no actions" do
52
+ @object.should_not be_nil
53
+ @actions.should be_empty
54
+ end
55
+
56
+ context "when run in the context of task 1" do
57
+
58
+ before(:each) do
59
+ @object.run( :one )
60
+ end
61
+
62
+ it "should produce one action" do
63
+ @actions.should have(1).item
64
+ @actions.first.should == :action_one
65
+ end
66
+
67
+ it "should be rescheduled for one hour's time" do
68
+ @object.scheduled_for_one.to_i.should eq(( Time.now + 1.hour).to_i )
69
+ end
70
+
71
+ end
72
+
73
+ context "when run in the context of task 2" do
74
+
75
+ before(:each) do
76
+ @object.run( :two )
77
+ end
78
+
79
+ it "should produce another action" do
80
+ @actions.should have(1).item
81
+ @actions.first.should == :action_two
82
+ end
83
+
84
+ it "should be rescheduled for two hours' time" do
85
+ @object.scheduled_for_two.to_i.should eq( (Time.now + 2.hours).to_i )
86
+ end
87
+
88
+ end
89
+
90
+ context "when run in the context of both tasks" do
91
+
92
+ before(:each) do
93
+ @object.run( :one )
94
+ @object.run( :two )
95
+ end
96
+
97
+ it "should produce two actions" do
98
+ @actions.should have(2).items
99
+ @actions.should include( :action_one)
100
+ @actions.should include( :action_two)
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+
107
+ end
@@ -0,0 +1,101 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ Logging.logger['simple'].tap {|logger|
4
+ logger.add_appenders 'colourful_stdout'
5
+ logger.level = :info
6
+ }
7
+
8
+
9
+ #
10
+ #
11
+ #
12
+ class Simple
13
+
14
+ include Urge::Scheduled
15
+
16
+ attr_accessor :scheduled_for
17
+ attr_reader :actions, :logger
18
+
19
+ def initialize( attrs = {} )
20
+ self.scheduled_for = attrs[:scheduled_for]
21
+
22
+ @logger = Logging.logger['simple']
23
+ @actions = []
24
+ end
25
+
26
+ urge_schedule( :something, :scheduled_for => :scheduled_for, :action => :do_something )
27
+
28
+ def do_something( options )
29
+ @actions << :foo
30
+ nil
31
+ end
32
+
33
+ end
34
+
35
+ describe Urge::Scheduled do
36
+
37
+ context "when applied to a simple, in memory object" do
38
+
39
+ before(:each) do
40
+ @object = Simple.new
41
+ end
42
+
43
+ it "should exist with no actions" do
44
+ @object.should_not be_nil
45
+ @object.actions.should be_empty
46
+ end
47
+
48
+ context "that is not scheduled" do
49
+
50
+ context "when run in the correct context" do
51
+
52
+ before(:each) do
53
+ @object.run( :something )
54
+ end
55
+
56
+ it "should do nothing" do
57
+ @object.actions.should be_empty
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
64
+ context "that has been scheduled to run 1 second ago" do
65
+
66
+ before(:each) do
67
+ @object.scheduled_for = 1.second.ago
68
+ end
69
+
70
+ context "when run in the correct context" do
71
+
72
+ before(:each) do
73
+ @object.run( :something )
74
+ end
75
+
76
+ it "should produce one action" do
77
+ @object.actions.should have(1).item
78
+ @object.actions.first.should == :foo
79
+ end
80
+
81
+ it "should not be rescheduled" do
82
+ @object.scheduled_for.should be_nil
83
+ end
84
+
85
+ end
86
+
87
+ context "when run in a non existent context" do
88
+
89
+ it "should raise an error" do
90
+ expect {
91
+ @object.run( :doesnt_exist )
92
+ }.to raise_error( RuntimeError )
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+
101
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,39 @@
1
+ ActiveRecord::Schema.define( :version => 0 ) do
2
+
3
+ create_table 'credit_control_tasks', :force => true do |t|
4
+ t.string 'name'
5
+ t.timestamp 'scheduled_for'
6
+ t.integer 'client_id'
7
+ end
8
+
9
+ create_table 'clients', :force => true do |t|
10
+ t.string 'name'
11
+ t.string 'code'
12
+ end
13
+
14
+ create_table "simple_guests", :force => true do |t|
15
+ t.string "first_name"
16
+ t.string "last_name"
17
+ t.string "email"
18
+ t.datetime "started_at"
19
+ t.datetime "created_at", :null => false
20
+ t.datetime "updated_at", :null => false
21
+ t.string "aasm_state"
22
+ t.datetime "joined_at"
23
+ t.datetime "scheduled_for"
24
+ end
25
+
26
+ create_table "dual_action_guests", :force => true do |t|
27
+ t.string "first_name"
28
+ t.string "last_name"
29
+ t.string "email"
30
+ t.datetime "started_at"
31
+ t.datetime "created_at", :null => false
32
+ t.datetime "updated_at", :null => false
33
+ t.string "aasm_state"
34
+ t.datetime "joined_at"
35
+ t.datetime "status_check_at"
36
+ t.datetime "insurance_check_at"
37
+ end
38
+
39
+ end
@@ -0,0 +1,77 @@
1
+ require 'urge'
2
+
3
+ Logging.configure do
4
+
5
+ Logging.logger.root.level = :debug
6
+ Logging.logger.root.appenders = [Logging.appenders.file( 'log/test.log' )]
7
+
8
+ # here we setup a color scheme called 'bright'
9
+ Logging.color_scheme( 'bright',
10
+ :levels => {
11
+ :info => :green,
12
+ :warn => :yellow,
13
+ :error => :red,
14
+ :fatal => [:white, :on_red]
15
+ },
16
+ :date => :blue,
17
+ :logger => :cyan,
18
+ :message => :magenta
19
+ )
20
+
21
+ Logging.appenders.stdout(
22
+ 'colourful_stdout',
23
+ :layout => Logging.layouts.pattern(
24
+ :pattern => '[%d] %-5l %c: %m\n',
25
+ :color_scheme => 'bright'
26
+ )
27
+ )
28
+
29
+ test_loggers = %w{ scheduled_task_test credit_control_test scheduled_test scheduled }
30
+ # test_loggers = []
31
+
32
+ test_loggers.each do |name|
33
+ Logging.logger[name].tap {|logger|
34
+ logger.add_appenders 'colourful_stdout'
35
+ logger.level = :info
36
+ }
37
+ end
38
+
39
+ end
40
+
41
+ require 'aasm'
42
+ require 'logging'
43
+ require 'urge/scheduled'
44
+ require 'active_support/time'
45
+ require 'active_record'
46
+ require 'factory_girl'
47
+
48
+ FactoryGirl.find_definitions
49
+
50
+ class ScheduledTask
51
+
52
+ include Urge::Scheduled
53
+
54
+ attr_reader :logger
55
+
56
+ def initialize( name, options = {} )
57
+ if options[:logger]
58
+ @logger = options[:logger]
59
+ else
60
+ @logger = Logging.logger['scheduled_task_test']
61
+ end
62
+ self.name = name
63
+ end
64
+
65
+ end
66
+
67
+ ActiveRecord::Base.logger = Logging.logger['credit_control_test']
68
+
69
+ def load_schema
70
+ config = YAML::load( IO.read( File.dirname(__FILE__) + '/database.yml' ) )
71
+ ActiveRecord::Base.establish_connection config['sqlite3']
72
+ load( File.dirname(__FILE__) + "/schema.rb" )
73
+ end
74
+
75
+ class Client < ActiveRecord::Base
76
+ end
77
+
data/urge.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'urge/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "urge"
8
+ gem.version = Urge::VERSION
9
+ gem.authors = ["Nick Adams"]
10
+ gem.email = ["nadams@dsc.net"]
11
+ gem.description = %q{Urges an object to take some form of action, on a timed basis}
12
+ gem.summary = %q{Urges action}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency( 'activerecord' )
21
+ gem.add_dependency( 'aasm' )
22
+ gem.add_dependency( 'logging' )
23
+
24
+ gem.add_development_dependency 'rspec'
25
+ gem.add_development_dependency 'sqlite3'
26
+ gem.add_development_dependency 'factory_girl'
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: urge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nick Adams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aasm
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: logging
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: factory_girl
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Urges an object to take some form of action, on a timed basis
98
+ email:
99
+ - nadams@dsc.net
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - lib/urge.rb
110
+ - lib/urge/persistence.rb
111
+ - lib/urge/persistence/active_record_persistence.rb
112
+ - lib/urge/persistence/base.rb
113
+ - lib/urge/scheduled.rb
114
+ - lib/urge/version.rb
115
+ - spec/aasm.sqlite3.db
116
+ - spec/database.yml
117
+ - spec/factories/factory.rb
118
+ - spec/lib/active_record/dual_action_model_spec.rb
119
+ - spec/lib/active_record/simple_model_spec.rb
120
+ - spec/lib/dual_action_spec.rb
121
+ - spec/lib/scheduled_spec.rb
122
+ - spec/schema.rb
123
+ - spec/spec_helper.rb
124
+ - urge.gemspec
125
+ homepage: ''
126
+ licenses: []
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.0.0
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: Urges action
148
+ test_files:
149
+ - spec/aasm.sqlite3.db
150
+ - spec/database.yml
151
+ - spec/factories/factory.rb
152
+ - spec/lib/active_record/dual_action_model_spec.rb
153
+ - spec/lib/active_record/simple_model_spec.rb
154
+ - spec/lib/dual_action_spec.rb
155
+ - spec/lib/scheduled_spec.rb
156
+ - spec/schema.rb
157
+ - spec/spec_helper.rb