subscribed_to 0.2.0 → 0.3.0

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.
Files changed (63) hide show
  1. data/Gemfile +1 -0
  2. data/Gemfile.lock +2 -0
  3. data/README.rdoc +32 -0
  4. data/app/controllers/subscribed_to/mail_chimp_web_hooks_controller.rb +19 -0
  5. data/config/routes.rb +5 -0
  6. data/lib/active_record_extensions.rb +10 -0
  7. data/lib/generators/subscribed_to/templates/migration.rb +8 -0
  8. data/lib/generators/subscribed_to/templates/subscribed_to.rb +4 -0
  9. data/lib/subscribed_to/engine.rb +7 -0
  10. data/lib/subscribed_to/mail_chimp/config.rb +5 -3
  11. data/lib/subscribed_to/mail_chimp/web_hook.rb +136 -0
  12. data/lib/subscribed_to/mail_chimp.rb +3 -2
  13. data/lib/subscribed_to/version.rb +1 -1
  14. data/lib/subscribed_to.rb +9 -0
  15. data/spec/app/controllers/subscribed_to/mail_chimp_web_hooks_spec.rb +147 -0
  16. data/spec/dummy/Rakefile +7 -0
  17. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  18. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  19. data/spec/dummy/app/models/user.rb +3 -0
  20. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  21. data/spec/dummy/config/application.rb +45 -0
  22. data/spec/dummy/config/boot.rb +10 -0
  23. data/spec/dummy/config/database.yml +22 -0
  24. data/spec/dummy/config/environment.rb +5 -0
  25. data/spec/dummy/config/environments/development.rb +26 -0
  26. data/spec/dummy/config/environments/production.rb +49 -0
  27. data/spec/dummy/config/environments/test.rb +35 -0
  28. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  29. data/spec/dummy/config/initializers/inflections.rb +10 -0
  30. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  31. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  32. data/spec/dummy/config/initializers/session_store.rb +8 -0
  33. data/spec/dummy/config/initializers/subscribed_to.rb +21 -0
  34. data/spec/dummy/config/locales/en.yml +5 -0
  35. data/spec/dummy/config/routes.rb +2 -0
  36. data/spec/dummy/config.ru +4 -0
  37. data/spec/dummy/db/migrate/1_create_users.rb +18 -0
  38. data/spec/dummy/db/schema.rb +26 -0
  39. data/spec/dummy/public/404.html +26 -0
  40. data/spec/dummy/public/422.html +26 -0
  41. data/spec/dummy/public/500.html +26 -0
  42. data/spec/dummy/public/favicon.ico +0 -0
  43. data/spec/dummy/public/javascripts/application.js +2 -0
  44. data/spec/dummy/public/javascripts/controls.js +965 -0
  45. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  46. data/spec/dummy/public/javascripts/effects.js +1123 -0
  47. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  48. data/spec/dummy/public/javascripts/rails.js +191 -0
  49. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  50. data/spec/dummy/script/rails +6 -0
  51. data/spec/factories/users.rb +1 -0
  52. data/spec/{generators → lib/generators}/install_generator_spec.rb +0 -0
  53. data/spec/{subscribed_to → lib/subscribed_to}/mail_chimp/config_spec.rb +0 -0
  54. data/spec/lib/subscribed_to/mail_chimp/web_hook_spec.rb +158 -0
  55. data/spec/lib/subscribed_to/mail_chimp_spec.rb +74 -0
  56. data/spec/{subscribed_to_spec.rb → lib/subscribed_to_spec.rb} +0 -0
  57. data/spec/spec_helper.rb +70 -28
  58. data/subscribed_to.gemspec +136 -0
  59. metadata +77 -26
  60. data/spec/app/models/user.rb +0 -43
  61. data/spec/database.yml.example +0 -17
  62. data/spec/schema.rb +0 -9
  63. data/spec/subscribed_to/mail_chimp_spec.rb +0 -53
@@ -0,0 +1,191 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+
176
+ Ajax.Responders.register({
177
+ onCreate: function(request) {
178
+ var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
179
+
180
+ if (csrf_meta_tag) {
181
+ var header = 'X-CSRF-Token',
182
+ token = csrf_meta_tag.readAttribute('content');
183
+
184
+ if (!request.options.requestHeaders) {
185
+ request.options.requestHeaders = {};
186
+ }
187
+ request.options.requestHeaders[header] = token;
188
+ }
189
+ }
190
+ });
191
+ })();
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -3,6 +3,7 @@ Factory.define :subscribed_user, :class => User do |u|
3
3
  u.last_name "Salczynski"
4
4
  u.email "eric@wehaventthetime.com"
5
5
  u.subscribed_to_list true
6
+ u.mail_chimp_id "123"
6
7
  u.password "abc123"
7
8
  end
8
9
 
@@ -0,0 +1,158 @@
1
+ require 'spec_helper'
2
+
3
+ describe SubscribedTo::MailChimp::WebHook do
4
+ before do
5
+ SubscribedTo.mail_chimp do |config|
6
+ config.lists = {
7
+ :mailing_list => {
8
+ :id => "abc123",
9
+ :merge_vars => {"FNAME" => :first_name, "LNAME" => :last_name, "EMAIL" => :email}}}
10
+
11
+ # normally not set in config, but necessary for testing
12
+ config.enabled_models = {"abc123" => ["User"]}
13
+ end
14
+ end
15
+
16
+ it "should quietly fail and log the error if no member is found" do
17
+ Rails.logger.expects(:warn).once
18
+ expect do
19
+ SubscribedTo::MailChimp::WebHook.process({
20
+ "type" => "subscribe",
21
+ "data" => {
22
+ "list_id" => "abc123",
23
+ "web_id" => "231546749",
24
+ "merges" => {
25
+ "EMAIL" => "fake.user@email.com" }}})
26
+ end.not_to raise_exception
27
+ end
28
+
29
+ it "should rate limit updates via the api to once every 2 minutes" do
30
+ # user was updated 2:01 ago
31
+ pretend_now_is(Time.zone.now - 120.seconds) do
32
+ @user = Factory.build(:non_subscribed_user)
33
+ @user.stubs(:subscribe_to_list)
34
+ @user.save
35
+ end
36
+
37
+ # pretend it's 1:59 from last update
38
+ pretend_now_is(Time.zone.now - 1.seconds) do
39
+ expect do
40
+ SubscribedTo::MailChimp::WebHook.process({
41
+ "type" => "upemail",
42
+ "data" => {
43
+ "list_id" => "abc123",
44
+ "new_email" => "my.new@email.com",
45
+ "old_email" => @user.email }})
46
+ end.not_to change { @user.reload.email }.to("my.new@email.com")
47
+ end
48
+
49
+ # pretend it's 2:01 from last update
50
+ pretend_now_is(Time.zone.now + 120.seconds) do
51
+ expect do
52
+ SubscribedTo::MailChimp::WebHook.process({
53
+ "type" => "upemail",
54
+ "data" => {
55
+ "list_id" => "abc123",
56
+ "new_email" => "my.new@email.com",
57
+ "old_email" => @user.email }})
58
+ end.to change { @user.reload.email }.to("my.new@email.com")
59
+ end
60
+ end
61
+
62
+ it "should write a warning to the logger if the event is not supported" do
63
+ Rails.logger.expects(:warn).once
64
+ expect do
65
+ SubscribedTo::MailChimp::WebHook.process({
66
+ "type" => "nonevent",
67
+ "data" => { "list_id" => "abc123" }})
68
+ end.not_to raise_exception
69
+ end
70
+
71
+ context "for a new user" do
72
+ before(:each) do
73
+ # user was updated 2:01 ago
74
+ pretend_now_is(Time.zone.now - 121.seconds) do
75
+ @user = Factory.build(:non_subscribed_user)
76
+ @user.stubs(:subscribe_to_list)
77
+ @user.save
78
+ @user.expects(:update_list_member).never
79
+ end
80
+ end
81
+
82
+ context "when they subscribe" do
83
+ it "should set subscribed_to_list to true, and set the mail_chimp_id" do
84
+ @user.mail_chimp_id.should be_nil
85
+ expect do
86
+ expect do
87
+ SubscribedTo::MailChimp::WebHook.process({
88
+ "type" => "subscribe",
89
+ "data" => {
90
+ "list_id" => "abc123",
91
+ "web_id" => "231546749",
92
+ "merges" => {
93
+ "EMAIL" => @user.email }}})
94
+ end.to change { @user.reload.subscribed_to_list }.from(false).to(true)
95
+ end.to change { @user.mail_chimp_id }.to(231546749)
96
+ end
97
+ end
98
+ end
99
+
100
+ context "for an existing user" do
101
+ before(:each) do
102
+ # user was updated 2:01 ago
103
+ pretend_now_is(Time.zone.now - 121.seconds) do
104
+ @user = Factory.build(:subscribed_user)
105
+ @user.stubs(:subscribe_to_list)
106
+ @user.save
107
+ @user.expects(:update_list_member).never
108
+ end
109
+ end
110
+
111
+ context "when they unsubscribe" do
112
+ it "should set subscribed_to_list to false" do
113
+ expect do
114
+ SubscribedTo::MailChimp::WebHook.process({
115
+ "type" => "unsubscribe",
116
+ "data" => {
117
+ "list_id" => "abc123",
118
+ "web_id" => "123",
119
+ "merges" => {
120
+ "EMAIL" => @user.email }}})
121
+ end.to change { @user.reload.subscribed_to_list }.from(true).to(false)
122
+ end
123
+ end
124
+
125
+ context "when they change their email" do
126
+ it "should update the user email" do
127
+ expect do
128
+ SubscribedTo::MailChimp::WebHook.process({
129
+ "type" => "upemail",
130
+ "data" => {
131
+ "list_id" => "abc123",
132
+ "new_email" => "my.new@email.com",
133
+ "old_email" => @user.email }})
134
+ end.to change { @user.reload.email }.to("my.new@email.com")
135
+ end
136
+ end
137
+
138
+ context "when they change their profile information" do
139
+ it "should update the attributes defined in the merge vars config" do
140
+ expect do
141
+ expect do
142
+ expect do
143
+ SubscribedTo::MailChimp::WebHook.process({
144
+ "type" => "profile",
145
+ "data" => {
146
+ "list_id" => "abc123",
147
+ "web_id" => "123",
148
+ "merges" => {
149
+ "EMAIL" => "my.new@email.com",
150
+ "FNAME" => "John",
151
+ "LNAME" => "Locke" }}})
152
+ end.to change { @user.reload.email }.to("my.new@email.com")
153
+ end.to change { @user.first_name }.to("John")
154
+ end.to change { @user.last_name }.to("Locke")
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+ require 'hominid'
3
+
4
+ describe SubscribedTo::MailChimp do
5
+ before(:each) do
6
+ SubscribedTo.mail_chimp do |config|
7
+ config.api_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-us1"
8
+ config.lists = {:mailing_list => {:id => "123456", :merge_vars => {"FNAME" => :first_name, "LNAME" => :last_name, "EMAIL" => :email}}}
9
+ end
10
+
11
+ @h = mock("hominid")
12
+ Hominid::API.stubs(:new).returns(@h)
13
+ end
14
+
15
+ context "for a new user" do
16
+ it "should subscribe the user if subscribed_to_list is true" do
17
+ @h.expects(:list_subscribe).once
18
+ Factory(:subscribed_user)
19
+ end
20
+
21
+ it "should not subscribe the user if subscribed_to_list is false" do
22
+ @h.expects(:list_subscribe).never
23
+ Factory(:non_subscribed_user)
24
+ end
25
+
26
+ it "should rescue and log Hominid::APIErrors" do
27
+ @h.expects(:list_subscribe).raises(Hominid::APIError, mock("FaultException", :faultCode => "xxx", :message => "api error"))
28
+ Rails.logger.expects(:warn).once
29
+ Factory(:subscribed_user)
30
+ end
31
+ end
32
+
33
+ context "for an existing user" do
34
+ context "who is not subscribed to the mailing list" do
35
+ before { @user = Factory(:non_subscribed_user) }
36
+
37
+ it "should subscribe the user" do
38
+ @h.expects(:list_subscribe).once
39
+ @user.update_attributes({:subscribed_to_list => true})
40
+ end
41
+ end
42
+
43
+ context "who is subscribed to the mailing list" do
44
+ before do
45
+ @user = Factory.build(:subscribed_user)
46
+ @user.stubs(:subscribe_to_list)
47
+ @user.save
48
+ end
49
+
50
+ it "should unsubscribe the user" do
51
+ @h.expects(:list_unsubscribe).once
52
+ @user.update_attributes({:subscribed_to_list => false})
53
+ end
54
+
55
+ it "should update list member attributes for the user" do
56
+ @h.expects(:list_update_member).once
57
+ @user.update_attributes({:first_name => "Ed", :last_name => "Salczynski", :email => "ed@whtt.me"})
58
+ end
59
+
60
+ it "should not update list member when attributes not defined in merge vars are changed" do
61
+ @h.expects(:list_subscribe).never
62
+ @h.expects(:list_unsubscribe).never
63
+ @h.expects(:list_update_member).never
64
+ @user.update_attributes({:password => "zyx321"})
65
+ end
66
+
67
+ it "should rescue and log Hominid::APIErrors" do
68
+ @h.expects(:list_unsubscribe).raises(Hominid::APIError, mock("FaultException", :faultCode => "xxx", :message => "api error"))
69
+ Rails.logger.expects(:warn).once
70
+ @user.update_attributes({:subscribed_to_list => false})
71
+ end
72
+ end
73
+ end
74
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,48 +1,90 @@
1
1
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
2
  $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
3
4
  require 'simplecov'
4
- SimpleCov.start
5
+ SimpleCov.start do
6
+ add_filter "/spec/"
7
+ end
5
8
 
6
- require 'rspec'
9
+ require 'database_cleaner'
7
10
  require 'factory_girl'
8
- require 'subscribed_to'
9
11
 
10
- # Requires supporting files with custom matchers and macros, etc,
11
- # in ./support/ and its subdirectories.
12
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
12
+ # Configure Rails Envinronment
13
+ ENV["RAILS_ENV"] = "test"
13
14
 
14
- RSpec.configure do |config|
15
- config.mock_with :mocha
16
- end
15
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
17
16
 
18
- ENV['DB'] ||= 'sqlite3'
17
+ ActionMailer::Base.delivery_method = :test
18
+ ActionMailer::Base.perform_deliveries = true
19
+ ActionMailer::Base.default_url_options[:host] = "test.com"
19
20
 
20
- database_yml = File.expand_path('../database.yml', __FILE__)
21
- if File.exists?(database_yml)
22
- active_record_configuration = YAML.load_file(database_yml)[ENV['DB']]
21
+ Rails.backtrace_cleaner.remove_silencers!
23
22
 
24
- ActiveRecord::Base.establish_connection(active_record_configuration)
25
- ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "log", "debug.log"))
23
+ # Run any available migration
24
+ ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__)
25
+
26
+ # Load support files
27
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
28
+
29
+ Dir["#{File.dirname(__FILE__)}/factories/*.rb"].each {|f| load f}
30
+
31
+ RSpec.configure do |config|
32
+ config.mock_with :mocha
26
33
 
27
- ActiveRecord::Base.silence do
28
- ActiveRecord::Migration.verbose = false
34
+ config.before(:suite) do
35
+ DatabaseCleaner.strategy = :transaction
36
+ DatabaseCleaner.clean_with(:truncation)
37
+ end
29
38
 
30
- load('schema.rb')
31
- Dir["#{File.dirname(__FILE__)}/app/**/*.rb"].each {|f| load f}
32
- Dir["#{File.dirname(__FILE__)}/factories/*.rb"].each {|f| load f}
39
+ config.before(:each) do
40
+ DatabaseCleaner.start
33
41
  end
34
42
 
35
- else
36
- raise "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample"
43
+ config.after(:each) do
44
+ DatabaseCleaner.clean
45
+ end
37
46
  end
38
47
 
39
- Rails.logger = Logger.new(File.join(File.dirname(__FILE__), "log", "debug.log"))
40
48
 
41
- def clean_database!
42
- models = [User]
43
- models.each do |model|
44
- ActiveRecord::Base.connection.execute "DELETE FROM #{model.table_name}"
49
+ # Time warp functionality from https://github.com/harvesthq/time-warp
50
+ # Extend the Time class so that we can offset the time that 'now'
51
+ # returns. This should allow us to effectively time warp for functional
52
+ # tests that require limits per hour, what not.
53
+ if !Time.respond_to?(:real_now) # assures there is no infinite looping when aliasing #now
54
+ Time.class_eval do
55
+ class << self
56
+ attr_accessor :testing_offset
57
+
58
+ alias_method :real_now, :now
59
+ def now
60
+ real_now - testing_offset
61
+ end
62
+ alias_method :new, :now
63
+
64
+ end
65
+ end
66
+ end
67
+ Time.testing_offset = 0
68
+
69
+ def pretend_now_is(*args)
70
+ Time.testing_offset = Time.now - time_from(*args)
71
+ if block_given?
72
+ begin
73
+ yield
74
+ ensure
75
+ reset_to_real_time
76
+ end
45
77
  end
46
78
  end
47
79
 
48
- clean_database!
80
+ ##
81
+ # Reset to real time.
82
+ def reset_to_real_time
83
+ Time.testing_offset = 0
84
+ end
85
+
86
+ def time_from(*args)
87
+ return args[0] if 1 == args.size && args[0].is_a?(Time)
88
+ return args[0].to_time if 1 == args.size && args[0].respond_to?(:to_time) # For example, if it's a Date.
89
+ Time.utc(*args)
90
+ end