junkfood 0.2.0 → 0.3.0

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.
data/README.md CHANGED
@@ -361,6 +361,11 @@ The Middleware:
361
361
  * Junkfood::Rack::ErrorHandler
362
362
  * Junkfood::Rack::TransientSession
363
363
 
364
+ Application and Middleware
365
+
366
+ * Junkfood::Rack::ChainRouter - A way to call multiple applications in
367
+ order until one returns a result without the X-Cascade header set to pass.
368
+
364
369
  Requires:
365
370
 
366
371
  * json
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
data/junkfood.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{junkfood}
8
- s.version = "0.2.0"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Benjamin Yu"]
12
- s.date = %q{2010-11-15}
12
+ s.date = %q{2010-11-20}
13
13
  s.description = %q{My mesh of an all-in-one library for disjoint code.}
14
14
  s.email = %q{benjaminlyu@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -43,6 +43,7 @@ Gem::Specification.new do |s|
43
43
  "lib/junkfood/one_time.rb",
44
44
  "lib/junkfood/paperclip_string_io.rb",
45
45
  "lib/junkfood/rack.rb",
46
+ "lib/junkfood/rack/chained_router.rb",
46
47
  "lib/junkfood/rack/error.rb",
47
48
  "lib/junkfood/rack/sessions.rb",
48
49
  "lib/junkfood/settings.rb",
@@ -59,6 +60,7 @@ Gem::Specification.new do |s|
59
60
  "spec/junkfood/ceb/executors/event_executor_spec.rb",
60
61
  "spec/junkfood/one_time_spec.rb",
61
62
  "spec/junkfood/paperclip_string_io_spec.rb",
63
+ "spec/junkfood/rack/chained_router_spec.rb",
62
64
  "spec/junkfood/rack/error_spec.rb",
63
65
  "spec/junkfood/rack/sessions_spec.rb",
64
66
  "spec/junkfood/settings_spec.rb",
@@ -82,6 +84,7 @@ Gem::Specification.new do |s|
82
84
  "spec/junkfood/ceb/executors/event_executor_spec.rb",
83
85
  "spec/junkfood/one_time_spec.rb",
84
86
  "spec/junkfood/paperclip_string_io_spec.rb",
87
+ "spec/junkfood/rack/chained_router_spec.rb",
85
88
  "spec/junkfood/rack/error_spec.rb",
86
89
  "spec/junkfood/rack/sessions_spec.rb",
87
90
  "spec/junkfood/settings_spec.rb",
@@ -13,6 +13,7 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+ require 'date'
16
17
  require 'junkfood/ceb/bus'
17
18
  require 'mongoid'
18
19
 
@@ -22,11 +23,45 @@ module Junkfood
22
23
  ##
23
24
  # BaseCommand abstract class from which one defines and implements
24
25
  # actual Commands to be run.
26
+ # * _id - The mongodb id of the created Command.
27
+ # * _type - The type of the command. Based off subclass Name.
28
+ # * created_at - When the event was created/saved to mongodb.
29
+ # * origin - A descriptive string about where this command originated.
30
+ # For example, from the main web application or a separate web service
31
+ # api.
32
+ #
33
+ # Example:
34
+ #
35
+ # ##
36
+ # # This is a simple command that creates a blog post, which is backed
37
+ # # by an ActiveRecord based model.
38
+ # #
39
+ # class Post::CreateCommand < Junkfood::Ceb::BaseCommand
40
+ #
41
+ # def perform
42
+ # # The constructor is set to be a Mongoid document constructor,
43
+ # # so all of the fields passed become dynamic attributes.
44
+ # # We access such attributes with the read_attribute method.
45
+ # #
46
+ # # The following example reads specific attributes to be passed
47
+ # # on to the actual Post model for creation.
48
+ # params = {
49
+ # 'user_id' => read_attribute(:author_id),
50
+ # 'title' => read_attribute(:title),
51
+ # 'body' => read_attribute(:body),
52
+ # }
53
+ # post = Post.new(params)
54
+ # # The BaseCommand object is an event_bus, so we can
55
+ # # use it to send off events... then return the sent event.
56
+ # if post.save
57
+ # event, result = send_event :post_created, post
58
+ # else
59
+ # event, result = send_event :post_failed, post
60
+ # end
61
+ # return event
62
+ # end
63
+ # end
25
64
  #
26
- # _id (id)
27
- # _type
28
- # created_at
29
- # origin
30
65
  class BaseCommand
31
66
  include Junkfood::Ceb::Bus
32
67
  include Mongoid::Document
@@ -38,7 +73,7 @@ module Junkfood
38
73
  :inverse_of => :command,
39
74
  :class_name => 'Junkfood::Ceb::BaseEvent')
40
75
 
41
- field :created_at, :type => Time
76
+ field :created_at, :type => DateTime
42
77
  field :origin, :type => String
43
78
 
44
79
  after_initialize :generate_default_values
@@ -55,7 +90,7 @@ module Junkfood
55
90
  # Before create hook to set the Command's default values.
56
91
  #
57
92
  def generate_default_values
58
- self.created_at = Time.now unless self.created_at
93
+ self.created_at = DateTime.now unless self.created_at
59
94
  end
60
95
  end
61
96
  end
@@ -13,20 +13,38 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+ require 'date'
16
17
  require 'mongoid'
17
18
 
18
19
  module Junkfood
19
20
  module Ceb
20
21
 
21
22
  ##
22
- # _id (id)
23
- # _type
24
- # created_at
25
- # command_id
23
+ # The base class from which other classes derive Event modeling.
24
+ # This is a mongoid document model with the following fields:
25
+ # * _id - The mongodb id of the created Event.
26
+ # * _type - The type of the event. Based off subclass Name.
27
+ # * created_at - When the event was created/saved to mongodb.
28
+ # * command_id - The command id to which this event was created in response.
29
+ #
30
+ # Example:
31
+ #
32
+ # class PostCreatedEvent < Junkfood::Ceb::BaseEvent
33
+ # field :post_id, :type => Integer
34
+ # field :post_title, :type => String
35
+ # field :post_date, :type => DateTime
36
+ # field :post_body, :type => String
37
+ # field :post_author, :type => String
38
+ # end
39
+ #
40
+ # Events are meant to state a fact that something occured, and also
41
+ # the relevant details of that event. In this case, we include all the
42
+ # details of the Post created.
43
+ #
26
44
  class BaseEvent
27
45
  include Mongoid::Document
28
46
 
29
- field :created_at, :type => Time
47
+ field :created_at, :type => DateTime
30
48
 
31
49
  referenced_in :command, :class_name => 'Junkfood::Ceb::BaseCommand'
32
50
 
@@ -35,7 +53,7 @@ module Junkfood
35
53
  protected
36
54
 
37
55
  def generate_default_values
38
- self.created_at = Time.now unless self.created_at
56
+ self.created_at = DateTime.now unless self.created_at
39
57
  end
40
58
  end
41
59
  end
data/lib/junkfood/rack.rb CHANGED
@@ -22,5 +22,6 @@ module Junkfood
22
22
  end
23
23
  end
24
24
 
25
+ require 'junkfood/rack/chained_router'
25
26
  require 'junkfood/rack/error'
26
27
  require 'junkfood/rack/sessions'
@@ -0,0 +1,130 @@
1
+ # encoding: utf-8
2
+ # Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module Junkfood
17
+ module Rack
18
+
19
+ ##
20
+ # A Rack Application and Middleware that will execute a list
21
+ # of callables in order until it receives a response without the
22
+ # 'X-Cascade' header value set to 'pass'. ChainedRouter will then return
23
+ # the response of said execution. The response of the last callable will
24
+ # be returned if it is executed even if it has the 'X-Cascade' header set.
25
+ # As Middleware, the given app will be the last callable in the chain.
26
+ #
27
+ # Example as Middleware:
28
+ #
29
+ # use Junkfood::Rack::ChainedRouter do |router|
30
+ # router.add_route Proc.new { |env|
31
+ # # This proc is called, but it returns a pass.
32
+ # [404, { 'X-Cascade' => 'pass' }, []]
33
+ # }
34
+ # router.add_route Proc.new { |env|
35
+ # # This proc is called, but it also returns a pass.
36
+ # [404, { 'X-Cascade' => 'pass' }, []]
37
+ # }
38
+ # end
39
+ # run Proc.new { |env|
40
+ # # The main app is the last callable in ChainRouter's list.
41
+ # # And this will be executed.
42
+ # [200, { 'Content-Type' => 'text/plain' }, ['Hello World']]
43
+ # }
44
+ #
45
+ # Example as Application:
46
+ #
47
+ # run Junkfood::Rack::ChainedRouter.new do |router|
48
+ # router.add_route Proc.new { |env|
49
+ # # This proc is called, but it returns a pass.
50
+ # [404, { 'X-Cascade' => 'pass' }, []]
51
+ # }
52
+ # router.add_route Proc.new { |env|
53
+ # # This is the last app to run.
54
+ # [200, { 'Content-Type' => 'text/plain' }, ['Hello World']]
55
+ # }
56
+ # router.add_route Proc.new { |env|
57
+ # # So this proc is never called.
58
+ # [200, { 'Content-Type' => 'text/plain' }, ['Never Run']]
59
+ # }
60
+ # end
61
+ #
62
+ class ChainedRouter
63
+
64
+ ##
65
+ # HTTP Not Found status code.
66
+ HTTP_NOT_FOUND = 404.freeze
67
+
68
+ ##
69
+ # Header to set the content type value in the response.
70
+ CONTENT_TYPE = 'Content-Type'.freeze
71
+
72
+ ##
73
+ # Header location where to look for the pass value.
74
+ X_CASCADE = 'X-Cascade'.freeze
75
+
76
+ ##
77
+ # The pass string to signify triggering the next callable in the list.
78
+ PASS = 'pass'.freeze
79
+
80
+ ##
81
+ # @param app the rack application. This will be last callable in chain.
82
+ # @yield [router] block to configure the ChainedRouter.
83
+ # @yieldparam router newly instantiated ChainedRouter
84
+ #
85
+ def initialize(app=nil, &block)
86
+ @chain = []
87
+ if block_given?
88
+ yield self
89
+ end
90
+ @chain << app if app.respond_to? :call
91
+
92
+ # Just validate the arguments
93
+ @chain.each do |link|
94
+ raise ArgumentError unless link.respond_to? :call
95
+ end
96
+ end
97
+
98
+ ##
99
+ # @param the rack request environment.
100
+ #
101
+ def call(env)
102
+ # Set some defaults if the route set is empty.
103
+ results = [
104
+ HTTP_NOT_FOUND,
105
+ {
106
+ X_CASCADE => PASS,
107
+ CONTENT_TYPE => 'text/plain'
108
+ },
109
+ []]
110
+ # Call each link in chain.
111
+ for link in @chain
112
+ results = link.call env
113
+ return results unless results[1][X_CASCADE] == PASS
114
+ end
115
+ # Return the results of the last link in chain, or the originally
116
+ # set results if chain is empty.
117
+ return results
118
+ end
119
+
120
+ ##
121
+ # Appends a callable to the end of the call chain.
122
+ #
123
+ # @param app the callable to append.
124
+ #
125
+ def add_route(app)
126
+ @chain << app
127
+ end
128
+ end
129
+ end
130
+ end
@@ -56,7 +56,15 @@ module Junkfood
56
56
  class ErrorHandler
57
57
  ##
58
58
  # The default HTTP status code for caught errors.
59
- DEFAULT_STATUS_CODE = 500
59
+ DEFAULT_STATUS_CODE = 500.freeze
60
+
61
+ ##
62
+ # Header to set the content type of error response
63
+ CONTENT_TYPE = 'Content-Type'.freeze
64
+
65
+ ##
66
+ # Json's content type value for the content type header.
67
+ JSON_CONTENT_TYPE = 'application/json'.freeze
60
68
 
61
69
  ##
62
70
  # @param app the rest of the rack stack
@@ -84,7 +92,7 @@ module Junkfood
84
92
 
85
93
  return [
86
94
  map['status_code'] ? map['status_code'].to_i : DEFAULT_STATUS_CODE,
87
- { 'Content-Type' => 'application/json' },
95
+ { CONTENT_TYPE => JSON_CONTENT_TYPE },
88
96
  [error.to_json]]
89
97
  end
90
98
  end
@@ -52,8 +52,9 @@ module Junkfood
52
52
  # @return a Rack response from the application.
53
53
  #
54
54
  def call(env)
55
- env['rack.session'] = {}
56
- @app.call(env)
55
+ new_env = env.dup
56
+ new_env['rack.session'] = {}
57
+ @app.call(new_env)
57
58
  end
58
59
  end
59
60
  end
@@ -33,7 +33,43 @@ describe 'Junkfood::Base32' do
33
33
  str1.string.should eql(str2.string)
34
34
  end
35
35
 
36
- it 'should have optional splitlines'
36
+ it 'should support dash splits at every 10th character' do
37
+ str = Junkfood::Base32.encode(
38
+ '1234567890abcdefghijklmnopqrstuvwxyz',
39
+ :split => :dash,
40
+ :split_length => 10)
41
+ str.string.should eql(
42
+ 'GEZDGNBVGY-3TQOJQMFRG-GZDFMZTWQ2-LKNNWG23TP-OBYXE43UOV-3HO6DZPI')
43
+ str.string.encoding.should eql(Encoding::ASCII)
44
+ end
45
+
46
+ it 'should support newline splits at every 10th character' do
47
+ str = Junkfood::Base32.encode(
48
+ '1234567890abcdefghijklmnopqrstuvwxyz',
49
+ :split => :newline,
50
+ :split_length => 10)
51
+ str.string.should eql(
52
+ "GEZDGNBVGY\n3TQOJQMFRG\nGZDFMZTWQ2\nLKNNWG23TP\nOBYXE43UOV\n3HO6DZPI")
53
+ str.string.encoding.should eql(Encoding::ASCII)
54
+ end
37
55
 
38
- it 'should have splitlines at specified lengths'
56
+ it 'should support space splits at every 5th character' do
57
+ str = Junkfood::Base32.encode(
58
+ '1234567890abcdefgh',
59
+ :split => :space,
60
+ :split_length => 5)
61
+ str.string.should eql(
62
+ 'GEZDG NBVGY 3TQOJ QMFRG GZDFM ZTWQ')
63
+ str.string.encoding.should eql(Encoding::ASCII)
64
+ end
65
+
66
+ it 'should support underscore splits at every 4th character' do
67
+ str = Junkfood::Base32.encode(
68
+ '1234567890abcdefgh',
69
+ :split => :underscore,
70
+ :split_length => 4)
71
+ str.string.should eql(
72
+ 'GEZD_GNBV_GY3T_QOJQ_MFRG_GZDF_MZTW_Q')
73
+ str.string.encoding.should eql(Encoding::ASCII)
74
+ end
39
75
  end
@@ -0,0 +1,100 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe 'Junkfood::Rack' do
4
+ describe 'ChainedRouter' do
5
+
6
+ it 'should return a not found result if route set is empty' do
7
+ # Set the not found expected results
8
+ expected_results = [
9
+ 404,
10
+ { 'X-Cascade' => 'pass', 'Content-Type' => 'text/plain'},
11
+ []]
12
+
13
+ # Create an empty router
14
+ router = Junkfood::Rack::ChainedRouter.new
15
+
16
+ # Execute and test
17
+ results = router.call({})
18
+ results.should eql expected_results
19
+ end
20
+
21
+ it 'should execute the next callable in chain when a pass is given' do
22
+ expected_results = [200, {}, ['Hello World']]
23
+
24
+ # Create the router
25
+ app = Proc.new { |env|
26
+ fail 'Should not be called'
27
+ }
28
+ router = Junkfood::Rack::ChainedRouter.new app do |router|
29
+ # This proc is called, but it returns a pass.
30
+ router.add_route Proc.new { |env|
31
+ [404, { 'X-Cascade' => 'pass' }, []]
32
+ }
33
+ # This proc is called, but it also returns a pass.
34
+ router.add_route Proc.new { |env|
35
+ [404, { 'X-Cascade' => 'pass' }, []]
36
+ }
37
+ # This proc is called and returns success
38
+ router.add_route Proc.new { |env|
39
+ [200, {}, ['Hello World']]
40
+ }
41
+ router.add_route Proc.new { |env|
42
+ fail 'Should not be called'
43
+ }
44
+ end
45
+
46
+ # Execute and test
47
+ results = router.call({})
48
+ results.should eql expected_results
49
+ end
50
+
51
+ it 'should execute the app as the last callable in the chain' do
52
+ expected_results = [200, {}, ['Hello World']]
53
+
54
+ # Last app to be called
55
+ app = Proc.new { |env|
56
+ [200, {}, ['Hello World']]
57
+ }
58
+
59
+ # Create the router
60
+ router = Junkfood::Rack::ChainedRouter.new app do |router|
61
+ # This proc is called, but it returns a pass.
62
+ router.add_route Proc.new { |env|
63
+ [404, { 'X-Cascade' => 'pass' }, []]
64
+ }
65
+ # This proc is called, but it also returns a pass.
66
+ router.add_route Proc.new { |env|
67
+ [404, { 'X-Cascade' => 'pass' }, []]
68
+ }
69
+ end
70
+
71
+ # Execute and test
72
+ results = router.call({})
73
+ results.should eql expected_results
74
+ end
75
+
76
+ it 'should return the last callable results even if it is a pass' do
77
+ expected_results = [404, { 'X-Cascade' => 'pass' }, ['last']]
78
+
79
+ # Last app to be called, and it's a pass
80
+ app = Proc.new { |env|
81
+ [404, { 'X-Cascade' => 'pass' }, ['last']]
82
+ }
83
+ # Create the router
84
+ router = Junkfood::Rack::ChainedRouter.new app do |router|
85
+ # This proc is called, but it returns a pass.
86
+ router.add_route Proc.new { |env|
87
+ [404, { 'X-Cascade' => 'pass' }, []]
88
+ }
89
+ # This proc is called, but it also returns a pass.
90
+ router.add_route Proc.new { |env|
91
+ [404, { 'X-Cascade' => 'pass' }, []]
92
+ }
93
+ end
94
+
95
+ # Execute and test
96
+ results = router.call({})
97
+ results.should eql expected_results
98
+ end
99
+ end
100
+ end
@@ -4,6 +4,9 @@ describe 'Junkfood::Rack' do
4
4
  describe 'TransientSession' do
5
5
 
6
6
  it 'should set the rack.session env parameter with an empty hash' do
7
+ original_env = {
8
+ 'rack.session' => { 'hello' => 'world' }
9
+ }
7
10
  # We manually create a rack application with the TransientSession
8
11
  # acting as the only middleware. The application itself is a
9
12
  # proc object that just checks that the passed rack environment
@@ -14,7 +17,9 @@ describe 'Junkfood::Rack' do
14
17
  env.should eql 'rack.session' => {}
15
18
  [200, {}, []]
16
19
  }
17
- app.call({})
20
+ app.call(original_env)
21
+
22
+ original_env['rack.session'].should eql({'hello' => 'world'})
18
23
  end
19
24
  end
20
25
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
7
+ - 3
8
8
  - 0
9
- version: 0.2.0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Benjamin Yu
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-15 00:00:00 -08:00
17
+ date: 2010-11-20 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -330,6 +330,7 @@ files:
330
330
  - lib/junkfood/one_time.rb
331
331
  - lib/junkfood/paperclip_string_io.rb
332
332
  - lib/junkfood/rack.rb
333
+ - lib/junkfood/rack/chained_router.rb
333
334
  - lib/junkfood/rack/error.rb
334
335
  - lib/junkfood/rack/sessions.rb
335
336
  - lib/junkfood/settings.rb
@@ -346,6 +347,7 @@ files:
346
347
  - spec/junkfood/ceb/executors/event_executor_spec.rb
347
348
  - spec/junkfood/one_time_spec.rb
348
349
  - spec/junkfood/paperclip_string_io_spec.rb
350
+ - spec/junkfood/rack/chained_router_spec.rb
349
351
  - spec/junkfood/rack/error_spec.rb
350
352
  - spec/junkfood/rack/sessions_spec.rb
351
353
  - spec/junkfood/settings_spec.rb
@@ -365,7 +367,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
365
367
  requirements:
366
368
  - - ">="
367
369
  - !ruby/object:Gem::Version
368
- hash: 529643725
370
+ hash: 405223505
369
371
  segments:
370
372
  - 0
371
373
  version: "0"
@@ -397,6 +399,7 @@ test_files:
397
399
  - spec/junkfood/ceb/executors/event_executor_spec.rb
398
400
  - spec/junkfood/one_time_spec.rb
399
401
  - spec/junkfood/paperclip_string_io_spec.rb
402
+ - spec/junkfood/rack/chained_router_spec.rb
400
403
  - spec/junkfood/rack/error_spec.rb
401
404
  - spec/junkfood/rack/sessions_spec.rb
402
405
  - spec/junkfood/settings_spec.rb