Module: Derailleur::Application

Included in:
Grease
Defined in:
lib/derailleur/core/application.rb,
lib/derailleur/base/application.rb

Overview

In Derailleur, an application is an object extending the Application module. It will have routes which hold handlers. By default, we use Derailleur’s components, but you can easily modify your application to use custom routes’ node, HTTP methods dispatcher, and handlers.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#default_dispatcherObject

The default HTTP method dispatcher ( Derailleur::Dispatcher ) See Derailleur::Dispatcher if you want a personal one



45
46
47
# File 'lib/derailleur/core/application.rb', line 45

def default_dispatcher
  @default_dispatcher ||= Dispatcher
end

#default_handlerObject

The default handler ( Derailleur::DefaultRackHandler ) See Derailleur::Handler to understand how to build your own



39
40
41
# File 'lib/derailleur/core/application.rb', line 39

def default_handler
  @default_handler ||= DefaultRackHandler
end

#default_internal_error_handlerObject

The default error handler ( Derailleur::InternalErrorHandler )



20
21
22
# File 'lib/derailleur/core/application.rb', line 20

def default_internal_error_handler
  @default_internal_error_handler ||= InternalErrorHandler
end

#default_root_node_typeObject

The default root node type ( Derailleur::ArrayTrie ) You could change it to ( Derailleur::HashTrie ) for better performance. The rule of thumb is: benchmark your application with both and pick the best one.



28
29
30
# File 'lib/derailleur/core/application.rb', line 28

def default_root_node_type
  @default_root_node_type ||= ArrayTrie
end

Instance Method Details

#build_route(path) ⇒ Object

Builds a route by appending nodes on the path. The implementation of nodes should create missing nodes on the path. See ArrayTrieNode#<< or HashTrieNode#<< Returns the last node for this path



70
71
72
73
74
75
76
# File 'lib/derailleur/core/application.rb', line 70

def build_route(path)
  current_node = routes
  chunk_path(path).each do |chunk|
    current_node = current_node << chunk
  end
  current_node
end

#call(env) ⇒ Object

Method implemented to comply to the Rack specification. see rack.rubyforge.org/doc/files/SPEC.html to understand what to return.

If everything goes right, an instance of default_handler will serve the request.

The routing handler will be created with three params

  • the application handler contained in the dispatcher

  • the Rack env

  • a context hash with three keys:

    • ‘derailleur’ at self, i.e., a reference to the application

    • ‘derailleur.params’ with the parameters/spalt in the path

    • ‘derailleur.node’ the node responsible for handling this path

If there is any exception during this, it will be catched and the default_internal_error_handler will be called.



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/derailleur/core/application.rb', line 207

def call(env)
  begin
    path = env['PATH_INFO'].sub(/\.\w+$/,'') #ignores the extension if any
    ctx = {}
    route, params = get_route_with_params(path)
    ctx['derailleur.node'] = route
    ctx['derailleur.params'] = params
    ctx['derailleur'] = self
    dispatcher = route.content
    raise NoSuchRoute, "no dispatcher for #{path}" if dispatcher.nil?
    handler = dispatcher.send(env['REQUEST_METHOD'])
    raise NoSuchRoute, "no handler for valid path: #{path}" if handler.nil?
    default_handler.new(handler, env, ctx).to_rack_output
  rescue Exception => err
    default_internal_error_handler.new(err, env, ctx).to_rack_output
  end
end

#chunk_path(path) ⇒ Object

Chunks a path, splitting around ‘/’ separators there always is an empty name ‘foo/bar’ => [”, ‘foo’, ‘bar’]



62
63
64
# File 'lib/derailleur/core/application.rb', line 62

def chunk_path(path)
  normalize(path).split('/')
end

#default_node_typeObject

The default node type (is the node_type of the default_root_node_type )



33
34
35
# File 'lib/derailleur/core/application.rb', line 33

def default_node_type
  default_root_node_type.node_type
end

#delete(path, content = nil, params = {}, &blk) ⇒ Object

registers a handler for the DELETE HTTP method see get for the use of parameters



55
56
57
# File 'lib/derailleur/base/application.rb', line 55

def delete(path, content=nil, params={}, &blk)
  register_route(path, :DELETE, content, params, &blk)
end

#get(path, content = nil, params = {}, &blk) ⇒ Object

registers a handler for the GET HTTP method the handler is either content OR the passed block if content is true and there is a block, then the handler will be the content params is a hash of parameters, the only parameter supported is:

  • :overwrite , if true, then you can rewrite a handler



11
12
13
# File 'lib/derailleur/base/application.rb', line 11

def get(path, content=nil, params={}, &blk)
  register_route(path, :GET, content, params, &blk)
end

#get_route(path) ⇒ Object

Same as get_route_silent but raise a NoSuchRoute error if there is no matching route.

Raises:



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/derailleur/core/application.rb', line 96

def get_route(path)
  node = if block_given?
           get_route_silent(path) do |node,chunk|
             yield node, chunk 
           end
         else
           get_route_silent(path) 
         end
  raise NoSuchRoute, "no such path #{path}" unless node
  node
end

#get_route_silent(path) ⇒ Object

Return the node corresponding to a given path.

Will (optionally) consecutively yield all the [node, chunk_name] this is useful when you want to interpret the members of the path as a parameter.



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/derailleur/core/application.rb', line 82

def get_route_silent(path)
  current_node = routes
  chunk_path(path).each do |chunk|
    unless current_node.absorbent?
      current_node = current_node.child_for_name(chunk)
      return nil unless current_node
    end
    yield current_node, chunk if block_given?
  end
  current_node
end

#get_route_with_params(path) ⇒ Object

Similar to get route, but also interprets nodes names as keys for a hash. The values in the parameters hash are the string corresponding to the nodes in the path. A specific key is :splat, which correspond to the remaining chunks in the paths. Does NOT take care of key collisions. This should be taken care of at the application level.



178
179
180
181
182
183
184
185
186
187
188
# File 'lib/derailleur/core/application.rb', line 178

def get_route_with_params(path)
  params = {:splat => []}
  route = get_route(path) do |node, val|
    if node.wildcard?
      params[node.name] = val 
    elsif node.absorbent?
      params[:splat] << val 
    end
  end
  [route, params]
end

#head(path, content = nil, params = {}, &blk) ⇒ Object

registers a handler for the HEAD HTTP method see get for the use of parameters



22
23
24
# File 'lib/derailleur/base/application.rb', line 22

def head(path, content=nil, params={}, &blk)
  register_route(path, :HEAD, content, params, &blk)
end

#normalize(path) ⇒ Object

Normalize a path by making sure it starts with ‘/’



55
56
57
# File 'lib/derailleur/core/application.rb', line 55

def normalize(path)
  File.join('/', path)
end

#post(path, content = nil, params = {}, &blk) ⇒ Object

registers a handler for the POST HTTP method see get for the use of parameters



33
34
35
# File 'lib/derailleur/base/application.rb', line 33

def post(path, content=nil, params={}, &blk)
  register_route(path, :POST, content, params, &blk)
end

#put(path, content = nil, params = {}, &blk) ⇒ Object

registers a handler for the PUT HTTP method see get for the use of parameters



44
45
46
# File 'lib/derailleur/base/application.rb', line 44

def put(path, content=nil, params={}, &blk)
  register_route(path, :PUT, content, params, &blk)
end

#register_route(path, method = :default, handler = nil, params = {}, &blk) ⇒ Object

Registers an handler for a given path. The path will be interpreted as an absolute path prefixed by ‘/’ .

Usually you will not use this method but a method from the base/application.rb code (with the name of the HTTP method: e.g. get post put)

The method argument is the HTTP method name as a symbol (e.g. :GET) (handler || blk) is the handler set. i.e., if there’s both a handler and a block, the block will be ignored.

Params is hash of parameters, currently, the only key looked at is :overwrite to overwrite an handler for an existing path/method pair.

Internally, the path will be created node by node when nodes for this path are missing. A default_dispatcher will be here to map the various HTTP methods for the same path to their respective handlers.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/derailleur/core/application.rb', line 127

def register_route(path, method=:default, handler=nil, params={}, &blk)
  if path == '*'
    raise ArgumentError.new("Cannot register on #{path} because of ambiguity, in Derailleur, '*' translated'/*' would not catch the path '/' like '/foo/*' doesn't catch '/foo'")
  end
  handler = handler || blk
  node = build_route(path)
  node.content ||= default_dispatcher.new
  if (params[:overwrite]) or (not node.content.has_handler?(method))
    node.content.set_handler(method, handler)
  else
    raise RouteObjectAlreadyPresent, "could not overwrite #{method} handler at path #{path}"
  end
  node
end

#routesObject

An object representing the routes. Usually, it is the root of a Trie



50
51
52
# File 'lib/derailleur/core/application.rb', line 50

def routes
  @routes ||= default_root_node_type.new
end

#split_to!(path, app) ⇒ Object

Split a whole branch of the application at the given path, and graft the branch to the app in second parameter. This method does NOT prevents you from cancelling handlers in the second app if any. Because it does not check for handlers in the receiving branch. Use with care. See ArrayNode#graft!



162
163
164
165
166
167
168
169
# File 'lib/derailleur/core/application.rb', line 162

def split_to!(path, app)
  app_node = app.build_route(path)

  split_node = get_route(normalize(path))
  split_node.prune!

  app_node.graft!(split_node)
end

#undelete(path) ⇒ Object

removes a handler for the DELETE HTTP method



60
61
62
# File 'lib/derailleur/base/application.rb', line 60

def undelete(path)
  unregister_route(path, :DELETE)
end

#unget(path) ⇒ Object

removes a handler for the GET HTTP method



16
17
18
# File 'lib/derailleur/base/application.rb', line 16

def unget(path)
  unregister_route(path, :GET)
end

#unhead(path) ⇒ Object

removes a handler for the HEAD HTTP method



27
28
29
# File 'lib/derailleur/base/application.rb', line 27

def unhead(path)
  unregister_route(path, :HEAD)
end

#unpost(path) ⇒ Object

removes a handler for the POST HTTP method



38
39
40
# File 'lib/derailleur/base/application.rb', line 38

def unpost(path)
  unregister_route(path, :POST)
end

#unput(path) ⇒ Object

removes a handler for the PUT HTTP method



49
50
51
# File 'lib/derailleur/base/application.rb', line 49

def unput(path)
  unregister_route(path, :PUT)
end

#unregister_route(path, method = :default) ⇒ Object

Removes an handler for a path/method pair. The path will be interpreted as an absolute path prefixed with ‘/’



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/derailleur/core/application.rb', line 144

def unregister_route(path, method=:default)
  node = get_route(normalize(path))
  if node.children.empty?
    if node.content.no_handler?
      node.prune! 
    else
      node.content.set_handler(method, nil)
    end
  else
    node.hand_off_to! default_node_type.new(node.name)
  end
end