governor_background 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +28 -0
  4. data/Gemfile.lock +163 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +91 -0
  7. data/Rakefile +50 -0
  8. data/VERSION +1 -0
  9. data/app/helpers/governor_background_helper.rb +11 -0
  10. data/governor_background.gemspec +206 -0
  11. data/lib/governor_background/controllers/methods.rb +18 -0
  12. data/lib/governor_background/delayed/job.rb +70 -0
  13. data/lib/governor_background/delayed/performer.rb +13 -0
  14. data/lib/governor_background/handler.rb +34 -0
  15. data/lib/governor_background/job_manager.rb +22 -0
  16. data/lib/governor_background/rails.rb +8 -0
  17. data/lib/governor_background/resque/job.rb +51 -0
  18. data/lib/governor_background/resque/performer.rb +12 -0
  19. data/lib/governor_background/resque/performer_with_state.rb +17 -0
  20. data/lib/governor_background.rb +22 -0
  21. data/spec/governor_background/delayed/job_spec.rb +47 -0
  22. data/spec/governor_background/handler_spec.rb +46 -0
  23. data/spec/governor_background/job_manager_spec.rb +44 -0
  24. data/spec/governor_background/resque/performer_spec.rb +17 -0
  25. data/spec/governor_background/resque/performer_with_state_spec.rb +18 -0
  26. data/spec/governor_background_spec.rb +7 -0
  27. data/spec/rails_app/.gitignore +4 -0
  28. data/spec/rails_app/Gemfile +43 -0
  29. data/spec/rails_app/Gemfile.lock +128 -0
  30. data/spec/rails_app/README +256 -0
  31. data/spec/rails_app/Rakefile +7 -0
  32. data/spec/rails_app/app/controllers/application_controller.rb +3 -0
  33. data/spec/rails_app/app/controllers/home_controller.rb +2 -0
  34. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  35. data/spec/rails_app/app/helpers/home_helper.rb +2 -0
  36. data/spec/rails_app/app/models/article.rb +5 -0
  37. data/spec/rails_app/app/models/user.rb +9 -0
  38. data/spec/rails_app/app/views/governor/articles/index.xml.builder +5 -0
  39. data/spec/rails_app/app/views/home/index.html.erb +0 -0
  40. data/spec/rails_app/app/views/layouts/application.html.erb +22 -0
  41. data/spec/rails_app/config/application.rb +42 -0
  42. data/spec/rails_app/config/boot.rb +14 -0
  43. data/spec/rails_app/config/database.yml +19 -0
  44. data/spec/rails_app/config/environment.rb +5 -0
  45. data/spec/rails_app/config/environments/development.rb +26 -0
  46. data/spec/rails_app/config/environments/production.rb +49 -0
  47. data/spec/rails_app/config/environments/test.rb +35 -0
  48. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  49. data/spec/rails_app/config/initializers/devise.rb +142 -0
  50. data/spec/rails_app/config/initializers/governor.rb +36 -0
  51. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  52. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  53. data/spec/rails_app/config/initializers/resque.rb +5 -0
  54. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  55. data/spec/rails_app/config/initializers/session_store.rb +8 -0
  56. data/spec/rails_app/config/locales/devise.en.yml +39 -0
  57. data/spec/rails_app/config/locales/en.yml +5 -0
  58. data/spec/rails_app/config/redis.yml +15 -0
  59. data/spec/rails_app/config/routes.rb +64 -0
  60. data/spec/rails_app/config.ru +4 -0
  61. data/spec/rails_app/db/migrate/20110329032256_devise_create_users.rb +26 -0
  62. data/spec/rails_app/db/migrate/20110330020108_governor_create_articles.rb +15 -0
  63. data/spec/rails_app/db/migrate/20110421014910_create_delayed_jobs.rb +21 -0
  64. data/spec/rails_app/db/schema.rb +59 -0
  65. data/spec/rails_app/db/seeds.rb +7 -0
  66. data/spec/rails_app/lib/tasks/.gitkeep +0 -0
  67. data/spec/rails_app/lib/tasks/resque.rake +2 -0
  68. data/spec/rails_app/public/404.html +26 -0
  69. data/spec/rails_app/public/422.html +26 -0
  70. data/spec/rails_app/public/500.html +26 -0
  71. data/spec/rails_app/public/favicon.ico +0 -0
  72. data/spec/rails_app/public/images/rails.png +0 -0
  73. data/spec/rails_app/public/javascripts/application.js +2 -0
  74. data/spec/rails_app/public/javascripts/controls.js +965 -0
  75. data/spec/rails_app/public/javascripts/dragdrop.js +974 -0
  76. data/spec/rails_app/public/javascripts/effects.js +1123 -0
  77. data/spec/rails_app/public/javascripts/prototype.js +6001 -0
  78. data/spec/rails_app/public/javascripts/rails.js +191 -0
  79. data/spec/rails_app/public/robots.txt +5 -0
  80. data/spec/rails_app/public/stylesheets/.gitkeep +0 -0
  81. data/spec/rails_app/script/delayed_job +5 -0
  82. data/spec/rails_app/script/rails +6 -0
  83. data/spec/rails_app/spec/factories.rb +12 -0
  84. data/spec/rails_app/vendor/plugins/.gitkeep +0 -0
  85. data/spec/spec_helper.rb +36 -0
  86. metadata +417 -0
@@ -0,0 +1,13 @@
1
+ module GovernorBackground
2
+ module Delayed
3
+ class Performer < Struct.new(:article, :method_name)
4
+ def perform
5
+ article.send(method_name)
6
+ end
7
+
8
+ def error(job, exception)
9
+ # handle failure
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ module GovernorBackground
2
+ class Handler
3
+ class << self
4
+ def run_in_background(object, method)
5
+ job = if delayed_job?
6
+ Delayed::Job.new(object, method, ::Delayed::Job.enqueue(Delayed::Performer.new(object, method)))
7
+ elsif resque?
8
+ resource_key, id = object.class.name.tableize.to_sym, object.id
9
+ if resque_with_status?
10
+ require File.expand_path('../resque/performer_with_state', __FILE__)
11
+ Resque::Job.new(object, method, Resque::PerformerWithState.create(:resource => resource_key, :id => id, :method_name => method))
12
+ else
13
+ ::Resque.enqueue(Resque::Performer, resource_key, id, method)
14
+ nil # not much use in holding on to state if we can't track it
15
+ end
16
+ end
17
+ JobManager.add(job) unless job.blank?
18
+ end
19
+
20
+ private
21
+ def delayed_job?
22
+ defined? ::Delayed::Job
23
+ end
24
+
25
+ def resque?
26
+ defined? ::Resque
27
+ end
28
+
29
+ def resque_with_status?
30
+ defined? ::Resque::Status
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ module GovernorBackground
2
+ class JobManager
3
+ @@finished_statuses = %w(completed failed killed).freeze
4
+ cattr_reader :jobs
5
+ class << self
6
+ @@jobs = []
7
+ def add(job)
8
+ @@jobs << job
9
+ end
10
+
11
+ def clean(time = 1.day.ago)
12
+ @@jobs.reject!{|j| j.created_at < time}
13
+ end
14
+
15
+ def finished_jobs
16
+ finished_jobs = @@jobs.select{|j| @@finished_statuses.include? j.status }
17
+ @@jobs -= finished_jobs
18
+ return finished_jobs
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ module GovernorBackground
2
+ # Standard <code>Rails::Engine</code>.
3
+ class Engine < ::Rails::Engine
4
+ config.to_prepare do
5
+ ApplicationController.helper(GovernorBackgroundHelper)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,51 @@
1
+ module Governor
2
+ module Resque
3
+ class Job
4
+ attr_reader :resource, :method_name, :created_at
5
+ def initialize(resource, method_name, job_id)
6
+ @resource = resource
7
+ @method_name = method_name
8
+ @id = job_id
9
+ @created_at = Time.now
10
+ end
11
+
12
+ def status
13
+ # if we can't find it, assume it's been killed (not really sure if this is the proper assumption)
14
+ (job = resque_status) ? job.status : 'killed'
15
+ end
16
+
17
+ def queued?
18
+ proxy :queued?
19
+ end
20
+
21
+ def working?
22
+ proxy :working?
23
+ end
24
+
25
+ def completed?
26
+ proxy :completed?
27
+ end
28
+
29
+ def failed?
30
+ proxy :failed?
31
+ end
32
+
33
+ def killed?
34
+ proxy :killed?
35
+ end
36
+
37
+ def message
38
+ proxy :message, ''
39
+ end
40
+
41
+ private
42
+ def resque_status
43
+ Resque::Status.get(@id)
44
+ end
45
+
46
+ def proxy(method, default=false)
47
+ resque_status.try(method) or default
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,12 @@
1
+ module GovernorBackground
2
+ module Resque
3
+ class Performer
4
+ @queue = :governor
5
+
6
+ def self.perform(resource, id, method_name)
7
+ article = Governor.resources[resource].to.find(id)
8
+ article.send(method_name)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module GovernorBackground
2
+ module Resque
3
+ class PerformerWithState < ::Resque::JobWithStatus
4
+ def self.queue
5
+ :governor
6
+ end
7
+
8
+ def perform
9
+ resource = options['resource']
10
+ id = options['id']
11
+ method_name = options['method_name']
12
+ article = Governor.resources[resource].to.find(id)
13
+ article.send(method_name)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ require 'governor_background/rails'
2
+ require 'governor_background/handler'
3
+ require 'governor_background/job_manager'
4
+ require 'governor_background/delayed/job'
5
+ require 'governor_background/delayed/performer'
6
+ require 'governor_background/resque/job'
7
+ require 'governor_background/resque/performer'
8
+ require 'governor_background/controllers/methods'
9
+
10
+ background = Governor::Plugin.new('background')
11
+
12
+ background.register_model_callback do |base|
13
+ module InstanceMethods
14
+ private
15
+ def run_in_background(method)
16
+ GovernorBackground::Handler.run_in_background self, method
17
+ end
18
+ end
19
+ base.send :include, InstanceMethods
20
+ end
21
+
22
+ Governor::PluginManager.register background
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ module GovernorBackground
4
+ module Delayed
5
+ describe Job do
6
+ let(:article) { Factory(:article, :author => Factory(:user)) }
7
+ it "has a status of queued if it's not currently locked" do
8
+ job = get_job_for stub(:id => article.id, :locked_at => nil)
9
+ job.should be_queued
10
+ job.status.should == 'queued'
11
+ end
12
+
13
+ it "has a status of working if it's currently locked and being run and hasn't failed" do
14
+ job = get_job_for stub(:id => article.id, :run_at => Time.now, :locked_at => Time.now, :failed? => nil)
15
+ job.should be_working
16
+ job.status.should == 'working'
17
+ end
18
+
19
+ it "has a status of completed if it can't be found" do
20
+ ::Delayed::Job.expects(:find_by_id).with(article.id).at_least_once.returns nil
21
+ job = Job.new(article, :post, stub(:id => article.id))
22
+ job.should be_completed
23
+ job.status.should == 'completed'
24
+ end
25
+
26
+ it "has a status of failed if it's marked as failed and has been run as many times as possible" do
27
+ job = get_job_for stub(:id => article.id, :run_at => Time.now, :locked_at => Time.now, :failed? => Time.now, :attempts => ::Delayed::Worker.max_attempts)
28
+ job.should be_failed
29
+ job.status.should == 'failed'
30
+ end
31
+
32
+ it "parses the message from DelayedJob" do
33
+ error_msg = "{DelayedJob error messages can be on
34
+ multiple lines"
35
+ job = get_job_for stub(:id => article.id, :run_at => Time.now, :locked_at => Time.now,
36
+ :failed? => Time.now, :attempts => ::Delayed::Worker.max_attempts, :last_error => error_msg)
37
+ job.message.should == "DelayedJob error messages can be on"
38
+ end
39
+
40
+ private
41
+ def get_job_for(delayed_job)
42
+ ::Delayed::Job.expects(:find_by_id).with(delayed_job.id).at_least_once.returns delayed_job
43
+ Job.new(article, :post, delayed_job)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ module GovernorBackground
4
+ class Handler
5
+ cattr_writer :use_resque # programmatically prefer resque
6
+ @@use_redis = false
7
+ class << self
8
+ private
9
+ def delayed_job?
10
+ defined?(::Delayed::Job) && !@@use_resque
11
+ end
12
+ end
13
+ end
14
+
15
+ module Resque
16
+ class Job
17
+ attr_reader :resource, :method_name, :id, :created_at # make ID accessible for testing
18
+ def initialize(resource, method_name, job_id)
19
+ @resource = resource
20
+ @method_name = method_name
21
+ @id = job_id
22
+ @created_at = Time.now
23
+ end
24
+ end
25
+ end
26
+
27
+ describe Handler do
28
+ before do
29
+ JobManager.jobs.clear
30
+ @article = Factory(:article, :author => Factory(:user))
31
+ end
32
+
33
+ it "adds delayed jobs successfully" do
34
+ expect {
35
+ Handler.run_in_background(@article, :post)
36
+ }.to change { ::Delayed::Job.count }.by 1
37
+ end
38
+
39
+ it "adds resque jobs successfully" do
40
+ Handler.use_resque = true
41
+ Handler.run_in_background(@article, :post)
42
+ id = JobManager.jobs.first.id
43
+ Resque::PerformerWithState.should have_queued(id, {:resource => :articles, :id => @article.id, :method_name => :post}).in(:governor)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ module GovernorBackground
4
+ describe JobManager do
5
+ before(:each) do
6
+ JobManager.jobs.clear
7
+ end
8
+ it "keeps track of jobs" do
9
+ job = mock()
10
+ JobManager.add(job)
11
+ JobManager.jobs.size.should == 1
12
+ end
13
+
14
+ it "can remove old jobs" do
15
+ JobManager.add(mock(:created_at => 2.days.ago))
16
+ JobManager.add(mock(:created_at => 3.hours.ago))
17
+ JobManager.add(mock(:created_at => Time.now))
18
+ JobManager.jobs.size.should == 3
19
+ JobManager.clean
20
+ JobManager.jobs.size.should == 2
21
+ end
22
+
23
+ it "can remove old jobs specified by time" do
24
+ JobManager.add(mock(:created_at => 2.days.ago))
25
+ JobManager.add(mock(:created_at => 3.hours.ago))
26
+ JobManager.add(mock(:created_at => Time.now))
27
+ JobManager.jobs.size.should == 3
28
+ JobManager.clean(2.hours.ago)
29
+ JobManager.jobs.size.should == 1
30
+ end
31
+
32
+ it "will return all finished jobs and remove them from the list" do
33
+ JobManager.add(queued = mock('queued', :status => 'queued'))
34
+ JobManager.add(working = mock('working', :status => 'working'))
35
+ JobManager.add(completed = mock('completed', :status => 'completed'))
36
+ JobManager.add(failed = mock('failed', :status => 'failed'))
37
+ JobManager.add(killed = mock('killed', :status => 'killed'))
38
+
39
+ JobManager.jobs.size.should == 5
40
+ JobManager.finished_jobs.should == [completed, failed, killed]
41
+ JobManager.jobs.size.should == 2
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ module GovernorBackground
4
+ module Resque
5
+ describe Performer do
6
+ before do
7
+ ResqueSpec.reset!
8
+ @article = Factory(:article, :author => Factory(:user))
9
+ ::Resque.enqueue(Performer, :articles, @article.id, :post)
10
+ end
11
+
12
+ it "adds article.post to the :governor queue" do
13
+ Performer.should have_queued(:articles, @article.id, :post).in(:governor)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require File.expand_path('../../../../lib/governor_background/resque/performer_with_state', __FILE__)
3
+
4
+ module GovernorBackground
5
+ module Resque
6
+ describe PerformerWithState do
7
+ before do
8
+ ResqueSpec.reset!
9
+ @article = Factory(:article, :author => Factory(:user))
10
+ @id = PerformerWithState.create(:resource => :articles, :id => @article.id, :method => :post)
11
+ end
12
+
13
+ it "adds article.post to the :governor queue" do
14
+ PerformerWithState.should have_queued(@id, {:resource => :articles, :id => @article.id, :method => :post}).in(:governor)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe GovernorBackground do
4
+ it "adds #run_in_background to Article" do
5
+ Article.private_instance_methods.include? 'run_in_background'
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ .bundle
2
+ db/*.sqlite3
3
+ log/*.log
4
+ tmp/
@@ -0,0 +1,43 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rails', '3.0.5'
4
+
5
+ # Bundle edge Rails instead:
6
+ # gem 'rails', :git => 'git://github.com/rails/rails.git'
7
+
8
+ gem 'sqlite3'
9
+
10
+ gem 'devise'
11
+ gem 'delayed_job'
12
+ gem 'resque', :require => 'resque/server'
13
+ gem 'resque-status', :require => 'resque/status'
14
+
15
+ gem 'governor'
16
+ gem 'governor_background', :path => '../..'
17
+
18
+ gem 'dynamic_form'
19
+
20
+ gem 'will_paginate', '~> 3.0.beta'
21
+
22
+ # Use unicorn as the web server
23
+ # gem 'unicorn'
24
+
25
+ # Deploy with Capistrano
26
+ # gem 'capistrano'
27
+
28
+ # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
29
+ # gem 'ruby-debug'
30
+ # gem 'ruby-debug19', :require => 'ruby-debug'
31
+
32
+ # Bundle the extra gems:
33
+ # gem 'bj'
34
+ # gem 'nokogiri'
35
+ # gem 'sqlite3-ruby', :require => 'sqlite3'
36
+ # gem 'aws-s3', :require => 'aws/s3'
37
+
38
+ # Bundle gems for the local environment. Make sure to
39
+ # put test-only gems in this group so their generators
40
+ # and rake tasks are available in development mode:
41
+ # group :development, :test do
42
+ # gem 'webrat'
43
+ # end
@@ -0,0 +1,128 @@
1
+ PATH
2
+ remote: ../..
3
+ specs:
4
+ governor_background (0.0.0)
5
+ governor
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ abstract (1.0.0)
11
+ actionmailer (3.0.5)
12
+ actionpack (= 3.0.5)
13
+ mail (~> 2.2.15)
14
+ actionpack (3.0.5)
15
+ activemodel (= 3.0.5)
16
+ activesupport (= 3.0.5)
17
+ builder (~> 2.1.2)
18
+ erubis (~> 2.6.6)
19
+ i18n (~> 0.4)
20
+ rack (~> 1.2.1)
21
+ rack-mount (~> 0.6.13)
22
+ rack-test (~> 0.5.7)
23
+ tzinfo (~> 0.3.23)
24
+ activemodel (3.0.5)
25
+ activesupport (= 3.0.5)
26
+ builder (~> 2.1.2)
27
+ i18n (~> 0.4)
28
+ activerecord (3.0.5)
29
+ activemodel (= 3.0.5)
30
+ activesupport (= 3.0.5)
31
+ arel (~> 2.0.2)
32
+ tzinfo (~> 0.3.23)
33
+ activeresource (3.0.5)
34
+ activemodel (= 3.0.5)
35
+ activesupport (= 3.0.5)
36
+ activesupport (3.0.5)
37
+ arel (2.0.9)
38
+ bcrypt-ruby (2.1.4)
39
+ builder (2.1.2)
40
+ daemons (1.1.2)
41
+ delayed_job (2.1.4)
42
+ activesupport (~> 3.0)
43
+ daemons
44
+ devise (1.3.1)
45
+ bcrypt-ruby (~> 2.1.2)
46
+ orm_adapter (~> 0.0.3)
47
+ warden (~> 1.0.3)
48
+ dynamic_form (1.1.4)
49
+ erubis (2.6.6)
50
+ abstract (>= 1.0.0)
51
+ governor (0.3.0)
52
+ rails (~> 3.0.5)
53
+ i18n (0.5.0)
54
+ json (1.4.6)
55
+ macaddr (1.0.0)
56
+ mail (2.2.17)
57
+ activesupport (>= 2.3.6)
58
+ i18n (>= 0.4.0)
59
+ mime-types (~> 1.16)
60
+ treetop (~> 1.4.8)
61
+ mime-types (1.16)
62
+ orm_adapter (0.0.4)
63
+ polyglot (0.3.1)
64
+ rack (1.2.2)
65
+ rack-mount (0.6.14)
66
+ rack (>= 1.0.0)
67
+ rack-test (0.5.7)
68
+ rack (>= 1.0)
69
+ rails (3.0.5)
70
+ actionmailer (= 3.0.5)
71
+ actionpack (= 3.0.5)
72
+ activerecord (= 3.0.5)
73
+ activeresource (= 3.0.5)
74
+ activesupport (= 3.0.5)
75
+ bundler (~> 1.0)
76
+ railties (= 3.0.5)
77
+ railties (3.0.5)
78
+ actionpack (= 3.0.5)
79
+ activesupport (= 3.0.5)
80
+ rake (>= 0.8.7)
81
+ thor (~> 0.14.4)
82
+ rake (0.8.7)
83
+ redis (2.2.0)
84
+ redis-namespace (0.10.0)
85
+ redis (< 3.0.0)
86
+ redisk (0.2.2)
87
+ redis (>= 0.1.1)
88
+ redis-namespace (>= 0.1.0)
89
+ resque (1.15.0)
90
+ json (~> 1.4.6)
91
+ redis-namespace (>= 0.10.0)
92
+ sinatra (>= 0.9.2)
93
+ vegas (~> 0.1.2)
94
+ resque-status (0.2.3)
95
+ redisk (>= 0.2.1)
96
+ resque (>= 1.3.1)
97
+ uuid (>= 2.0.2)
98
+ sinatra (1.2.3)
99
+ rack (~> 1.1)
100
+ tilt (>= 1.2.2, < 2.0)
101
+ sqlite3 (1.3.3)
102
+ thor (0.14.6)
103
+ tilt (1.2.2)
104
+ treetop (1.4.9)
105
+ polyglot (>= 0.3.1)
106
+ tzinfo (0.3.26)
107
+ uuid (2.3.2)
108
+ macaddr (~> 1.0)
109
+ vegas (0.1.8)
110
+ rack (>= 1.0.0)
111
+ warden (1.0.3)
112
+ rack (>= 1.0.0)
113
+ will_paginate (3.0.pre2)
114
+
115
+ PLATFORMS
116
+ ruby
117
+
118
+ DEPENDENCIES
119
+ delayed_job
120
+ devise
121
+ dynamic_form
122
+ governor
123
+ governor_background!
124
+ rails (= 3.0.5)
125
+ resque
126
+ resque-status
127
+ sqlite3
128
+ will_paginate (~> 3.0.beta)