./lib/lux/controller/controller.rb in lux-fw-0.5.37 vs ./lib/lux/controller/controller.rb in lux-fw-0.6.2

- old
+ new

@@ -1,248 +1,299 @@ -# frozen_string_literal: true +# filters stack for call - before, before_action, :action, after +# if action is missing capture it via def action_missing name -# filters stack for call -# before, before_action, :action, after +module Lux + class Controller + include ClassCallbacks + include ::Lux::Application::Shared -class Lux::Controller - # define maser layout - # string is template, symbol is metod pointer and lambda is lambda - class_attribute :layout + # define master layout + # string is template, symbol is method pointer and lambda is lambda + cattr :layout, class: true - # define helper contest, by defult derived from class name - class_attribute :helper + # define helper contest, by defult derived from class name + # cattr :helper, class: true - # before and after any action filter, ignored in controllers, after is called just before render - [:before, :before_action, :before_render, :after].each { |filter| class_callback filter } + # custom template root instead calcualted one + cattr :template_root, default: './app/views', class: true - class << self - # simple shortcut allows direct call to action, bypasing call - def action *args - new.action(*args) - end + # before and after any action filter, ignored in controllers, after is called just before render + define_callback :before + define_callback :before_action + define_callback :before_render - # create mock function, to enable template rendering - # mock :index, :login - def mock *args - args.each do |el| - define_method(el) { true } + class << self + # simple shortcut allows direct call to action, bypasing call + def action *args + new.action(*args) end + + # create mock function, to enable template rendering + # mock :index, :login + def mock *args + args.each do |el| + define_method(el) { true } + end + end end - end - ### INSTANCE METHODS + ### INSTANCE METHODS - attr_reader :controller_action + IVARS ||= Struct.new 'LuxControllerIvars', :template_sufix, :action, :layout, :render_cache + RENDER_OPTS ||= Struct.new 'LuxControllerRenderOpts', :inline, :text, :plain, :html, :json, :javascript, :xml, :cache, :template, :layout, :render_to_string, :status, :ttl, :content_type - def initialize - # before and after should be exected only once - @executed_filters = {} - @base_template = self.class.to_s.include?('::') ? self.class.to_s.sub(/Controller$/,'').underscore : self.class.to_s.sub(/Controller$/,'').downcase - end + attr_reader :controller_action - def cache *args, &block - Lux.cache.fetch *args, &block - end + def initialize + # before and after should be exected only once + @lux = IVARS.new + @lux.template_sufix = self.class.to_s.sub(/Controller$/,'').underscore.downcase.split('/').first + end - # action(:show) - # action(:select', ['users']) - def action method_name, *args - raise ArgumentError.new('Controller action called with blank action name argument') if method_name.blank? + # action(:show) + # action(:select', ['users']) + def action method_name, *args + if method_name.blank? + raise ArgumentError.new('Controller action called with blank action name argument') + end - method_name = method_name.to_s.gsub('-', '_').gsub(/[^\w]/, '') + if method_name.is_a?(Symbol) + raise Lux.error.internal_server_error('Forbiden action name :%s' % method_name) if [:action, :error].include?(method_name) + else + return controller_action_call(method_name, *args) + end - # dev console log - Lux.log " #{self.class.to_s}##{method_name}".light_blue + method_name = method_name.to_s.gsub('-', '_').gsub(/[^\w]/, '') - @controller_action = method_name.to_sym - if @controller_format = current.nav.format - current.nav.format = nil - end + # dev console log + Lux.log { ' %s#%s (action)'.light_blue % [self.class, method_name] } + # Lux.log { ' %s' % self.class.source_location } - # format error unless method found - report_not_found_error unless respond_to? method_name + @lux.action = method_name.to_sym - catch :done do - filter :before - filter :before_action - send method_name, *args - render - end + run_callback :before, @lux.action - filter :after + catch :done do + unless response.body? + run_callback :before_action, @lux.action - throw :done - rescue => e - response.body { nil } - on_error(e) - end + # if action not found + if respond_to?(method_name) + send method_name, *args + else + action_missing method_name + end - def error *args - args.first.nil? ? Lux::Error::AutoRaise : Lux::Error.report(*args) - end + render + end + end + rescue => error + rescue_from error + end - def on_error error - raise error - end + def timeout seconds + Lux.current.var[:app_timeout] = seconds + end - def send_file file, opts={} - Lux::Response::File.send(file, opts) - end + def flash + response.flash + end - # render :index - # render 'main/root/index' - # render text: 'ok' - def render name=nil, opts={} - on_error Lux::Error.new(404, '%s document Not Found' % @controller_format.to_s.upcase) if @controller_format - - if name.class == Hash - opts.merge! name - else - opts[:template] = name + def rescue_from error + Lux::Error.log error + data = Lux.env.show_errors? ? Lux::Error.inline(error) : 'Server error: %s (%s)' % [error.message, error.class] + render html: data, status: 400 end - filter :before_render + private - opts = opts.to_opts :text, :html, :json, :javascript, :cache, :template, :layout, :render_to_string, :data, :status, :ttl, :content_type + # delegated to current + define_method(:get?) { request.request_method == 'GET' } + define_method(:post?) { request.request_method == 'POST' } + define_method(:etag) { |*args| current.response.etag *args } + define_method(:layout) { |arg = :_nil| arg == :_nil ? @lux.layout : (@lux.layout = arg) } + define_method(:cache_control) { |arg| response.headers['cache-control'] = arg } - response.status opts.status if opts.status - response.content_type = opts.content_type if opts.content_type + # send file to browser + def send_file file, opts = {} + response.send_file(file, opts) + end - page = - if opts.cache - Lux.cache.fetch(opts.cache, opts.ttl || 3600) { render_resolve(opts) } - else - render_resolve(opts) + # does not set the body, returns body string + def render_to_string name=nil, opts={} + opts[:render_to_string] = true + render name, opts end - if opts.render_to_string - page - else - response.body { page } - throw :done + # shortcut to render javascript + def render_javascript name=nil, opts={} + opts[:content_type] = :javascript + opts[:layout] = false + render name, opts end - end - def render_to_string name=nil, opts={} - opts[:render_to_string] = true - render name, opts - end + # render :index + # render 'main/root/index' + # render text: 'ok' + def render name = nil, opts = {} + return if response.body? - def render_javascript name=nil, opts={} - opts[:content_type] = :javascript - opts[:layout] = false - render name, opts - end + if name.class == Hash + opts.merge! name + else + opts[:template] = name + end - private + opts = RENDER_OPTS.new **opts - # delegated to current - define_method(:current) { Lux.current } - define_method(:request) { current.request } - define_method(:response) { current.response } - define_method(:params) { current.request.params } - define_method(:nav) { current.nav } - define_method(:session) { current.session } - define_method(:get?) { request.request_method == 'GET' } - define_method(:post?) { request.request_method == 'POST' } - define_method(:redirect) { |where, flash={}| current.redirect where, flash } - define_method(:etag) { |*args| current.response.etag *args } - define_method(:layout) { |arg| current.var[:controller_layout] = arg } + # set response status and content_type + response.status opts.status if opts.status + response.content_type = opts.content_type if opts.content_type - # called be render - def render_resolve opts - # render static types - for el in [:text, :html, :json, :javascript] - if value = opts[el] - response.content_type = "text/#{el}" - return value + # match rails nameing + opts.text = opts.plain if opts.plain + + # copy value from render_cache + opts.cache = @lux.render_cache if @lux.render_cache + + # we do not want to cache pages that have flashes in response + opts.cache = nil if response.flash.present? + + # define which layout we use + opts.layout ||= @lux.layout.nil? ? self.class.cattr.layout : @lux.layout + + # render static types + for el in [:text, :html, :json, :javascript, :xml] + if value = opts[el] + response.body value, content_type: el + end end + + data = if cache = opts.cache + return if etag(cache) + + add_info = true + from_cache = Lux.cache.fetch opts.cache, ttl: 1_000_000 do + add_info = false + render_template(opts) + end + + if add_info && from_cache + response.header['x-lux-cache'] = 'render-cache' + from_cache += '<!-- from page cache -->' if from_cache =~ %r{</html>\s*$} + end + + from_cache + else + render_template(opts) + end + + response.body data end - # resolve page data, without template - page_part = opts.data || render_body(opts) + def render_template opts + run_callback :before_render, @lux.action - # resolve data with layout - layout = opts.layout - layout = nil if layout.class == TrueClass - layout = false if current.var[:controller_layout].class == FalseClass + local_helper = self.helper opts.layout.or(cattr.layout) # if layout is false (for dialogs) we fallback to controller default layout - if layout.class == FalseClass - page_part - else - layout_define = layout || self.class.layout + page_template = cattr.template_root + opts.template.to_s + Lux.current.var.root_template_path = page_template.sub(%r{/[\w]+$}, '') + data = opts.inline || Lux::Template.render(local_helper, page_template) - layout = case layout_define - when String - 'layouts/%s' % layout_define - when Symbol - send(layout_define) - when Proc - layout_define.call - else - 'layouts/%s' % @base_template.split('/')[0] + if opts.layout + path = Lux::Template.find_layout cattr.template_root, opts.layout + data = Lux::Template.render(local_helper, path) { data } end - Lux::View.new(layout, helper).render_part { page_part } + data end - end - def render_body opts - if template = opts.template - template = template.to_s - template = "#{@base_template}/#{template}" unless template.starts_with?('/') - else - template = "#{@base_template}/#{@controller_action}" + def namespace + self.class.to_s.split('::').first.underscore.to_sym end - Lux::View.render_part(template, helper) - end + def helper helper + helper = nil unless helper + Lux::Template::Helper.new self, :html, helper + end - def halt status, desc=nil - response.status = status - response.body = desc || "Hatlt code #{status}" + # respond_to :js do ... + # respond_to do |format| ... + def respond_to ext=nil + if ext + if ext == nav.format + yield if block_given? + true + elsif nav.format + Lux.error.not_found '%s document Not Found' % nav.format.to_s.upcase + end + else + yield nav.format + end + end - throw :done - end + def cache *args, &block + Lux.cache.fetch *args, &block + end - def namespace - @base_template.split('/')[0].to_sym - end + def render_cache key = :_nil + if key == :_nil + @lux.render_cache + else + unless @lux.render_cache == false + @lux.render_cache = key + end + end + end - def helper ns=nil - Lux::View::Helper.new self, :html, self.class.helper, ns - end + def controller_action_call controller_action, *args + object, action = nil - def report_not_found_error - raise Lux::Error.not_found unless Lux.config(:dump_errors) + if controller_action.is_a?(String) + object, action = controller_action.split('#') if controller_action.include?('#') + object = ('%s_controller' % object).classify.constantize + elsif object.is_a?(Array) + object, action = controller_action + else + raise ArgumentError.new('Not supported') + end - err = [%[Method "#{@controller_action}" not found found in #{self.class.to_s}]] - err.push "You have defined \n- %s" % (methods - Lux::Controller.instance_methods).join("\n- ") + object.action action.to_sym, *args + end - return Lux.error err.join("\n\n") - end + def action_missing name + path = [cattr.template_root, @lux.template_sufix, name].join('/') - def respond_to ext=nil - fmt = @controller_format - @controller_format = nil + if template = Dir['%s.*' % path].first + unless Lux.config.use_autoroutes + raise 'Autoroute for "%s" is found but it is disabled in Lux.config.use_autoroutes' % name + end - if ext - if ext == fmt - yield if block_given? - true - elsif fmt - on_error Lux::Error.new(404, '%s document Not Found' % fmt.to_s.upcase) + self.class.define_method(name) {} + Lux.log ' created method %s#%s | found template %s'.yellow % [self.class, name, template] + return true + else + # if called via super from `action_missing', return false, + # so once can easily fallback to custom template search pattern + return false if caller[0].include?("`action_missing'") end - else - yield fmt - end - end - # because we can call action multiple times - # ensure we execute filters only once - def filter fiter_name, arg=nil - return if @executed_filters[fiter_name] - @executed_filters[fiter_name] = true + message = 'Method "%s" not found found in "%s" (nav: %s).' % [name, self.class, nav] - Object.class_callback fiter_name, self, @controller_action + if Lux.env.show_errors? + defined_methods = (methods - Lux::Controller.instance_methods).map(&:to_s) + defined = '<br /><br />Defined methods %s' % defined_methods.sort.to_ul + + if Lux.config.use_autoroutes + root = [cattr.template_root, @lux.template_sufix].join('/') + files = Dir.files(root).sort.filter {|f| f =~ /^[a-z]/ }.map {|f| f.sub(/\.\w+$/, '') } + files = files - defined_methods + defined += '<br />Defined via templates in %s%s' % [root, files.to_ul] + else + defined += 'Defined templates - disabled' + end + end + + raise Lux::Error.not_found [message, defined].join(' ') + end end end