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
Carriers, Services and Views on Diet- RMR 2015
By Vipul Amler
Carriers, Services and Views on Diet- RMR 2015
Carriers, Services and Views on Diet
- 3,027