urge 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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