lib/nyara/controller.rb in nyara-0.0.1.pre.5 vs lib/nyara/controller.rb in nyara-0.0.1.pre.6

- old
+ new

@@ -13,11 +13,11 @@ action = RouteEntry.new action.http_method = HTTP_METHODS[method] action.path = path action.set_accept_exts @formats - action.id = @curr_id.to_sym if @curr_id + action.id = @curr_id if @curr_id action.blk = blk @route_entries << action if @curr_id raise ArgumentError, "action id #{@curr_id} already in use" if @used_ids[@curr_id] @@ -43,11 +43,11 @@ end if tag # todo scan class id = tag[/\#\w++(\-\w++)*/] - @curr_id = id + @curr_id = id.to_sym end if opts # todo add opts: strong param, etag, cache-control @formats = opts[:formats] @@ -79,12 +79,12 @@ # HTTP PATCH def patch path, &blk http 'PATCH', path, &blk end - # HTTP OPTIONS - # todo generate options response for a url + # HTTP OPTIONS<br> + # todo generate options response for a url<br> # see http://tools.ietf.org/html/rfc5789 def options path, &blk http 'OPTIONS', path, &blk end @@ -102,12 +102,11 @@ def set_name n @controller_name = n end attr_reader :controller_name - # :nodoc: - def preprocess_actions + def compile_route_entries scope # :nodoc: raise "#{self}: no action defined" unless @route_entries curr_id = :'#0' next_id = proc{ while @used_ids[curr_id] @@ -116,72 +115,131 @@ @used_ids[curr_id] = true curr_id } next_id[] + @path_templates = {} @route_entries.each do |e| - e.id ||= next_id[] + e.id = next_id[] if e.id.empty? define_method e.id, &e.blk + e.compile self, scope + e.validate + @path_templates[e.id] = e.path_template end @route_entries end + + attr_accessor :path_templates end include Renderable - # :nodoc: def self.inherited klass # klass will also have this inherited method # todo check class name klass.extend ClassMethods - [:@route_entries, :@usred_ids, :@default_layout].each do |iv| + [:@used_ids, :@default_layout].each do |iv| klass.instance_variable_set iv, klass.superclass.instance_variable_get(iv) end + + route_entries = klass.superclass.instance_variable_get :@route_entries + if route_entries + route_entries.map! {|e| e.dup } + klass.instance_variable_set :@route_entries, route_entries + end end # Path helper - def path_for id, *args + def path_to id, *args if args.last.is_a?(Hash) opts = args.pop end - r = Route.path_template(self.class, id) % args + r = self.class.path_templates[id.to_s] % args if opts - r << ".#{opts[:format]}" if opts[:format] - query = opts.map do |k, v| - next if k == :format - "#{CGI.escape k.to_s}=#{CGI.escape v}" - end - query.compact! - r << '?' << query.join('&') unless query.empty? + format = opts.delete :format + r << ".#{format}" if format + r << '?' << opts.to_query unless opts.empty? end r end - # Url helper - # NOTE: host can include port - def url_for id, *args, scheme: nil, host: Config['host'], **opts + # Url helper<br> + # NOTE: host string can include port number<br> + # TODO: user and password? + def url_to id, *args, scheme: nil, host: nil, **opts scheme = scheme ? scheme.sub(/\:?$/, '://') : '//' - host ||= 'localhost' - path = path_for id, *args, opts + host ||= request.host_with_port + path = path_to id, *args, opts scheme << host << path end + # Redirect to a url or path, terminates action<br> + # +status+ can be one of: + # + # - 300 multiple choices (e.g. offer different languages) + # - 301 moved permanently + # - 302 found (default) + # - 303 see other (e.g. for results of cgi-scripts) + # - 307 temporary redirect + # + # Caveats: there's no content in a redirect response yet, if you want one, you can configure nginx to add it + def redirect url_or_path, status=302 + status = status.to_i + raise "unsupported redirect status: #{status}" unless HTTP_REDIRECT_STATUS.include?(status) + + r = request + header = r.header + self.status status + + uri = URI.parse url_or_path + if uri.host.nil? + uri.host = request.domain + uri.port = request.port + end + uri.scheme = r.ssl? ? 'https' : 'http' + r.header['Location'] = uri.to_s + + # similar to send_header, but without content-type + Ext.request_send_data r, HTTP_STATUS_FIRST_LINES[r.status] + data = header.serialize + data.concat r.response_header_extra_lines + data << "\r\n" + Ext.request_send_data r, data.join + + Fiber.yield :term_close + end + + # Shortcut for +redirect url_to *xs+ + def redirect_to *xs + redirect url_to(*xs) + end + + # Request extension or generated by `Accept` def format request.format end + # Request header<br> + # NOTE to change response header, use +set_header+ def header request.header end alias headers header - def set_header k, v - request.response_header[k] = v + # Set response header + def set_header field, value + request.response_header[field] = value end + # Append an extra line in reponse header + # + # :call-seq: + # + # add_header_line "X-Myheader: here we are" + # def add_header_line h raise 'can not modify sent header' if request.response_header.frozen? h = h.sub /(?<![\r\n])\z/, "\r\n" request.response_header_extra_lines << h end @@ -237,10 +295,14 @@ def session request.session end + def flash + request.flash + end + # Set response status def status n raise ArgumentError, "unsupported status: #{n}" unless HTTP_STATUS_FIRST_LINES[n] Ext.request_set_status request, n end @@ -266,18 +328,19 @@ template_deduced_content_type || 'text/html' header.reverse_merge! OK_RESP_HEADER - data = header.map do |k, v| - "#{k}: #{v}\r\n" - end + data = header.serialize data.concat r.response_header_extra_lines + data << Session.encode_set_cookie(r.session, r.ssl?) data << "\r\n" Ext.request_send_data r, data.join # forbid further modification header.freeze + session.freeze + flash.next.freeze end # Send raw data, that is, not wrapped in chunked encoding<br> # NOTE: often you should call send_header before doing this. def send_data data