q 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,24 +1,179 @@
1
- q is for Journal
2
- ================
3
-
4
- Why 'q'? Because 'j' was alread established as a rubygem
5
- and I wanted to completely re-write ["j is for Journal"](https://github.com/hooobs/j)
6
- because it's been a failed attempt from the start.
7
-
8
- What is 'q'? It's a command-line activity tracker. You tell
9
- it what you're working on and (optionally) when you worked on
10
- it in natural language and it'll record that information for you.
11
-
12
- The app is backed by sqlite, so no messy text files are necessary and
13
- hopefully search features will be easy to implement.
14
-
15
- Laundry List
16
- ------------
17
-
18
- * add entries for the current time
19
- * add entries for past and future dates in natural language (chronic)
20
- * list entries with pretty formatting and fuzzy time (also chronic)
21
- * search entries
22
- * hash tags
23
- * some kind of fancy interbutt/cloud integration
24
- * import/export feature
1
+ ## Q
2
+
3
+ Forget queue boilerplate: focus on your code.
4
+
5
+ ## Install
6
+
7
+ In your `Gemfile` add:
8
+
9
+ ```ruby
10
+ gem 'q'
11
+ ```
12
+
13
+ Then run `$ bundle install`
14
+
15
+ ## What
16
+
17
+ Q is an interface for your background queues. Are you using Resque, Sidekiq, delayed_job, queue_classic, or some other queue? Awesome sauce, because with `Q` you can write your queuing code once and re-use against different backends.
18
+
19
+ ```ruby
20
+ Q.setup do |config|
21
+ config.queue = :resque
22
+ end
23
+ ```
24
+
25
+ Now in your code when you need to enqueue something first you need to add the `Q::Methods` module:
26
+
27
+ ```ruby
28
+ class Poro
29
+ include Q::Methods
30
+ end
31
+ ```
32
+
33
+ Now you can define tasks using the `queue` class method like this:
34
+
35
+ ```ruby
36
+ class Poro
37
+ include Q::Methods
38
+
39
+ queue(:send_issues) do |id, state|
40
+ user = User.find(id)
41
+ issues = user.issues.where(state: state).all
42
+ UserMailer.send_issues(user: user, issues: issues).deliver
43
+ end
44
+ end
45
+ ```
46
+
47
+ Here we're building a background task called `send_issues` that will send out an email when executed.
48
+
49
+ Now that the task is defined, you can enqueue a `send_issues` job to be executed later by calling `queue` and then `send_issues` like this:
50
+
51
+ ```ruby
52
+ user = User.last
53
+ state = 'open'
54
+ Poro.queue.send_issues(user.id, state)
55
+ ```
56
+
57
+ The Q interface expects json-able objects, numbers, arrays, hashes, etc. This is important if you want your code to be re-usable across multiple queue backends.
58
+
59
+ ## No Queue? No Problem
60
+
61
+ The `Q` library comes with a threaded queue that does not need a backend (such as Redis) by default, so you can write your code today and figure out what queue you want to use tomorrow.
62
+
63
+ Note: that this threaded queue is very basic and should not be used in production. If you stop your Ruby process while there are jobs in memory you will lose your jobs see [threaded_in_memory_queue](https://github.com/schneems/threaded_in_memory_queue) for more information.
64
+
65
+ ## Starting your Queue
66
+
67
+ Most background queue libraries must be run in a seperate process. The `Q` library makes starting these background tasks easy.
68
+
69
+ Make sure there is a Rake task named `:environment` that loads your app (Rails provides one by default). Then add this line to your `Procfile`:
70
+
71
+ ```
72
+ worker: bundle exec rake q:work
73
+ ```
74
+
75
+ Now if you are running on Heroku the background task will automatically be run. Locally you can run the task manually by executing:
76
+
77
+ ```sh
78
+ $ bundle exec rake q:work
79
+ ```
80
+
81
+ Or through your `Procfile` with foreman:
82
+
83
+ ```sh
84
+ $ foreman start
85
+ ```
86
+
87
+ If your queueing library supports any custom environment variables or flags you can add them to your `rake q:work` command and they will be passed to the supporting background queue's task.
88
+
89
+ Note: the default threaded queue does not need to be started as it runs in your web process
90
+
91
+ ## Config
92
+
93
+ You can configure the behavior of your background queue using `Q.queue_config`. For example if you are using Resque and want to run commands inline you could execute:
94
+
95
+ ```ruby
96
+ Q.queue_config.inline = true
97
+ ```
98
+
99
+ Now any calls to `enqueue` will bypass resque and be run immediately. Different queues will have different configuration options so you will need to see their docs for configuration options.
100
+
101
+ You can access this config in the setup command:
102
+
103
+ ```ruby
104
+ Q.setup do |config|
105
+ config.queue = :resque
106
+ config.queue_config.inline = true
107
+ end
108
+ ```
109
+
110
+ It also accepts a block:
111
+
112
+ ```ruby
113
+ Q.queue_config do |config|
114
+ config.inline = true
115
+ end
116
+ ```
117
+
118
+ Don't confuse `queue_config` which will configure your background queue (such as Resque) with `setup` which configures the `Q` library itself.
119
+
120
+ ## Diverging Backends
121
+
122
+ As much as we try to make all front end code similar, you'll still need to setup your queue. To make sqitching back and forth easier, we provide a `Q.env` object that responds to the backend you are using such as `Q.env.resque?`.
123
+
124
+ That way you could keep multiple queue configurations in your app and it won't raise any errors if you're running a different backend.
125
+
126
+ ```ruby
127
+ if Q.env.resque?
128
+ # config resque here
129
+ end
130
+
131
+ if Q.env.sidekiq?
132
+ # configure sidekiq here
133
+ else
134
+ ```
135
+
136
+ ## Supported Queue Backends
137
+
138
+ ```
139
+ config.queue = :sidekiq
140
+ config.queue = :resque
141
+ config.queue = :threaded
142
+ ```
143
+
144
+ Coming soon:
145
+
146
+ ```
147
+ config.queue = :delayed_job
148
+ ```
149
+
150
+
151
+
152
+ ## Blocks
153
+
154
+ You can set default values in blocks like this:
155
+
156
+ ```ruby
157
+ queue(:foo) do |id, state = 'open', username = 'schneems'|
158
+ # ...
159
+ end
160
+ ```
161
+
162
+ You can have an unlimited amount of args using a splat:
163
+
164
+ ```ruby
165
+ queue(:foo) do |id, *args|
166
+ # ...
167
+ end
168
+ ```
169
+
170
+
171
+ ## Q Authors
172
+
173
+ Did you write a background queuing library? Want to add support for the `Q` interface? Check out the [QUEUE_AUTHORS.md](QUEUE_AUTHORS.md) file to get started.
174
+
175
+ ## License
176
+
177
+ Brought to you by [@schneems](http://twitter.com/schneems)
178
+
179
+ MIT
data/Rakefile CHANGED
@@ -1 +1,14 @@
1
+ # encoding: UTF-8
1
2
  require 'bundler/gem_tasks'
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+
7
+ task :default => [:test]
8
+
9
+ test_task = Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = false
14
+ end
data/lib/q.rb CHANGED
@@ -1,5 +1,82 @@
1
- require "q/version"
1
+ require 'proc_to_lambda'
2
2
 
3
3
  module Q
4
- # Your code goes here...
4
+ class Queue
5
+ end
5
6
  end
7
+
8
+ require 'q/version'
9
+ require 'q/helpers'
10
+ require 'q/errors'
11
+ require 'q/methods/base'
12
+ require 'q/methods'
13
+ require 'q/tasks'
14
+
15
+ module Q
16
+ extend Q::Helpers
17
+ DEFAULT_QUEUE = ->{ @env = :threaded; Q::Methods::Threaded }
18
+ FALSEY_HASH = Hash.new(false)
19
+
20
+ def self.queue
21
+ @queue_method || DEFAULT_QUEUE.call
22
+ end
23
+
24
+ def self.setup(&block)
25
+ yield self
26
+ end
27
+
28
+ def self.env
29
+ name = queue.to_s.split("::").last
30
+ @env ||= Q.underscore(name)
31
+
32
+ OpenStruct.new(FALSEY_HASH.merge("#{@env}?" => true))
33
+ end
34
+
35
+ def self.reset_queue!
36
+ @queue_method = nil
37
+ @env = nil
38
+ end
39
+
40
+ def self.module_from_klass_name(name)
41
+ unless defined?(Q::Methods.const_get(name))
42
+ require "q/methods/#{name}"
43
+ end
44
+ return Q::Methods.const_get(name)
45
+ rescue LoadError => e
46
+ raise LoadError, "Could not find queue: #{name}, expected to be defined in q/methods/#{name}\n" + e.message
47
+ rescue NameError => e
48
+ raise NameError, "Could not load queue: #{name}, expected to be defined in q/methods/#{name}\n" + e.message
49
+ end
50
+
51
+ def self.module_from_queue_name(queue_name)
52
+ module_from_klass_name(camelize(queue_name))
53
+ end
54
+
55
+ def self.queue_lookup
56
+ @queue_lookup ||= Hash.new do |hash, key|
57
+ hash[key] = -> {
58
+ require "q/methods/#{key}"
59
+ const = Q.camelize(key)
60
+ ::Q::Methods.const_get(const)
61
+ }
62
+ end
63
+ @queue_lookup
64
+ end
65
+
66
+ def self.queue=(queue)
67
+ if queue.is_a?(Module)
68
+ @queue_method = queue
69
+ else
70
+ @env = queue
71
+ @queue_method = queue_lookup[queue].call
72
+ end
73
+ end
74
+
75
+ def self.queue_config(&block)
76
+ @config_class ||= queue::QueueConfig.call
77
+ yield @config_class if block_given?
78
+ @config_class
79
+ end
80
+ end
81
+
82
+ require 'q/methods/threaded_in_memory_queue'
@@ -0,0 +1,32 @@
1
+ module Q
2
+ class StandardError < ::StandardError; end
3
+
4
+ class MissingClassError < StandardError
5
+ def initialize(base, missing_klass)
6
+ msg = "#{base} must define '#{missing_klass}' class with a call method"
7
+ super(msg)
8
+ end
9
+ end
10
+
11
+ class InstanceQueueDefinitionError < StandardError
12
+ def initialize(obj)
13
+ msg = "Cannot define a queue on an instance: #{obj}. Try defining it directly on the class #{obj.class}"
14
+ super(msg)
15
+ end
16
+ end
17
+
18
+ class DuplicateQueueClassError < StandardError
19
+ def initialize(base, duplicate_klass)
20
+ msg = "Cannot create queue class: '#{duplicate_klass}' because #{duplicate_klass} is already defined on #{base}"
21
+ super(msg)
22
+ end
23
+ end
24
+
25
+ class DuplicateQueueMethodError < StandardError
26
+ def initialize(base, method)
27
+ msg = "Cannot create queue method: '#{method}'. Method already exists on #{base}.queue, cannot overwrite"
28
+ msg << "Originally defined at #{base.queue.method(method).source_location}"
29
+ super(msg)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ module Q
2
+ module Helpers
3
+ def camelize(term)
4
+ string = term.to_s
5
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
6
+ string = string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$2.capitalize}" }.gsub('/', '::')
7
+ string
8
+ end
9
+
10
+ def underscore(term)
11
+ string = term.to_s
12
+ string = string.sub(/^[a-z\d]*/) { "#{$&.downcase}_" }
13
+ string = string.gsub(/^_/, '')
14
+ string
15
+ end
16
+
17
+ def const_defined_on?(on, const)
18
+ on.constants.include?(const.to_sym)
19
+ end
20
+
21
+ def proc_to_lambda(block = nil, &proc)
22
+ ::ProcToLambda.to_lambda(block || proc)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ module Q
2
+ module Methods
3
+ include Q::Methods::Base
4
+
5
+ class QueueConfig
6
+ def self.call
7
+ Q.queue::QueueConfig
8
+ end
9
+ end
10
+
11
+ class QueueTask
12
+ def self.call(*rake_args)
13
+ Q.queue::QueueTask.call(rake_args)
14
+ end
15
+ end
16
+
17
+ class QueueBuild
18
+ def self.call(options={}, &job)
19
+ Q.queue::QueueBuild.call(options, &job)
20
+ end
21
+ end
22
+
23
+ class QueueMethod
24
+ def self.call(options = {})
25
+ Q.queue::QueueMethod.call(options)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ Q::Method = Q::Methods
@@ -0,0 +1,53 @@
1
+ module Q
2
+ module Methods
3
+ module Base
4
+ def self.included(base)
5
+ base.const_set("Queue", Class.new(::Q::Queue)) unless base.const_get("Queue") != ::Queue
6
+
7
+ included = base.method(:included) if base.respond_to?(:included)
8
+ base.define_singleton_method(:included) do |target|
9
+ included.call(target) unless included.nil?
10
+
11
+ raise Q::MissingClassError.new(base, :QueueMethod) unless Q.const_defined_on?(base, :QueueMethod)
12
+ raise Q::MissingClassError.new(base, :QueueBuild) unless Q.const_defined_on?(base, :QueueBuild)
13
+ raise Q::MissingClassError.new(base, :QueueTask) unless Q.const_defined_on?(base, :QueueTask)
14
+ raise Q::MissingClassError.new(base, :QueueConfig) unless Q.const_defined_on?(base, :QueueConfig)
15
+
16
+ target.extend(ClassMethods)
17
+ target.send(:include, InstanceMethods)
18
+
19
+ target.class_variable_set(:@@_q_klass, base) unless target.class_variable_defined?(:@@_q_klass)
20
+ target.class_variable_set(:@@_q_queue, base::Queue.new) unless target.class_variable_defined?(:@@_q_queue)
21
+ end
22
+ end
23
+
24
+ module InstanceMethods
25
+ def queue
26
+ raise Q::InstanceQueueDefinitionError.new(self) if block_given?
27
+ self.class.queue
28
+ end
29
+ end
30
+
31
+ module ClassMethods
32
+ def queue(*args, &block)
33
+ queue = self.class_variable_get(:@@_q_queue)
34
+
35
+ return queue unless block_given?
36
+
37
+ queue_name = args.shift
38
+ job = Q.proc_to_lambda(&block)
39
+
40
+ raise "first argument #{queue_name.inspect} must be a symbol to define a queue" unless queue_name.is_a?(Symbol)
41
+
42
+ options = { base: self,
43
+ queue_name: queue_name,
44
+ queue_klass_name: Q.camelize(queue_name) }
45
+
46
+ queue_klass = self.class_variable_get(:@@_q_klass)
47
+ queue_klass::QueueBuild.call(options, &job)
48
+ queue_klass::QueueMethod.call(options)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end