chaps-io/public_activity

View on GitHub
lib/public_activity/renderable.rb

Summary

Maintainability
A
25 mins
Test Coverage
module PublicActivity
  # Provides logic for rendering activities. Handles both i18n strings
  # support and smart partials rendering (different templates per activity key).
  module Renderable
    # Virtual attribute returning text description of the activity
    # using the activity's key to translate using i18n.
    def text(params = {})
      # TODO: some helper for key transformation for two supported formats
      k = key.split('.')
      k.unshift('activity') if k.first != 'activity'
      k = k.join('.')

      I18n.t(k, parameters.merge(params) || {})
    end

    # Renders activity from views.
    #
    # @param [ActionView::Base] context
    # @return [nil] nil
    #
    # Renders activity to the given ActionView context with included
    # AV::Helpers::RenderingHelper (most commonly just ActionView::Base)
    #
    # The *preferred* *way* of rendering activities is
    # to provide a template specifying how the rendering should be happening.
    # However, one may choose using _I18n_ based approach when developing
    # an application that supports plenty of languages.
    #
    # If partial view exists that matches the *key* attribute
    # renders that partial with local variables set to contain both
    # Activity and activity_parameters (hash with indifferent access).
    #
    # If the partial view does not exist and you wish to fallback to rendering
    # through the I18n translation, you can do so by passing in a :fallback
    # parameter whose value equals :text.
    #
    # If you do not want to define a partial view, and instead want to have
    # all missing views fallback to a default, you can define the :fallback
    # value equal to the partial you wish to use when the partial defined
    # by the activity *key* does not exist.
    #
    # @example Render a list of all activities from a view (erb)
    #   <ul>
    #     <% for activity in PublicActivity::Activity.all %>
    #      <li><%= render_activity(activity) %></li>
    #     <% end %>
    #   </ul>
    #
    # @example Fallback to the I18n text translation if the view is missing 
    #   <ul>
    #     <% for activity in PublicActivity::Activity.all %>
    #      <li><%= render_activity(activity, fallback: :text) %></li>
    #     <% end %>
    #   </ul>
    #
    # @example Fallback to a default view if the view for the current activity key is missing. The string is the partial name you wish to use.
    #   <ul>
    #     <% for activity in PublicActivity::Activity.all %>
    #      <li><%= render_activity(activity, fallback: 'default') %></li>
    #     <% end %>
    #   </ul>
    #
    #
    # # Layouts
    # 
    # You can supply a layout that will be used for activity partials
    # with :layout param.
    # Keep in mind that layouts for partials are also partials.
    # @example Supply a layout
    #   # in views:
    #   #   All examples look for a layout in app/views/layouts/_activity.erb
    #    render_activity @activity, :layout => "activity"
    #    render_activity @activity, :layout => "layouts/activity"
    #    render_activity @activity, :layout => :activity
    #
    #   # app/views/layouts/_activity.erb
    #   <p><%= a.created_at %></p>
    #   <%= yield %>
    #
    # ## Custom Layout Location
    # 
    # You can customize the layout directory by supplying :layout_root
    # or by using an absolute path.
    #
    # @example Declare custom layout location
    #
    #   # Both examples look for a layout in "app/views/custom/_layout.erb"
    #
    #    render_activity @activity, :layout_root => "custom"
    #    render_activity @activity, :layout      => "/custom/layout"
    #
    # # Creating a template
    #
    # To use templates for formatting how the activity should render,
    # create a template based on activity key, for example:
    #
    # Given a key _activity.article.create_, create directory tree
    # _app/views/public_activity/article/_ and create the _create_ partial there
    #
    # Note that if a key consists of more than three parts splitted by commas, your
    # directory structure will have to be deeper, for example:
    #   activity.article.comments.destroy => app/views/public_activity/articles/comments/_destroy.html.erb
    #
    # ## Custom Directory
    #
    # You can override the default `public_directory` template root with the :root parameter
    #
    # @example Custom template root
    #    # look for templates inside of /app/views/custom instead of /app/views/public_directory
    #    render_activity @activity, :root => "custom"
    #
    # ## Variables in templates
    #
    # From within a template there are two variables at your disposal:
    # * activity (aliased as *a* for a shortcut)
    # * parameters   (aliased as *p*) [converted into a HashWithIndifferentAccess]
    #
    # @example Template for key: _activity.article.create_ (erb)
    #   <p>
    #     Article <strong><%= p[:name] %></strong>
    #     was written by <em><%= p["author"] %></em>
    #     <%= distance_of_time_in_words_to_now(a.created_at) %>
    #   </p>
    def render(context, params = {})
      params[:i18n] and return context.render :text => self.text(params)

      partial = partial_path(*params.values_at(:partial, :partial_root))
      layout  = layout_path(*params.values_at(:layout, :layout_root))
      locals  = prepare_locals(params)

      begin 
        context.render params.merge(partial: partial, layout: layout, locals: locals)
      rescue ActionView::MissingTemplate => e
        if params[:fallback] == :text
          context.render :text => self.text(params)
        elsif params[:fallback].present?
          partial = partial_path(*params.values_at(:fallback, :partial_root))
          context.render params.merge(partial: partial, layout: layout, locals: locals)
        else
          raise e
        end
      end
    end

    def partial_path(path = nil, root = nil)
      root ||= 'public_activity'
      path ||= self.key.to_s.gsub('.', '/')
      select_path path, root
    end

    def layout_path(path = nil, root = nil)
      path.nil? and return
      root ||= 'layouts'
      select_path path, root
    end

    def prepare_locals(params)
      locals = params.delete(:locals) || Hash.new

      controller          = PublicActivity.get_controller
      prepared_parameters = prepare_parameters(params)
      locals.merge\
        activity:     self,
        controller:   controller,
        current_user: controller.respond_to?(:current_user) ? controller.current_user : nil,
        parameters:   prepared_parameters
    end

    def prepare_parameters(params)
      @prepared_params ||= self.parameters.with_indifferent_access.merge(params)
    end

    private
    def select_path path, root
      [root, path].map(&:to_s).join('/')
    end
  end
end