Carriers, Services and Views on Diet

Vipul A M

@vipulnsward

😓

 

rubyconfindia.org

 

gardencityruby.org

 

deccanrubyconf.org

Weather

48

Weather Channels

48

Weather Channels

48

Weather Channels

48

Weather Channels

Part One: 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

There was No

ActionView

What AV does?

Rendering

Templating

Creating Context

Views use this ViewContext

Subclasses of ActionView::Base

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

Part Duex

- 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?

Antipattern- Lots of partials/shared. 

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



# app/models/user.rb
class User < ActiveRecord::Base
  # first_name:string last_name:string website:string
end

# app/decorators/user_decorator.rb
module UserDecorator
  def full_name
    "#{first_name} #{last_name}"
  end

  def link
    link_to full_name, website
  end
end

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end
end


# app/views/users/index.html.erb
<% @users.each do |user| %>
  <%= user.link %><br>
<% end %>

ViewModels

- Cells





class Comment::Cell < Cell::ViewModel
  def show
    "This: #{model.inspect}"
  end
end







- # app/views/comments/index.html.haml
%h1 Comments
@comments.each do |comment|
  = concept("comment/cell", comment) 
  #=> Comment::Cell.new(comment).show


- # app/concepts/comment/views/show.haml
%li
  = body
  By #{author_link}



class Comment::Cell < Cell::ViewModel
  def show
    render
  end

private
  def body
    model.body
  end

  def author_link
    link_to model.author.email, author_path(model.author)
  end
end






class Comment::Cell < Cell::ViewModel
  property :body
  property :author

  def show
    render
  end

private
  def author_link
    link_to author.email, author_path(author)
  end
end

Draper, Virtus, etc

Thanks!

@vipulnsward

bigbinary.com/videos

how-we-work.bigbinary.com

 

 

Vipul A M

@vipulnsward

@bigbinary

Made with Slides.com