require 'facets/more/annotation' require 'facets/more/inheritor' require 'facets/more/aspects' require 'nitro/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 Aspects include Flashing include Helpers # The collection of 'models' ie classes that are linked # to this Publishable/Controller. ann :self, :models => [] # This is a helper that 'links' one or more classes to this # controller. In practice it annotates each class with the # controller and stores the controller's list of 'models'. def self.model *classes for c in classes c.ann :self, :controller => self self.ann.self.models! << c end end 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, :template => 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 - [Object, Kernel, Render, Controller, Caching] classes.delete(PP::ObjectMixin) if defined?(PP::ObjectMixin) methods = classes.inject([]) do |action_methods, klass| action_methods + klass.public_instance_methods(false) end 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 action? respond_to_action? end end # Does this publishable respond to the given template? base.module_eval do class << self def respond_to_template?(action) Compiler.new(self).template?(action.to_s) end alias template? respond_to_template? 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) #-- # gmosx, FIXME: optimize this! #++ return self.respond_to_action?(sym) || self.respond_to_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(Nitro::Template.root, path).gsub(/\/$/, '') @template_root.reverse! end alias_method :mount, :mount_at # Returns the path where this controller is mounted. def mount_path @mount_path end alias mount_point mount_path end end end private # Cookie helpers. #-- # TODO: move elsewhere, probably to a default helper. #++ def cookies @context.cookies end # Send the cookie to the response stream. def send_cookie(name, value = nil) @context.add_cookie(name, value) end # Delete the cookie by setting the expire time to now and # clearing the value. def delete_cookie(name) cookie = Cookie.new(name, '') cookie.expires = Time.now @context.add_cookie(cookie) 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 # encode_url article # => article.to_href # # Alternatively you can pass options with a hash: # # encode_url :controller => ForaController, :action => :delete, :params => { :title => 'Hello' } # encode_url :action => :delete #-- # FIXME: better implementation? optimize this? # TODO: move elsewhere. #++ def encode_url(*args) f = args.first # A standard url as string, return as is. if f.is_a? String return f end # If the passed param is an object that responds to :to_href # returns the url to this object. if f.respond_to? :to_href return args.first.to_href end if f.is_a? Symbol # 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 if action == :index url = "#{controller.mount_path}" else mount_path = controller.mount_path mount_path = nil if mount_path == '/' url = "#{mount_path}/#{action}" end unless args.empty? if controller.respond_to_action_or_template? 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 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 RA encode_absolute_url # Allows instances of Publishable classes to know where they # are mounted. def mount_path self.class.mount_path end alias mount_point mount_path end end