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