Class: Gin::Controller

Inherits:
Object
  • Object
show all
Extended by:
Mountable, GinClass
Includes:
Constants, Errorable, Filterable
Defined in:
lib/gin/controller.rb

Overview

Gin controllers follow only a few rules:

* ALL instance methods are actions (put your helper methods in a module or
  parent class not mounted to the app).
* Filters are defined by blocks passed to special class methods.
* Controller-level error handlers are defined by blocks passed to the 'error'
  class method.

Gin controller actions are any instance method defined on the controller
class. The HTTP response body takes the value of the method's return value.

If the instance method takes arguments, matching params will be assigned to
them. A required argument with a missing param triggers a Gin::BadRequest
error, resulting in a 400 response.

  class UserController < Gin::Controller
    # Get params id and email
    def show id, email=nil
      ...
    end
  end

Gin actions also support Ruby 2.0 keyed arguments, which are more flexible
for assigning default values when dealing with missing params.

  class UserController < Gin::Controller
    def show(id, email: nil, full: false)
      ...
    end
  end

Views are rendered by calling the 'view' method from a controller instance.
Views don't halt the action but return a String of the rendered view.
If a layout was set, the rendered view will include the layout.

  class UserController < Gin::Controller
    layout :user

    def show id
      # Renders <app.views_dir>/show_user.* with the layout <app.layouts_dir>/user.*
      view :show_user
    end

    def tos
      # Renders <app.views_dir>/tos.* with no layout
      view :tos, :layout => false
    end
  end

Constant Summary collapse

DEFAULT_ACTION_MAP =
{
  :index   => %w{GET /},
  :show    => %w{GET /:id},
  :new     => %w{GET /new},
  :create  => %w{POST /},
  :edit    => %w{GET /:id/edit},
  :update  => %w{PUT /:id},
  :destroy => %w{DELETE /:id}
}

Constants included from Constants

Gin::Constants::ASYNC_CALLBACK, Gin::Constants::CACHE_CTRL, Gin::Constants::CNT_DISPOSITION, Gin::Constants::CNT_LENGTH, Gin::Constants::CNT_TYPE, Gin::Constants::ENV_DEV, Gin::Constants::ENV_PROD, Gin::Constants::ENV_STAGE, Gin::Constants::ENV_TEST, Gin::Constants::EPOCH, Gin::Constants::ETAG, Gin::Constants::EXPIRES, Gin::Constants::FWD_FOR, Gin::Constants::FWD_HOST, Gin::Constants::GIN_APP, Gin::Constants::GIN_CTRL, Gin::Constants::GIN_ERRORS, Gin::Constants::GIN_PATH_PARAMS, Gin::Constants::GIN_RELOADED, Gin::Constants::GIN_ROUTE, Gin::Constants::GIN_STACK, Gin::Constants::GIN_STATIC, Gin::Constants::GIN_TARGET, Gin::Constants::GIN_TEMPLATES, Gin::Constants::GIN_TIMESTAMP, Gin::Constants::HOST_NAME, Gin::Constants::HTTP_VERSION, Gin::Constants::IF_MATCH, Gin::Constants::IF_MOD_SINCE, Gin::Constants::IF_NONE_MATCH, Gin::Constants::IF_UNMOD_SINCE, Gin::Constants::LAST_MOD, Gin::Constants::LOCATION, Gin::Constants::PATH_INFO, Gin::Constants::PRAGMA, Gin::Constants::QUERY_STRING, Gin::Constants::RACK_INPUT, Gin::Constants::REMOTE_ADDR, Gin::Constants::REMOTE_USER, Gin::Constants::REQ_METHOD, Gin::Constants::SERVER_NAME, Gin::Constants::SERVER_PORT, Gin::Constants::SESSION_SECRET

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Mountable

actions, controller_name, default_route_for, display_name, route_name_for, verify_mount!

Methods included from Errorable

#handle_error, #handle_status, included

Methods included from Filterable

#filter, included

Constructor Details

#initialize(app, env) ⇒ Controller

Returns a new instance of Controller.


243
244
245
246
247
248
249
250
# File 'lib/gin/controller.rb', line 243

def initialize app, env
  @app      = app
  @action   = nil
  @env      = env
  @request  = Gin::Request.new env
  @response = Gin::Response.new
  @request.autocast_params = self.class.autocast_params
end

Instance Attribute Details

#actionObject (readonly)

The action that the HTTP request resolved to, based on the App's router.


237
238
239
# File 'lib/gin/controller.rb', line 237

def action
  @action
end

#appObject (readonly)

The Gin::App instance used by the controller. The App instance is meant for read-only use. Writes are not thread-safe and at your own risk.


228
229
230
# File 'lib/gin/controller.rb', line 228

def app
  @app
end

#envObject (readonly)

The Rack env hash.


240
241
242
# File 'lib/gin/controller.rb', line 240

def env
  @env
end

#requestObject (readonly)

Gin::Request instance representing the HTTP request.


231
232
233
# File 'lib/gin/controller.rb', line 231

def request
  @request
end

#responseObject (readonly)

Gin::Response instance representing the HTTP response.


234
235
236
# File 'lib/gin/controller.rb', line 234

def response
  @response
end

Class Method Details

.actionsObject

Array of action names for this controller.


89
90
91
# File 'lib/gin/controller.rb', line 89

def self.actions
  instance_methods(false).map{|a| a.to_sym }
end

.autocast_params(arg = nil) ⇒ Object

Define if params should be cast to autodetected types. This is an inherited attribute.

  • Passing a boolean turns auto-casting on or off for all params.

  • Passing a hash with :only or :except limits auto-casting to the given param names

By default all params are cast to their autodetected types.

autocast_params true  # enabled for all params
autocast_params false # disabled for all params
autocast_params except: [:zip, :phone, :fax]
autocast_params only: [:timestamp, :age]

Params get cast as follows:

* TrueClass     true
* FalseClass:   false
* Fixnum:       1234, -1234
* Float:        1.123, -1.123
* String:       Everything else, including numbers that start with a 0

203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/gin/controller.rb', line 203

def self.autocast_params arg=nil
  if Hash === arg && Hash === @autocast_params
    arg.each do |k, v|
      if @autocast_params[k]
        @autocast_params[k] |= [*v]
      else
        @autocast_params[k] = [*v]
      end
    end

  elsif arg == true || arg == false
    @autocast_params = arg

  elsif Hash === arg
    @autocast_params = arg.dup
  end

  return @autocast_params
end

.call(env) ⇒ Object

Call the Controller with an Rack env hash. Requires the hash to have the keys 'gin.target' with the action name as the second item of the array, and 'gin.app'.


163
164
165
166
167
# File 'lib/gin/controller.rb', line 163

def self.call env
  inst = new(env[GIN_APP], env)
  env[GIN_CTRL] = inst
  inst.call_action(env[GIN_TARGET][1])
end

.content_type(new_type = nil) ⇒ Object

Set or get the default content type for this Gin::Controller. Default value is “text/html”. This attribute is inherited.


139
140
141
142
143
144
# File 'lib/gin/controller.rb', line 139

def self.content_type new_type=nil
  @content_type = new_type if new_type
  return @content_type if defined?(@content_type) && @content_type
  self.superclass.respond_to?(:content_type) &&
    self.superclass.content_type || "text/html"
end

.controller_name(new_name = nil) ⇒ Object

String representing the controller name. Underscores the class name and removes mentions of 'controller'.

MyApp::FooController.controller_name
#=> "my_app/foo"

Note that when route names get autogenerated, the namespacing is dropped. If two routes have the same name, the last one defined wins.


103
104
105
106
# File 'lib/gin/controller.rb', line 103

def self.controller_name new_name=nil
  @ctrl_name = new_name if new_name
  @ctrl_name
end

.default_route_for(action) ⇒ Object

:nodoc:


120
121
122
# File 'lib/gin/controller.rb', line 120

def self.default_route_for action #:nodoc:
  DEFAULT_ACTION_MAP[action] || ['GET', "/#{action}"]
end

.display_name(action = nil) ⇒ Object

:nodoc:


130
131
132
# File 'lib/gin/controller.rb', line 130

def self.display_name action=nil #:nodoc:
  [self, action].compact.join("#")
end

.exec(app, env, &block) ⇒ Object

Execute arbitrary code in the context of a Gin::Controller instance. Returns a Rack response Array.


151
152
153
154
155
# File 'lib/gin/controller.rb', line 151

def self.exec app, env, &block
  inst = new(app, env)
  inst.invoke{ inst.instance_exec(&block) }
  inst.response.finish
end

.inherited(subclass) ⇒ Object


70
71
72
73
74
# File 'lib/gin/controller.rb', line 70

def self.inherited subclass
  subclass.setup
  subclass.autocast_params self.autocast_params
  super
end

.layout(name = nil) ⇒ Object

Get or set a layout for a given controller. Value can be a symbol or filepath. Layout file is expected to be in the Gin::App.layout_dir directory Defaults to the parent class layout, or Gin::App.layout.


176
177
178
179
180
# File 'lib/gin/controller.rb', line 176

def self.layout name=nil
  @layout = name if name
  return @layout if @layout
  return self.superclass.layout if self.superclass.respond_to?(:layout)
end

.route_name_for(action) ⇒ Object

:nodoc:


125
126
127
# File 'lib/gin/controller.rb', line 125

def self.route_name_for action #:nodoc:
  "#{action}_#{controller_name.sub(%r{^.*/},'')}".to_sym
end

.setupObject

:nodoc:


77
78
79
80
81
# File 'lib/gin/controller.rb', line 77

def self.setup   # :nodoc:
  @layout = nil
  @autocast_params = true
  @ctrl_name = Gin.underscore(self.to_s).gsub(/_?controller_?/,'')
end

Instance Method Details

#asset(path) ⇒ Object

Check if an asset exists. Returns the full system path to the asset if found, otherwise nil.


820
821
822
# File 'lib/gin/controller.rb', line 820

def asset path
  @app.asset path
end

#asset_path(name) ⇒ Object

Returns the HTTP path to the local asset.


800
801
802
803
804
805
806
807
808
809
810
811
812
813
# File 'lib/gin/controller.rb', line 800

def asset_path name
  fdpath = @app.asset(name)

  if fdpath && fdpath.start_with?(@app.assets_dir)
    if fdpath.start_with?(@app.public_dir)
      fdpath[@app.public_dir.length..-1]
    else
      fdpath[@app.assets_dir.length..-1]
    end
  else
    path = File.join('', name)
    [path, *@app.asset_version(name)].compact.join("?")
  end
end

#asset_url(name) ⇒ Object

Returns the url to an asset, including predefined asset cdn hosts if set.


790
791
792
793
794
# File 'lib/gin/controller.rb', line 790

def asset_url name
  host = @app.asset_host_for(name)
  return asset_path(name) if !host
  File.join(host, name)
end

#body(body = nil) ⇒ Object

Get or set the HTTP response body.


277
278
279
280
# File 'lib/gin/controller.rb', line 277

def body body=nil
  @response.body = body if body
  @response.body
end

#cache_control(*values) ⇒ Object

Specify response freshness policy for HTTP caches (Cache-Control header). Any number of non-value directives (:public, :private, :no_cache, :no_store, :must_revalidate, :proxy_revalidate) may be passed along with a Hash of value directives (:max_age, :min_stale, :s_max_age).

cache_control :public, :must_revalidate, :max_age => 60
#=> Cache-Control: public, must-revalidate, max-age=60

730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
# File 'lib/gin/controller.rb', line 730

def cache_control *values
  if Hash === values.last
    hash = values.pop
    hash.reject!{|k,v| v == false || v == true && values << k }
  else
    hash = {}
  end

  values.map! { |value| value.to_s.tr('_','-') }
  hash.each do |key, value|
    key = key.to_s.tr('_', '-')
    value = value.to_i if key == "max-age"
    values << [key, value].join('=')
  end

  @response[CACHE_CTRL] = values.join(', ') if values.any?
end

#call_action(action = nil) ⇒ Object

Calls the given or preset action and returns a Rack response Array.


256
257
258
259
260
261
262
# File 'lib/gin/controller.rb', line 256

def call_action action=nil
  action ||= @action
  invoke{ dispatch action }
  invoke{ handle_status(@response.status) }
  content_type self.class.content_type unless @response[CNT_TYPE]
  @response.finish
end

#configObject

Accessor for @app.config.


286
287
288
# File 'lib/gin/controller.rb', line 286

def config
  @app.config
end

#content_type(type = nil, params = {}) ⇒ Object

Get or set the HTTP response Content-Type header.

content_type :json
content_type 'application/json;charset=us-ascii'
content_type :json, charset: 'us-ascii'

305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/gin/controller.rb', line 305

def content_type type=nil, params={}
  return @response[CNT_TYPE] unless type

  default   = params.delete(:default)
  mime_type = mime_type(type) || default
  raise "Unknown media type: %p" % type if mime_type.nil?

  mime_type = mime_type.dup
  unless params.include? :charset
    params[:charset] = params.delete('charset') || 'UTF-8'
  end

  params.delete :charset if mime_type.include? 'charset'
  unless params.empty?
    mime_type << (mime_type.include?(';') ? ', ' : ';')
    mime_type << params.map do |key, val|
      val = val.inspect if val =~ /[";,]/
      "#{key}=#{val}"
    end.join(', ')
  end

  @response[CNT_TYPE] = mime_type
end

#cookiesObject

Access the request cookies.


445
446
447
# File 'lib/gin/controller.rb', line 445

def cookies
  @request.cookies
end

Delete the response cookie with the given name. Does not affect request cookies.


471
472
473
# File 'lib/gin/controller.rb', line 471

def delete_cookie name
  @response.delete_cookie name
end

#dispatch(action) ⇒ Object

Dispatch the call to the action, calling before and after filers, and including error handling.


945
946
947
948
949
950
951
952
953
954
955
956
957
958
# File 'lib/gin/controller.rb', line 945

def dispatch action
  @action = action

  invoke do
    filter(*before_filters_for(action))
    args = action_arguments action
    __send__(action, *args)
  end

rescue => err
  invoke{ handle_error err }
ensure
  filter(*after_filters_for(action))
end

#error(code, body = nil) ⇒ Object

Halt processing and return the error status provided.


351
352
353
354
355
# File 'lib/gin/controller.rb', line 351

def error code, body=nil
  code, body     = 500, code if code.respond_to? :to_str
  @response.body = body unless body.nil?
  halt code
end

#etag(value, opts = {}) ⇒ Object

Set the ETag header. If the ETag was set in a previous request and matches the current one, halts the action and returns a 304 on GET and HEAD requests.


363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/gin/controller.rb', line 363

def etag value, opts={}
  opts         = {:kind => opts} unless Hash === opts
  kind         = opts[:kind] || :strong
  new_resource = opts.fetch(:new_resource) { @request.post? }

  unless [:strong, :weak].include?(kind)
    raise ArgumentError, ":strong or :weak expected"
  end

  value = '"%s"' % value
  value = 'W/' + value if kind == :weak
  @response[ETAG] = value

  if (200..299).include?(status) || status == 304
    if etag_matches? @env[IF_NONE_MATCH], new_resource
      halt(@request.safe? ? 304 : 412)
    end

    if @env[IF_MATCH]
      halt 412 unless etag_matches? @env[IF_MATCH], new_resource
    end
  end
end

#etag_matches?(list, new_resource = @request.post?) ⇒ Boolean

:nodoc:

Returns:

  • (Boolean)

388
389
390
391
# File 'lib/gin/controller.rb', line 388

def etag_matches? list, new_resource=@request.post? #:nodoc:
  return !new_resource if list == '*'
  list.to_s.split(/\s*,\s*/).include? response[ETAG]
end

#expire_cache_controlObject

Sets Cache-Control, Expires, and Pragma headers to tell the browser not to cache the response.


781
782
783
784
# File 'lib/gin/controller.rb', line 781

def expire_cache_control
  @response[PRAGMA] = 'no-cache'
  expires EPOCH, :no_cache, :no_store, :must_revalidate, :max_age => 0
end

#expires(amount, *values) ⇒ Object

Set the Expires header and Cache-Control/max-age directive. Amount can be an integer number of seconds in the future or a Time object indicating when the response should be considered “stale”. The remaining “values” arguments are passed to the #cache_control helper:

expires 500, :public, :must_revalidate
=> Cache-Control: public, must-revalidate, max-age=60
=> Expires: Mon, 08 Jun 2009 08:50:17 GMT

759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
# File 'lib/gin/controller.rb', line 759

def expires amount, *values
  values << {} unless Hash === values.last

  if Integer === amount
    time    = Time.now + amount.to_i
    max_age = amount
  else
    time    = String === amount ? Time.parse(amount) : amount
    max_age = time - Time.now
  end

  values.last.merge!(:max_age => max_age) unless values.last[:max_age]
  cache_control(*values)

  @response[EXPIRES] = time.httpdate
end

#h(obj) ⇒ Object

HTML-escape the given String.


998
999
1000
# File 'lib/gin/controller.rb', line 998

def h obj
  CGI.escapeHTML obj.to_s
end

#halt(*resp) ⇒ Object

Stop the execution of an action and return the response. May be given a status code, string, header Hash, or a combination:

halt 400, "Badly formed request"
halt "Done early! WOOO!"
halt 302, {'Location' => 'http://example.com'}, "You are being redirected"

337
338
339
340
341
342
343
344
345
# File 'lib/gin/controller.rb', line 337

def halt *resp
  if @app.development?
    line = caller.find{|l| !l.start_with?(Gin::LIB_DIR) && !l.include?("/ruby/gems/")}
    logger << "[HALT] #{line}\n" if line
  end

  resp = resp.first if resp.length == 1
  throw :halt, resp
end

#headers(hash = nil) ⇒ Object

Set multiple response headers with Hash.


397
398
399
400
# File 'lib/gin/controller.rb', line 397

def headers hash=nil
  @response.headers.merge! hash if hash
  @response.headers
end

#html_error_page(err, code = nil) ⇒ Object

In development mode, returns an HTML page displaying the full error and backtrace, otherwise shows a generic error page.

Production error pages are first looked for in the public directory as <status>.html or 500.html. If none is found, falls back on Gin's internal error html pages.


969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
# File 'lib/gin/controller.rb', line 969

def html_error_page err, code=nil
  if @app.development?
    backtrace = err.backtrace || ['No backtrace :(']
    fulltrace = backtrace.join("\n")
    fulltrace = "<pre>#{h(fulltrace)}</pre>"

    apptrace  = Gin.app_trace(backtrace).join("\n")
    apptrace  = "<pre>#{h(apptrace)}</pre>" unless apptrace.empty?

    DEV_ERROR_HTML %
      [h(err.class), h(err.class), h(err.message), apptrace, fulltrace]

  else
    code ||= status
    filepath = asset("#{code}.html") || asset("500.html")

    unless filepath
      filepath = File.join(Gin::PUBLIC_DIR, "#{code}.html")
      filepath = File.join(Gin::PUBLIC_DIR, "500.html") if !File.file?(filepath)
    end

    File.open(filepath, "rb")
  end
end

#invokeObject

Taken from Sinatra.

Run the block with 'throw :halt' support and apply result to the response.


926
927
928
929
930
931
932
933
934
935
936
937
938
# File 'lib/gin/controller.rb', line 926

def invoke
  res = catch(:halt) { yield }
  res = [res] if Fixnum === res || String === res
  if Array === res && Fixnum === res.first
    res = res.dup
    status(res.shift)
    body(res.pop)
    headers(*res)
  elsif res.respond_to? :each
    body res
  end
  nil # avoid double setting the same response tuple twice
end

#last_modified(time) ⇒ Object

Set the last modified time of the resource (HTTP 'Last-Modified' header) and halt if conditional GET matches. The time argument is a Time, DateTime, or other object that responds to to_time or httpdate.


688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
# File 'lib/gin/controller.rb', line 688

def last_modified time
  return unless time

  time = if Integer === time
           Time.at(time)
         elsif time.respond_to?(:to_time)
           time.to_time
         elsif !time.is_a?(Time)
           Time.parse time.to_s
         else
           time
         end

  @response[LAST_MOD] = time.httpdate
  return if @env[IF_NONE_MATCH]

  if status == 200 && @env[IF_MOD_SINCE]
    # compare based on seconds since epoch
    since = Time.httpdate(@env[IF_MOD_SINCE]).to_i
    halt 304 if since >= time.to_i
  end

  if @env[IF_UNMOD_SINCE] &&
    ((200..299).include?(status) || status == 412)

    # compare based on seconds since epoch
    since = Time.httpdate(@env[IF_UNMOD_SINCE]).to_i
    halt 412 if since < time.to_i
  end
rescue ArgumentError
end

#layoutObject

Value of the layout to use for rendering. See also Gin::Controller.layout and Gin::App.layout.


829
830
831
# File 'lib/gin/controller.rb', line 829

def layout
  self.class.layout || @app.layout
end

#loggerObject

Accessor for main application logger.


421
422
423
# File 'lib/gin/controller.rb', line 421

def logger
  @app.logger
end

#mime_type(type) ⇒ Object

Get the normalized mime-type matching the given input.


294
295
296
# File 'lib/gin/controller.rb', line 294

def mime_type type
  @app.mime_type type
end

#paramsObject

Get the request params.


429
430
431
# File 'lib/gin/controller.rb', line 429

def params
  @request.params
end

#path_to(*args) ⇒ Object

Build a path to the given controller and action or route name, with any expected params. If no controller is specified and the current controller responds to the symbol given, uses the current controller for path lookup.

path_to FooController, :show, :id => 123
#=> "/foo/123"

# From FooController
path_to :show, :id => 123
#=> "/foo/123"

# Default named route
path_to :show_foo, :id => 123
#=> "/foo/123"

493
494
495
496
497
498
# File 'lib/gin/controller.rb', line 493

def path_to *args
  return "#{args[0]}#{"?" << Gin.build_query(args[1]) if args[1]}" if String === args[0]
  args.unshift(self.class) if Symbol === args[0] &&
                              self.class.actions.include?(args[0])
  @app.router.path_to(*args)
end

#redirect(uri, *args) ⇒ Object

Send a 301, 302, or 303 redirect and halt. Supports passing a full URI, partial path.

redirect "http://google.com"
redirect "/foo"
redirect "/foo", 301, "You are being redirected..."
redirect to(MyController, :action, :id => 123)
redirect to(:show_foo, :id => 123)

551
552
553
554
555
556
557
558
559
560
# File 'lib/gin/controller.rb', line 551

def redirect uri, *args
  if @env[HTTP_VERSION] == 'HTTP/1.1' && @env[REQ_METHOD] != 'GET'
    status 303
  else
    status 302
  end

  @response[LOCATION] = url_to(uri.to_s)
  halt(*args)
end

#reroute(*args) ⇒ Object

Unlike Gin::Controller#rewrite, the reroute method forwards the current request and params to the provided controller and/or action, or named route. Halts further execution in the current action. Raises RouterError if a given named route isn't found in the app's routes.

reroute MyController, :action
#=> Executes MyController#action

reroute MyController, :show, :id => 123
#=> Executes MyController#action with the given params merged to
#=> the current params.

reroute :show_foo
#=> Executes the current controller's :show_foo action, or if missing
#=> the controller and action for the :show_foo named route.

# Reroute with the given headers.
reroute :show_foo, {}, 'HTTP_X_CUSTOM_HEADER' => 'foo'

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
# File 'lib/gin/controller.rb', line 628

def reroute *args
  args.unshift(self.class) if Symbol === args[0] &&
                              self.class.actions.include?(args[0])
  nheaders = args.pop if Hash === args.last && Hash === args[-2] && args[-2] != args[-1]
  nparams  = args.pop if Hash === args.last

  if Class === args[0]
    ctrl_klass, naction = args[0..1]
  else
    route = @app.router.route_to(*args)
    ctrl_klass, naction = route.target
  end

  nenv = @env.merge(nheaders || {})
  nenv[GIN_PATH_PARAMS] = {}
  ctrl = ctrl_klass.new(@app, nenv)

  ctrl.params.merge!(params)
  ctrl.params.merge!(nparams) if nparams

  halt(*ctrl.call_action(naction))
end

#rewrite(*args) ⇒ Object

Halt execution of the current controller action and create a new request to another action and/or controller, or path.

Raises Gin::RouterError if the controller and action don't have a route. Returns a 404 response if an unrecognized path is given.

The rewrite method acts just as if a request had been sent from the client, and will re-run any of the in-app middleware. If the app is itself running as middleware, you may use rewrite to pass a request down to the next item in the stack by specifying a path not supported by the app.

Supports the same arguments at the Gin::Controller#url_to method.

rewrite MyController, :action
#=> Calls app with route for MyController#action

rewrite MyController, :show, :id => 123
#=> Calls app with route for MyController#action with the given params

rewrite :show_foo
#=> Calls app with route for the current controller's :show_foo action,
#=> or if missing the controller and action for the :show_foo named route.

# Rewrite and execute the request with the given headers.
rewrite :show_foo, 'HTTP_X_CUSTOM_HEADER' => 'foo'
rewrite :show_foo, params, 'HTTP_X_CUSTOM_HEADER' => 'foo'

# Rewrite to an arbitrary path.
rewrite '/path/to/something/else', {}, 'REQUEST_METHOD' => 'POST'

Note that params are not forwarded with the rewrite call. The app considers this to be a completely different request, which means all params required must be passed explicitely.

Streamed and IO request content is also ignored unless it is explicitely assigned to the 'rack.input' (as a part of the headers argument).


601
602
603
604
605
# File 'lib/gin/controller.rb', line 601

def rewrite *args
  args.unshift(self.class) if Symbol === args[0] &&
                              self.class.actions.include?(args[0])
  halt(*@app.rewrite!(@env, *args))
end

#send_file(path, opts = {}) ⇒ Object

Assigns a file to the response body and halts the execution of the action. Produces a 404 response if no file is found.


656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
# File 'lib/gin/controller.rb', line 656

def send_file path, opts={}
  if opts[:type] || !@response[CNT_TYPE]
    content_type opts[:type] || File.extname(path),
                  :default => 'application/octet-stream'
  end

  disposition = opts[:disposition]
  filename    = opts[:filename]
  disposition = 'attachment'        if disposition.nil? && filename
  filename    = File.basename(path) if filename.nil?

  if disposition
    @response[CNT_DISPOSITION] =
      "%s; filename=\"%s\"" % [disposition, filename]
  end

  last_modified opts[:last_modified] || File.mtime(path).httpdate
  halt 200 if @request.head?

  @response[CNT_LENGTH] = File.size?(path).to_s
  halt 200, File.open(path, "rb")

rescue Errno::ENOENT
  halt 404
end

#sessionObject

Access the request session.


437
438
439
# File 'lib/gin/controller.rb', line 437

def session
  @request.session
end

Set a cookie on the Rack response.

set_cookie "mycookie", "FOO", :expires => 600, :path => "/"
set_cookie "mycookie", :expires => 600

456
457
458
459
460
461
462
463
464
# File 'lib/gin/controller.rb', line 456

def set_cookie name, value=nil, opts={}
  if Hash === value
    opts = value
  else
    opts[:value] = value
  end

  @response.set_cookie name, opts
end

#status(code = nil) ⇒ Object

Set or get the HTTP response status code.


268
269
270
271
# File 'lib/gin/controller.rb', line 268

def status code=nil
  @response.status = code if code
  @response.status
end

#stream(keep_open = false, &block) ⇒ Object

Assigns a Gin::Stream to the response body, which is yielded to the block. The block execution is delayed until the action returns.

stream do |io|
  file = File.open "somefile", "rb"
  io << file.read(1024) until file.eof?
  file.close
end

412
413
414
415
# File 'lib/gin/controller.rb', line 412

def stream keep_open=false, &block
  scheduler = env[ASYNC_CALLBACK] ? EventMachine : Gin::Stream
  body Gin::Stream.new(scheduler, keep_open){ |out| yield(out) }
end

#template_path(template, is_layout = false) ⇒ Object

Returns the path to where the template is expected to be.

template_path :foo
#=> "<views_dir>/foo"

template_path "sub/foo"
#=> "<views_dir>/sub/foo"

template_path "sub/foo", :layout
#=> "<layouts_dir>/sub/foo"

template_path "/other/foo"
#=> "<root_dir>/other/foo"

848
849
850
851
852
853
854
855
856
857
858
859
860
# File 'lib/gin/controller.rb', line 848

def template_path template, is_layout=false
  dir = if template.to_s[0] == ?/
          @app.root_dir
        elsif is_layout
          @app.layouts_dir
        else
          @app.views_dir
        end

  path = File.join(dir, template.to_s)
  path.gsub!('*', controller_name)
  File.expand_path(path)
end

#url_to(*args) ⇒ Object Also known as: to


520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
# File 'lib/gin/controller.rb', line 520

def url_to *args
  path = path_to(*args)

  return path if path =~ /\A[A-z][A-z0-9\+\.\-]*:/

  uri  = [host = ""]
  host << "http#{'s' if @request.ssl?}://"

  if @request.forwarded? || @request.port != (@request.ssl? ? 443 : 80)
    host << @request.host_with_port
  else
    host << @request.host
  end

  uri << @request.script_name.to_s
  uri << path
  File.join uri
end

#view(template, opts = {}, &block) ⇒ Object

Render a template with the given view template. Options supported:

:locals

Hash - local variables used in template

:layout

Symbol/String - a custom layout to use

:scope

Object - The scope in which to render the template: default self

:content_type

Symbol/String - Content-Type header to set

:engine

String - Tilt rendering engine to use

:layout_engine

String - Tilt layout rendering engine to use

The template argument may be a String or a Symbol. By default the template location will be looked for under Gin::App.views_dir, but the directory may be specified as any directory under Gin::App.root_dir by using the '/' prefix:

view 'foo/template'
#=> Renders file "<views_dir>/foo/template"

view '/foo/template'
#=> Renders file "<root_dir>/foo/template"

# Render without layout
view 'foo/template', layout: false

887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
# File 'lib/gin/controller.rb', line 887

def view template, opts={}, &block
  content_type(opts.delete(:content_type)) if opts[:content_type]

  scope  = opts[:scope]  || self
  locals = opts[:locals] || {}

  template   = template_path(template)
  v_template = @app.template_for template, opts[:engine]
  raise Gin::TemplateMissing, "No such template `#{template}'" unless v_template

  if opts[:layout] != false
    r_layout   = template_path((opts[:layout] || layout), true)
    r_template = @app.template_for r_layout, opts[:layout_engine] if r_layout
  end

  if !@response[CNT_TYPE]
    mime_type = v_template.class.default_mime_type ||
      r_template && r_template.class.default_mime_type
    content_type(mime_type) if mime_type
  end

  @env[GIN_TEMPLATES] ||= []

  if r_template
    @env[GIN_TEMPLATES] << r_template.file << v_template.file
    r_template.render(scope, locals){
      v_template.render(scope, locals, &block) }
  else
    @env[GIN_TEMPLATES] << v_template.file
    v_template.render(scope, locals, &block)
  end
end