Class: Kenji::Controller

Inherits:
Object
  • Object
show all
Defined in:
lib/kenji/controller.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#kenjiObject

use the reader freely to grab the kenji object



6
7
8
# File 'lib/kenji/controller.rb', line 6

def kenji
  @kenji
end

Class Method Details

.all(path, &block) ⇒ Object

Route all methods for given path



44
45
46
# File 'lib/kenji/controller.rb', line 44

def self.all(path, &block)
  route(:get, :post, :put, :delete, path, &block)
end

.before(&block) ⇒ Object

This lets you define before blocks.

class MyController < Kenji::Controller
  before do
    # eg. ensure authentication, you can use kenji.respond in here.
  end
end


111
112
113
114
115
116
# File 'lib/kenji/controller.rb', line 111

def self.before(&block)
  define_method(:_tmp_before_action, &block)
  block = instance_method(:_tmp_before_action)
  remove_method(:_tmp_before_action)
  (@befores ||= []) << block
end

.delete(path, &block) ⇒ Object

Route DELETE



34
35
36
# File 'lib/kenji/controller.rb', line 34

def self.delete(path, &block)
  route(:delete, path, &block)
end

.fallback(&block) ⇒ Object



48
49
50
51
# File 'lib/kenji/controller.rb', line 48

def self.fallback(&block)
  define_method(:fallback, &block)
  nil # void method
end

.get(path, &block) ⇒ Object

Route GET



19
20
21
# File 'lib/kenji/controller.rb', line 19

def self.get(path, &block)
  route(:get, path, &block)
end

.pass(path, controller) ⇒ Object

This lets us pass the routing down to another controller for a sub-path.

class MyController < Kenji::Controller
  pass '/admin/*', AdminController
  # regular routes
end


91
92
93
94
95
96
97
98
99
100
101
# File 'lib/kenji/controller.rb', line 91

def self.pass(path, controller)
  node = (@passes ||= {})
  segments = path.split('/')
  # discard leading /'s empty segment
  segments = segments.drop(1) if segments.first == ''
  segments.each do |segment|
    node = (node[segment.to_sym] ||= {})
    break if segment == '*'
  end
  node[:@controller] = controller
end

.patch(path, &block) ⇒ Object

Route PATCH



39
40
41
# File 'lib/kenji/controller.rb', line 39

def self.patch(path, &block)
  route(:patch, path, &block)
end

.post(path, &block) ⇒ Object

Route POST



24
25
26
# File 'lib/kenji/controller.rb', line 24

def self.post(path, &block)
  route(:post, path, &block)
end

.put(path, &block) ⇒ Object

Route PUT



29
30
31
# File 'lib/kenji/controller.rb', line 29

def self.put(path, &block)
  route(:put, path, &block)
end

.route(*methods, path, &block) ⇒ Object

Route a given path to the correct block, for any given methods

Note: this works by building a tree for the path, each node being a path segment or variable segment, and the leaf @action being the block



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/kenji/controller.rb', line 58

def self.route(*methods, path, &block)

  # bind the block to self as an instance method, so its context is correct
  define_method(:_tmp_route_action, &block)
  block = instance_method(:_tmp_route_action)
  remove_method(:_tmp_route_action)

  # store the block for each method
  methods.each do |method|

    node = ((@routes ||= {})[method] ||= {})
    segments = path.split('/')
    # discard leading /'s empty segment
    segments = segments.drop(1) if segments.first == ''
    # lazily create tree
    segments.each do |segment|
      # discard :variable name
      segment = ':' if segment =~ /^:/
      node = (node[segment.to_sym] ||= {})
    end
    # store block as leaf in @action
    node[:@action] = block
  end
  nil # void method
end

Instance Method Details

#attempt_fallback(path) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
# File 'lib/kenji/controller.rb', line 171

def attempt_fallback(path)
  if respond_to? :fallback
    if self.class.instance_method(:fallback).arity == 1
      return fallback(path)
    else
      return fallback
    end
  else
    kenji.respond(404, 'Not found!')
  end
end

#call(method, path) ⇒ Object

Most likely only used by Kenji itself. Override to implement your own routing, if you’d like.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/kenji/controller.rb', line 121

def call(method, path)

  self.class.befores.each {|b| b.bind(self).call }

  segments = path.split('/')
  # discard leading /'s empty segment
  segments = segments.drop(1) if segments.first == ''

  # check for passes
  node = self.class.passes
  remaining_segments = segments.dup

  f = fetch_passes(node, remaining_segments)

  if f[:match] && f[:controller]
    instance = f[:controller].new
    instance.kenji = kenji if instance.respond_to?(:kenji=)
    f[:variables].each do |k, v|
      instance.instance_variable_set(:"@#{k}", v)
    end
    return instance.call(method, f[:remaining_segments].join('/'))
  end

  # regular routing
  node = self.class.routes[method] || {}
  variables = []
  searching = true
                          # traverse tree to find
  segments.each do |segment|
    if searching && node[segment.to_sym]
      # attempt to move down to the plain text segment
      node = node[segment.to_sym]
    elsif searching && node[:':']
      # attempt to find a variable segment
      node = node[:':']
      # either we've found a variable, or the `unless` below will trigger
      variables << segment
    else
      # route failed to match variable or segment node so attempt fallback
      return attempt_fallback(path)
    end
  end
  # the block is stored in the @action leaf
  if node && (action = node[:@action])
    return action.bind(self).call(*variables)
  else # or, fallback if necessary store the block for each method
    return attempt_fallback(path)
  end
end

#log(*args) ⇒ Object

Utility method: this can be used to log to stderr cleanly.



186
187
188
# File 'lib/kenji/controller.rb', line 186

def log(*args)
  kenji.stderr.puts(*args)
end