./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