junkfood 0.2.0 → 0.3.0

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