require 'facet/annotation' require 'facet/inheritor' require 'glue/aspects' require 'glue/markup' require 'nitro' require 'nitro/render' require 'nitro/scaffolding' require 'nitro/caching' require 'nitro/flash' require 'nitro/helper' require 'nitro/compiler' module Nitro # Include this Mixin to a class to make objects of this class # publishable, ie accessible through a standard web (REST) # interface. # # === Instance variables # # ==== mount_path # # Where the publishable is mounted. # # ==== template_root # # Where to look for templates for this publishable # object / controller. The template root is actually a stack # to implement some form of template root inheritance, # thus allowing for more reusable controllers. Ie you can # 'extend' a controller, and only override the templates # you want to change. The compiler will traverse the # template root stack and use the templates from parent # controllers if they are not overriden. module Publishable def self.included(base) super base.module_eval do include Render include Glue::Aspects include Flashing include Helpers end # Aliases an action #-- # gmosx, FIXME: better implementation needed. # gmosx, FIXME: copy all annotations. #++ base.module_eval do def self.alias_action(new, old) alias_method new, old ann new, :view => old end end # Return the 'action' methods for this Controller. # Some dangerous methods from ancestors are removed. # All private methods are ignored. base.module_eval do def self.action_methods classes = self.ancestors.reject do |a| [Object, Kernel, Render, Controller, Caching].include?(a) end classes.delete(PP::ObjectMixin) if defined?(PP::ObjectMixin) methods = classes.inject([]) do |action_methods, klass| action_methods + klass.public_instance_methods(false) end # gmosx: add the default action (leave this?) # methods << 'index' return methods end end # Use the method_missing hook to compile the actions # for this controller. base.module_eval do def method_missing(action, *args) if Compiler.new(self.class).compile(action) send(action, *args) else super end end end # Does this publishable respond to the given action? base.module_eval do class << self def respond_to_action?(action) action_methods.include?(action.to_s) end alias_method :action?, :respond_to_action? end end # Does this class respond to the given action? # Also looks for templates in the template root. # # Prefer to use the compiler for this. #-- # THINK: maybe move template? here #++ base.module_eval do class << self def respond_to_action_or_template?(sym) return self.respond_to_action?(sym.to_s) || Compiler.new(self).template?(sym) end end end base.module_eval do class << self # Override this method to customize the template_root. # Typically used in controllers defined in reusable Parts. # Call super to include the parent class's customizations. # # def setup_template_root(path) # super # @template_root << "custom/route/#{path}" # @template_root << "another/route/#{path}" # end def setup_template_root(path) end def mount_at(path) # Store the mount_path (where the controller is mounted). @mount_path = path # Update template_root. Unshift the PROTO_TEMPLATE_ROOT, # and unshift a template_root relative to the # application template root. @template_root = [] @template_root << File.join(Compiler::PROTO_TEMPLATE_ROOT, path).gsub(/\/$/, '') setup_template_root(path) @template_root << File.join(Glue::Template.root, path).gsub(/\/$/, '') @template_root.reverse! end alias_method :mount, :mount_at end end end private # Cookie helpers. #-- # TODO: move elsewhere. #++ def cookies @context.cookies end def send_cookie(name, value = nil) @context.add_cookie(name, value) end # Encode controller, action, params into a valid url. # Automatically respects nice urls and routing. # # Handles parameters either as a hash or as an array. # Use the array method to pass parameters to 'nice' actions. # # Pass Controller, action, and (param_name, param_value) # pairs. # # === Examples # # encode_url ForaController, :post, :title, 'Hello', :body, 'World' # encode_url :post, :title, 'Hello', :body, 'World' # => implies controller == self # encode_url :kick, :oid, 4 #-- # FIXME: better implementation? optimize this? # TODO: move elsewhere. #++ def encode_url(*args) if args.first.is_a?(Symbol) or args.first.is_a?(String) # no controller passed, imply controller == self! args.unshift(self.class) end # Try to encode using the router. if url = context.dispatcher.encode_route(*args) return url end # No routing rule, manual encoding. controller = args.shift action = args.shift.to_sym url = "/#{controller.ann.self.mount_point}/#{action}" unless args.empty? if controller.respond_to? action param_count = controller.instance_method(action).arity if param_count != 0 param_count.times do args.shift # name url << "/#{CGI.escape(args.shift.to_s)}" end end end unless args.empty? url << '?' params = [] (args.size / 2).times do params << "#{args.shift}=#{args.shift}" end url << params.join(';') end end return url end alias_method :R, :encode_url # Just like encode_url, but generates an absolute url instead. def encode_absolute_url(*args) return "#{request.host_url}#{encode_url(*args)}" end alias_method :RA, :encode_absolute_url end # The Controller part in the MVC paradigm. The controller's # published methods are called actrions. The controller class # contains the Publishable mixin and additional helper mixins. class Controller include Publishable include Scaffolding include Caching include Helpers helper Glue::Markup # This callback is called after the Controller is mounted. def self.mounted(path) # Resolve aspects. Glue::Aspects.include_advice_modules(self) # The scaffolding code is compiled after the mount, so # that template roots are finalized. compile_scaffolding_code() end end end # * George Moschovitis