blue_light_special 0.2.0 → 0.2.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.
Files changed (41) hide show
  1. data/README.rdoc +28 -0
  2. data/VERSION +1 -1
  3. data/app/controllers/blue_light_special/impersonations_controller.rb +6 -3
  4. data/test/rails_root/app/controllers/admin/admin_controller.rb +14 -0
  5. data/test/rails_root/app/controllers/admin/users_controller.rb +52 -0
  6. data/test/rails_root/config/initializers/blue_light_special.rb +20 -3
  7. data/test/rails_root/db/migrate/{20100305173127_blue_light_special_create_users.rb → 20100727214301_blue_light_special_create_users.rb} +4 -1
  8. data/test/rails_root/db/migrate/{20100305173129_create_delayed_jobs.rb → 20100727214302_create_delayed_jobs.rb} +0 -0
  9. data/test/rails_root/test/factories/user.rb +10 -0
  10. data/test/rails_root/test/integration/admin/users_test.rb +201 -0
  11. data/test/rails_root/test/integration/edit_profile_test.rb +35 -0
  12. data/test/rails_root/test/integration/facebook_test.rb +48 -36
  13. data/test/rails_root/test/integration/impersonation_test.rb +5 -4
  14. data/test/rails_root/test/integration/password_reset_test.rb +5 -4
  15. data/test/rails_root/test/integration/sign_in_test.rb +0 -6
  16. data/test/rails_root/test/integration/sign_up_test.rb +3 -40
  17. data/test/rails_root/vendor/gems/delayed_job-1.8.4/generators/delayed_job/delayed_job_generator.rb +22 -0
  18. data/test/rails_root/vendor/gems/delayed_job-1.8.4/generators/delayed_job/templates/migration.rb +20 -0
  19. data/test/rails_root/vendor/gems/delayed_job-1.8.4/init.rb +1 -0
  20. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/command.rb +76 -0
  21. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/job.rb +270 -0
  22. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/message_sending.rb +22 -0
  23. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/performable_method.rb +55 -0
  24. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/recipes.rb +27 -0
  25. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/tasks.rb +15 -0
  26. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed/worker.rb +54 -0
  27. data/test/rails_root/vendor/gems/delayed_job-1.8.4/lib/delayed_job.rb +13 -0
  28. data/test/rails_root/vendor/gems/delayed_job-1.8.4/recipes/delayed_job.rb +1 -0
  29. data/test/rails_root/vendor/gems/delayed_job-1.8.4/spec/database.rb +42 -0
  30. data/test/rails_root/vendor/gems/delayed_job-1.8.4/spec/delayed_method_spec.rb +150 -0
  31. data/test/rails_root/vendor/gems/delayed_job-1.8.4/spec/job_spec.rb +406 -0
  32. data/test/rails_root/vendor/gems/delayed_job-1.8.4/spec/story_spec.rb +17 -0
  33. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb +21 -0
  34. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/lib/formtastic.rb +1312 -0
  35. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/lib/justin_french/formtastic.rb +10 -0
  36. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/rails/init.rb +3 -0
  37. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/spec/formtastic_spec.rb +3079 -0
  38. data/test/rails_root/vendor/gems/justinfrench-formtastic-0.2.4/spec/test_helper.rb +14 -0
  39. data/test/rails_root/vendor/gems/mini_fb-0.2.2/lib/mini_fb.rb +284 -0
  40. data/test/rails_root/vendor/gems/mini_fb-0.2.2/test/test_mini_fb.rb +29 -0
  41. metadata +111 -6
@@ -5,8 +5,8 @@ class ImpersonationTest < ActionController::IntegrationTest
5
5
  context 'When impersonating another user' do
6
6
 
7
7
  setup do
8
- @bob = Factory(:user, :email => 'bob@bob.bob', :display_name => 'Bobby')
9
- @admin_user = Factory(:admin_user, :email => 'admin@twongo.com')
8
+ @bob = Factory(:user, :email => 'bob@bob.bob')
9
+ @admin_user = Factory(:admin_user, :email => 'admin@example.com')
10
10
  sign_in_as @admin_user.email, @admin_user.password
11
11
  impersonate(@bob)
12
12
  end
@@ -20,7 +20,7 @@ class ImpersonationTest < ActionController::IntegrationTest
20
20
  end
21
21
 
22
22
  should 'be able to go back to the original admin user' do
23
- delete impersonate_url
23
+ click_link "Stop impersonating"
24
24
  assert controller.signed_in?
25
25
  assert_equal controller.current_user, @admin_user
26
26
  end
@@ -32,7 +32,8 @@ class ImpersonationTest < ActionController::IntegrationTest
32
32
 
33
33
 
34
34
  def impersonate(user)
35
- post impersonate_url(user)
35
+ visit impersonations_url
36
+ click_link "impersonate_#{user.id}"
36
37
  end
37
38
 
38
39
  end
@@ -41,11 +41,12 @@ class PasswordResetTest < ActionController::IntegrationTest
41
41
  request_password_reset(@user.email)
42
42
  @user.reload # catch updated confirmation token
43
43
  Delayed::Job.work_off
44
- sent = ActionMailer::Base.deliveries.last
45
- assert_equal @user.email, sent.recipients
46
- assert_match /password/i, sent.subject
47
44
  assert !@user.password_reset_token.blank?
48
- assert_match /#{@user.password_reset_token}/, sent.body[:url]
45
+ assert_sent_email do |email|
46
+ email.recipients =~ /#{Regexp.escape @user.email}/i &&
47
+ email.subject =~ /password/i &&
48
+ email.body[:url] =~ /#{Regexp.escape @user.password_reset_token}/
49
+ end
49
50
  end
50
51
 
51
52
  end
@@ -41,12 +41,6 @@ class SignInTest < ActionController::IntegrationTest
41
41
  assert controller.signed_in?
42
42
  end
43
43
 
44
- should 'redirect back to tender' do
45
- sign_in_as 'bob@bob.bob', 'password', sign_in_url(:to => 'http://help.twongo.com')
46
- assert_response :redirect
47
- assert_match(%r{\Ahttp://help.twongo.com/\?sso=}, response['Location'])
48
- end
49
-
50
44
  end
51
45
 
52
46
  context 'when confirmed but with bad credentials' do
@@ -15,8 +15,10 @@ class SignUpTest < ActionController::IntegrationTest
15
15
  context 'with invalid data' do
16
16
 
17
17
  should 'show error messages' do
18
- sign_up(:email => 'invalidemail', :password_confirmation => '')
18
+ sign_up(:email => 'invalidemail', :password_confirmation => '', :first_name => '', :last_name => '')
19
19
  assert_match /Email is invalid/, response.body
20
+ assert_match /First name.*blank/, response.body
21
+ assert_match /Last name.*blank/, response.body
20
22
  assert_match /Password doesn't match confirmation/, response.body
21
23
  end
22
24
 
@@ -29,11 +31,6 @@ class SignUpTest < ActionController::IntegrationTest
29
31
  assert controller.signed_in?
30
32
  end
31
33
 
32
- should 'see "Thanks for signing up with Twongo!"' do
33
- sign_up(:email => 'bob@bob.bob', :password => 'password', :password_confirmation => 'password')
34
- assert_match /Thanks for signing up with Twongo!/, response.body
35
- end
36
-
37
34
  should 'send a welcome email' do
38
35
  sign_up(:email => 'bob@bob.bob', :password => 'password', :password_confirmation => 'password')
39
36
  user = User.find_by_email('bob@bob.bob')
@@ -45,40 +42,6 @@ class SignUpTest < ActionController::IntegrationTest
45
42
 
46
43
  end
47
44
 
48
- context 'with an email for an existing deleted user' do
49
-
50
- setup do
51
- @user = Factory(:user, :email => 'deleted@example.com')
52
- @user.destroy
53
- end
54
-
55
- should 'undelete the existing user' do
56
- sign_up(:email => 'deleted@example.com')
57
- @user.reload
58
- assert !@user.destroyed?
59
- end
60
-
61
- should 'sign in the user' do
62
- sign_up(:email => 'deleted@example.com', :password => 'password', :password_confirmation => 'password')
63
- assert controller.signed_in?
64
- end
65
-
66
- should 'see "Thanks for signing up with Twongo!"' do
67
- sign_up(:email => 'deleted@example.com', :password => 'password', :password_confirmation => 'password')
68
- assert_match /Thanks for signing up with Twongo!/, response.body
69
- end
70
-
71
- should 'send a welcome email' do
72
- sign_up(:email => 'deleted@example.com', :password => 'password', :password_confirmation => 'password')
73
- user = User.find_by_email('deleted@example.com')
74
- Delayed::Job.work_off
75
- sent = ActionMailer::Base.deliveries.last
76
- assert_equal user.email, sent.recipients
77
- assert_match /welcome/i, sent.subject
78
- end
79
-
80
- end
81
-
82
45
  end
83
46
 
84
47
  end
@@ -0,0 +1,22 @@
1
+ class DelayedJobGenerator < Rails::Generator::Base
2
+ default_options :skip_migration => false
3
+
4
+ def manifest
5
+ record do |m|
6
+ m.template 'script', 'script/delayed_job', :chmod => 0755
7
+ unless options[:skip_migration]
8
+ m.migration_template "migration.rb", 'db/migrate',
9
+ :migration_file_name => "create_delayed_jobs"
10
+ end
11
+ end
12
+ end
13
+
14
+ protected
15
+
16
+ def add_options!(opt)
17
+ opt.separator ''
18
+ opt.separator 'Options:'
19
+ opt.on("--skip-migration", "Don't generate a migration") { |v| options[:skip_migration] = v }
20
+ end
21
+
22
+ end
@@ -0,0 +1,20 @@
1
+ class CreateDelayedJobs < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :delayed_jobs, :force => true do |table|
4
+ table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
5
+ table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
6
+ table.text :handler # YAML-encoded string of the object that will do work
7
+ table.text :last_error # reason for last failure (See Note below)
8
+ table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future.
9
+ table.datetime :locked_at # Set when a client is working on this object
10
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11
+ table.string :locked_by # Who is working on this object (if locked)
12
+ table.timestamps
13
+ end
14
+
15
+ end
16
+
17
+ def self.down
18
+ drop_table :delayed_jobs
19
+ end
20
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/lib/delayed_job'
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'daemons'
3
+ require 'optparse'
4
+
5
+ module Delayed
6
+ class Command
7
+ attr_accessor :worker_count
8
+
9
+ def initialize(args)
10
+ @files_to_reopen = []
11
+ @options = {:quiet => true}
12
+
13
+ @worker_count = 1
14
+
15
+ opts = OptionParser.new do |opts|
16
+ opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"
17
+
18
+ opts.on('-h', '--help', 'Show this message') do
19
+ puts opts
20
+ exit 1
21
+ end
22
+ opts.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |e|
23
+ STDERR.puts "The -e/--environment option has been deprecated and has no effect. Use RAILS_ENV and see http://github.com/collectiveidea/delayed_job/issues/#issue/7"
24
+ end
25
+ opts.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
26
+ @options[:min_priority] = n
27
+ end
28
+ opts.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
29
+ @options[:max_priority] = n
30
+ end
31
+ opts.on('-n', '--number_of_workers=workers', "Number of unique workers to spawn") do |worker_count|
32
+ @worker_count = worker_count.to_i rescue 1
33
+ end
34
+ end
35
+ @args = opts.parse!(args)
36
+ end
37
+
38
+ def daemonize
39
+ ObjectSpace.each_object(File) do |file|
40
+ @files_to_reopen << file unless file.closed?
41
+ end
42
+
43
+ worker_count.times do |worker_index|
44
+ process_name = worker_count == 1 ? "delayed_job" : "delayed_job.#{worker_index}"
45
+ Daemons.run_proc(process_name, :dir => "#{RAILS_ROOT}/tmp/pids", :dir_mode => :normal, :ARGV => @args) do |*args|
46
+ run process_name
47
+ end
48
+ end
49
+ end
50
+
51
+ def run(worker_name = nil)
52
+ Dir.chdir(RAILS_ROOT)
53
+
54
+ # Re-open file handles
55
+ @files_to_reopen.each do |file|
56
+ begin
57
+ file.reopen File.join(RAILS_ROOT, 'log', 'delayed_job.log'), 'w+'
58
+ file.sync = true
59
+ rescue ::Exception
60
+ end
61
+ end
62
+
63
+ Delayed::Worker.logger = Rails.logger
64
+ ActiveRecord::Base.connection.reconnect!
65
+
66
+ Delayed::Job.worker_name = "#{worker_name} #{Delayed::Job.worker_name}"
67
+
68
+ Delayed::Worker.new(@options).start
69
+ rescue => e
70
+ Rails.logger.fatal e
71
+ STDERR.puts e.message
72
+ exit 1
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,270 @@
1
+ require 'timeout'
2
+
3
+ module Delayed
4
+
5
+ class DeserializationError < StandardError
6
+ end
7
+
8
+ # A job object that is persisted to the database.
9
+ # Contains the work object as a YAML field.
10
+ class Job < ActiveRecord::Base
11
+ MAX_ATTEMPTS = 25
12
+ MAX_RUN_TIME = 4.hours
13
+ set_table_name :delayed_jobs
14
+
15
+ # By default failed jobs are destroyed after too many attempts.
16
+ # If you want to keep them around (perhaps to inspect the reason
17
+ # for the failure), set this to false.
18
+ cattr_accessor :destroy_failed_jobs
19
+ self.destroy_failed_jobs = true
20
+
21
+ # Every worker has a unique name which by default is the pid of the process.
22
+ # There are some advantages to overriding this with something which survives worker retarts:
23
+ # Workers can safely resume working on tasks which are locked by themselves. The worker will assume that it crashed before.
24
+ cattr_accessor :worker_name
25
+ self.worker_name = "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
26
+
27
+ NextTaskSQL = '(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR (locked_by = ?)) AND failed_at IS NULL'
28
+ NextTaskOrder = 'priority DESC, run_at ASC'
29
+
30
+ ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/
31
+
32
+ cattr_accessor :min_priority, :max_priority
33
+ self.min_priority = nil
34
+ self.max_priority = nil
35
+
36
+ # When a worker is exiting, make sure we don't have any locked jobs.
37
+ def self.clear_locks!
38
+ update_all("locked_by = null, locked_at = null", ["locked_by = ?", worker_name])
39
+ end
40
+
41
+ def failed?
42
+ failed_at
43
+ end
44
+ alias_method :failed, :failed?
45
+
46
+ def payload_object
47
+ @payload_object ||= deserialize(self['handler'])
48
+ end
49
+
50
+ def name
51
+ @name ||= begin
52
+ payload = payload_object
53
+ if payload.respond_to?(:display_name)
54
+ payload.display_name
55
+ else
56
+ payload.class.name
57
+ end
58
+ end
59
+ end
60
+
61
+ def payload_object=(object)
62
+ self['handler'] = object.to_yaml
63
+ end
64
+
65
+ # Reschedule the job in the future (when a job fails).
66
+ # Uses an exponential scale depending on the number of failed attempts.
67
+ def reschedule(message, backtrace = [], time = nil)
68
+ if (self.attempts += 1) < MAX_ATTEMPTS
69
+ time ||= Job.db_time_now + (attempts ** 4) + 5
70
+
71
+ self.run_at = time
72
+ self.last_error = message + "\n" + backtrace.join("\n")
73
+ self.unlock
74
+ save!
75
+ else
76
+ logger.info "* [JOB] PERMANENTLY removing #{self.name} because of #{attempts} consequetive failures."
77
+ destroy_failed_jobs ? destroy : update_attribute(:failed_at, Time.now)
78
+ end
79
+ end
80
+
81
+
82
+ # Try to run one job. Returns true/false (work done/work failed) or nil if job can't be locked.
83
+ def run_with_lock(max_run_time, worker_name)
84
+ logger.info "* [JOB] acquiring lock on #{name}"
85
+ unless lock_exclusively!(max_run_time, worker_name)
86
+ # We did not get the lock, some other worker process must have
87
+ logger.warn "* [JOB] failed to acquire exclusive lock for #{name}"
88
+ return nil # no work done
89
+ end
90
+
91
+ begin
92
+ runtime = Benchmark.realtime do
93
+ Timeout.timeout(max_run_time.to_i) { invoke_job }
94
+ destroy
95
+ end
96
+ # TODO: warn if runtime > max_run_time ?
97
+ logger.info "* [JOB] #{name} completed after %.4f" % runtime
98
+ return true # did work
99
+ rescue Exception => e
100
+ reschedule e.message, e.backtrace
101
+ log_exception(e)
102
+ return false # work failed
103
+ end
104
+ end
105
+
106
+ # Add a job to the queue
107
+ def self.enqueue(*args, &block)
108
+ object = block_given? ? EvaledJob.new(&block) : args.shift
109
+
110
+ unless object.respond_to?(:perform) || block_given?
111
+ raise ArgumentError, 'Cannot enqueue items which do not respond to perform'
112
+ end
113
+
114
+ priority = args.first || 0
115
+ run_at = args[1]
116
+
117
+ Job.create(:payload_object => object, :priority => priority.to_i, :run_at => run_at)
118
+ end
119
+
120
+ # Find a few candidate jobs to run (in case some immediately get locked by others).
121
+ def self.find_available(limit = 5, max_run_time = MAX_RUN_TIME)
122
+
123
+ time_now = db_time_now
124
+
125
+ sql = NextTaskSQL.dup
126
+
127
+ conditions = [time_now, time_now - max_run_time, worker_name]
128
+
129
+ if self.min_priority
130
+ sql << ' AND (priority >= ?)'
131
+ conditions << min_priority
132
+ end
133
+
134
+ if self.max_priority
135
+ sql << ' AND (priority <= ?)'
136
+ conditions << max_priority
137
+ end
138
+
139
+ conditions.unshift(sql)
140
+
141
+ ActiveRecord::Base.silence do
142
+ find(:all, :conditions => conditions, :order => NextTaskOrder, :limit => limit)
143
+ end
144
+ end
145
+
146
+ # Run the next job we can get an exclusive lock on.
147
+ # If no jobs are left we return nil
148
+ def self.reserve_and_run_one_job(max_run_time = MAX_RUN_TIME)
149
+
150
+ # We get up to 5 jobs from the db. In case we cannot get exclusive access to a job we try the next.
151
+ # this leads to a more even distribution of jobs across the worker processes
152
+ find_available(5, max_run_time).each do |job|
153
+ t = job.run_with_lock(max_run_time, worker_name)
154
+ return t unless t == nil # return if we did work (good or bad)
155
+ end
156
+
157
+ nil # we didn't do any work, all 5 were not lockable
158
+ end
159
+
160
+ # Lock this job for this worker.
161
+ # Returns true if we have the lock, false otherwise.
162
+ def lock_exclusively!(max_run_time, worker = worker_name)
163
+ now = self.class.db_time_now
164
+ affected_rows = if locked_by != worker
165
+ # We don't own this job so we will update the locked_by name and the locked_at
166
+ self.class.update_all(["locked_at = ?, locked_by = ?", now, worker], ["id = ? and (locked_at is null or locked_at < ?) and (run_at <= ?)", id, (now - max_run_time.to_i), now])
167
+ else
168
+ # We already own this job, this may happen if the job queue crashes.
169
+ # Simply resume and update the locked_at
170
+ self.class.update_all(["locked_at = ?", now], ["id = ? and locked_by = ?", id, worker])
171
+ end
172
+ if affected_rows == 1
173
+ self.locked_at = now
174
+ self.locked_by = worker
175
+ return true
176
+ else
177
+ return false
178
+ end
179
+ end
180
+
181
+ # Unlock this job (note: not saved to DB)
182
+ def unlock
183
+ self.locked_at = nil
184
+ self.locked_by = nil
185
+ end
186
+
187
+ # This is a good hook if you need to report job processing errors in additional or different ways
188
+ def log_exception(error)
189
+ logger.error "* [JOB] #{name} failed with #{error.class.name}: #{error.message} - #{attempts} failed attempts"
190
+ logger.error(error)
191
+ end
192
+
193
+ # Do num jobs and return stats on success/failure.
194
+ # Exit early if interrupted.
195
+ def self.work_off(num = 100)
196
+ success, failure = 0, 0
197
+
198
+ num.times do
199
+ case self.reserve_and_run_one_job
200
+ when true
201
+ success += 1
202
+ when false
203
+ failure += 1
204
+ else
205
+ break # leave if no work could be done
206
+ end
207
+ break if $exit # leave if we're exiting
208
+ end
209
+
210
+ return [success, failure]
211
+ end
212
+
213
+ # Moved into its own method so that new_relic can trace it.
214
+ def invoke_job
215
+ payload_object.perform
216
+ end
217
+
218
+ private
219
+
220
+ def deserialize(source)
221
+ handler = YAML.load(source) rescue nil
222
+
223
+ unless handler.respond_to?(:perform)
224
+ if handler.nil? && source =~ ParseObjectFromYaml
225
+ handler_class = $1
226
+ end
227
+ attempt_to_load(handler_class || handler.class)
228
+ handler = YAML.load(source)
229
+ end
230
+
231
+ return handler if handler.respond_to?(:perform)
232
+
233
+ raise DeserializationError,
234
+ 'Job failed to load: Unknown handler. Try to manually require the appropriate file.'
235
+ rescue TypeError, LoadError, NameError => e
236
+ raise DeserializationError,
237
+ "Job failed to load: #{e.message}. Try to manually require the required file."
238
+ end
239
+
240
+ # Constantize the object so that ActiveSupport can attempt
241
+ # its auto loading magic. Will raise LoadError if not successful.
242
+ def attempt_to_load(klass)
243
+ klass.constantize
244
+ end
245
+
246
+ # Get the current time (GMT or local depending on DB)
247
+ # Note: This does not ping the DB to get the time, so all your clients
248
+ # must have syncronized clocks.
249
+ def self.db_time_now
250
+ (ActiveRecord::Base.default_timezone == :utc) ? Time.now.utc : Time.now
251
+ end
252
+
253
+ protected
254
+
255
+ def before_save
256
+ self.run_at ||= self.class.db_time_now
257
+ end
258
+
259
+ end
260
+
261
+ class EvaledJob
262
+ def initialize
263
+ @job = yield
264
+ end
265
+
266
+ def perform
267
+ eval(@job)
268
+ end
269
+ end
270
+ end