blue_light_special 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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