kenji 1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -81,7 +81,7 @@ have:
81
81
  Getting started with Kenji could not be any easier. All it takes is a few lines
82
82
  and a terminal:
83
83
 
84
- $ gem install kenji # (once kenji is on the rubygems main source)
84
+ $ gem install kenji
85
85
  $ kenji init app_name; cd app_name
86
86
  $ rackup # launch the webserver
87
87
 
@@ -100,6 +100,30 @@ And already, your app is ready to go:
100
100
 
101
101
  ## Changelog
102
102
 
103
+ #### 1.1.1
104
+
105
+ - No longer catching ArgumentErrors when calling the block for a route. This
106
+ fixes a bug where Kenji incorrectly responds with a 404 when the block is
107
+ passed the wrong number of arguments.
108
+ - Fixed logic for matching a pass. the path now must match the pass exactly
109
+ whereas before the pass would match if any subset of the path matched the
110
+ pass.
111
+
112
+ #### 1.1
113
+
114
+ - Kenji::App is a simply wrapper that can and should be used in `config.ru`
115
+ files. It avoids the need to wrap the Kenji initialization in a lambda.
116
+ - Kenji's stderr is now configurable as an option.
117
+ - The new option `catch_exceptions` (default true) configures whether Kenji
118
+ will automatically rescue and log exceptions.
119
+ - The root path argument to initializing Kenji is now deprecated, and replaced
120
+ with the `directory` named option. It is only necessary to set this when not
121
+ using a `root_controller`.
122
+
123
+ #### 1.0
124
+
125
+ - ? TODO: fill me in
126
+
103
127
  #### 0.7
104
128
 
105
129
  - Pass can now contain variables, that get set as @ivars on the controller.
data/lib/kenji/app.rb ADDED
@@ -0,0 +1,25 @@
1
+
2
+ module Kenji
3
+
4
+ # Kenji::App is a simple wrapper class that helps avoid the awkward wrapping
5
+ # in a lambda typically necessary for using Kenji as a Rack app. Instead,
6
+ # simply do the following:
7
+ #
8
+ # run Kenji::App.new(directory: Dir.getwd)
9
+ #
10
+ # Any options passed will also be forwarded to Kenji.
11
+ #
12
+ # Kenji::App has one instance for the app, unlike Kenji::Kenji which has one
13
+ # instance per request.
14
+ #
15
+ class App
16
+
17
+ def initialize(opts={})
18
+ @opts = opts
19
+ end
20
+
21
+ def call(env)
22
+ Kenji.new(env, @opts).call
23
+ end
24
+ end
25
+ end
@@ -6,11 +6,14 @@ module Kenji
6
6
  attr_accessor :kenji
7
7
 
8
8
  # Routes below will accept routes in the format, eg.:
9
+ #
9
10
  # /hello/:id/children
11
+ #
10
12
  # Can contain any number of :id, but must be in their own url segment.
11
- # Colon-prefixed segments become variables, passed onto the given block as arguments.
12
- # The name given to the variable is irrelevant, and is thrown away: /hello/:/children is equivalent to the example above.
13
- # Given block must have correct arity.
13
+ # Colon-prefixed segments become variables, passed onto the given block as
14
+ # arguments. The name given to the variable is irrelevant, and is thrown
15
+ # away: /hello/:/children is equivalent to the example above. Given block
16
+ # must have correct arity.
14
17
 
15
18
  # Route GET
16
19
  def self.get(path, &block)
@@ -46,9 +49,9 @@ module Kenji
46
49
 
47
50
  # Route a given path to the correct block, for any given methods
48
51
  #
49
- # Note: this works by building a tree for the path,
50
- # each node being a path segment or variable segment, and the leaf @action being the block
51
-
52
+ # Note: this works by building a tree for the path, each node being a path
53
+ # segment or variable segment, and the leaf @action being the block
54
+ #
52
55
  def self.route(*methods, path, &block)
53
56
  # bind the block to self as an instance method, so its context is correct
54
57
  define_method(:_tmp_route_action, &block)
@@ -139,16 +142,11 @@ module Kenji
139
142
  node = node[:':'] # attempt to find a variable segment
140
143
  variables << segment # either we've found a variable, or the `unless` below will trigger
141
144
  else
142
- variables << segment # dump the remaining segments if we cannot drill down further
143
- searching = false
145
+ return attempt_fallback(path) # route failed to match variable or segment node so attempt fallback
144
146
  end
145
147
  end
146
148
  if node && action = node[:@action] # the block is stored in the @action leaf
147
- begin
148
- return action.bind(self).call(*variables)
149
- rescue ArgumentError # assuming argument error means route not defined
150
- return attempt_fallback(path) # TODO: might want to check arity instead
151
- end
149
+ return action.bind(self).call(*variables)
152
150
  else # or, fallback if necessary store the block for each method
153
151
  return attempt_fallback(path)
154
152
  end
@@ -197,8 +195,9 @@ module Kenji
197
195
  variables[match[1].to_sym] = e
198
196
  match = true
199
197
  else
200
- match = node.has_key?(e.to_sym)
201
- node = node[e.to_sym] if match
198
+ # if there is no match it should not pass
199
+ break unless match = node.has_key?(e.to_sym)
200
+ node = node[e.to_sym]
202
201
  end
203
202
 
204
203
  break if node[:@controller]
data/lib/kenji/version.rb CHANGED
@@ -1,5 +1,4 @@
1
-
2
1
  module Kenji
3
- VERSION = '1.0'
2
+ VERSION = '1.1.1'
4
3
  end
5
4
 
data/lib/kenji.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'kenji/controller'
3
+ require 'kenji/app'
3
4
  require 'kenji/string_extensions'
4
5
  require 'rack'
5
6
 
@@ -20,32 +21,65 @@ module Kenji
20
21
  #
21
22
  # `env` should be the environment hash provided by Rack.
22
23
  #
23
- # `root` is the root directory (as output by File.expand_path) of the Kenji
24
- # directory structure.
24
+ # *deprecated* `root` is the root directory (as output by File.expand_path)
25
+ # of the Kenji directory structure. This is deprecated, please use the
26
+ # :directory option below.
25
27
  #
26
28
  # `options` is an options hash that accepts the following keys:
27
29
  #
28
- # - :auto_cors => true | false # automatically deal with
29
- # CORS / Access-Control
30
- # - :root_controller => Object # when set, Kenji will not attempt to
31
- # discover controllers based on
32
- # convention, but rather will always
33
- # use this controller. use `pass` to
34
- # build a controller hierarchy
30
+ # :auto_cors => true | false
35
31
  #
36
- def initialize(env, root, options = {})
32
+ # automatically deal with CORS / Access-Control
33
+ #
34
+ # :directory => String (path)
35
+ #
36
+ # this is the preferred way of setting the root directory, when
37
+ # necessary. you should either set a root directory (which defaults to
38
+ # the current working directory), or set a root_controller. both are
39
+ # not necessary, as the directory is only used for auto-discovering
40
+ # controllers.
41
+ #
42
+ # :root_controller => Object
43
+ #
44
+ # when set, Kenji will not attempt to discover controllers based on
45
+ # convention, but rather will always use this controller. use `pass` to
46
+ # build a controller hierarchy
47
+ #
48
+ # :catch_exceptions => true | false
49
+ #
50
+ # when true, Kenji will catch exceptions, print them in stderr, and and
51
+ # return a standard 500 error
52
+ #
53
+ # :stderr => IO
54
+ #
55
+ # an IO stread, this is where Kenji logging goes by default. defaults
56
+ # to $stderr
57
+ #
58
+ def initialize(env, *rest)
59
+ raise ArgumentError unless rest.count == 2 || rest.count == 1
60
+ root, options = *rest
61
+ options, root = root, options if root.is_a?(Hash)
62
+ options ||= {}
63
+
37
64
  @headers = {
38
65
  'Content-Type' => 'application/json'
39
66
  }
40
67
  @status = 200
41
- @root = File.expand_path(root) + '/'
42
- @stderr = $stderr
43
68
  @env = env
69
+
70
+ # deal with legacy root argument behavior
71
+ options[:directory] = File.expand_path(root) if root
44
72
 
45
73
  @options = {
46
74
  auto_cors: true,
47
- root_controller: nil
75
+ catch_exceptions: true,
76
+ root_controller: nil,
77
+ directory: File.expand_path(Dir.getwd),
78
+ stderr: $stderr
48
79
  }.merge(options)
80
+
81
+ @stderr = @options[:stderr]
82
+ @root = @options[:directory] + '/'
49
83
  end
50
84
 
51
85
  # This method does all the work!
@@ -78,10 +112,8 @@ module Kenji
78
112
  while head = segments.shift
79
113
  acc = "#{acc}/#{head}"
80
114
  if controller = controller_for(acc) # if we have a valid controller
81
- begin
82
- subpath = '/'+segments.join('/')
83
- out = controller.call(method, subpath).to_json
84
- end
115
+ subpath = '/'+segments.join('/')
116
+ out = controller.call(method, subpath).to_json
85
117
  success = true
86
118
  break
87
119
  end
@@ -93,6 +125,7 @@ module Kenji
93
125
  [@status, @headers, [out]]
94
126
  end
95
127
  rescue Exception => e
128
+ raise e unless @options[:catch_exceptions]
96
129
  @stderr.puts e.inspect # log exceptions
97
130
  e.backtrace.each {|b| @stderr.puts " #{b}" }
98
131
  response_500
@@ -3,8 +3,21 @@ module Spec5
3
3
  get '/' do
4
4
  { :foo => 'bar' }
5
5
  end
6
+
6
7
  get '/path' do
7
8
  { :baz => 'bar' }
8
9
  end
10
+
11
+ get '/foo/:foo_id/:bar_id' do |foo_id|
12
+ { :baz => 'bar' }
13
+ end
14
+
15
+ get "/bar/:foo_id/:bar_id" do |foo_id, bar_id, baz_id|
16
+ { :baz => 'bar' }
17
+ end
18
+
19
+ get "/foobar/:foo_id/:bar_id" do |foo_id, bar_id|
20
+ { :foo => foo_id, :bar => bar_id }
21
+ end
9
22
  end
10
23
  end
data/spec/kenji_spec.rb CHANGED
@@ -17,7 +17,7 @@ def app_for(path, opts={})
17
17
  end
18
18
  end
19
19
 
20
- describe Kenji::Kenji, 'expected reponses' do
20
+ describe Kenji::Kenji, 'expected responses' do
21
21
  include Rack::Test::Methods
22
22
 
23
23
  context '1' do
@@ -106,6 +106,10 @@ describe Kenji::Kenji, 'expected reponses' do
106
106
  last_response.body.should == expected_response
107
107
  end
108
108
 
109
+ it 'should not match subsets of the route' do
110
+ get 'something/child/foo'
111
+ last_response.status.should == 404
112
+ end
109
113
  end
110
114
 
111
115
  context '3' do
@@ -183,6 +187,27 @@ describe Kenji::Kenji, 'expected reponses' do
183
187
  put '/main/foo/foo_id/pass/pass_id'
184
188
  last_response.status.should == 404
185
189
  end
190
+
191
+ it "should pass variables to blocks" do
192
+ get "/foobar/foo_id/bar_id"
193
+ expected_response = { :foo => "foo_id", :bar => "bar_id" }.to_json
194
+ last_response.body.should == expected_response
195
+ last_response.status.should == 200
196
+ end
197
+
198
+ it "should throw ArgumentError when there are too many arguments" do
199
+ get "foo/foo_id/bar_id"
200
+ last_response.status.should == 500
201
+ end
202
+
203
+ it "should throw ArgumentError when there are too few arguments" do
204
+ get "bar/foo_id/bar_id"
205
+ last_response.status.should == 500
206
+ end
186
207
  end
208
+
209
+ # TODO: Write unit tests for :catch_exceptions option.
210
+ # TODO: Write unit tests for Kenji::App
211
+ # TODO: Write unit tests for new root directory behavior.
187
212
 
188
213
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kenji
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.0'
4
+ version: 1.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-16 00:00:00.000000000 Z
12
+ date: 2013-08-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -117,6 +117,7 @@ files:
117
117
  - inited/tmp/always_restart.txt
118
118
  - kenji.gemspec
119
119
  - lib/kenji.rb
120
+ - lib/kenji/app.rb
120
121
  - lib/kenji/controller.rb
121
122
  - lib/kenji/string_extensions.rb
122
123
  - lib/kenji/version.rb