require 'abstract_controller' require 'cell' module Cell class Rails < AbstractController::Base include Cell include AbstractController include Rendering, Layouts, Helpers, Callbacks, Translation, Logger include ActionController::RequestForgeryProtection class View < ActionView::Base def render(*args, &block) options = args.first.is_a?(::Hash) ? args.first : {} # this is copied from #render by intention. return controller.render(*args, &block) if options[:state] or options[:view] super end end class MissingTemplate < ActionView::ActionViewError def initialize(message, possible_paths) super(message + " and possible paths #{possible_paths}") end end module Rendering # Invoke the state method for +state+ which usually renders something nice. def render_state(state, *args) process(state, *args) end end module Metal delegate :session, :params, :request, :config, :to => :parent_controller end include Metal include Rendering include Caching attr_reader :parent_controller attr_accessor :options abstract! def initialize(parent_controller, options={}) @parent_controller = parent_controller @options = options @opts = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :options) end def self.view_context_class controller = self View.class_eval do include controller._helpers include controller._routes.url_helpers end @view_context_class ||= View end def self.controller_path @controller_path ||= name.sub(/Cell$/, '').underscore unless anonymous? end # Renders the view for the current state and returns the markup. # Don't forget to return the markup itself from the state method. # # === Options # +:view+:: Specifies the name of the view file to render. Defaults to the current state name. # +:layout+:: Renders the state wrapped in the layout. Layouts reside in app/cells/layouts. # +:locals+:: Makes the named parameters available as variables in the view. # +:text+:: Just renders plain text. # +:inline+:: Renders an inline template as state view. See ActionView::Base#render for details. # +:file+:: Specifies the name of the file template to render. # +:nothing+:: Doesn't invoke the rendering process. # +:state+:: Instantly invokes another rendering cycle for the passed state and returns. You may pass arbitrary state-args to the called state. # # Example: # class MusicianCell < ::Cell::Base # def sing # # ... laalaa # render # end # # renders the view musician/sing.html. # # def sing # # ... laalaa # render :view => :shout, :layout => 'metal' # end # # renders musician/shout.html and wrap it in app/cells/layouts/metal.html.erb. # # === #render is explicit! # You can also alter the markup from #render. Just remember to return it. # # def sing # render + render + render # end # # will render three concated views. # # === Partials? # # In Cells we abandoned the term 'partial' in favor of plain 'views' - we don't need to distinguish # between both terms. A cell view is both, a view and a kind of partial as it represents only a fragment # of the page. # # Just use :view and enjoy. # # === Using states instead of helpers # # Sometimes it's useful to not only render a view but also invoke the associated state. This is # especially helpful when replacing helpers. Do that with render :state. # # def show_cheap_item(item) # render if item.price <= 1 # end # # A view could use this state in place of an odd helper. # # - @items.each do |item| # = render({:state => :show_cheap_item}, item) # # This calls the state method which in turn will render its view - if the item isn't too expensive. def render(*args) render_view_for(self.action_name, *args) end private # Climbs up the inheritance chain, looking for a view for the current +state+. def find_family_view_for_state(state) exception = nil possible_paths = possible_paths_for_state(state) possible_paths.each do |template_path| begin template = find_template(template_path) return template if template rescue ::ActionView::MissingTemplate => exception end end raise MissingTemplate.new(exception.message, possible_paths) end # Renders the view belonging to the given state. Will raise ActionView::MissingTemplate # if it can't find a view. def render_view_for(state, *args) opts = args.first.is_a?(::Hash) ? args.shift : {} return "" if opts[:nothing] rails_options = [:text, :inline, :file] if (opts.keys & rails_options).present? elsif opts[:state] opts[:text] = render_state(opts[:state], *args) else opts = defaultize_render_options_for(opts, state) template = find_family_view_for_state(opts[:view]) opts[:template] = template end opts = sanitize_render_options(opts) render_to_string(opts).html_safe # ActionView::Template::Text doesn't do that for us. end # Defaultize the passed options from #render. def defaultize_render_options_for(opts, state) opts.reverse_merge!(:view => state) end def sanitize_render_options(opts) opts.except!(:view, :state) end end end