Module: Plezi::Controller

Included in:
Base::Err404Ctrl, Base::Err500Ctrl
Defined in:
lib/plezi/controller/controller.rb,
lib/plezi/controller/cookies.rb,
lib/plezi/controller/controller_class.rb

Overview

This module contains the functionality provided to any Controller class.

This module will be included within every Class that is asigned to a route, providing the functionality without forcing an inheritance model.

Defined Under Namespace

Modules: ClassMethods Classes: Cookies

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#cookiesObject (readonly)

A cookie jar for both accessing and setting cookies. Unifies `request.set_cookie`, `request.delete_cookie` and `request.cookies` with a single Hash like inteface.

Read a cookie:

cookies["name"]

Set a cookie:

cookies["name"] = "value"
cookies["name"] = {value: "value", secure: true}

Delete a cookie:

cookies["name"] = nil

36
37
38
# File 'lib/plezi/controller/controller.rb', line 36

def cookies
  @cookies
end

#paramsObject (readonly)

A union between the `request.params` and the route's inline parameters. This is different then `request.params`


20
21
22
# File 'lib/plezi/controller/controller.rb', line 20

def params
  @params
end

#requestObject (readonly)

A Rack::Request object for the current request.


16
17
18
# File 'lib/plezi/controller/controller.rb', line 16

def request
  @request
end

#responseObject (readonly)

A Rack::Response object used for the current request.


18
19
20
# File 'lib/plezi/controller/controller.rb', line 18

def response
  @response
end

Class Method Details

.included(base) ⇒ Object


11
12
13
# File 'lib/plezi/controller/controller.rb', line 11

def self.included(base)
  base.extend ::Plezi::Controller::ClassMethods
end

Instance Method Details

#_pl_ad_httpreview(data) ⇒ Object

This function is used internally by Plezi, do not call.


242
243
244
245
# File 'lib/plezi/controller/controller.rb', line 242

def _pl_ad_httpreview(data)
  return data.to_json if self.class._pl_is_ad? && data.is_a?(Hash)
  data
end

#_pl_ad_mapObject

This function is used internally by Plezi, do not call.


201
202
203
# File 'lib/plezi/controller/controller.rb', line 201

def _pl_ad_map
  @_pl_ad_map ||= self.class._pl_ad_map.dup
end

#_pl_ad_review(data) ⇒ Object

This function is used internally by Plezi, do not call.


228
229
230
231
232
233
234
235
236
237
238
# File 'lib/plezi/controller/controller.rb', line 228

def _pl_ad_review(data)
  case data
  when Hash
    write data.to_json
  when String
    write data    # when Array
    #   write ret

  end if self.class._pl_is_ad?
  data
end

#_pl_respond(request, response, params) ⇒ Object

This function is used internally by Plezi, do not call.


40
41
42
43
44
45
46
47
48
49
# File 'lib/plezi/controller/controller.rb', line 40

def _pl_respond(request, response, params)
  @request = request
  @response = response
  @params = params
  @cookies = Cookies.new(request, response)
  m = requested_method  # puts "m == #{m.nil? ? 'nil' : m.to_s}"

  return _pl_ad_httpreview(__send__(m)) if m
  false
end

#_pl_ws_mapObject

This function is used internally by Plezi, do not call.


195
196
197
# File 'lib/plezi/controller/controller.rb', line 195

def _pl_ws_map
  @_pl_ws_map ||= self.class._pl_ws_map.dup
end

#broadcast(event_method, *args) ⇒ Object

Invokes a method on every websocket connection (except `self`) that belongs to this Controller / Type. When using Iodine, the method is invoked asynchronously.

self.broadcast :my_method, "argument 1", "argument 2", 3

Methods invoked using #unicast, #broadcast or #multicast will quietly fail if the connection was lost, the requested method is undefined or the 'target' was invalid.


180
181
182
# File 'lib/plezi/controller/controller.rb', line 180

def broadcast(event_method, *args)
  ::Plezi::Base::MessageDispatch.broadcast(id ? self : self.class, event_method, args)
end

#conn_idObject

This is the process specific Websocket's UUID. This function is here to protect you from yourself. Don't call it.


129
130
131
# File 'lib/plezi/controller/controller.rb', line 129

def conn_id
  defined?(super) && super
end

#extend(mod) ⇒ Object

Experimental: takes a module to be used for Websocket callbacks events.

This function can only be called *after* a websocket connection was established (i.e., within the `on_open` callback).

This allows a module “library” to be used similar to the way “rooms” are used in node.js, so that a number of different Controllers can listen to shared events.

By dynamically extending a Controller instance using a module, Websocket broadcasts will be allowed to invoke the module's functions.

Notice: It is impossible to `unextend` an extended module at this time.

Raises:

  • (TypeError)

149
150
151
152
153
154
155
156
# File 'lib/plezi/controller/controller.rb', line 149

def extend(mod)
  raise TypeError, '`mod` should be a module' unless mod.class == Module
  raise "#{self} already extended by #{mod.name}" if is_a?(mod)
  mod.extend ::Plezi::Controller::ClassMethods
  super(mod)
  _pl_ws_map.update mod._pl_ws_map
  _pl_ad_map.update mod._pl_ad_map
end

#idObject

A connection's Plezi ID uniquely identifies the connection across application instances, allowing it to receive and send messages using #unicast.


123
124
125
# File 'lib/plezi/controller/controller.rb', line 123

def id
  @_pl_id ||= (conn_id && "#{::Plezi::Base::MessageDispatch.pid}-#{conn_id.to_s(16)}")
end

#multicast(event_method, *args) ⇒ Object

Invokes a method on every websocket connection in the application (except `self`).

self.multicast :my_method, "argument 1", "argument 2", 3

Methods invoked using #unicast, #broadcast or #multicast will quietly fail if the connection was lost, the requested method is undefined or the 'target' was invalid.


189
190
191
# File 'lib/plezi/controller/controller.rb', line 189

def multicast(event_method, *args)
  ::Plezi::Base::MessageDispatch.multicast(id ? self : self.class, event_method, args)
end

#on_message(data) ⇒ Object

This function is used internally by Plezi, for Auto-Dispatch support do not call.


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

def on_message(data)
  json = nil
  begin
    json = JSON.parse(data, symbolize_names: true)
  rescue
    puts 'AutoDispatch Warnnig: Received non-JSON message. Closing Connection.'
    close
    return
  end
  envt = _pl_ad_map[json[:event]] || _pl_ad_map[:unknown]
  if json[:event].nil? || envt.nil?
    puts _pl_ad_map
    puts "AutoDispatch Warnnig: JSON missing/invalid `event` name '#{json[:event]}' for class #{self.class.name}. Closing Connection."
    close
  end
  write("{\"event\":\"_ack_\",\"_EID_\":#{json[:_EID_].to_json}}") if json[:_EID_]
  _pl_ad_review __send__(envt, json)
end

#pre_connectObject

Override this method to read / write cookies, perform authentication or perform validation before establishing a Websocket connecion.

Return `false` or `nil` to refuse the websocket connection.


136
137
138
# File 'lib/plezi/controller/controller.rb', line 136

def pre_connect
  true
end

#redirect_to(target, status = 302) ⇒ Object

A shortcut for Rack's `response.redirect`.


112
113
114
115
# File 'lib/plezi/controller/controller.rb', line 112

def redirect_to(target, status = 302)
  response.redirect target, status
  true
end

#render(template, &block) ⇒ Object

Renders the requested template (should be a string, subfolders are fine).

Template name shouldn't include the template's extension or format - this allows for dynamic format template resolution, so that `json` and `html` requests can share the same code. i.e.

Plezi.templates = "views/"
render "users/index"

Using layouts (nested templates) is easy by using a block (a little different then other frameworks):

render("users/layout") { render "users/index" }

72
73
74
75
76
77
78
# File 'lib/plezi/controller/controller.rb', line 72

def render(template, &block)
  frmt = params['format'.freeze] || 'html'.freeze
  mime = nil
  ret = ::Plezi::Renderer.render "#{File.join(::Plezi.templates, template.to_s)}.#{frmt}", binding, &block
  response[Rack::CONTENT_TYPE] = mime if ret && !response.content_type && (mime = Rack::Mime.mime_type(".#{frmt}".freeze, nil))
  ret
end

#requested_methodObject

Returns the method that was called by the HTTP request.

It's possible to override this method to change the default Controller behavior.

For Websocket connections this method is most likely to return :preform_upgrade


56
57
58
59
# File 'lib/plezi/controller/controller.rb', line 56

def requested_method
  params['_method'.freeze] = (params['_method'.freeze] || request.request_method.downcase).to_sym
  self.class._pl_params2method(params, request.env)
end

#send_data(data, options = {}) ⇒ Object

Sends a block of data, setting a file name, mime type and content disposition headers when possible. This should also be a good choice when sending large amounts of data.

By default, `send_data` sends the data as an attachment, unless `inline: true` was set.

If a mime type is provided, it will be used to set the Content-Type header. i.e. `mime: “text/plain”`

If a file name was provided, Rack will be used to find the correct mime type (unless provided). i.e. `filename: “sample.pdf”` will set the mime type to `application/pdf`

Available options: `:inline` (`true` / `false`), `:filename`, `:mime`.


89
90
91
92
93
94
95
96
97
98
99
# File 'lib/plezi/controller/controller.rb', line 89

def send_data(data, options = {})
  response.write data if data  # set headers

  content_disposition = options[:inline] ? 'inline'.dup : 'attachment'.dup
  content_disposition << "; filename=#{::File.basename(options[:filename])}" if options[:filename]

  response['content-type'.freeze] = (options[:mime] ||= options[:filename] && Rack::Mime.mime_type(::File.extname(options[:filename])))
  response.delete('content-type'.freeze) unless response['content-type'.freeze]
  response['content-disposition'.freeze] = content_disposition
  true
end

#send_file(filename, options = {}) ⇒ Object

Same as #send_data, but accepts a file name (to be opened and sent) rather then a String.

See #send_data for available options.


104
105
106
107
108
109
# File 'lib/plezi/controller/controller.rb', line 104

def send_file(filename, options = {})
  response['X-Sendfile'.freeze] = filename
  options[:filename] ||= File.basename(filename)
  filename = File.open(filename, 'rb'.freeze) # unless Iodine::Rack.public
  send_data filename, options
end

#unicast(target, event_method, *args) ⇒ Object

Invokes a method on the `target` websocket connection. When using Iodine, the method is invoked asynchronously.

def perform_poke(target)
  unicast target, :poke, self.id
end
def poke(from)
  unicast from, :poke_back, self.id
end
def poke_back(from)
  puts "#{from} is available"
end

Methods invoked using #unicast, #broadcast or #multicast will quietly fail if the connection was lost, the requested method is undefined or the 'target' was invalid.


171
172
173
# File 'lib/plezi/controller/controller.rb', line 171

def unicast(target, event_method, *args)
  ::Plezi::Base::MessageDispatch.unicast(id ? self : self.class, target, event_method, args)
end

#url_for(func, params = {}) ⇒ Object

Returns a relative URL for the controller, placing the requested parameters in the URL (inline, where possible and as query data when not possible).


118
119
120
# File 'lib/plezi/controller/controller.rb', line 118

def url_for(func, params = {})
  ::Plezi::Base::Router.url_for self.class, func, params
end