SlideShare a Scribd company logo
1 of 70
Download to read offline
Beyond MVC
Kyle Rames
@krames
Conventions
app/model/store.rb
stores
db/migrate/
20140903103121_create
_store.rb
http://host/stores/edit
app/controller/
stores_controller.rb
app/views/store
app/helpers
Reduce Cognitive
Burden
What happens if the
action interacts with an
external service?
What happens when if
the action interacts with
multiple models?
How do we deal with
complex processes?
?
/lib model
controller gem
Let’s talk about some
new patterns
Service Objects
Model Processes
1 class UserAuthenticator
2 def initialize(user)
3 @user = user
4 end
5
6 def authenticate(unencrypted_password)
7 return false unless @user
8
9 if BCrypt::Password.new(@user.password_digest) ==
10 unencrypted_password
11 @user
12 else
13 false
14 end
15 end
16 end
1 class UserAuthenticator
2 def initialize(user)
3 @user = user
4 end
5
6 def authenticate(unencrypted_password)
7 return false unless @user
8
9 if BCrypt::Password.new(@user.password_digest) ==
10 unencrypted_password
11 @user
12 else
13 false
14 end
15 end
16 end
1 class UserAuthenticator
2 def initialize(user)
3 @user = user
4 end
5
6 def authenticate(unencrypted_password)
7 return false unless @user
8
9 if BCrypt::Password.new(@user.password_digest) ==
10 unencrypted_password
11 @user
12 else
13 false
14 end
15 end
16 end
1 class SessionsController < ApplicationController
2 def create
3 user = User.where(email: params[:email]).first
4
5 if UserAuthenticator.new(user).
authenticate(params[:password])
6 self.current_user = user
7 redirect_to dashboard_path
8 else
9 flash[:alert] = "Login failed."
10 render "new"
11 end
12 end
13 end
1 class SessionsController < ApplicationController
2 def create
3 user = User.where(email: params[:email]).first
4
5 if UserAuthenticator.new(user).
authenticate(params[:password])
6 self.current_user = user
7 redirect_to dashboard_path
8 else
9 flash[:alert] = "Login failed."
10 render "new"
11 end
12 end
13 end
Martin Fowler Says
The conceptual problem I run into in a lot
of codebases is that rather than
representing a process, the "service
objects" represent "a thing that does the
process".
Adding User
1. Create User
2. Collect Additional Information
3. Approve
4. Complete
1 class UserAddition
2 def begin
3 # ...
4 end
5
6 def collect_extra_info
7 # ...
8 end
9
10 def approve
11 # ...
12 end
13
14 def complete
15 # ...
16 end
17 end
app/services
dhh says
Concerns
Form Objects
Model Stand-In
1 class SignupForm
2 include ActiveModel::Model
3
4 validates_presence_of :email
5
6 delegate :name, :name= to: :company, prefix: true
7 delegate :name, :name=, :email, :email= to: :user
8
9 def company
10 @company ||= Company.new
11 end
12
13 def user
14 @user ||= company.build_user
15 end
16
17 def persisted?
18 false # Forms are never themselves persisted
19 end
20
21 def save
22 valid? ? persist!; true : false
23 end
24
25 private
26
27 def persist!
28 company.save && user.save
29 end
30 end
1 class SignupForm
2 include ActiveModel::Model
3
4 validates_presence_of :email
5
6 delegate :name, :name= to: :company, prefix: true
7 delegate :name, :name=, :email, :email= to: :user
8
9 def company
10 @company ||= Company.new
11 end
12
13 def user
14 @user ||= company.build_user
15 end
16
17 def persisted?
18 false # Forms are never themselves persisted
19 end
20
21 def save
22 valid? ? persist!; true : false
23 end
24
25 private
26
27 def persist!
28 company.save && user.save
29 end
30 end
1 class SignupForm
2 include ActiveModel::Model
3
4 validates_presence_of :email
5
6 delegate :name, :name= to: :company, prefix: true
7 delegate :name, :name=, :email, :email= to: :user
8
9 def company
10 @company ||= Company.new
11 end
12
13 def user
14 @user ||= company.build_user
15 end
16
17 def persisted?
18 false # Forms are never themselves persisted
19 end
20
21 def save
22 valid? ? persist!; true : false
23 end
24
25 private
26
27 def persist!
28 company.save && user.save
29 end
30 end
1 class SignupForm
2 include ActiveModel::Model
3
4 validates_presence_of :email
5
6 delegate :name, :name= to: :company, prefix: true
7 delegate :name, :name=, :email, :email= to: :user
8
9 def company
10 @company ||= Company.new
11 end
12
13 def user
14 @user ||= company.build_user
15 end
16
17 def persisted?
18 false # Forms are never themselves persisted
19 end
20
21 def save
22 valid? ? persist!; true : false
23 end
24
25 private
26
27 def persist!
28 company.save && user.save
29 end
30 end
1 class SignupsController < ApplicationController
2 def create
3 @signup = SignupForm.new(params[:signup_form])
4
5 if @signup.save
6 redirect_to dashboard_path
7 else
8 render "new"
9 end
10 end
11 end
1 class SignupsController < ApplicationController
2 def create
3 @signup = SignupForm.new(params[:signup_form])
4
5 if @signup.save
6 redirect_to dashboard_path
7 else
8 render "new"
9 end
10 end
11 end
dhh says
accepts_nested_attributes_for
1 class User < ActiveRecord::Base
2
3 validates_length_of :new_password,
4 minimum: 6,
5 if: :changing_password
6
7 ...
8 end
1 class User < ActiveRecord::Base
2
3 validates_length_of :new_password,
4 minimum: 6,
5 if: :changing_password
6
7 ...
8 end
1 class PasswordForm
2 include ActiveModel::Model
3
4 attr_accessor :original_password, :new_password
5
6 validate :verify_original_password
7 validates_presence_of :original_password, :new_password
8 validates_confirmation_of :new_password
9 validates_length_of :new_password, minimum: 6
10
11 def submit(params)
12 self.original_password = params[:original_password]
13 self.new_password = params[:new_password]
14 self.new_password_confirmation = params[
15 :new_password_confirmation]
16 if valid?
17 @user.password = new_password; @user.save; true
18 else
19 false
20 end
21 end
22
23 def initialize(user); @user = user; end
24 def persisted?; false; end
25 def verify_original_password; ...; end
26 end
1 class PasswordForm
2 include ActiveModel::Model
3
4 attr_accessor :original_password, :new_password
5
6 validate :verify_original_password
7 validates_presence_of :original_password, :new_password
8 validates_confirmation_of :new_password
9 validates_length_of :new_password, minimum: 6
10
11 def submit(params)
12 self.original_password = params[:original_password]
13 self.new_password = params[:new_password]
14 self.new_password_confirmation = params[
15 :new_password_confirmation]
16 if valid?
17 @user.password = new_password; @user.save; true
18 else
19 false
20 end
21 end
22
23 def initialize(user); @user = user; end
24 def persisted?; false; end
25 def verify_original_password; ...; end
26 end
1 class PasswordsController < ApplicationController
2 def new
3 @password_form = PasswordForm.new(current_user)
4 end
5
6 def create
7 @password_form = PasswordForm.new(current_user)
8 if @password_form.submit(params[:password_form])
9 redirect_to current_user, notice: “Success!"
10 else
11 render "new"
12 end
13 end
14 end
app/forms
Presenters
1 <div id="profile">
2 <dl>
3 <dt>Username:</dt>
4 <dd><%= @user.username %></dd>
5 <dt>Member Since:</dt>
6 <dd><%= @user.member_since %></dd>
7 <dt>Website:</dt>
8 <dd>
9 <% if @user.url.present? %>
10 <%= link_to @user.url, @user.url %>
11 <% else %>
12 <span class="none">None given</span>
13 <% end %>
14 </dd>
15 <dt>Twitter:</dt>
16 <dd>
17 <% if @user.twitter_name.present? %>
18 <%= link_to @user.twitter_name, "http://twitter.com/#{@user.
19 twitter_name}" %>
20 <% else %>
21 <span class="none">None given</span>
22 <% end %>
23 </dd>
24 </dl>
25 </div>
1 <div id="profile">
2 <dl>
3 <dt>Username:</dt>
4 <dd><%= @user.username %></dd>
5 <dt>Member Since:</dt>
6 <dd><%= @user.member_since %></dd>
7 <dt>Website:</dt>
8 <dd>
9 <% if @user.url.present? %>
10 <%= link_to @user.url, @user.url %>
11 <% else %>
12 <span class="none">None given</span>
13 <% end %>
14 </dd>
15 <dt>Twitter:</dt>
16 <dd>
17 <% if @user.twitter_name.present? %>
18 <%= link_to @user.twitter_name, "http://twitter.com/#{@user.
19 twitter_name}" %>
20 <% else %>
21 <span class="none">None given</span>
22 <% end %>
23 </dd>
24 </dl>
25 </div>
1 class UserPresenter
2 attr_reader :user
3 delegate :username, :url, :twitter_name,
4 :full_name,:created_at to: :user
5
6 def initialize(user, template)
7 @user, @template = user, template
8 end
9
10 def member_since
11 user.created_at.strftime("%B %e, %Y")
12 end
13
14 def website
15 handle_none user.url { h.link_to(user.url, user.url) }
16 end
17
18 def twitter
19 handle_none user.twitter_name { h.link_to user.twitter_name, "http:
20 //twitter.com/#{user.twitter_name}" }
21 end
22
23 private
24 def h
25 @template
26 end
27
28 def handle_none(value)
29 ...
30 end
31 end
1 class UserPresenter
2 attr_reader :user
3 delegate :username, :url, :twitter_name,
4 :full_name,:created_at to: :user
5
6 def initialize(user, template)
7 @user, @template = user, template
8 end
9
10 def member_since
11 user.created_at.strftime("%B %e, %Y")
12 end
13
14 def website
15 handle_none user.url { h.link_to(user.url, user.url) }
16 end
17
18 def twitter
19 handle_none user.twitter_name { h.link_to user.twitter_name, "http:
20 //twitter.com/#{user.twitter_name}" }
21 end
22
23 private
24 def h
25 @template
26 end
27
28 def handle_none(value)
29 ...
30 end
31 end
1 class UserPresenter
2 attr_reader :user
3 delegate :username, :url, :twitter_name,
4 :full_name,:created_at to: :user
5
6 def initialize(user, template)
7 @user, @template = user, template
8 end
9
10 def member_since
11 user.created_at.strftime("%B %e, %Y")
12 end
13
14 def website
15 handle_none user.url { h.link_to(user.url, user.url) }
16 end
17
18 def twitter
19 handle_none user.twitter_name { h.link_to user.twitter_name, "http:
20 //twitter.com/#{user.twitter_name}" }
21 end
22
23 private
24 def h
25 @template
26 end
27
28 def handle_none(value)
29 ...
30 end
31 end
1 class UserPresenter
2 attr_reader :user
3 delegate :username, :url, :twitter_name,
4 :full_name,:created_at to: :user
5
6 def initialize(user, template)
7 @user, @template = user, template
8 end
9
10 def member_since
11 user.created_at.strftime("%B %e, %Y")
12 end
13
14 def website
15 handle_none user.url { h.link_to(user.url, user.url) }
16 end
17
18 def twitter
19 handle_none user.twitter_name { h.link_to user.twitter_name, "http:
20 //twitter.com/#{user.twitter_name}" }
21 end
22
23 private
24 def h
25 @template
26 end
27
28 def handle_none(value)
29 ...
30 end
31 end
1 <div id="profile">
2 <dl>
3 <dt>Username:</dt>
4 <dd><%= @user_presenter.username %></dd>
5 <dt>Member Since:</dt>
6 <dd><%= @user_presenter.member_since %></dd>
7 <dt>Website:</dt>
8 <dd><%= @user_presenter.website %></dd>
9 <dt>Twitter:</dt>
10 <dd><%= @user_presenter.twitter %></dd>
11 </dl>
12 </div>
1 <div id="profile">
2 <dl>
3 <dt>Username:</dt>
4 <dd><%= @user_presenter.username %></dd>
5 <dt>Member Since:</dt>
6 <dd><%= @user_presenter.member_since %></dd>
7 <dt>Website:</dt>
8 <dd><%= @user_presenter.website %></dd>
9 <dt>Twitter:</dt>
10 <dd><%= @user_presenter.twitter %></dd>
11 </dl>
12 </div>
1 def show
2 user = User.find(params[:id])
3 @user_presenter = UserPresenter.new(user, view_context)
4 end
1 def show
2 user = User.find(params[:id])
3 @user_presenter = UserPresenter.new(user, view_context)
4 end
1 class UserPresenter
2 attr_reader :user
3 delegate :username, :member_since, :url,
4 :twitter_name, :full_name,:created_at to: :user
5
6 def initialize(user, template)
7 @user, @template = user, template
8 end
9
10 def member_since
11 user.created_at.strftime("%B %e, %Y")
12 end
13
14 def website
15 handle_none user.url { h.link_to(user.url, user.url) }
16 end
17
18 def twitter
19 handle_none user.twitter_name { h.link_to user.twitter_name, "http:
20 //twitter.com/#{user.twitter_name}" }
21 end
22
23 private
24 def h
25 @template
26 end
27
28 def handle_none(value)
29 ...
30 end
31 end
gem 'draper'
1 class UserPresenter < Draper::Decorator
2 delegate_all
3
4 def member_since
5 object.created_at.strftime("%B %e, %Y")
6 end
7
8 def website
9 handle_none object.url do
10 h.link_to(object.url, object.url)
11 end
12 end
13
14 def twitter
15 handle_none object.twitter_name do
16 h.link_to object.twitter_name,
17 "http://twitter.com/#{object.twitter_name}"
18 end
19 end
20
21 private
22 def handle_none(value)
23 ...
24 end
25 end
1 class UserPresenter < Draper::Decorator
2 delegate_all
3
4 def member_since
5 object.created_at.strftime("%B %e, %Y")
6 end
7
8 def website
9 handle_none object.url do
10 h.link_to(object.url, object.url)
11 end
12 end
13
14 def twitter
15 handle_none object.twitter_name do
16 h.link_to object.twitter_name,
17 "http://twitter.com/#{object.twitter_name}"
18 end
19 end
20
21 private
22 def handle_none(value)
23 ...
24 end
25 end
1 class UserPresenter < Draper::Decorator
2 delegate_all
3
4 def member_since
5 object.created_at.strftime("%B %e, %Y")
6 end
7
8 def website
9 handle_none object.url do
10 h.link_to(object.url, object.url)
11 end
12 end
13
14 def twitter
15 handle_none object.twitter_name do
16 h.link_to object.twitter_name,
17 "http://twitter.com/#{object.twitter_name}"
18 end
19 end
20
21 private
22 def handle_none(value)
23 ...
24 end
25 end
1 class UserPresenter < Draper::Decorator
2 delegate_all
3
4 def member_since
5 object.created_at.strftime("%B %e, %Y")
6 end
7
8 def website
9 handle_none object.url do
10 h.link_to(object.url, object.url)
11 end
12 end
13
14 def twitter
15 handle_none object.twitter_name do
16 h.link_to object.twitter_name,
17 "http://twitter.com/#{object.twitter_name}"
18 end
19 end
20
21 private
22 def handle_none(value)
23 ...
24 end
25 end
1 class UserPresenter < Draper::Decorator
2 delegate_all
3
4 def member_since
5 object.created_at.strftime("%B %e, %Y")
6 end
7
8 def website
9 handle_none object.url do
10 h.link_to(object.url, object.url)
11 end
12 end
13
14 def twitter
15 handle_none object.twitter_name do
16 h.link_to object.twitter_name,
17 "http://twitter.com/#{object.twitter_name}"
18 end
19 end
20
21 private
22 def handle_none(value)
23 ...
24 end
25 end
1 class UserPresenter < Draper::Decorator
2 delegate_all
3
4 def member_since
5 object.created_at.strftime("%B %e, %Y")
6 end
7
8 def website
9 handle_none object.url do
10 h.link_to(object.url, object.url)
11 end
12 end
13
14 def twitter
15 handle_none object.twitter_name do
16 h.link_to object.twitter_name,
17 "http://twitter.com/#{object.twitter_name}"
18 end
19 end
20
21 private
22 def handle_none(value)
23 ...
24 end
25 end
1 def show
2 @user = User.find(params[:id]).decorate
3 end
app/presenters
dhh says
helpers
Summary
References
• http://blog.codeclimate.com/blog/2012/10/17/7-
ways-to-decompose-fat-activerecord-models/
• https://gist.github.com/blaix/5764401
• https://blog.engineyard.com/2014/keeping-your-
rails-controllers-dry-with-services
• https://medium.com/@ryakh/rails-form-
objects-84b6849c886e
Railscasts
• http://railscasts.com/episodes/416-form-objects
• http://railscasts.com/episodes/398-service-
objects
• http://railscasts.com/episodes/287-presenters-
from-scratch
• http://railscasts.com/episodes/286-draper
Questions?
@krames

More Related Content

What's hot

20150812 4시간만에 따라해보는 windows 10 앱 개발
20150812  4시간만에 따라해보는 windows 10 앱 개발20150812  4시간만에 따라해보는 windows 10 앱 개발
20150812 4시간만에 따라해보는 windows 10 앱 개발영욱 김
 
JBoss Fuse - Fuse workshop Error Handling
JBoss Fuse - Fuse workshop Error HandlingJBoss Fuse - Fuse workshop Error Handling
JBoss Fuse - Fuse workshop Error HandlingChristina Lin
 
Getting started-with-oracle-so a-9
Getting started-with-oracle-so a-9Getting started-with-oracle-so a-9
Getting started-with-oracle-so a-9Amit Sharma
 
Lt web quiz_answer
Lt web quiz_answerLt web quiz_answer
Lt web quiz_answer0983676660
 
&lt;img src="../i/r_14.png" />
&lt;img src="../i/r_14.png" />&lt;img src="../i/r_14.png" />
&lt;img src="../i/r_14.png" />tutorialsruby
 
Angular js form validation shashi-19-7-16
Angular js form validation shashi-19-7-16Angular js form validation shashi-19-7-16
Angular js form validation shashi-19-7-16Shashikant Bhongale
 
Angular js form validation with ngmessages shashi-19-7-16
Angular js form validation with ngmessages shashi-19-7-16Angular js form validation with ngmessages shashi-19-7-16
Angular js form validation with ngmessages shashi-19-7-16Shashikant Bhongale
 

What's hot (7)

20150812 4시간만에 따라해보는 windows 10 앱 개발
20150812  4시간만에 따라해보는 windows 10 앱 개발20150812  4시간만에 따라해보는 windows 10 앱 개발
20150812 4시간만에 따라해보는 windows 10 앱 개발
 
JBoss Fuse - Fuse workshop Error Handling
JBoss Fuse - Fuse workshop Error HandlingJBoss Fuse - Fuse workshop Error Handling
JBoss Fuse - Fuse workshop Error Handling
 
Getting started-with-oracle-so a-9
Getting started-with-oracle-so a-9Getting started-with-oracle-so a-9
Getting started-with-oracle-so a-9
 
Lt web quiz_answer
Lt web quiz_answerLt web quiz_answer
Lt web quiz_answer
 
&lt;img src="../i/r_14.png" />
&lt;img src="../i/r_14.png" />&lt;img src="../i/r_14.png" />
&lt;img src="../i/r_14.png" />
 
Angular js form validation shashi-19-7-16
Angular js form validation shashi-19-7-16Angular js form validation shashi-19-7-16
Angular js form validation shashi-19-7-16
 
Angular js form validation with ngmessages shashi-19-7-16
Angular js form validation with ngmessages shashi-19-7-16Angular js form validation with ngmessages shashi-19-7-16
Angular js form validation with ngmessages shashi-19-7-16
 

Similar to Beyond MVC

Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelpauldix
 
Desenvolvimento web com Ruby on Rails (parte 6)
Desenvolvimento web com Ruby on Rails (parte 6)Desenvolvimento web com Ruby on Rails (parte 6)
Desenvolvimento web com Ruby on Rails (parte 6)Joao Lucas Santana
 
Ruby on rails
Ruby on rails Ruby on rails
Ruby on rails Mohit Jain
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weiboshaokun
 
Rupicon 2014 Action pack
Rupicon 2014 Action packRupicon 2014 Action pack
Rupicon 2014 Action packrupicon
 
Rails MVC by Sergiy Koshovyi
Rails MVC by Sergiy KoshovyiRails MVC by Sergiy Koshovyi
Rails MVC by Sergiy KoshovyiPivorak MeetUp
 
FormObject For Building Complex Forms. Dmytro Pilugin
FormObject For Building Complex Forms. Dmytro PiluginFormObject For Building Complex Forms. Dmytro Pilugin
FormObject For Building Complex Forms. Dmytro PiluginSphere Consulting Inc
 
OSDC 2009 Rails Turtorial
OSDC 2009 Rails TurtorialOSDC 2009 Rails Turtorial
OSDC 2009 Rails TurtorialYi-Ting Cheng
 
Desenvolvimento web com Ruby on Rails (parte 3)
Desenvolvimento web com Ruby on Rails (parte 3)Desenvolvimento web com Ruby on Rails (parte 3)
Desenvolvimento web com Ruby on Rails (parte 3)Joao Lucas Santana
 
HTML::FormFu talk for Sydney PM
HTML::FormFu talk for Sydney PMHTML::FormFu talk for Sydney PM
HTML::FormFu talk for Sydney PMDean Hamstead
 
Easy logins for JavaScript web applications
Easy logins for JavaScript web applicationsEasy logins for JavaScript web applications
Easy logins for JavaScript web applicationsFrancois Marier
 
Engines: Team Development on Rails (2005)
Engines: Team Development on Rails (2005)Engines: Team Development on Rails (2005)
Engines: Team Development on Rails (2005)lazyatom
 
How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30fiyuer
 
Boston Computing Review - Ruby on Rails
Boston Computing Review - Ruby on RailsBoston Computing Review - Ruby on Rails
Boston Computing Review - Ruby on RailsJohn Brunswick
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Railsrstankov
 

Similar to Beyond MVC (20)

Migrating legacy data
Migrating legacy dataMigrating legacy data
Migrating legacy data
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
 
Desenvolvimento web com Ruby on Rails (parte 6)
Desenvolvimento web com Ruby on Rails (parte 6)Desenvolvimento web com Ruby on Rails (parte 6)
Desenvolvimento web com Ruby on Rails (parte 6)
 
Ruby on rails
Ruby on rails Ruby on rails
Ruby on rails
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weibo
 
Rupicon 2014 Action pack
Rupicon 2014 Action packRupicon 2014 Action pack
Rupicon 2014 Action pack
 
Rails MVC by Sergiy Koshovyi
Rails MVC by Sergiy KoshovyiRails MVC by Sergiy Koshovyi
Rails MVC by Sergiy Koshovyi
 
FormObject For Building Complex Forms. Dmytro Pilugin
FormObject For Building Complex Forms. Dmytro PiluginFormObject For Building Complex Forms. Dmytro Pilugin
FormObject For Building Complex Forms. Dmytro Pilugin
 
OSDC 2009 Rails Turtorial
OSDC 2009 Rails TurtorialOSDC 2009 Rails Turtorial
OSDC 2009 Rails Turtorial
 
Desenvolvimento web com Ruby on Rails (parte 3)
Desenvolvimento web com Ruby on Rails (parte 3)Desenvolvimento web com Ruby on Rails (parte 3)
Desenvolvimento web com Ruby on Rails (parte 3)
 
HTML::FormFu talk for Sydney PM
HTML::FormFu talk for Sydney PMHTML::FormFu talk for Sydney PM
HTML::FormFu talk for Sydney PM
 
Ruby For Startups
Ruby For StartupsRuby For Startups
Ruby For Startups
 
Django quickstart
Django quickstartDjango quickstart
Django quickstart
 
Devise and Rails
Devise and RailsDevise and Rails
Devise and Rails
 
Easy logins for JavaScript web applications
Easy logins for JavaScript web applicationsEasy logins for JavaScript web applications
Easy logins for JavaScript web applications
 
Engines: Team Development on Rails (2005)
Engines: Team Development on Rails (2005)Engines: Team Development on Rails (2005)
Engines: Team Development on Rails (2005)
 
Riding Rails 4
Riding Rails 4Riding Rails 4
Riding Rails 4
 
How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30
 
Boston Computing Review - Ruby on Rails
Boston Computing Review - Ruby on RailsBoston Computing Review - Ruby on Rails
Boston Computing Review - Ruby on Rails
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 

Recently uploaded

CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistandanishmna97
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherRemote DBA Services
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWERMadyBayot
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native ApplicationsWSO2
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfOverkill Security
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...Zilliz
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusZilliz
 
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKJago de Vreede
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesrafiqahmad00786416
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAndrey Devyatkin
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...apidays
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyKhushali Kathiriya
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsNanddeep Nachan
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024The Digital Insurer
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfOverkill Security
 

Recently uploaded (20)

CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdf
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 

Beyond MVC

  • 9.
  • 11. What happens if the action interacts with an external service?
  • 12. What happens when if the action interacts with multiple models?
  • 13. How do we deal with complex processes?
  • 14.
  • 15. ?
  • 17. Let’s talk about some new patterns
  • 20. 1 class UserAuthenticator 2 def initialize(user) 3 @user = user 4 end 5 6 def authenticate(unencrypted_password) 7 return false unless @user 8 9 if BCrypt::Password.new(@user.password_digest) == 10 unencrypted_password 11 @user 12 else 13 false 14 end 15 end 16 end
  • 21. 1 class UserAuthenticator 2 def initialize(user) 3 @user = user 4 end 5 6 def authenticate(unencrypted_password) 7 return false unless @user 8 9 if BCrypt::Password.new(@user.password_digest) == 10 unencrypted_password 11 @user 12 else 13 false 14 end 15 end 16 end
  • 22. 1 class UserAuthenticator 2 def initialize(user) 3 @user = user 4 end 5 6 def authenticate(unencrypted_password) 7 return false unless @user 8 9 if BCrypt::Password.new(@user.password_digest) == 10 unencrypted_password 11 @user 12 else 13 false 14 end 15 end 16 end
  • 23. 1 class SessionsController < ApplicationController 2 def create 3 user = User.where(email: params[:email]).first 4 5 if UserAuthenticator.new(user). authenticate(params[:password]) 6 self.current_user = user 7 redirect_to dashboard_path 8 else 9 flash[:alert] = "Login failed." 10 render "new" 11 end 12 end 13 end
  • 24. 1 class SessionsController < ApplicationController 2 def create 3 user = User.where(email: params[:email]).first 4 5 if UserAuthenticator.new(user). authenticate(params[:password]) 6 self.current_user = user 7 redirect_to dashboard_path 8 else 9 flash[:alert] = "Login failed." 10 render "new" 11 end 12 end 13 end
  • 25. Martin Fowler Says The conceptual problem I run into in a lot of codebases is that rather than representing a process, the "service objects" represent "a thing that does the process".
  • 26. Adding User 1. Create User 2. Collect Additional Information 3. Approve 4. Complete
  • 27. 1 class UserAddition 2 def begin 3 # ... 4 end 5 6 def collect_extra_info 7 # ... 8 end 9 10 def approve 11 # ... 12 end 13 14 def complete 15 # ... 16 end 17 end
  • 32. 1 class SignupForm 2 include ActiveModel::Model 3 4 validates_presence_of :email 5 6 delegate :name, :name= to: :company, prefix: true 7 delegate :name, :name=, :email, :email= to: :user 8 9 def company 10 @company ||= Company.new 11 end 12 13 def user 14 @user ||= company.build_user 15 end 16 17 def persisted? 18 false # Forms are never themselves persisted 19 end 20 21 def save 22 valid? ? persist!; true : false 23 end 24 25 private 26 27 def persist! 28 company.save && user.save 29 end 30 end
  • 33. 1 class SignupForm 2 include ActiveModel::Model 3 4 validates_presence_of :email 5 6 delegate :name, :name= to: :company, prefix: true 7 delegate :name, :name=, :email, :email= to: :user 8 9 def company 10 @company ||= Company.new 11 end 12 13 def user 14 @user ||= company.build_user 15 end 16 17 def persisted? 18 false # Forms are never themselves persisted 19 end 20 21 def save 22 valid? ? persist!; true : false 23 end 24 25 private 26 27 def persist! 28 company.save && user.save 29 end 30 end
  • 34. 1 class SignupForm 2 include ActiveModel::Model 3 4 validates_presence_of :email 5 6 delegate :name, :name= to: :company, prefix: true 7 delegate :name, :name=, :email, :email= to: :user 8 9 def company 10 @company ||= Company.new 11 end 12 13 def user 14 @user ||= company.build_user 15 end 16 17 def persisted? 18 false # Forms are never themselves persisted 19 end 20 21 def save 22 valid? ? persist!; true : false 23 end 24 25 private 26 27 def persist! 28 company.save && user.save 29 end 30 end
  • 35. 1 class SignupForm 2 include ActiveModel::Model 3 4 validates_presence_of :email 5 6 delegate :name, :name= to: :company, prefix: true 7 delegate :name, :name=, :email, :email= to: :user 8 9 def company 10 @company ||= Company.new 11 end 12 13 def user 14 @user ||= company.build_user 15 end 16 17 def persisted? 18 false # Forms are never themselves persisted 19 end 20 21 def save 22 valid? ? persist!; true : false 23 end 24 25 private 26 27 def persist! 28 company.save && user.save 29 end 30 end
  • 36. 1 class SignupsController < ApplicationController 2 def create 3 @signup = SignupForm.new(params[:signup_form]) 4 5 if @signup.save 6 redirect_to dashboard_path 7 else 8 render "new" 9 end 10 end 11 end
  • 37. 1 class SignupsController < ApplicationController 2 def create 3 @signup = SignupForm.new(params[:signup_form]) 4 5 if @signup.save 6 redirect_to dashboard_path 7 else 8 render "new" 9 end 10 end 11 end
  • 39. 1 class User < ActiveRecord::Base 2 3 validates_length_of :new_password, 4 minimum: 6, 5 if: :changing_password 6 7 ... 8 end
  • 40. 1 class User < ActiveRecord::Base 2 3 validates_length_of :new_password, 4 minimum: 6, 5 if: :changing_password 6 7 ... 8 end
  • 41. 1 class PasswordForm 2 include ActiveModel::Model 3 4 attr_accessor :original_password, :new_password 5 6 validate :verify_original_password 7 validates_presence_of :original_password, :new_password 8 validates_confirmation_of :new_password 9 validates_length_of :new_password, minimum: 6 10 11 def submit(params) 12 self.original_password = params[:original_password] 13 self.new_password = params[:new_password] 14 self.new_password_confirmation = params[ 15 :new_password_confirmation] 16 if valid? 17 @user.password = new_password; @user.save; true 18 else 19 false 20 end 21 end 22 23 def initialize(user); @user = user; end 24 def persisted?; false; end 25 def verify_original_password; ...; end 26 end
  • 42. 1 class PasswordForm 2 include ActiveModel::Model 3 4 attr_accessor :original_password, :new_password 5 6 validate :verify_original_password 7 validates_presence_of :original_password, :new_password 8 validates_confirmation_of :new_password 9 validates_length_of :new_password, minimum: 6 10 11 def submit(params) 12 self.original_password = params[:original_password] 13 self.new_password = params[:new_password] 14 self.new_password_confirmation = params[ 15 :new_password_confirmation] 16 if valid? 17 @user.password = new_password; @user.save; true 18 else 19 false 20 end 21 end 22 23 def initialize(user); @user = user; end 24 def persisted?; false; end 25 def verify_original_password; ...; end 26 end
  • 43. 1 class PasswordsController < ApplicationController 2 def new 3 @password_form = PasswordForm.new(current_user) 4 end 5 6 def create 7 @password_form = PasswordForm.new(current_user) 8 if @password_form.submit(params[:password_form]) 9 redirect_to current_user, notice: “Success!" 10 else 11 render "new" 12 end 13 end 14 end
  • 46. 1 <div id="profile"> 2 <dl> 3 <dt>Username:</dt> 4 <dd><%= @user.username %></dd> 5 <dt>Member Since:</dt> 6 <dd><%= @user.member_since %></dd> 7 <dt>Website:</dt> 8 <dd> 9 <% if @user.url.present? %> 10 <%= link_to @user.url, @user.url %> 11 <% else %> 12 <span class="none">None given</span> 13 <% end %> 14 </dd> 15 <dt>Twitter:</dt> 16 <dd> 17 <% if @user.twitter_name.present? %> 18 <%= link_to @user.twitter_name, "http://twitter.com/#{@user. 19 twitter_name}" %> 20 <% else %> 21 <span class="none">None given</span> 22 <% end %> 23 </dd> 24 </dl> 25 </div>
  • 47. 1 <div id="profile"> 2 <dl> 3 <dt>Username:</dt> 4 <dd><%= @user.username %></dd> 5 <dt>Member Since:</dt> 6 <dd><%= @user.member_since %></dd> 7 <dt>Website:</dt> 8 <dd> 9 <% if @user.url.present? %> 10 <%= link_to @user.url, @user.url %> 11 <% else %> 12 <span class="none">None given</span> 13 <% end %> 14 </dd> 15 <dt>Twitter:</dt> 16 <dd> 17 <% if @user.twitter_name.present? %> 18 <%= link_to @user.twitter_name, "http://twitter.com/#{@user. 19 twitter_name}" %> 20 <% else %> 21 <span class="none">None given</span> 22 <% end %> 23 </dd> 24 </dl> 25 </div>
  • 48. 1 class UserPresenter 2 attr_reader :user 3 delegate :username, :url, :twitter_name, 4 :full_name,:created_at to: :user 5 6 def initialize(user, template) 7 @user, @template = user, template 8 end 9 10 def member_since 11 user.created_at.strftime("%B %e, %Y") 12 end 13 14 def website 15 handle_none user.url { h.link_to(user.url, user.url) } 16 end 17 18 def twitter 19 handle_none user.twitter_name { h.link_to user.twitter_name, "http: 20 //twitter.com/#{user.twitter_name}" } 21 end 22 23 private 24 def h 25 @template 26 end 27 28 def handle_none(value) 29 ... 30 end 31 end
  • 49. 1 class UserPresenter 2 attr_reader :user 3 delegate :username, :url, :twitter_name, 4 :full_name,:created_at to: :user 5 6 def initialize(user, template) 7 @user, @template = user, template 8 end 9 10 def member_since 11 user.created_at.strftime("%B %e, %Y") 12 end 13 14 def website 15 handle_none user.url { h.link_to(user.url, user.url) } 16 end 17 18 def twitter 19 handle_none user.twitter_name { h.link_to user.twitter_name, "http: 20 //twitter.com/#{user.twitter_name}" } 21 end 22 23 private 24 def h 25 @template 26 end 27 28 def handle_none(value) 29 ... 30 end 31 end
  • 50. 1 class UserPresenter 2 attr_reader :user 3 delegate :username, :url, :twitter_name, 4 :full_name,:created_at to: :user 5 6 def initialize(user, template) 7 @user, @template = user, template 8 end 9 10 def member_since 11 user.created_at.strftime("%B %e, %Y") 12 end 13 14 def website 15 handle_none user.url { h.link_to(user.url, user.url) } 16 end 17 18 def twitter 19 handle_none user.twitter_name { h.link_to user.twitter_name, "http: 20 //twitter.com/#{user.twitter_name}" } 21 end 22 23 private 24 def h 25 @template 26 end 27 28 def handle_none(value) 29 ... 30 end 31 end
  • 51. 1 class UserPresenter 2 attr_reader :user 3 delegate :username, :url, :twitter_name, 4 :full_name,:created_at to: :user 5 6 def initialize(user, template) 7 @user, @template = user, template 8 end 9 10 def member_since 11 user.created_at.strftime("%B %e, %Y") 12 end 13 14 def website 15 handle_none user.url { h.link_to(user.url, user.url) } 16 end 17 18 def twitter 19 handle_none user.twitter_name { h.link_to user.twitter_name, "http: 20 //twitter.com/#{user.twitter_name}" } 21 end 22 23 private 24 def h 25 @template 26 end 27 28 def handle_none(value) 29 ... 30 end 31 end
  • 52. 1 <div id="profile"> 2 <dl> 3 <dt>Username:</dt> 4 <dd><%= @user_presenter.username %></dd> 5 <dt>Member Since:</dt> 6 <dd><%= @user_presenter.member_since %></dd> 7 <dt>Website:</dt> 8 <dd><%= @user_presenter.website %></dd> 9 <dt>Twitter:</dt> 10 <dd><%= @user_presenter.twitter %></dd> 11 </dl> 12 </div>
  • 53. 1 <div id="profile"> 2 <dl> 3 <dt>Username:</dt> 4 <dd><%= @user_presenter.username %></dd> 5 <dt>Member Since:</dt> 6 <dd><%= @user_presenter.member_since %></dd> 7 <dt>Website:</dt> 8 <dd><%= @user_presenter.website %></dd> 9 <dt>Twitter:</dt> 10 <dd><%= @user_presenter.twitter %></dd> 11 </dl> 12 </div>
  • 54. 1 def show 2 user = User.find(params[:id]) 3 @user_presenter = UserPresenter.new(user, view_context) 4 end
  • 55. 1 def show 2 user = User.find(params[:id]) 3 @user_presenter = UserPresenter.new(user, view_context) 4 end
  • 56. 1 class UserPresenter 2 attr_reader :user 3 delegate :username, :member_since, :url, 4 :twitter_name, :full_name,:created_at to: :user 5 6 def initialize(user, template) 7 @user, @template = user, template 8 end 9 10 def member_since 11 user.created_at.strftime("%B %e, %Y") 12 end 13 14 def website 15 handle_none user.url { h.link_to(user.url, user.url) } 16 end 17 18 def twitter 19 handle_none user.twitter_name { h.link_to user.twitter_name, "http: 20 //twitter.com/#{user.twitter_name}" } 21 end 22 23 private 24 def h 25 @template 26 end 27 28 def handle_none(value) 29 ... 30 end 31 end
  • 58. 1 class UserPresenter < Draper::Decorator 2 delegate_all 3 4 def member_since 5 object.created_at.strftime("%B %e, %Y") 6 end 7 8 def website 9 handle_none object.url do 10 h.link_to(object.url, object.url) 11 end 12 end 13 14 def twitter 15 handle_none object.twitter_name do 16 h.link_to object.twitter_name, 17 "http://twitter.com/#{object.twitter_name}" 18 end 19 end 20 21 private 22 def handle_none(value) 23 ... 24 end 25 end
  • 59. 1 class UserPresenter < Draper::Decorator 2 delegate_all 3 4 def member_since 5 object.created_at.strftime("%B %e, %Y") 6 end 7 8 def website 9 handle_none object.url do 10 h.link_to(object.url, object.url) 11 end 12 end 13 14 def twitter 15 handle_none object.twitter_name do 16 h.link_to object.twitter_name, 17 "http://twitter.com/#{object.twitter_name}" 18 end 19 end 20 21 private 22 def handle_none(value) 23 ... 24 end 25 end
  • 60. 1 class UserPresenter < Draper::Decorator 2 delegate_all 3 4 def member_since 5 object.created_at.strftime("%B %e, %Y") 6 end 7 8 def website 9 handle_none object.url do 10 h.link_to(object.url, object.url) 11 end 12 end 13 14 def twitter 15 handle_none object.twitter_name do 16 h.link_to object.twitter_name, 17 "http://twitter.com/#{object.twitter_name}" 18 end 19 end 20 21 private 22 def handle_none(value) 23 ... 24 end 25 end
  • 61. 1 class UserPresenter < Draper::Decorator 2 delegate_all 3 4 def member_since 5 object.created_at.strftime("%B %e, %Y") 6 end 7 8 def website 9 handle_none object.url do 10 h.link_to(object.url, object.url) 11 end 12 end 13 14 def twitter 15 handle_none object.twitter_name do 16 h.link_to object.twitter_name, 17 "http://twitter.com/#{object.twitter_name}" 18 end 19 end 20 21 private 22 def handle_none(value) 23 ... 24 end 25 end
  • 62. 1 class UserPresenter < Draper::Decorator 2 delegate_all 3 4 def member_since 5 object.created_at.strftime("%B %e, %Y") 6 end 7 8 def website 9 handle_none object.url do 10 h.link_to(object.url, object.url) 11 end 12 end 13 14 def twitter 15 handle_none object.twitter_name do 16 h.link_to object.twitter_name, 17 "http://twitter.com/#{object.twitter_name}" 18 end 19 end 20 21 private 22 def handle_none(value) 23 ... 24 end 25 end
  • 63. 1 class UserPresenter < Draper::Decorator 2 delegate_all 3 4 def member_since 5 object.created_at.strftime("%B %e, %Y") 6 end 7 8 def website 9 handle_none object.url do 10 h.link_to(object.url, object.url) 11 end 12 end 13 14 def twitter 15 handle_none object.twitter_name do 16 h.link_to object.twitter_name, 17 "http://twitter.com/#{object.twitter_name}" 18 end 19 end 20 21 private 22 def handle_none(value) 23 ... 24 end 25 end
  • 64. 1 def show 2 @user = User.find(params[:id]).decorate 3 end
  • 68. References • http://blog.codeclimate.com/blog/2012/10/17/7- ways-to-decompose-fat-activerecord-models/ • https://gist.github.com/blaix/5764401 • https://blog.engineyard.com/2014/keeping-your- rails-controllers-dry-with-services • https://medium.com/@ryakh/rails-form- objects-84b6849c886e
  • 69. Railscasts • http://railscasts.com/episodes/416-form-objects • http://railscasts.com/episodes/398-service- objects • http://railscasts.com/episodes/287-presenters- from-scratch • http://railscasts.com/episodes/286-draper