maiha-merb_background 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 maiha@wota.jp
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,105 @@
1
+ MerbBackground
2
+ ==============
3
+
4
+ A slice for the Merb framework that offers background tasks for DataMapper.
5
+ This is ported from background-fu that works with Rails + AR.
6
+
7
+ Original:
8
+ http://github.com/ncr/background-fu/
9
+
10
+
11
+ Methods
12
+ =======
13
+
14
+ same as background-fu.
15
+
16
+ MerbBackground::Job.enqueue!(ExampleWorker, :add, 1, 2)
17
+
18
+
19
+ Setup
20
+ =====
21
+
22
+ MerbBackground::Job.auto_upgrade!
23
+
24
+ or
25
+ rake slices:merb_background:migrate
26
+
27
+
28
+ Daemon
29
+ ======
30
+
31
+ ruby slices/merb_background/script/background.rb
32
+
33
+
34
+
35
+ ------------------------------------------------------------------------------
36
+
37
+
38
+ To see all available tasks for MerbBackground run:
39
+
40
+ rake -T slices:merb_background
41
+
42
+ ------------------------------------------------------------------------------
43
+
44
+ Instructions for installation:
45
+
46
+ file: config/init.rb
47
+
48
+ # add the slice as a regular dependency
49
+
50
+ dependency 'merb_background'
51
+
52
+ # if needed, configure which slices to load and in which order
53
+
54
+ Merb::Plugins.config[:merb_slices] = { :queue => ["MerbBackground", ...] }
55
+
56
+ # optionally configure the plugins in a before_app_loads callback
57
+
58
+ Merb::BootLoader.before_app_loads do
59
+
60
+ Merb::Slices::config[:merb_background][:option] = value
61
+
62
+ end
63
+
64
+ file: config/router.rb
65
+
66
+ # example: /merb_background/:controller/:action/:id
67
+
68
+ add_slice(:MerbBackground)
69
+
70
+ # example: /:lang/:controller/:action/:id
71
+
72
+ add_slice(:MerbBackground, :path => ':lang')
73
+
74
+ # example: /:controller/:action/:id
75
+
76
+ slice(:MerbBackground)
77
+
78
+ Normally you should also run the following rake task:
79
+
80
+ rake slices:merb_background:install
81
+
82
+ ------------------------------------------------------------------------------
83
+
84
+ You can put your application-level overrides in:
85
+
86
+ host-app/slices/merb_background/app - controllers, models, views ...
87
+
88
+ Templates are located in this order:
89
+
90
+ 1. host-app/slices/merb_background/app/views/*
91
+ 2. gems/merb_background/app/views/*
92
+ 3. host-app/app/views/*
93
+
94
+ You can use the host application's layout by configuring the
95
+ merb_background slice in a before_app_loads block:
96
+
97
+ Merb::Slices.config[:merb_background] = { :layout => :application }
98
+
99
+ By default :merb_background is used. If you need to override
100
+ stylesheets or javascripts, just specify your own files in your layout
101
+ instead/in addition to the ones supplied (if any) in
102
+ host-app/public/slices/merb_background.
103
+
104
+ In any case don't edit those files directly as they may be clobbered any time
105
+ rake merb_background:install is run.
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+
4
+ require 'merb-core'
5
+ require 'merb-core/tasks/merb'
6
+
7
+ GEM_NAME = "merb_background"
8
+ AUTHOR = "maiha"
9
+ EMAIL = "maiha@wota.jp"
10
+ HOMEPAGE = "http://github.com/maiha/merb_background"
11
+ SUMMARY = "A slice for the Merb framework that offers background tasks for DataMapper"
12
+ GEM_VERSION = "0.1"
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.rubyforge_project = 'merb'
16
+ s.name = GEM_NAME
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.author = AUTHOR
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+ s.add_dependency('merb-slices', '>= 1.0.11')
27
+ s.require_path = 'lib'
28
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec,app,public,stubs}/**/*")
29
+ end
30
+
31
+ Rake::GemPackageTask.new(spec) do |pkg|
32
+ pkg.gem_spec = spec
33
+ end
34
+
35
+ desc "Install the gem"
36
+ task :install do
37
+ Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
38
+ end
39
+
40
+ desc "Uninstall the gem"
41
+ task :uninstall do
42
+ Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
43
+ end
44
+
45
+ desc "Create a gemspec file"
46
+ task :gemspec do
47
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
48
+ file.puts spec.to_ruby
49
+ end
50
+ end
51
+
52
+ require 'spec/rake/spectask'
53
+ require 'merb-core/test/tasks/spectasks'
54
+ desc 'Default: run spec examples'
55
+ task :default => 'spec'
data/TODO ADDED
@@ -0,0 +1,13 @@
1
+ TODO:
2
+
3
+ - Add rake tasks especially for control daemons
4
+ - Add BonusFeature
5
+ - Add codes for AR
6
+
7
+ Remove anything that you don't need:
8
+
9
+ - app/controllers/main.rb MerbBackground::Main controller
10
+ - app/views/layout/merb_background.html.erb
11
+ - spec/controllers/main_spec.rb controller specs
12
+ - public/* any public files
13
+ - stubs/* any stub files
@@ -0,0 +1,5 @@
1
+ class MerbBackground::Application < Merb::Controller
2
+
3
+ controller_for_slice
4
+
5
+ end
@@ -0,0 +1,7 @@
1
+ class MerbBackground::Main < MerbBackground::Application
2
+
3
+ def index
4
+ render
5
+ end
6
+
7
+ end
@@ -0,0 +1,64 @@
1
+ module Merb
2
+ module MerbBackground
3
+ module ApplicationHelper
4
+
5
+ # @param *segments<Array[#to_s]> Path segments to append.
6
+ #
7
+ # @return <String>
8
+ # A path relative to the public directory, with added segments.
9
+ def image_path(*segments)
10
+ public_path_for(:image, *segments)
11
+ end
12
+
13
+ # @param *segments<Array[#to_s]> Path segments to append.
14
+ #
15
+ # @return <String>
16
+ # A path relative to the public directory, with added segments.
17
+ def javascript_path(*segments)
18
+ public_path_for(:javascript, *segments)
19
+ end
20
+
21
+ # @param *segments<Array[#to_s]> Path segments to append.
22
+ #
23
+ # @return <String>
24
+ # A path relative to the public directory, with added segments.
25
+ def stylesheet_path(*segments)
26
+ public_path_for(:stylesheet, *segments)
27
+ end
28
+
29
+ # Construct a path relative to the public directory
30
+ #
31
+ # @param <Symbol> The type of component.
32
+ # @param *segments<Array[#to_s]> Path segments to append.
33
+ #
34
+ # @return <String>
35
+ # A path relative to the public directory, with added segments.
36
+ def public_path_for(type, *segments)
37
+ ::MerbBackground.public_path_for(type, *segments)
38
+ end
39
+
40
+ # Construct an app-level path.
41
+ #
42
+ # @param <Symbol> The type of component.
43
+ # @param *segments<Array[#to_s]> Path segments to append.
44
+ #
45
+ # @return <String>
46
+ # A path within the host application, with added segments.
47
+ def app_path_for(type, *segments)
48
+ ::MerbBackground.app_path_for(type, *segments)
49
+ end
50
+
51
+ # Construct a slice-level path.
52
+ #
53
+ # @param <Symbol> The type of component.
54
+ # @param *segments<Array[#to_s]> Path segments to append.
55
+ #
56
+ # @return <String>
57
+ # A path within the slice source (Gem), with added segments.
58
+ def slice_path_for(type, *segments)
59
+ ::MerbBackground.slice_path_for(type, *segments)
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5
+ <title>Fresh MerbBackground Slice</title>
6
+ <link href="<%= public_path_for :stylesheet, 'master.css' %>" type="text/css" charset="utf-8" rel="stylesheet" media="all" />
7
+ <script src="<%= public_path_for :javascript, 'master.js' %>" type="text/javascript" charset="utf-8"></script>
8
+ </head>
9
+ <!-- you can override this layout at slices/merb_background/app/views/layout/merb_background.html.erb -->
10
+ <body class="merb_background-slice">
11
+ <div id="container">
12
+ <h1>MerbBackground Slice</h1>
13
+ <div id="main"><%= catch_content :for_layout %></div>
14
+ </div>
15
+ </body>
16
+ </html>
@@ -0,0 +1 @@
1
+ <strong><%= slice.description %></strong> (v. <%= slice.version %>)
@@ -0,0 +1,88 @@
1
+ if defined?(Merb::Plugins)
2
+
3
+ $:.unshift File.dirname(__FILE__)
4
+
5
+ dependency 'merb-slices', :immediate => true
6
+ Merb::Plugins.add_rakefiles "merb_background/merbtasks", "merb_background/slicetasks", "merb_background/spectasks"
7
+
8
+ # Register the Slice for the current host application
9
+ Merb::Slices::register(__FILE__)
10
+
11
+ # Slice configuration - set this in a before_app_loads callback.
12
+ # By default a Slice uses its own layout, so you can swicht to
13
+ # the main application layout or no layout at all if needed.
14
+ #
15
+ # Configuration options:
16
+ # :layout - the layout to use; defaults to :merb_background
17
+ # :mirror - which path component types to use on copy operations; defaults to all
18
+ Merb::Slices::config[:merb_background][:layout] ||= :merb_background
19
+
20
+ # All Slice code is expected to be namespaced inside a module
21
+ module MerbBackground
22
+
23
+ # Slice metadata
24
+ self.description = "a merb slice for background-fu"
25
+ self.version = "0.0.1"
26
+ self.author = "maiha"
27
+
28
+ Config = Mash.new(
29
+ :cleanup_interval => :on_startup,
30
+ :monitor_interval => 10
31
+ )
32
+
33
+ # Stub classes loaded hook - runs before LoadClasses BootLoader
34
+ # right after a slice's classes have been loaded internally.
35
+ def self.loaded
36
+ require 'merb_background/job'
37
+ end
38
+
39
+ # Initialization hook - runs before AfterAppLoads BootLoader
40
+ def self.init
41
+ end
42
+
43
+ # Activation hook - runs after AfterAppLoads BootLoader
44
+ def self.activate
45
+ end
46
+
47
+ # Deactivation hook - triggered by Merb::Slices.deactivate(MerbBackground)
48
+ def self.deactivate
49
+ end
50
+
51
+ # Setup routes inside the host application
52
+ #
53
+ # @param scope<Merb::Router::Behaviour>
54
+ # Routes will be added within this scope (namespace). In fact, any
55
+ # router behaviour is a valid namespace, so you can attach
56
+ # routes at any level of your router setup.
57
+ #
58
+ # @note prefix your named routes with :merb_background_
59
+ # to avoid potential conflicts with global named routes.
60
+ def self.setup_router(scope)
61
+ # example of a named route
62
+ scope.match('/index(.:format)').to(:controller => 'main', :action => 'index').name(:index)
63
+ # the slice is mounted at /merb_background - note that it comes before default_routes
64
+ scope.match('/').to(:controller => 'main', :action => 'index').name(:home)
65
+ # enable slice-level default routes by default
66
+ scope.default_routes
67
+ end
68
+
69
+ end
70
+
71
+ # Setup the slice layout for MerbBackground
72
+ #
73
+ # Use MerbBackground.push_path and MerbBackground.push_app_path
74
+ # to set paths to merb_background-level and app-level paths. Example:
75
+ #
76
+ # MerbBackground.push_path(:application, MerbBackground.root)
77
+ # MerbBackground.push_app_path(:application, Merb.root / 'slices' / 'merb_background')
78
+ # ...
79
+ #
80
+ # Any component path that hasn't been set will default to MerbBackground.root
81
+ #
82
+ # Or just call setup_default_structure! to setup a basic Merb MVC structure.
83
+ MerbBackground.setup_default_structure!
84
+
85
+ # Add dependencies for other MerbBackground classes below. Example:
86
+ # dependency "merb_background/other"
87
+
88
+ end
File without changes
@@ -0,0 +1,138 @@
1
+ # Example:
2
+ #
3
+ # job = Job.enqueue!(MyWorker, :my_method, "my_arg_1", "my_arg_2")
4
+
5
+ #require File.dirname(__FILE__) + '/job/ar.rb'
6
+ require File.dirname(__FILE__) + '/job/dm.rb'
7
+
8
+ class MerbBackground::Job
9
+ cattr_accessor :states
10
+ self.states = %w(pending running finished failed)
11
+
12
+ # attr_readonly :worker_class, :worker_method, :args
13
+
14
+ def self.enqueue!(worker_class, worker_method, *args)
15
+ job = create!(
16
+ :worker_class => worker_class.to_s,
17
+ :worker_method => worker_method.to_s,
18
+ :args => args
19
+ )
20
+
21
+ logger.info("MerbBackground: Job enqueued. Job(id: #{job.id}, worker: #{worker_class}, method: #{worker_method}, argc: #{args.size}).")
22
+
23
+ job
24
+ end
25
+
26
+ def self.execute!(config = nil)
27
+ config = MerbBackground::Config.merge(config || {})
28
+ cleanup_finished_jobs if config[:cleanup_interval] == :at_start
29
+
30
+ config.each do |k,v|
31
+ logger.info "MerbBackground: #{k}: #{v}"
32
+ end
33
+
34
+ loop do
35
+ if job = pending.ready.first(:order => [:priority.desc, :start_at.asc])
36
+ job.get_done!
37
+ else
38
+ logger.info("MerbBackground: Waiting for jobs...")
39
+ sleep config[:monitor_interval]
40
+ end
41
+ cleanup_finished_jobs if config[:cleanup_interval] == :continuous
42
+ end
43
+ end
44
+
45
+
46
+ # Invoked by a background daemon.
47
+ def get_done!
48
+ initialize_worker
49
+ invoke_worker
50
+ rescue Exception => e
51
+ rescue_worker(e)
52
+ ensure
53
+ ensure_worker
54
+ end
55
+
56
+ # Restart a failed job.
57
+ def restart!
58
+ if failed?
59
+ update_attributes!(
60
+ :result => nil,
61
+ :progress => nil,
62
+ :started_at => nil,
63
+ :state => "pending"
64
+ )
65
+ logger.info("MerbBackground: Job restarted. Job(id: #{id}).")
66
+ end
67
+ end
68
+
69
+ def initialize_worker
70
+ update_attributes!(:started_at => Time.now, :state => "running")
71
+ @worker = worker_class.constantize.new
72
+ logger.info("MerbBackground: Job initialized. Job(id: #{id}).")
73
+ end
74
+
75
+ def invoke_worker
76
+ self.result = @worker.send(worker_method, *args)
77
+ self.state = "finished"
78
+ logger.info("MerbBackground: Job finished. Job(id: #{id}).")
79
+ end
80
+
81
+ def rescue_worker(exception)
82
+ self.result = [exception.message, exception.backtrace.join("\n")].join("\n\n")
83
+ self.state = "failed"
84
+ logger.info("MerbBackground: Job failed. Job(id: #{id}).")
85
+ end
86
+
87
+ def ensure_worker
88
+ self.progress = @worker.instance_variable_get("@progress")
89
+ save!
90
+ rescue StaleObjectError
91
+ # Ignore this exception as its only purpose is
92
+ # not allowing multiple daemons execute the same job.
93
+ logger.info("MerbBackground: Race condition handled (It's OK). Job(id: #{id}).")
94
+ end
95
+
96
+ # Delete finished jobs that are more than a week old.
97
+ def self.cleanup_finished_jobs
98
+ logger.info "MerbBackground: Cleaning up finished jobs."
99
+
100
+ # TODO: AR compats
101
+ Job.finished.all(:updated_at.lt => 1.week.ago).destroy!
102
+ end
103
+
104
+ def self.generate_state_helpers
105
+ states.each do |state_name|
106
+ define_method("#{state_name}?") do
107
+ state == state_name
108
+ end
109
+
110
+ # Job.running => array of running jobs, etc.
111
+ self.class.send(:define_method, state_name) do
112
+ all(:state => state_name, :order => [:id.desc])
113
+ end
114
+ end
115
+ end
116
+ generate_state_helpers
117
+
118
+ def setup_state
119
+ return unless state.blank?
120
+
121
+ self.state = "pending"
122
+ end
123
+
124
+ # Default priority is 0. Jobs will be executed in descending priority order (negative priorities allowed).
125
+ def setup_priority
126
+ return unless priority.blank?
127
+
128
+ self.priority = 0
129
+ end
130
+
131
+ # Job will be executed after this timestamp.
132
+ def setup_start_at
133
+ return unless start_at.blank?
134
+
135
+ self.start_at = Time.now
136
+ end
137
+
138
+ end
@@ -0,0 +1,10 @@
1
+ class MerbBackground::Job < ActiveRecord::Base
2
+ cattr_accessor :states
3
+ self.states = %w(pending running finished failed)
4
+
5
+ serialize :args, Array
6
+ serialize :result
7
+
8
+ before_create :setup_state, :setup_priority, :setup_start_at
9
+ validates_presence_of :worker_class, :worker_method
10
+ end
@@ -0,0 +1,37 @@
1
+ module DataMapper
2
+ module ActiveRecordMethods
3
+ def self.included(model)
4
+ model.class_eval do
5
+ extend ClassMethods
6
+ include InstanceMethods
7
+ end
8
+ end
9
+
10
+ class RecordNotSaved < DataMapper::PersistenceError; end
11
+
12
+ module InstanceMethods
13
+ def logger
14
+ self.class.logger
15
+ end
16
+
17
+ def bang(&block)
18
+ block.call or raise RecordNotSaved
19
+ return self
20
+ end
21
+
22
+ def update_attributes!(hash)
23
+ bang{update_attributes(hash)}
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def logger
29
+ DataMapper.logger
30
+ end
31
+
32
+ def create!(attributes)
33
+ new(attributes).bang{save}
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,98 @@
1
+ module Job::BonusFeatures
2
+
3
+ def self.included(base)
4
+ base.states += %w(stopping stopped)
5
+ base.generate_state_helpers
6
+
7
+ base.alias_method_chain :invoke_worker, :threads
8
+ base.alias_method_chain :ensure_worker, :threads
9
+ base.alias_method_chain :restart!, :threads
10
+ end
11
+
12
+ def invoke_worker_with_threads
13
+ monitor_worker
14
+
15
+ res = catch(:stopping) do
16
+ invoke_worker_without_threads; nil
17
+ end
18
+
19
+ self.reload
20
+ self.state = res ? "stopped" : "finished"
21
+ end
22
+
23
+ def ensure_worker_with_threads
24
+ ensure_worker_without_threads
25
+ cleanup_after_threads
26
+ end
27
+
28
+ # The record_progress() method becomes available when your worker class includes
29
+ # Background::WorkerMonitoring.
30
+ #
31
+ # Every time worker invokes record_progress() is a possible stopping place.
32
+ #
33
+ # How it works:
34
+ # 1. invoke job.stop! to set a state (stopping) in a db
35
+ # 2. Monitoring thread picks up the state change from db
36
+ # and sets @stopping to true in the worker.
37
+ # 3. The worker invokes a register_progress() somewhere during execution.
38
+ # 4. The record_progress() method throws :stopping symbol if @stopping == true
39
+ # 5. The job catches the :stopping symbol and reacts upon it.
40
+ # 6. The job is stopped in a merciful way. No one gets harmed.
41
+ def stop!
42
+ if running?
43
+ update_attribute(:state, "stopping")
44
+ logger.info("BackgroundFu: Stopping job. Job(id: #{id}).")
45
+ end
46
+ end
47
+
48
+ # Overridden because of new "stopped" state.
49
+ def restart_with_threads!
50
+ if stopped? || failed?
51
+ update_attributes!(
52
+ :result => nil,
53
+ :progress => nil,
54
+ :started_at => nil,
55
+ :state => "pending"
56
+ )
57
+ logger.info("BackgroundFu: Restarting job. Job(id: #{id}).")
58
+ end
59
+ end
60
+
61
+ # Monitors the worker and updates the job progress. If the job's status
62
+ # is changed to 'stopping', the worker is requested to stop.
63
+ def monitor_worker
64
+ Thread.new do
65
+ while running? && !Job.find(id).stopping?
66
+ current_progress = @worker.instance_variable_get("@progress")
67
+
68
+ if current_progress == progress
69
+ sleep 5
70
+ else
71
+ update_attribute(:progress, current_progress)
72
+ sleep 1
73
+ end
74
+ end
75
+
76
+ if Job.find(id).stopping?
77
+ @worker.instance_variable_set("@stopping", true)
78
+ end
79
+ end
80
+
81
+ logger.info("BackgroundFu: Job monitoring started. Job(id: #{id}).")
82
+ end
83
+
84
+ # Closes database connections left after finished threads.
85
+ def cleanup_after_threads
86
+ ActiveRecord::Base.verify_active_connections!
87
+ end
88
+
89
+ def elapsed
90
+ (updated_at.to_f - started_at.to_f).to_i if !pending?
91
+ end
92
+
93
+ # seconds to go, based on estimated and progress
94
+ def estimated
95
+ ((elapsed * 100) / progress) - elapsed if running? && (1..99).include?(progress.to_i)
96
+ end
97
+
98
+ end
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/ar2dm'
2
+
3
+ class MerbBackground::Job
4
+ include DataMapper::Resource
5
+
6
+ property :id, Serial
7
+ property :worker_class, String, :size => 255
8
+ property :worker_method, String, :size => 255
9
+ property :args, Yaml
10
+ property :result, Yaml
11
+ property :priority, Integer
12
+ property :progress, Integer
13
+ property :state, String
14
+ property :lock_version, Integer, :default => 0
15
+
16
+ property :start_at, DateTime
17
+ property :started_at, DateTime
18
+
19
+ property :created_at, DateTime
20
+ property :updated_at, DateTime
21
+
22
+ before :save, :setup_state
23
+ before :save, :setup_priority
24
+ before :save, :setup_start_at
25
+
26
+ validates_present :worker_class, :worker_method
27
+
28
+ include DataMapper::ActiveRecordMethods
29
+
30
+ def self.ready
31
+ all(:start_at.lte => Time.now)
32
+ end
33
+ end
34
+
@@ -0,0 +1,103 @@
1
+ namespace :slices do
2
+ namespace :merb_background do
3
+
4
+ desc "Install MerbBackground"
5
+ task :install => [:preflight, :setup_directories, :copy_assets, :migrate]
6
+
7
+ desc "Test for any dependencies"
8
+ task :preflight do # see slicetasks.rb
9
+ end
10
+
11
+ desc "Setup directories"
12
+ task :setup_directories do
13
+ puts "Creating directories for host application"
14
+ MerbBackground.mirrored_components.each do |type|
15
+ if File.directory?(MerbBackground.dir_for(type))
16
+ if !File.directory?(dst_path = MerbBackground.app_dir_for(type))
17
+ relative_path = dst_path.relative_path_from(Merb.root)
18
+ puts "- creating directory :#{type} #{File.basename(Merb.root) / relative_path}"
19
+ mkdir_p(dst_path)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # desc "Copy stub files to host application"
26
+ # task :stubs do
27
+ # puts "Copying stubs for MerbBackground - resolves any collisions"
28
+ # copied, preserved = MerbBackground.mirror_stubs!
29
+ # puts "- no files to copy" if copied.empty? && preserved.empty?
30
+ # copied.each { |f| puts "- copied #{f}" }
31
+ # preserved.each { |f| puts "! preserved override as #{f}" }
32
+ # end
33
+
34
+ # desc "Copy stub files and views to host application"
35
+ # task :patch => [ "stubs", "freeze:views" ]
36
+
37
+ desc "Copy public assets to host application"
38
+ task :copy_assets do
39
+ puts "Copying assets for MerbBackground - resolves any collisions"
40
+ copied, preserved = MerbBackground.mirror_public!
41
+ puts "- no files to copy" if copied.empty? && preserved.empty?
42
+ copied.each { |f| puts "- copied #{f}" }
43
+ preserved.each { |f| puts "! preserved override as #{f}" }
44
+ end
45
+
46
+ desc "Migrate the database"
47
+ task :migrate do # see slicetasks.rb
48
+ end
49
+
50
+ desc "Freeze MerbBackground into your app (only merb_background/app)"
51
+ task :freeze => [ "freeze:app" ]
52
+
53
+ namespace :freeze do
54
+
55
+ # desc "Freezes MerbBackground by installing the gem into application/gems"
56
+ # task :gem do
57
+ # ENV["GEM"] ||= "merb_background"
58
+ # Rake::Task['slices:install_as_gem'].invoke
59
+ # end
60
+
61
+ desc "Freezes MerbBackground by copying all files from merb_background/app to your application"
62
+ task :app do
63
+ puts "Copying all merb_background/app files to your application - resolves any collisions"
64
+ copied, preserved = MerbBackground.mirror_app!
65
+ puts "- no files to copy" if copied.empty? && preserved.empty?
66
+ copied.each { |f| puts "- copied #{f}" }
67
+ preserved.each { |f| puts "! preserved override as #{f}" }
68
+ end
69
+
70
+ desc "Freeze all views into your application for easy modification"
71
+ task :views do
72
+ puts "Copying all view templates to your application - resolves any collisions"
73
+ copied, preserved = MerbBackground.mirror_files_for :view
74
+ puts "- no files to copy" if copied.empty? && preserved.empty?
75
+ copied.each { |f| puts "- copied #{f}" }
76
+ preserved.each { |f| puts "! preserved override as #{f}" }
77
+ end
78
+
79
+ desc "Freeze all models into your application for easy modification"
80
+ task :models do
81
+ puts "Copying all models to your application - resolves any collisions"
82
+ copied, preserved = MerbBackground.mirror_files_for :model
83
+ puts "- no files to copy" if copied.empty? && preserved.empty?
84
+ copied.each { |f| puts "- copied #{f}" }
85
+ preserved.each { |f| puts "! preserved override as #{f}" }
86
+ end
87
+
88
+ desc "Freezes MerbBackground as a gem and copies over merb_background/app"
89
+ task :app_with_gem => [:gem, :app]
90
+
91
+ desc "Freezes MerbBackground by unpacking all files into your application"
92
+ task :unpack do
93
+ puts "Unpacking MerbBackground files to your application - resolves any collisions"
94
+ copied, preserved = MerbBackground.unpack_slice!
95
+ puts "- no files to copy" if copied.empty? && preserved.empty?
96
+ copied.each { |f| puts "- copied #{f}" }
97
+ preserved.each { |f| puts "! preserved override as #{f}" }
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,21 @@
1
+ namespace :slices do
2
+ namespace :merb_background do
3
+
4
+ # add your own merb_background tasks here
5
+
6
+ # # Uncomment the following lines and edit the pre defined tasks
7
+ #
8
+ # # implement this to test for structural/code dependencies
9
+ # # like certain directories or availability of other files
10
+ # desc "Test for any dependencies"
11
+ # task :preflight do
12
+ # end
13
+ #
14
+ # # implement this to perform any database related setup steps
15
+ desc "Migrate the database"
16
+ task :migrate do
17
+ MerbBackground::Job.auto_upgrade!
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,53 @@
1
+ namespace :slices do
2
+ namespace :merb_background do
3
+
4
+ desc "Run slice specs within the host application context"
5
+ task :spec => [ "spec:explain", "spec:default" ]
6
+
7
+ namespace :spec do
8
+
9
+ slice_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
10
+
11
+ task :explain do
12
+ puts "\nNote: By running MerbBackground specs inside the application context any\n" +
13
+ "overrides could break existing specs. This isn't always a problem,\n" +
14
+ "especially in the case of views. Use these spec tasks to check how\n" +
15
+ "well your application conforms to the original slice implementation."
16
+ end
17
+
18
+ Spec::Rake::SpecTask.new('default') do |t|
19
+ t.spec_opts = ["--format", "specdoc", "--colour"]
20
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
21
+ end
22
+
23
+ desc "Run all model specs, run a spec for a specific Model with MODEL=MyModel"
24
+ Spec::Rake::SpecTask.new('model') do |t|
25
+ t.spec_opts = ["--format", "specdoc", "--colour"]
26
+ if(ENV['MODEL'])
27
+ t.spec_files = Dir["#{slice_root}/spec/models/**/#{ENV['MODEL']}_spec.rb"].sort
28
+ else
29
+ t.spec_files = Dir["#{slice_root}/spec/models/**/*_spec.rb"].sort
30
+ end
31
+ end
32
+
33
+ desc "Run all request specs, run a spec for a specific request with REQUEST=MyRequest"
34
+ Spec::Rake::SpecTask.new('request') do |t|
35
+ t.spec_opts = ["--format", "specdoc", "--colour"]
36
+ if(ENV['REQUEST'])
37
+ t.spec_files = Dir["#{slice_root}/spec/requests/**/#{ENV['REQUEST']}_spec.rb"].sort
38
+ else
39
+ t.spec_files = Dir["#{slice_root}/spec/requests/**/*_spec.rb"].sort
40
+ end
41
+ end
42
+
43
+ desc "Run all specs and output the result in html"
44
+ Spec::Rake::SpecTask.new('html') do |t|
45
+ t.spec_opts = ["--format", "html"]
46
+ t.libs = ['lib', 'server/lib' ]
47
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
File without changes
@@ -0,0 +1,2 @@
1
+ html, body { margin: 0; padding: 0; }
2
+ #container { width: 800px; margin: 4em auto; padding: 4em 4em 6em 4em; background: #DDDDDD; }
@@ -0,0 +1,20 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "MerbBackground (module)" do
4
+
5
+ # Implement your MerbBackground specs here
6
+
7
+ # To spec MerbBackground you need to hook it up to the router like this:
8
+
9
+ # before :all do
10
+ # Merb::Router.prepare { add_slice(:MerbBackground) } if standalone?
11
+ # end
12
+ #
13
+ # after :all do
14
+ # Merb::Router.reset! if standalone?
15
+ # end
16
+ #
17
+ #
18
+ # it "should have proper specs"
19
+
20
+ end
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
+
3
+ describe "/merb_background/" do
4
+
5
+ before(:all) do
6
+ mount_slice
7
+ end
8
+
9
+ describe "GET /" do
10
+
11
+ before(:each) do
12
+ @response = request("/merb_background/")
13
+ end
14
+
15
+ it "should be successful" do
16
+ @response.status.should be_successful
17
+ end
18
+
19
+ # This is just an example of what you can do
20
+ # You can also use the other webrat methods to click links,
21
+ # fill up forms etc...
22
+ it "should render the default slice layout" do
23
+ @response.should have_tag(:h1, :content => "MerbBackground Slice")
24
+ @response.should have_selector("div#container div#main")
25
+ @response.should have_xpath("//div[@id='container']/div[@id='main']")
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'merb-core'
3
+ require 'merb-slices'
4
+ require 'spec'
5
+
6
+ # Add merb_background.rb to the search path
7
+ Merb::Plugins.config[:merb_slices][:auto_register] = true
8
+ Merb::Plugins.config[:merb_slices][:search_path] = File.join(File.dirname(__FILE__), '..', 'lib', 'merb_background.rb')
9
+
10
+ # Require merb_background.rb explicitly so any dependencies are loaded
11
+ require Merb::Plugins.config[:merb_slices][:search_path]
12
+
13
+ # Using Merb.root below makes sure that the correct root is set for
14
+ # - testing standalone, without being installed as a gem and no host application
15
+ # - testing from within the host application; its root will be used
16
+ Merb.start_environment(
17
+ :testing => true,
18
+ :adapter => 'runner',
19
+ :environment => ENV['MERB_ENV'] || 'test',
20
+ :session_store => 'memory'
21
+ )
22
+
23
+ module Merb
24
+ module Test
25
+ module SliceHelper
26
+
27
+ # The absolute path to the current slice
28
+ def current_slice_root
29
+ @current_slice_root ||= File.expand_path(File.join(File.dirname(__FILE__), '..'))
30
+ end
31
+
32
+ # Whether the specs are being run from a host application or standalone
33
+ def standalone?
34
+ Merb.root == ::MerbBackground.root
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+
41
+ Spec::Runner.configure do |config|
42
+ config.include(Merb::Test::ViewHelper)
43
+ config.include(Merb::Test::RouteHelper)
44
+ config.include(Merb::Test::ControllerHelper)
45
+ config.include(Merb::Test::SliceHelper)
46
+ end
47
+
48
+ # You can add your own helpers here
49
+ #
50
+ Merb::Test.add_helpers do
51
+ def mount_slice
52
+ Merb::Router.prepare { add_slice(:MerbBackground, "merb_background") } if standalone?
53
+ end
54
+
55
+ def dismount_slice
56
+ Merb::Router.reset! if standalone?
57
+ end
58
+ end
@@ -0,0 +1,2 @@
1
+ class MerbBackground::Application < Merb::Controller
2
+ end
@@ -0,0 +1,2 @@
1
+ class MerbBackground::Main < MerbBackground::Application
2
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maiha-merb_background
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - maiha
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-01 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: merb-slices
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.11
24
+ version:
25
+ description: A slice for the Merb framework that offers background tasks for DataMapper
26
+ email: maiha@wota.jp
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ - LICENSE
34
+ - TODO
35
+ files:
36
+ - LICENSE
37
+ - README
38
+ - Rakefile
39
+ - TODO
40
+ - lib/merb_background
41
+ - lib/merb_background/config.rb
42
+ - lib/merb_background/spectasks.rb
43
+ - lib/merb_background/job.rb
44
+ - lib/merb_background/job
45
+ - lib/merb_background/job/dm.rb
46
+ - lib/merb_background/job/ar2dm.rb
47
+ - lib/merb_background/job/ar.rb
48
+ - lib/merb_background/job/bonus_features.rb
49
+ - lib/merb_background/slicetasks.rb
50
+ - lib/merb_background/merbtasks.rb
51
+ - lib/merb_background.rb
52
+ - spec/merb_background_spec.rb
53
+ - spec/spec_helper.rb
54
+ - spec/requests
55
+ - spec/requests/main_spec.rb
56
+ - app/views
57
+ - app/views/layout
58
+ - app/views/layout/merb_background.html.erb
59
+ - app/views/main
60
+ - app/views/main/index.html.erb
61
+ - app/controllers
62
+ - app/controllers/main.rb
63
+ - app/controllers/application.rb
64
+ - app/helpers
65
+ - app/helpers/application_helper.rb
66
+ - public/stylesheets
67
+ - public/stylesheets/master.css
68
+ - public/javascripts
69
+ - public/javascripts/master.js
70
+ - stubs/app
71
+ - stubs/app/controllers
72
+ - stubs/app/controllers/main.rb
73
+ - stubs/app/controllers/application.rb
74
+ has_rdoc: true
75
+ homepage: http://github.com/maiha/merb_background
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ version:
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: "0"
92
+ version:
93
+ requirements: []
94
+
95
+ rubyforge_project: merb
96
+ rubygems_version: 1.2.0
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: A slice for the Merb framework that offers background tasks for DataMapper
100
+ test_files: []
101
+