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 #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 class View < ActionView::Base def render(options = {}, locals = {}, &block) if options[:state] or options[:view] return @_controller.render(options, &block) end super end end def self.view_context_class controller = self # Unfortunately, there is currently an abstraction leak between AC::Base # and AV::Base which requires having the URL helpers in both AC and AV. # To do this safely at runtime for tests, we need to bump up the helper serial # to that the old AV subclass isn't cached. # # TODO: Make this unnecessary #if @controller # @controller.singleton_class.send(:include, _routes.url_helpers) # @controller.view_context_class = Class.new(@controller.view_context_class) do # include _routes.url_helpers View.class_eval do include controller._helpers include Cell::Base.url_helpers if Cell::Rails.respond_to?(:url_helpers) and 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 ### TODO: discuss with yehuda. end #attr_internal :request 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? def render_state(state, request=ActionDispatch::Request.new({})) ### FIXME: where to set Request if none given? leave blank? rack_response = dispatch(state, parent_controller.request) return rack_response[2].last if rack_response[2].kind_of?(Array) ### FIXME: HACK for testing, wtf is going on here? rack_response[2] ### TODO: discuss with yehuda. # rack_response in test mode: [nil, nil, ["Doo"]] # rack_response in dev mode: [nil, nil, "
..."] end include Cell::Caching 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. # As soon as a view file is found it is returned as an ActionView::Template # instance. ### DISCUSS: moved to Cell::View#find_template in rainhead's fork: def find_family_view_for_state(state) missing_template_exception = nil possible_paths_for_state(state).each do |template_path| # we need to catch MissingTemplate, since we want to try for all possible family views. begin template = find_template(template_path) return template if template rescue ::ActionView::MissingTemplate => missing_template_exception end end raise missing_template_exception end # In production mode, the view for a state/template_format is cached. ### DISCUSS: ActionView::Base already caches results for #pick_template, so maybe ### we should just cache the family path for a state/format? def find_family_view_for_state_with_caching(state) return find_family_view_for_state(state) unless self.class.cache_configured? # in production mode: key = "#{state}/#{action_view.template_format}" state2view = self.class.state2view_cache state2view[key] || state2view[key] = find_family_view_for_state(state, action_view) 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) # set instance vars, include helpers: #prepare_action_view_for(action_view, opts) #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 prepare_action_view_for(action_view, opts) # make helpers available: include_helpers_in_class(action_view.class) import_active_helpers_into(action_view) # in Cells::Cell::ActiveHelper. action_view.assigns = assigns_for_view # make instance vars available. action_view.template_format = opts[:template_format] end # Prepares opts to be passed to ActionView::Base#render by removing # unknown parameters. def sanitize_render_options(opts) opts.except!(:view, :state) end end end