require 'abstract_controller' require 'action_controller' module Cell class Rails < ActionController::Metal include BaseMethods include AbstractController include Rendering, Layouts, Helpers, Callbacks, Translation include ActionController::RequestForgeryProtection module Rendering def render_state(state) rack_response = dispatch(state, parent_controller.request) str = '' # copied from Response#body. rack_response[2].each { |part| str << part.to_s } str.html_safe # in fact, i'd love to return a real OutputBuffer here. end end class View < ActionView::Base def render(options = {}, locals = {}, &block) if options[:state] or options[:view] return @_controller.render(options, &block) end super end end include Rendering include Caching #include AbstractController::Logger #include Cell::ActiveHelper cattr_accessor :url_helpers ### TODO: discuss if we really need that or can handle that in cells.rb already. abstract! ### DISCUSS: should we pass the parent_controller here? def initialize(parent_controller=nil, options={}) ### FIXME: move to BaseMethods. @parent_controller = parent_controller @opts = @options = options end attr_reader :parent_controller def log(*args); end def self.view_context_class controller = self View.class_eval do include controller._helpers include Cell::Base.url_helpers if Cell::Rails.url_helpers end @view_context_class ||= View ### DISCUSS: copy behaviour from abstract_controller/rendering-line 49? (helpers) end def self.controller_path @controller_path ||= name.sub(/Cell$/, '').underscore unless anonymous? end def process(*) # defined in AC::Metal. self.response_body = super end delegate :request, :to => :parent_controller delegate :config, :to => :parent_controller # DISCUSS: what if a cell has its own config (eg for assets, cells/bassist/images)? # DISCUSS: let @controller point to @parent_controller in views, and @cell is the actual real controller? class << self def state2view_cache @state2view_cache ||= {} end end # Renders the view for the current state and returns the markup for the component. # Usually called and returned at the end of a state method. # # ==== Options # * :view - Specifies the name of the view file to render. Defaults to the current state name. # * :template_format - Allows using a format different to :html. # * :layout - If set to a valid filename inside your cell's view_paths, the current state view will be rendered inside the layout (as known from controller actions). Layouts should 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 - Will make the component kinda invisible and doesn't invoke the rendering cycle. # * :state - Instantly invokes another rendering cycle for the passed state and returns. # Example: # class MyCell < ::Cell::Base # def my_first_state # # ... do something # render # end # # will just render the view my_first_state.html. # # def my_first_state # # ... do something # render :view => :my_first_state, :layout => 'metal' # end # # will also use the view my_first_state.html as template and even put it in the layout # metal that's located at $RAILS_ROOT/app/cells/layouts/metal.html.erb. # # def say_your_name # render :locals => {:name => "Nick"} # end # # will make the variable +name+ available in the view say_your_name.html. # # def say_your_name # render :nothing => true # end # # will render an empty string thus keeping your name a secret. # # # ==== Where have all the partials gone? # # 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 small # part of the page. # Just use :view and enjoy. def render(opts={}) render_view_for(opts, self.action_name) end # Climbs up the inheritance hierarchy of the Cell, looking for a view for the current +state+ in each level. def find_family_view_for_state(state) missing_template_exception = nil possible_paths_for_state(state).each do |template_path| begin template = find_template(template_path) return template if template rescue ::ActionView::MissingTemplate => missing_template_exception end end raise missing_template_exception end # Render the view belonging to the given state. Will raise ActionView::MissingTemplate # if it can not find one of the requested view template. Note that this behaviour was # introduced in cells 2.3 and replaces the former warning message. def render_view_for(opts, state) return '' if opts[:nothing] ### TODO: dispatch dynamically: if opts[:text] ### FIXME: generic option? elsif opts[:inline] elsif opts[:file] elsif opts[:state] ### FIXME: generic option opts[:text] = render_state(opts[:state]) else # handle :layout, :template_format, :view opts = defaultize_render_options_for(opts, state) #template = find_family_view_for_state_with_caching(opts[:view], action_view) template = find_family_view_for_state(opts[:view]) opts[:template] = template end opts = sanitize_render_options(opts) render_to_string(opts) end # Defaultize the passed options from #render. def defaultize_render_options_for(opts, state) opts[:template_format] ||= self.class.default_template_format opts[:view] ||= state opts end def sanitize_render_options(opts) opts.except!(:view, :state) end end end