Carriers, Services and Views on Diet

Vipul A M

@vipulnsward

Rails 5

Weather

48

Weather Channels

48

Weather Channels

48

Weather Channels

48

Weather Channels

Render Flow

Process of Rendering

ActionController

+

ActionDispatch

+

ActionPack

 

Source:  http://andrewberls.com/images/posts/controller_hierarchy_large.png

    module AbstractController
      module Helpers
        extend ActiveSupport::Concern
        included do
          class_attribute :_helpers
          self._helpers = Module.new   
          class_attribute :_helper_methods
          self._helper_methods = Array.new
        end
      ...
      end
      ...
    end


   def helper_method(*meths)
      ...  
      self._helper_methods += meths
      ...
   end


    # Returns a module with all the helpers defined for the engine.
    def helpers
      @helpers ||= begin
        helpers = Module.new
        all = ActionController::Base.all_helpers_from_path(helpers_paths)
        ActionController::Base.modules_for_helpers(all).each do |mod|
          helpers.include(mod)
        end
        helpers
      end
    end

ActionView

Rendering

Templating

Creating Context

Views use this ViewContext

module ActionView
  module Rendering
    extend ActiveSupport::Concern
    ...
   
     module ClassMethods
      def view_context_class
        @view_context_class ||= begin
          supports_path = supports_path?
          routes  = respond_to?(:_routes)  && _routes
          helpers = respond_to?(:_helpers) && _helpers

          Class.new(ActionView::Base) do
            if routes
              include routes.url_helpers(supports_path)
              include routes.mounted_helpers
            end

            if helpers
              include helpers
            end
          end
        end
      end
    end

    ...
  end
end

- Ernie Miller

Less Code =

Better/Faster Code Review

ERB => HAML/Slim

<div class="navbar-header">
  <button class="navbar-toggle full-width" data-toggle="collapse" type="button">
    Navigation
    <span class="sr-only">Toggle navigation</span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
  </button>
</div>


.navbar-header
  %button.navbar-toggle.full-width{"data-toggle" => "collapse", :type => "button"}
    Navigation
    %span.sr-only Toggle navigation
    %span.icon-bar
    %span.icon-bar
    %span.icon-bar

But can't we just tear down into multiple partials?

Like this?

Line Wraps

Code Smell-

Having Logic and ActiveRecord Queries in Views

ERB = gsub!(<template>, context)

 

LogicLess

 

ViewCarriers

 

😎

 

 

ViewServices, Presenters, etc

Why Carriers?

-LogicLess Views

-Skewing

-Double Dots

 

class User
  def super_admin?
    self.role == 'super_admin'
  end

  def manager?
    self.role == 'manager'
  end
end
<% if @user.super_admin? %>
  <%= link_to 'All Profiles', profiles_path %>
<% elsif @user.manager? %>
  <%= link_to 'Manager Profile', manager_profile_path %>
<% end %>

<h3 class="<%= if @user.manager? 
                  'hidden' 
               elsif @user.super_admin? 
                  'active' 
               end %>"> 

   Hello, <%= @user.name %>
</h3>
# app/helpers/users_helper.rb

module UsersHelper
  
  def class_for_user user
    if @user.manager?  
      'hidden' 
    elsif @user.super_admin? 
      'active' 
    end
  end

end


<h3 class="<%= class_for_user(@user) %>">
   Hello, <%= @user.name %>
</h3>
     
      def view_context_class
        @view_context_class ||= begin
          supports_path = supports_path?
          routes  = respond_to?(:_routes)  && _routes
          helpers = respond_to?(:_helpers) && _helpers

          Class.new(ActionView::Base) do
            if routes
              include routes.url_helpers(supports_path)
              include routes.mounted_helpers
            end

            if helpers
              include helpers
            end
          end
        end
      end

Why not just use a Rails Helper?

Helpers are not Classes

-Tough to refactor

-Collisions

- Un-Natural Testing

-Clutter ApplicationHelper

 

class UserCarrier
  attr_reader :user
  
  def initialize user
    @user = user
  end
 
  def user_message_style_class
    if user.manager? 
      'hidden' 
    elsif user.super_admin? 
      'active' 
    end
  end 
end
class UserController < ApplicationController
    def show
       @user = User.find(params[:id])
       @user_carrier = UserCarrier.new @user
    end
end

<% if @user.super_admin? %>
  <%= link_to 'All Profiles', profiles_path %>
<% elsif @user.manager?%>
  <%= link_to 'Manager Profile', manager_profile_path %>
<% end %>

<h3 class="<%= @user_carrier.user_message_style_class %>"> 
  Hello, <%= @user.name %>
</h3>
<% if @user.super_admin? %>
  <%= link_to 'All Profiles', profiles_path %>
<% elsif @user.manager? %>
  <%= link_to 'Manager Profile', manager_profile_path %>
<% end %>

<h3 class="<%= if @user.manager? 
                  'hidden' 
               elsif @user.super_admin? 
                  'active' 
               end %>"> 

   Hello, <%= @user.name %>
</h3>

<% if @user.super_admin? %>
  <%= link_to 'All Profiles', profiles_path %>
<% elsif @user.manager?%>
  <%= link_to 'Manager Profile', manager_profile_path %>
<% end %>

<h3 class="<%= @user_carrier.user_message_style_class %>"> 
  Hello, <%= @user.name %>
</h3>
class MessageDetailCarrier

  attr_reader :message

  delegate :body, :read, :from_id, :created_at, :message_type,
           :id, :notification?, :message?, :notifier, :notifier_type, 
           :to_param,
           to: :message

  def self.wrap_messages(messages)
    messages.map do |message|
      new(message)
    end
  end

  def initialize(message)
    @message = message
  end

  def from_user_name
    Employee.find(from_id).full_name
  end

  ...

  def notification_body_key
    "messages.#{notifier.task_type}"
  end

end

Collection/ Full Example

No html markup in the carriers

No link_to in the carriers

Overcoming Double Dots

- Law of Delimiter

Email Preference for Tuesday: <%= @user.email_preferences.tuesday_preference %>




<%= @article.publisher.active.not_overdue.try(:full_name) %>
class UserCarrier
  attr_reader :user :email_preferences
  
  delegate :tuesday_preference, to: :email_preferences
  
  def initialize user
    @user = user
    @email_preferences = user.email_preferences
  end
    
end

require 'test_helper'

class UserCarrierTest < ActiveSupport::TestCase
  fixture :users
  
  def setup
    manager = users(:manager)
    @user_carrier = UserCarrier.new manager 
  end

  def test_css_class_returned_for_manager
    assert_equal 'hidden', @user_carrier.user_message_style_class
  end
  
end

Testing

Ruby gives us very powerful OOP

Decorators

ActiveDecorator

ViewModels

- Cells

Draper, Virtus, etc

Thanks!

Vipul A M

@vipulnsward

@bigbinary

Carriers, Services and Views on Diet

By Vipul Amler

Carriers, Services and Views on Diet

Carriers, Services and Views on Diet - SOA Rails Meetup

  • 1,263