module RocketIO class Controller # if called without arguments render a template with lowercased name of current request method, e.g. get for GET, post for POST etc. # at first it will look between defined templates. # then it will search a file. # it will try each extension the effective engine has registered, e.g. .erb, .rhtml for ERB. # it will search in the folder the controller was defined in(NOT in path_to_templates, which is used for defined templates only). # so put each controller in a separate folder to avoid templates clash. # # if no file found a TemplateError will be raised. # if a block given it will use the the string returned by the block as template # and wont search for defined nor file templates. # # by default ERB engine will be used (@see engine). # # for layout it will take one given through options or one defined at class level. # if none given, it will render without layout. # to use a layout path_to_layouts should be defined at class level. # # by default it will use current instance as scope. # to render in a isolated scope, set it via :scope option # # to pass some local variables use :locals option # # @example render ./get.erb without layout # class Pages < RocketIO::Controller # # def get # render # end # end # # @example render ./get.erb with :master layout # class Pages < RocketIO::Controller # layout :master # # def get # render # end # end # # @example render ./get.erb with explicit :master layout # class Pages < RocketIO::Controller # # def get # render(layout: :master) # end # end # # @example render within isolated scope # class Pages < RocketIO::Controller # # def get # render(scope: Object.new) # end # end # # @example render with custom locals # class Pages < RocketIO::Controller # # def get # render(locals: {x: 'y'}) # end # end # def render template = nil, opts = {} opts, template = template, nil if template.is_a?(::Hash) engine, engine_opts = resolve_engine(opts) template = block_given? ? yield : resolve_template(template, engine) scope = opts.fetch(:scope, self) locals = template_vars.merge(opts.fetch(:locals, RocketIO::EMPTY_HASH)).freeze layout = opts.fetch(:layout, self.layout) template = compile_template(template, engine, engine_opts).render(scope, locals) layout ? render_layout(layout, opts) {template} : template end # render a template that yields the given block. # that's it, a layout is a template that yields given string. # # layout can be specified two ways: # - as layout name # - as string # # if both given a ArgumentError error raised. # if :template option given, no layout lookups will occur. # # otherwise... # if no layout name given, it will use the one set at class level. # if no layout set at class level and no layout given, it will raise a RuntimeError. # # then it will search for given layout between defined ones. # if none found, it will search a file in `path_to_layouts` folder. # it will try each extension registered with effective engine. # if no file found it will raise a TemplateError. # # block is required and should return the string to be yielded. # def render_layout template = nil, opts = {}, &block template && opts[:template] && raise(ArgumentError, 'Both layout name and :template option given. Please use either one.') opts, template = template, nil if template.is_a?(::Hash) engine, engine_opts = resolve_engine(opts) template = if template resolve_layout(template, engine) else opts[:template] || begin self.layout || raise(RocketIO::LayoutError, 'No default layout set and no explicit layout given') resolve_layout(self.layout, engine) end end scope = opts.fetch(:scope, self) locals = template_vars.merge(opts.fetch(:locals, RocketIO::EMPTY_HASH)).freeze compile_template(template, engine, engine_opts) .render(scope, locals, &(block || RocketIO::EMPTY_STRING_PROC)) end private def resolve_template template, engine template ||= requested_method return __send__(templates[template]) if templates[template] read_template(find_template(dirname, template, engine)) end def resolve_layout layout, engine return __send__(layouts[layout]) if layouts[layout] read_template(find_template(dirname, layout, engine)) end def resolve_engine opts = nil return engine_const(engine_class(opts[:engine])) if opts && opts[:engine] [engine_const(engine[0]), engine[1]] end def read_template file RocketIO::READ_TEMPLATES[[file, ::File.mtime(file).to_i]] ||= ::File.read(file) end def find_template path, template, engine RocketIO::FOUND_TEMPLATES[[path, template, engine]] ||= begin extensions = ::Tilt.default_mapping.extensions_for(engine) file = nil extensions.each do |ext| try = RocketIO::TEMPLATE_PATH_FORMAT % [path, template, ext] next unless File.exists?(try) file = try break end file || raise(RocketIO::TemplateError, '"%s" template not found in "%s". Tried extensions: %s' % [template, path, extensions.join(', ')]) end end def compile_template template, engine, engine_opts = nil RocketIO::COMPILED_TEMPLATES[[template.hash, engine, engine_opts]] ||= begin engine.new(0, *engine_opts) { template } end end end end require 'rocketio/controller/render/engine' require 'rocketio/controller/render/layout' require 'rocketio/controller/render/layouts' require 'rocketio/controller/render/templates' require 'rocketio/controller/render/template_vars'