./lib/lux/application/application.rb in lux-fw-0.2.3 vs ./lib/lux/application/application.rb in lux-fw-0.5.32
- old
+ new
@@ -1,194 +1,367 @@
+# frozen_string_literal: true
+
+# Main application router
+
class Lux::Application
- ClassCallbacks.define self, :before, :after, :routes, :on_error
+ class_callback :config # pre boot app config
+ class_callback :web_boot # rack_handler is passed as argument
+ class_callback :info # called by "lux config" cli
+ class_callback :before # before any page load
+ class_callback :routes # routes resolve
+ class_callback :after # after any page load
- attr_reader :route_target
+ web_boot do |rack_handler|
+ # deafult host is required
+ unless Lux.config.host.to_s.include?('http')
+ raise 'Invalid "Lux.config.host"'
+ end
- # define common http methods as constants
- [:get, :head, :post, :delete, :put, :patch].map(&:to_s).map(&:upcase).each { |m| eval "#{m} ||= '#{m}'" }
+ if Lux.config(:dump_errors)
+ require 'better_errors'
+ rack_handler.use BetterErrors::Middleware
+ BetterErrors.editor = :sublime
+ end
+ end
+
###
- def initialize current
- @current = current
- @route_test = Lux::Application::RouteTest.new self
- end
+ attr_reader :route_target, :current
- def debug
- { :@locale=>@locale, :@nav=>nav, :@subdomain=>@subdomain, :@domain=>@domain }
+ # define common http methods as get? methods
+ [:get, :head, :post, :delete, :put, :patch].map(&:to_s).each do |m|
+ define_method('%s?' % m) { @current.request.request_method == m.upcase }
end
- def body?
- @current.response.body ? true : false
- end
+ # simple one liners and delegates
+ define_method(:request) { @current.request }
+ define_method(:params) { @current.request.params }
+ define_method(:nav) { @current.nav }
+ define_method(:redirect) { |where, flash={}| @current.redirect where, flash }
+ define_method(:body?) { response.body? }
- def body data
- @current.response.body data
- end
+ def initialize current
+ raise 'Config is not loaded (Lux.start not called), cant render page' unless Lux.config.lux_config_loaded
- def current
- # Lux.current
- @current
+ @current = current
end
- def nav
- @current.nav
+ # Triggers HTTP page error
+ # ```
+ # error.not_found
+ # error.not_found 'Doc not fount'
+ # error(404)
+ # error(404, 'Doc not fount')
+ # ```
+ def error code=nil, message=nil
+ if code
+ error = Lux::Error.new code
+ error.message = message if message
+ raise error
+ else
+ Lux::Error::AutoRaise
+ end
end
- def params
- @current.params
- end
+ # Quick response to page body
+ # ```
+ # response 'ok' if nav.root == 'healthcheck'
+ # ```
+ def response body=nil, status=nil
+ return @current.response unless body
- def redirect where, msgs={}
- @current.redirect where, msgs
+ response.status status || 200
+ response.body body
end
- def request
- @current.request
- end
+ # Tests current root against the string to get a mach.
+ # Used by map function
+ def test? route
+ root = nav.root.to_s
- # gets only root
- def root cell
- if RouteTest::LUX_PRINT_ROUTES
- @route_target = cell
- @route_test.print_route ''
+ ok = case route
+ when String
+ root == route.sub(/^\//,'')
+ when Symbol
+ route.to_s == root
+ when Regexp
+ !!(route =~ root)
+ when Array
+ !!route.map(&:to_s).include?(root)
+ else
+ raise 'Route type %s is not supported' % route.class
end
- call cell unless nav.root
+ nav.shift if ok
+
+ ok
end
- def done?
- throw :done if @current.response.body
+ # Matches if there is not root in nav
+ # Example calls MainController.action(:index) if in root
+ # ```
+ # root 'main#index'
+ # ```
+ def root target
+ call target unless nav.root
end
- def get?
- @current.request.request_method == 'GET'
+ # standard route match
+ # match '/:city/people', Main::PeopleController
+ def match base, target
+ base = base.split('/').slice(1, 100)
+
+ base.each_with_index do |el, i|
+ if el[0,1] == ':'
+ params[el.sub(':','').to_sym] = nav.path[i]
+ else
+ return unless el == nav.path[i]
+ end
+ end
+
+ call target
end
- def post?
- @current.request.request_method == 'POST'
+ @@namespaces ||= {}
+ def self.namespace name, &block
+ @@namespaces[name] = block
end
- def plug name, &block
- done?
+ # Matches namespace block in a path
+ # if returns true value, match is positive and nav is shifted
+ # if given `Symbol`, it will call the function to do a match
+ # if given `String`, it will match the string value
+ # ```
+ # self.namespace :city do
+ # @city = City.first slug: nav.root
+ # !!@city
+ # end
+ # namespace 'dashboard' do
+ # map orgs: 'dashboard/orgs'
+ # end
+ # ```
+ def namespace name
+ if String === name
+ return unless test?(name.to_s)
+ else
+ if @@namespaces[name]
+ return unless instance_exec &@@namespaces[name]
+ nav.shift
+ else
+ raise ArgumentError.new('Namespace block :%s is not defined' % name)
+ end
+ end
- m = "#{name}_plug".to_sym
- return Lux.error(%[Method "#{m}" not defined in #{self.class}]) unless respond_to?(m)
- send m, &block
+ yield
- done?
+ raise Lux::Error.not_found("Namespace <b>:#{name}</b> matched but nothing is called")
end
- # map Main::RootCell do
- # action :about
- # action '/verified-user'
+ # Matches given subdomain name
+ def subdomain name
+ return unless nav.subdomain == name
+ error.not_found 'Subdomain "%s" matched but nothing called' % name
+ end
+
+ # Main routing object, maps path part to target
+ # if path part is positively matched with `test?` method, target is called with `call` method
+ # ```
+ # map api: ApiController
+ # map api: 'api'
+ # map [:api, ApiController]
+ # map 'main/root' do
+ # map [:login, :signup] => 'main/root'
+ # map Main::RootController do
+ # map :about
+ # map '/verified-user'
# end
- # map api: ApiCell
- def map route_object, &block
- done?
+ # ```
+ def map route_object
+ klass = nil
+ route = nil
+ action = nil
- if route_object.class == Hash
- @route_name = route_object.keys.first
- @route_target = route_object.values.first
- else
- @route_name = nav.root
- @route_target = route_object
+ # map 'root' do
+ # ...
+ if block_given?
+ @lux_action_target = route_object
+ yield
+ @lux_action_target = nil
+ return
+ elsif @lux_action_target
+ klass = @lux_action_target
+ route = route_object
+ action = route_object
- if block_given?
- # map :dashboard do
- # map o: DashboardOrgsCell
- # root DashboardCell
- # end
- if route_object.class == Symbol
- if test?(route_object)
- current.nav.shift_to_root
- instance_exec &block
- raise NotFoundError.new %[Route "#{route_object}" matched but nothing is called]
- end
+ # map :foo => :some_action
+ if route_object.is_a?(Hash)
+ route = route_object.keys.first
+ action = route_object.values.first
+ end
- return
- end
+ if test?(route)
+ call klass, action
else
- @route_test.print_route '*'
- return route_object.call
+ return
end
end
- if block_given?
- @route_test.instance_exec &block
- return
- end
+ case route_object
+ when String
+ # map 'root#call'
+ call route_object
+ when Hash
+ route = route_object.keys.first
+ klass = route_object.values.first
- @route_test.print_route '%s/*' % @route_name
+ if route.class == Array
+ # map [:foo, :bar] => 'root'
+ for route_action in route
+ if test?(route_action)
+ call klass, route_action
+ end
+ end
- call @route_target if test? @route_name
- end
-
- def action route_object
- map route_object.values.first do
- action route_object.keys.first
+ return
+ elsif route.is_a?(String) && route[0,1] == '/'
+ # map '/skils/:skill' => 'main/skills#show'
+ return match route, klass
+ end
+ when Array
+ # map [:foo, 'main/root']
+ route, klass = *route_object
+ else
+ Lux.error 'Unsupported route type "%s"' % route_object.class
end
- end
- def test? route
- @route_test.test? route
+ test?(route) ? call(klass) : nil
end
+ # Calls target action in a controller, if no action is given, defaults to :index
+ # ```
# call :api_router
- # call Main::UsersCell
- # call Main::UsersCell, :index
+ # call proc { 'string' }
+ # call proc { [400, {}, 'error: ...'] }
+ # call [200, {}, ['ok']]
+ # call Main::UsersController
+ # call Main::UsersController, :index
+ # call [Main::UsersController, :index]
# call 'main/orgs#show'
+ # ```
def call object=nil, action=nil
- done?
+ # log original app caller
+ root = Lux.root.join('app/').to_s
+ sources = caller.select { |it| it.include?(root) }.map { |it| 'app/' + it.sub(root, '').split(':in').first }
+ action = action.gsub('-', '_').to_sym if action && action.is_a?(String)
+ Lux.log ' Routed from: %s' % sources.join(' ') if sources.first
+
case object
when Symbol
return send(object)
when Hash
object = [object.keys.first, object.values.first]
when String
- if object.include?('#')
- object = object.split('#')
- object[0] = ('%s_cell' % object[0]).classify.constantize
+ object, action = object.split('#') if object.include?('#')
+ when Array
+ if object[0].class == Integer && object[1].class == Hash
+ # [200, {}, 'ok']
+ for key, value in object[1]
+ response.header key, value
+ end
+
+ response.status object[0]
+ response.body object[2]
else
- object = ('%s_cell' % object).classify.constantize
+ object, action = object
end
+ when Proc
+ case data = object.call
+ when Array
+ response.status = data.first
+ response.body data[2]
+ else
+ response.body data
+ end
end
- object, action = object if object.is_a? Array
+ # figure our action unless defined
+ unless action
+ id =
+ if respond_to?(:id?)
+ nav.root { |root_id| id?(root_id) }
+ else
+ nav.root { |root_id| root_id.is_numeric? ? root_id.to_i : nil }
+ end
- if action
- object.action action
- else
- object.call
+ if id
+ current.nav.id = id
+ action = nav.shift || :show
+ else
+ action = nav.shift || :index
+ end
end
- if body?
- done?
- else
+ object = ('%s_controller' % object).classify.constantize if object.is_a?(String)
+
+ controller_name = "app/controllers/#{object.to_s.underscore}.rb"
+ Lux.log ' %s' % controller_name
+ current.files_in_use controller_name
+
+ object.action action.to_sym
+
+ unless response.body
Lux.error 'Lux cell "%s" called via route "%s" but page body is not set' % [object, nav.root]
end
end
- def main
- catch(:done) do
- begin
- ClassCallbacks.execute self, :before
- ClassCallbacks.execute self, :routes
- ClassCallbacks.execute self, :after
- rescue => e
- ClassCallbacks.execute self, :on_error, e
- end
- end
+ # Action to do if there is an application error.
+ # You want to overload this in a production.
+ def on_error error
+ raise error
end
def render
- Lux.log "\n#{current.request.request_method.white} #{current.request.path.white}"
+ Lux.log "\n#{current.request.request_method.white} #{current.request.url}"
Lux::Config.live_require_check! if Lux.config(:auto_code_reload)
main
- current.response.render
+ response.render
end
-end
\ No newline at end of file
+ private
+
+ # internall call to resolve the routes
+ def main
+ return if deliver_static_assets
+
+ catch(:done) do
+ class_callback :before
+ class_callback :routes unless body?
+ end
+
+ catch(:done) do
+ class_callback :after
+ end
+ rescue => e
+ response.body { nil }
+
+ catch(:done) do
+ on_error(e)
+ end
+ end
+
+ # Deliver static assets if `Lux.config.serve_static_files == true`
+ def deliver_static_assets
+ return if body? || !Lux.config(:serve_static_files)
+
+ ext = request.path.split('.').last
+ return unless ext.length > 1 && ext.length < 5
+ file = Lux::Response::File.new request.path.sub('/', ''), inline: true
+ file.send if file.is_static_file?
+ end
+
+end
+