SlideShare a Scribd company logo
Michael Mahlberg, Consulting Guild AG
Jens-Christian Fischer, InVisible GmbH

SOLID Ruby -
SOLID Rails
Establishing a sustainable
codebase




                                         1
Who?

       Michael Mahlberg




                          2
Founder of

     roughly a dozen companies
     over the last two decades



                                 3
>> relevance
=> nil




               4
Working as

     A consultant on software
     processes, architecture &
     design for > 2 decades


                                 5
>> relevance != nil
=> true




                      6
Who?

       Jens-Christian Fischer




                                7
Tinkerer, Practician,
      Author
            and generally
          interested in way
           too many things


                              8
What is
SOLID?

          9
SOLID
  is
 not
  a
 Law

        10
PPP
(by Robert C. Martin)




                        Agile Software
                        Development,
                        Principles, Patterns,
                        and Practices




                                                11
Principles!

        You know - more
         like guidelines




                           12
SOLID

SRP OCP LSP ISP DIP

                      13
S OL I D

SRP OCP LSP ISP DIP

                      14
SRP
      Single
      Responsibility
      Principle


                   A class should have
                   one, and only one,
                   reason to change.




                                         15
require	
  'digest/sha1'

class	
  User	
  <	
  ActiveRecord::Base
	
  	
  include	
  Authentication
                                         User Class
	
  	
  include	
  Authentication::ByPassword
	
  	
  include	
  Authentication::ByCookieToken

	
  	
  #TODO	
  Check	
  login	
  redirect	
  if	
  this	
  filter	
  is	
  skipped
	
  	
  #skip_after_filter	
  :store_location

	
  	
  #	
  Virtual	
  attribute	
  for	
  the	
  unencrypted	
  password
	
  	
  attr_accessor	
  :password

	
  	
  belongs_to	
  :country

	
  	
  has_one	
  :user_profile,	
  :dependent	
  =>	
  :destroy
	
  	
  has_one	
  :note
	
  	
  	
  	
  
	
  	
  has_many	
  :queries
	
  	
  has_many	
  :tags,	
  :foreign_key	
  =>	
  "created_by"
	
  	
  has_many	
  :taggings,	
  :as	
  =>	
  :tagger
	
  	
  has_many	
  :organizations,	
  :through	
  =>	
  :affiliations
	
  	
  has_many	
  :affiliations
	
  	
  has_many	
  :locations,	
  :through	
  =>	
  :affiliations
	
  	
  has_many	
  :projects,	
  :through	
  =>	
  :memberships
	
  	
  has_many	
  :memberships
	
  	
  has_many	
  :public_assets,	
  :through	
  =>	
  :privileges
	
  	
  has_many	
  :descriptions,	
  :through	
  =>	
  :privileges
	
  	
  has_many	
  :assessments,	
  :through	
  =>	
  :privileges
	
  	
  has_many	
  :description_profiles,	
  :through	
  =>	
  :privileges
	
  	
  has_many	
  :privileges
	
  	
  has_many	
  :diaries
	
  	
  has_many	
  :roles,	
  :through	
  =>	
  :commitments
	
  	
  has_many	
  :commitments
	
  	
  has_many	
  :activities
	
  	
  has_many	
  :messages
	
  	
  has_many	
  :fellowships
	
  	
  has_many	
  :user_groups,	
  :through	
  =>	
  :fellowships
	
  	
  has_many	
  :survey_responses	
  	
  	
                                        16
So what‘s wrong with
        this?


                       17
From: user.rb
class User < ActiveRecord::Base
  include Authentication
  include Authentication::ByPassword
  include Authentication::ByCookieToken
...
 belongs_to :country
...
  has_one :user_profile, :dependent => :destroy
 has_many :queries
  has_many :tags, :foreign_key => "created_by"
...
  validates_presence_of     :login, :email, :country_id
  validates_presence_of     :password, :if => :password_required?
...




                                                                    18
From: user.rb
  acts_as_state_machine :initial => :pending

  state :pending, :enter => :make_activation_code
  state :active, :enter => :do_activate
...
  event :register do
    transitions :from => :passive, :to => :pending, :guard =>
Proc.new {|u| !(u.crypted_password.blank? &&
u.password.blank?) }
  end
...
  def message_threads
    self.message_threads + self.message_threads
  end




                                                                19
From: user.rb
  def forum_nickname
    self.user_profile.nickname.blank? ? "#{self.first_name} #
{self.last_name}" : self.user_profile.nickname
  end

  def name
    "#{self.first_name} #{self.last_name}" rescue 'n/a'
  end

  def email_with_name
    "#{self.first_name} #{self.last_name} <#{self.email}>"
  end




                                                                20
From: user.rb
def is_admin?
  self.roles.collect{|role| role.title}.include?('admin')
end

def countries
  [self.country]
end




                                                            21
From: user.rb
 def boards
    Board.all :conditions => { :user_group_id =>
self.user_groups.collect{ |g| g.id }}
  end

  def discussions
    Discussion.all :conditions => { :board_id =>
self.boards.collect{ |b| b.id }}
  end

  def organization_roles
    role_ids = Affiliation.all(:conditions => {:user_id =>
self.id}).collect{|a| a.role_id}.uniq
    roles = Role.find(role_ids)
  end




                                                             22
From: user.rb
  def make_password_reset_code
    self.password_reset_code = Digest::SHA1.hexdigest
( Time.now.to_s.split(//).sort_by {rand}.join )
  end

  def self.published_users
    User.all(:conditions => ['state = ?',
'published'], :order => 'login ASC', :include =>
[:user_profile])
  end




                                                        23
Anyone notice a pattern?



                           24
Neither do we



                25
Separation of Concerns



                         26
Authentication
    Roles
   Mailers
    State
     ...

                 27
So how?


  Mixins




           28
New User Model
class User < ActiveRecord::Base
  include Authentication
  include Authentication::ByPassword
  include Authentication::ByCookieToken

  include   Project::UserStates
  include   Project::UserMailer
  include   Project::UserForum
  include   Project::UserMessages
...
end




                                          29
UserMessages
module Project
  module UserMessages
    # to be included in User Model

    has_many :messages
    def message_threads
      MessageThread.all(:conditions =>
        ["sender_id = ? or receiver_id = ?",
          self.id, self.id])
  end
end
end




                                               30
Methods



          31
def transfer(data, url)
  h = Net::HTTP.new(self.uri.host, self.uri.port)
  RAILS_DEFAULT_LOGGER.debug "connecting to CL: #{self.uri}"
  RAILS_DEFAULT_LOGGER.debug "connecting to CL: #{url}"

  resp = h.post(url, data, {'Content-Type' => 'application/xml'})
  response_code = resp.code.to_i
  location = if response_code == 201
    resp['Location']
  else
    RAILS_DEFAULT_LOGGER.debug "error from CL: #{response_code}"
    RAILS_DEFAULT_LOGGER.debug "error from CL: #{resp.body}"
    @error = resp.body
    nil
  end
  [response_code, location]
end




                                                                    32
def transfer(data, document)

  if document.cl_document_url != nil
    self.uri = URI.parse(document.cl_document_url )
    h = Net::HTTP.new(self.uri.host, self.uri.port)
    response = h.post(self.uri, data, {'Content-Type' =>
'application/xml'})
  else
    h = Net::HTTP.new(self.uri.host, self.uri.port)
    response = h.post("/tasks", data, {'Content-Type' =>
'application/xml'})
  end
  response_code = response.code.to_i
  if response_code == 201
    location = response['Location']
    document.cl_document_url = location
    document.save!
  else
    nil
  end
  [response_code, location]
end



                                                           33
SRP Transfer
def transfer data
  open_connection
  post data
  return location
end

def open_connection
  @http = Net::HTTP.new(self.uri.host, self.uri.port)
end

def post data
  @response = http.post(self.url, data, {'Content-Type' =>
                                         'application/xml'})
end




                                                               34
def location
  get_location if created? # returns nil if not created?
end

def response_code
  @response.code.to_i
end

def created?
  response_code == 201
end

def get_location
  @response['Location']
end

def error
  @response.body
end




                                                           35
Add a 16-band
 equalizer & a
   BlueRay
player to this...




                    36
And now to
  this...




             37
S OL I D

SRP OCP LSP ISP DIP

                      38
OCP
  Open
  Closed
  Principle


              You should be able
              to extend a classes
              behavior, without
              modifying it.



                                    39
40
41
42
43
def makemove(map)
                                     From the Google
  x, y = map.my_position
  # calculate a move ...               AI Challenge
  if(valid_moves.size == 0)
    map.make_move( :NORTH )
                                        (Tronbot)
  else
    # choose move ...
    puts move # debug (like in the old days)
    map.make_move( move )
  end
end

class Map
  ...
  def make_move(direction)
    $stdout << ({:NORTH=>1, :SOUTH=>3, :EAST=>2, :WEST=>4}[direction])
    $stdout << "n"
    $stdout.flush
  end
end



                                                                         44
From the Google AI Challenge (Tronbot)
def puts(*args)
  $stderr.puts *args
end

def p(*args)
  args.map!{|arg| arg.inspect}
  puts args
end

def print(*args)
  $stderr.print *args
end




                                         45
Design Sketch




                46
class Outputter

  def initialize(io = $stderr)
    @io = io
  end

  def puts(*args)
    @io.puts *args
  end

  ...
end

out = Outputter.new
out.puts "Testing"




                                 47
S OL I D

SRP OCP LSP ISP DIP

                      48
LSP
      Liskov
      Substitution
      Principle


                     Derived classes
                     must be substitutable
                     for their base
                     classes.



                                             49
No Problem
  in Ruby

        Or so it seems...




                            50
No Interface...

            no problem?




                          51
Wrong !



          52
The classic violation



                        53
A square is a rectangle



                          54
Rectangle

setX
setY




        Square

setX
setY




                   55
Rectange
>>   class Rectangle
>>     attr_accessor :width, :height
>>   end
=>   nil
>>
?>   shape = Rectangle.new
=>   #<Rectangle:0x10114fad0>
>>   shape.width
=>   nil
>>   shape.width=3
>>   shape.width
=>   3
>>   shape.height=5
>>   shape.height
=>   5
>>   shape.width
=>   3



                                       56
Square
>> class Square
?>   def width
>>     @dimension
                            ?> shape = Square.new
>>   end
                            => #<Square:0x101107e88>
?>   def height
                            ?> puts shape.width
>>     @dimension
                            nil
>>   end
                            ?> shape.width=3
?>   def width= n
                            => 3
>>     @dimension = n
                            ?> shape.width
>>   end
                            => 3
?>   def height= n
                            ?> shape.height
>>     @dimension = n
                            => 3
>>   end
>> end




                                                       57
A Problem...
>>   s = [Rectangle.new, Square.new]
=>   [#<Rectangle:0x1005642e8>, #<Square:0x100564298>]
>>   a_rectangle = s[rand(2)]
=>   #<Square:0x100564298>
>>   a_rectangle.height=1
=>   1
>>   a_rectangle.width=3
=>   3
                                Text
>>   a_rectangle.height
=>   3




                                                         58
CCD Common Conceptual
     Denominator


                    59
dup



      60
irb 1:0> 5.respond_to? :dup
=> true
irb 2:0> 5.dup
TypeError: can't dup Fixnum
         from (irb):1:in `dup'
         from (irb):1
irb 3:0>




           http://blog.objectmentor.com/articles/2007/02/17/
           liskov-substitution-principle-and-the-ruby-core-libraries



                                                                  61
S OL I D

SRP OCP LSP ISP DIP

                      62
ISP
      Interface
      Segregation
      Principle


                    Make fine grained
                    interfaces that are
                    client specific.




                                          63
64
Users Controller
class UsersController < ApplicationController

  ssl_required :new, :create, :edit, :update, :destroy, :activate,
:change_passwort, :forgot_password, :reset_password, :make_profile,
:my_contacts
  ssl_allowed :eula, :index, :show

  access_control
[:suspend, :unsuspend, :destroy, :purge, :delete, :admin, :ban, :remove_ban] =>
'admin'

  before_filter :find_user

  skip_after_filter :store_location

  def show
    unless @user == current_user
      redirect_to access_denied_path(@locale)
    else
      respond_to do |format|
         format.html
         format.js { render :partial => "users/#{@context.title}/#{@partial}" }
      end
    end
  end
...


                                                                                  65
more UsersController
def activate
  logout_keeping_session!
  user = User.find_by_activation_code(params[:activation_code]) unless
                          params[:activation_code].blank?

  case
  when (!params[:activation_code].blank?) && user && !user.active?
    user.activate!
    flash[:notice] = t(:message_sign_up_complete)
    unless params[:context].blank?
       redirect_to login_path(:context => params[:context])
    else
       redirect_to "/login"
    end
  when params[:activation_code].blank?
    flash[:error] = t(:message_activation_code_missing)
    redirect_back_or_default("/")
  else
    flash[:error] = t(:message_user_with_that_activation_code_missing)
    redirect_back_or_default("/")
  end
end



                                                                         66
User Class Revisited
class User < ActiveRecord::Base
  ...
end



class Registration < ActiveRecord::Base
   set_table_name "users"

      acts_as_state_machine :initial => :pending

      state :pending, :enter => :make_activation_code
      state :active, :enter => :do_activate
      ...

      event :activate do
        transitions :from => :pending, :to => :active
      end
      ...
end




                                                        67
class RegistrationController < ApplicationController
  ...
  def activate
    logout_keeping_session!
    code_is_blank = params[:activation_code].blank?
    registration = Registration.find_by_activation_code(params
[:activation_code]) unless code_is_blank

    case
    when (!code_is_blank) && registration && !registratio.active?
      registration.activate!
      flash[:notice] = t(:message_sign_up_complete)
      unless params[:context].blank?
         redirect_to login_path(:context => params[:context])
      else
         redirect_to "/login"
      end
    when code_is_blank
      flash[:error] = t(:message_activation_code_missing)
      redirect_back_or_default("/")
    else
      flash[:error] = t(:message_user_with_that_activation_code_missing)
      redirect_back_or_default("/")
    end
  end
  ...
end

                                                                           68
S OL I D

SRP OCP LSP ISP DIP

                      69
DIP
      Dependency
      Inversion
      Principle


                   Depend on
                   abstractions, not on
                   concretions.




                                          70
71
From our OCP example to DIP



out = Outputter.new
out.puts "Testing"




                               72
The code we wish we had
class TronBot
  def initialize
    @@out = TRON_ENVIRONMENT[:debugger]
  end

  def some_method
    ...
    @@out.puts "Testing"
    ...
  end

end




                                          73
TSTTCPW


TRON_ENVIRONMENT = {
        :debugger => Outputter.new ($stderr),
        :game_engine => Outputter.new ($stdout),
        :user_io => Outputter.new ($stderr)
        }




                                                   74
Later...


TRON_ENVIRONMENT = {
        :debugger => Outputter.new ($stderr),
        :game_engine => Outputter.new (TCP_OUTPUTTER),
        :user_io => Outputter.new ($stderr)
        }




                                                         75
DIP Violation in Controller
format.js do
  render :update do |page|
    if @parent_object.class == EspGoal
      @esp_goal_descriptor = @current_object
      page.replace_html "descriptor_#{@current_object.id}",
          :partial => "edit_esp_goal_descriptor",
          :locals => {:esp_goal_descriptor => @esp_goal_descriptor,
                      :parent_object => @parent_object}
    else
      @goal_descriptor = @current_object
      page.replace_html "descriptor_#{@current_object.id}",
          :partial => "edit_goal_descriptor",
          :locals => {:goal_descriptor => @goal_descriptor,
                      :parent_object => @parent_object}
    end
  end
end




                                                                      76
DIP Violation in Controller
format.js do
  render :update do |page|
    if @parent_object.class == EspGoal
      @esp_goal_descriptor = @current_object
      page.replace_html "descriptor_#{@current_object.id}",
          :partial => "edit_esp_goal_descriptor",
          :locals => {:esp_goal_descriptor => @esp_goal_descriptor,
                      :parent_object => @parent_object}
    else if @parent_object.class == Goal
      @goal_descriptor = @current_object
      page.replace_html "descriptor_#{@current_object.id}",
          :partial => "edit_goal_descriptor",
          :locals => {:goal_descriptor => @goal_descriptor,
                      :parent_object => @parent_object}
    else if @parent_object.class == LearningGoal
      ...
      ...
    end
  end
end


                                                                      77
78
1st Refactoring
def show
  ...
  format.js do
    render :update do |page|
      page.replace_html "descriptor_#{@current_object.id}",
                        @parent_object.page_replacement(@current_object)
    end
  end
end

class EspGoal
  def page_replacement child
      { :partial => "edit_esp_goal_descriptor",
        :locals => {:esp_goal_descriptor => child,
                    :parent_object => self}
      }
  end
end

class Goal
  def page_replacement child
    { :partial => "edit_goal_descriptor",
      :locals => {:goal_descriptor => child,
                  :parent_object => self}
    }
  end
end

                                                                           79
80
2nd Refactoring
                                             (wiring)
class PartialContainer
  def add class_symbol, partial_replacement
    @@partinal_replacements.add( class_symbol => partial_replacement)
  end

  def self.partial_replacement an_object
    unless @@partial_replacments
      self.add( EspGoalReplacement.my_class_sym, EspGoalReplacment.new)
      self.add( GoalReplacement.my_class_sym, GoalReplacment.new)
    end
    @@partial_replacement[an_object.class]
  end
end




                                                                          81
class EspGoalReplacmenent
                                                   2nd Refactoring
  def self.my_class_sym

  end
      EspGoal.to_sym                               (Behaviour)
  def partial_definition child
  { :partial => "edit_esp_goal_descriptor",
       :locals => {:esp_goal_descriptor => child,
                   :parent_object => child.esp_goal}
    }
  end
end

class GoalReplacmenent
  def self.my_class_sym
      Goal.to_sym
  end
  def partial_definition child
  { :partial => "edit_goal_descriptor",
       :locals => {:goal_descriptor => child,
                   :parent_object => child.goal}
    }
  end
end



                                                                 82
DIP Violation in Controller
format.js do
  render :update do |page|
    if @parent_object.class == EspGoal
      @esp_goal_descriptor = @current_object
      page.replace_html "descriptor_#{@current_object.id}",
:partial => "edit_esp_goal_descriptor",
          :locals => {:esp_goal_descriptor => @esp_goal_descriptor,
                      :parent_object => @parent_object}
    else
      @goal_descriptor = @current_object
      page.replace_html "descriptor_#{@current_object.id}",
:partial => "edit_goal_descriptor",
          :locals => {:goal_descriptor => @goal_descriptor,
          :parent_object => @parent_object}
    end
  end
end



                                                                      83
2nd Refactoring
                   - the Controller -
def show
  ...
  format.js do
    render :update do |page|
      page.replace_html "descriptor_#{@current_object.id}",
                        PartialContainer.partial_replacement(@parent_object).
                                         partial_definition(@current_object)
    end
  end
end




                                                                                84
85
SOLID

SRP OCP LSP ISP DIP

                      86
SRP OCP LSP ISP DIP

                      87
Questions?
      S OL ID

SRP OCP LSP ISP DIP

                        88
Vielen Dank!



               89
Credits (1/2)
PPP-Article (online)
http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

Photos
http://www.flickr.com/photos/dieterkarner/370967891/
http://www.flickr.com/photos/popcorncx/2221630487/sizes/l/
http://www.flickr.com/photos/bdesham/2432400623/
http://www.flickr.com/photos/popcorncx/2221630487/
http://www.flickr.com/photos/glennbatuyong/4081599002/in/photostream/
http://www.flickr.com/photos/glennbatuyong/4081599168/in/photostream/
http://www.flickr.com/photos/renfield/3865907619/


                                                                        90
                                                                             90
Credits (2/2)
Photos
http://www.flickr.com/photos/renfield/3865907619/
http://www.flickr.com/photos/maxpower/5160699/
http://programmer.97things.oreilly.com/wiki/index.php/Uncle_Bob
http://www.flickr.com/photos/georgivar/3288942086/
http://www.everystockphoto.com/photo.php?imageId=237523
http://www.flickr.com/photos/pasukaru76/3992935923/




                                                                  91
                                                                       91
Lizense


http://creativecommons.org/licenses/by-sa/
  3.0/de/




                                         92
                                              92
Jens-Christian Fischer        Michael Mahlberg

InVisible GmbH                Consulting Guild AG



@jcfischer                     @MMahlberg

jens-christian@invisible.ch   mm@michaelmahlberg.de

http://blog.invisible.ch      http://agile-aspects.blogspot.com




                                                             93
                                                                  93

More Related Content

What's hot (20)

ODP
Rich domain model with symfony 2.5 and doctrine 2.5
Leonardo Proietti
 
PPTX
Python advance
Mukul Kirti Verma
 
PDF
Designing a JavaFX Mobile application
Fabrizio Giudici
 
PDF
Object Calisthenics Adapted for PHP
Chad Gray
 
PDF
Pim Elshoff "Technically DDD"
Fwdays
 
PDF
The IoC Hydra
Kacper Gunia
 
PDF
PHP for Adults: Clean Code and Object Calisthenics
Guilherme Blanco
 
PDF
Alexander Makarov "Let’s talk about code"
Fwdays
 
ODP
Symfony2, creare bundle e valore per il cliente
Leonardo Proietti
 
PDF
Rich Model And Layered Architecture in SF2 Application
Kirill Chebunin
 
PPTX
Web Security - Hands-on
Andrea Valenza
 
PDF
Advanced Php - Macq Electronique 2010
Michelangelo van Dam
 
PDF
Using Dojo
Richard Paul
 
PDF
Couchbase Korea User Group 2nd Meetup #2
won min jang
 
PDF
OSDC.fr 2012 :: Cascalog : progammation logique pour Hadoop
Publicis Sapient Engineering
 
PDF
The IoC Hydra - Dutch PHP Conference 2016
Kacper Gunia
 
PDF
Object Oriented Programming with PHP 5 - More OOP
Wildan Maulana
 
PDF
Xdebug confoo11
Bachkoutou Toutou
 
PDF
Design how your objects talk through mocking
Konstantin Kudryashov
 
PDF
Active Record Inheritance in Rails
Sandip Ransing
 
Rich domain model with symfony 2.5 and doctrine 2.5
Leonardo Proietti
 
Python advance
Mukul Kirti Verma
 
Designing a JavaFX Mobile application
Fabrizio Giudici
 
Object Calisthenics Adapted for PHP
Chad Gray
 
Pim Elshoff "Technically DDD"
Fwdays
 
The IoC Hydra
Kacper Gunia
 
PHP for Adults: Clean Code and Object Calisthenics
Guilherme Blanco
 
Alexander Makarov "Let’s talk about code"
Fwdays
 
Symfony2, creare bundle e valore per il cliente
Leonardo Proietti
 
Rich Model And Layered Architecture in SF2 Application
Kirill Chebunin
 
Web Security - Hands-on
Andrea Valenza
 
Advanced Php - Macq Electronique 2010
Michelangelo van Dam
 
Using Dojo
Richard Paul
 
Couchbase Korea User Group 2nd Meetup #2
won min jang
 
OSDC.fr 2012 :: Cascalog : progammation logique pour Hadoop
Publicis Sapient Engineering
 
The IoC Hydra - Dutch PHP Conference 2016
Kacper Gunia
 
Object Oriented Programming with PHP 5 - More OOP
Wildan Maulana
 
Xdebug confoo11
Bachkoutou Toutou
 
Design how your objects talk through mocking
Konstantin Kudryashov
 
Active Record Inheritance in Rails
Sandip Ransing
 

Viewers also liked (20)

PPTX
UnME jeans:Branding in web 2
Sameer Mathur
 
PDF
Rg 463
johnmichal1
 
ODP
una pequeña presentación
Rumus
 
PDF
MASAIGO
CARLOS FELIX
 
PDF
Ferrovial Investors Presentation Jan Sep 2014 | Presentación Inversores Ene S...
Ferrovial
 
DOCX
Helena Ruiz y Gustavo Lerma Evaluación de Recursos Web
Dielmer Fernando Giraldo Rendon
 
DOCX
Comparación entre skydrive, google drive, zoho docs y thinkfree
Senovia_Sarango
 
PPT
Presentación Isabel Muñoz-Presidenta del Consejo de Defensa de la Competencia...
CompetenciaAnd
 
PPT
medikamente-per-klick.de
Matthias M. Meringer
 
DOCX
La viejecita dichosa
HEBER ISRAIN PEREZ LOPEZ
 
PDF
NETMIND - Catálogo de Formación 2014 -2015
netmind
 
PDF
Fundamentos de mercadeo
Catalina Melo Chaves
 
PDF
Acord de govern centralitzacio tic
Antoni Davia
 
PDF
Story Studio l Storee1
제다이
 
PDF
Makefile Martial Arts - Chapter 1. The morning of creation
Quyen Le Van
 
PDF
Campaña Vanilla Dee Lite | LUSH
Benito Guerrero
 
PDF
Facebook Comparison Report of Top Casinos on the Las Vegas Strip
Unmetric
 
PDF
ARQUITECTURA DEL COMPUTADOR (Plantilla fase 1)
DvdM1
 
DOCX
Reporte sexualidad y familia
delacruzs
 
PDF
La imagen en la escuela Vs el texto
Marisa Elena Conde
 
UnME jeans:Branding in web 2
Sameer Mathur
 
Rg 463
johnmichal1
 
una pequeña presentación
Rumus
 
MASAIGO
CARLOS FELIX
 
Ferrovial Investors Presentation Jan Sep 2014 | Presentación Inversores Ene S...
Ferrovial
 
Helena Ruiz y Gustavo Lerma Evaluación de Recursos Web
Dielmer Fernando Giraldo Rendon
 
Comparación entre skydrive, google drive, zoho docs y thinkfree
Senovia_Sarango
 
Presentación Isabel Muñoz-Presidenta del Consejo de Defensa de la Competencia...
CompetenciaAnd
 
medikamente-per-klick.de
Matthias M. Meringer
 
La viejecita dichosa
HEBER ISRAIN PEREZ LOPEZ
 
NETMIND - Catálogo de Formación 2014 -2015
netmind
 
Fundamentos de mercadeo
Catalina Melo Chaves
 
Acord de govern centralitzacio tic
Antoni Davia
 
Story Studio l Storee1
제다이
 
Makefile Martial Arts - Chapter 1. The morning of creation
Quyen Le Van
 
Campaña Vanilla Dee Lite | LUSH
Benito Guerrero
 
Facebook Comparison Report of Top Casinos on the Las Vegas Strip
Unmetric
 
ARQUITECTURA DEL COMPUTADOR (Plantilla fase 1)
DvdM1
 
Reporte sexualidad y familia
delacruzs
 
La imagen en la escuela Vs el texto
Marisa Elena Conde
 
Ad

Similar to SOLID Ruby SOLID Rails (20)

KEY
SOLID Ruby, SOLID Rails
Jens-Christian Fischer
 
PDF
td_mxc_rubyrails_shin
tutorialsruby
 
PDF
td_mxc_rubyrails_shin
tutorialsruby
 
PDF
Ruby on Rails 101 - Presentation Slides for a Five Day Introductory Course
peter_marklund
 
PDF
Ruby on-rails-101-presentation-slides-for-a-five-day-introductory-course-1194...
Nilesh Panchal
 
PDF
Pragmatic Patterns of Ruby on Rails - Ruby Kaigi2009
Yasuko Ohba
 
PDF
Rails 3 Beautiful Code
GreggPollack
 
PDF
Connecting the Worlds of Java and Ruby with JRuby
Nick Sieger
 
KEY
Desarrollando aplicaciones web en minutos
Edgar Suarez
 
ZIP
Rails 3 (beta) Roundup
Wayne Carter
 
PDF
devise tutorial - 2011 rubyconf taiwan
Tse-Ching Ho
 
PDF
When To Use Ruby On Rails
dosire
 
KEY
Building Web Service Clients with ActiveModel
pauldix
 
KEY
Building Web Service Clients with ActiveModel
pauldix
 
PDF
Ruby on Rails 中級者を目指して - 大場寧子
Yasuko Ohba
 
PDF
OSDC 2009 Rails Turtorial
Yi-Ting Cheng
 
PDF
Ruby On Rails Introduction
Thomas Fuchs
 
PDF
Rails2 Pr
xibbar
 
KEY
Refactor like a boss
gsterndale
 
ZIP
Barcamp Auckland Rails3 presentation
Sociable
 
SOLID Ruby, SOLID Rails
Jens-Christian Fischer
 
td_mxc_rubyrails_shin
tutorialsruby
 
td_mxc_rubyrails_shin
tutorialsruby
 
Ruby on Rails 101 - Presentation Slides for a Five Day Introductory Course
peter_marklund
 
Ruby on-rails-101-presentation-slides-for-a-five-day-introductory-course-1194...
Nilesh Panchal
 
Pragmatic Patterns of Ruby on Rails - Ruby Kaigi2009
Yasuko Ohba
 
Rails 3 Beautiful Code
GreggPollack
 
Connecting the Worlds of Java and Ruby with JRuby
Nick Sieger
 
Desarrollando aplicaciones web en minutos
Edgar Suarez
 
Rails 3 (beta) Roundup
Wayne Carter
 
devise tutorial - 2011 rubyconf taiwan
Tse-Ching Ho
 
When To Use Ruby On Rails
dosire
 
Building Web Service Clients with ActiveModel
pauldix
 
Building Web Service Clients with ActiveModel
pauldix
 
Ruby on Rails 中級者を目指して - 大場寧子
Yasuko Ohba
 
OSDC 2009 Rails Turtorial
Yi-Ting Cheng
 
Ruby On Rails Introduction
Thomas Fuchs
 
Rails2 Pr
xibbar
 
Refactor like a boss
gsterndale
 
Barcamp Auckland Rails3 presentation
Sociable
 
Ad

More from Michael Mahlberg (20)

PDF
Heavyweight agile Processes? Let's make them leaner!
Michael Mahlberg
 
PDF
Skaliert Arbeiten statt zu skalieren - 2022.pdf
Michael Mahlberg
 
PDF
Agile Tuesday München: Was ist eigentlich aus Lean geworden?
Michael Mahlberg
 
PDF
Process-Tinder – Wenn ich mich nur nach den schönen Bildern entscheide…
Michael Mahlberg
 
PDF
Was ist aus dem L-Wort (in Lean Kanban) geworden?
Michael Mahlberg
 
PDF
Immer Ärger mit Jira - LWIPCGN#118 (2021)
Michael Mahlberg
 
PDF
Continuous Integration - I Don't Think That Word Means What You Think It Means
Michael Mahlberg
 
PDF
Flow – DAS Ziel von Kanban?
Michael Mahlberg
 
PDF
What's in a Story? Drei Ansätze, um mit Anforderungen gemeinsam erfolgreich z...
Michael Mahlberg
 
PDF
Lwipcgn#110 2020-die agilekeuleueberleben
Michael Mahlberg
 
PDF
The Trouble with Jira – TAG 2019
Michael Mahlberg
 
PDF
Michael Mahlberg - Leichtgewichtige Kanban-Metriken auf der LKCE 2018
Michael Mahlberg
 
PDF
Stances of Coaching - OOP2018
Michael Mahlberg
 
PDF
From ceremonies to events
Michael Mahlberg
 
PDF
The Product Owner's Survival Kit - ein Überblick [DE]
Michael Mahlberg
 
PDF
What coaching stances can do for you in Kanban settings...
Michael Mahlberg
 
PDF
A3 thinking - background, process and examples
Michael Mahlberg
 
PDF
Lws cologne leansoftwaredevelopment
Michael Mahlberg
 
PDF
Team models-t4 at2015
Michael Mahlberg
 
PDF
Ökonomie und Architektur als effektives Duo
Michael Mahlberg
 
Heavyweight agile Processes? Let's make them leaner!
Michael Mahlberg
 
Skaliert Arbeiten statt zu skalieren - 2022.pdf
Michael Mahlberg
 
Agile Tuesday München: Was ist eigentlich aus Lean geworden?
Michael Mahlberg
 
Process-Tinder – Wenn ich mich nur nach den schönen Bildern entscheide…
Michael Mahlberg
 
Was ist aus dem L-Wort (in Lean Kanban) geworden?
Michael Mahlberg
 
Immer Ärger mit Jira - LWIPCGN#118 (2021)
Michael Mahlberg
 
Continuous Integration - I Don't Think That Word Means What You Think It Means
Michael Mahlberg
 
Flow – DAS Ziel von Kanban?
Michael Mahlberg
 
What's in a Story? Drei Ansätze, um mit Anforderungen gemeinsam erfolgreich z...
Michael Mahlberg
 
Lwipcgn#110 2020-die agilekeuleueberleben
Michael Mahlberg
 
The Trouble with Jira – TAG 2019
Michael Mahlberg
 
Michael Mahlberg - Leichtgewichtige Kanban-Metriken auf der LKCE 2018
Michael Mahlberg
 
Stances of Coaching - OOP2018
Michael Mahlberg
 
From ceremonies to events
Michael Mahlberg
 
The Product Owner's Survival Kit - ein Überblick [DE]
Michael Mahlberg
 
What coaching stances can do for you in Kanban settings...
Michael Mahlberg
 
A3 thinking - background, process and examples
Michael Mahlberg
 
Lws cologne leansoftwaredevelopment
Michael Mahlberg
 
Team models-t4 at2015
Michael Mahlberg
 
Ökonomie und Architektur als effektives Duo
Michael Mahlberg
 

Recently uploaded (20)

PPTX
care of patient with elimination needs.pptx
Rekhanjali Gupta
 
PDF
I3PM Industry Case Study Siemens on Strategic and Value-Oriented IP Management
MIPLM
 
PPTX
Introduction to Indian Writing in English
Trushali Dodiya
 
PDF
Is Assignment Help Legal in Australia_.pdf
thomas19williams83
 
PPTX
Different types of inheritance in odoo 18
Celine George
 
PPTX
Post Dated Cheque(PDC) Management in Odoo 18
Celine George
 
PPTX
Ward Management: Patient Care, Personnel, Equipment, and Environment.pptx
PRADEEP ABOTHU
 
PDF
WATERSHED MANAGEMENT CASE STUDIES - ULUGURU MOUNTAINS AND ARVARI RIVERpdf
Ar.Asna
 
PPTX
AIMA UCSC-SV Leadership_in_the_AI_era 20250628 v16.pptx
home
 
PDF
STATEMENT-BY-THE-HON.-MINISTER-FOR-HEALTH-ON-THE-COVID-19-OUTBREAK-AT-UG_revi...
nservice241
 
PPTX
Light Reflection and Refraction- Activities - Class X Science
SONU ACADEMY
 
PPT
Indian Contract Act 1872, Business Law #MBA #BBA #BCOM
priyasinghy107
 
PDF
epi editorial commitee meeting presentation
MIPLM
 
PDF
Vani - The Voice of Excellence - Jul 2025 issue
Savipriya Raghavendra
 
PDF
IMPORTANT GUIDELINES FOR M.Sc.ZOOLOGY DISSERTATION
raviralanaresh2
 
PPTX
How to Manage Expiry Date in Odoo 18 Inventory
Celine George
 
PPTX
How to Create a Customer From Website in Odoo 18.pptx
Celine George
 
PDF
Android Programming - Basics of Mobile App, App tools and Android Basics
Kavitha P.V
 
PPTX
How to Configure Re-Ordering From Portal in Odoo 18 Website
Celine George
 
PPTX
Difference between write and update in odoo 18
Celine George
 
care of patient with elimination needs.pptx
Rekhanjali Gupta
 
I3PM Industry Case Study Siemens on Strategic and Value-Oriented IP Management
MIPLM
 
Introduction to Indian Writing in English
Trushali Dodiya
 
Is Assignment Help Legal in Australia_.pdf
thomas19williams83
 
Different types of inheritance in odoo 18
Celine George
 
Post Dated Cheque(PDC) Management in Odoo 18
Celine George
 
Ward Management: Patient Care, Personnel, Equipment, and Environment.pptx
PRADEEP ABOTHU
 
WATERSHED MANAGEMENT CASE STUDIES - ULUGURU MOUNTAINS AND ARVARI RIVERpdf
Ar.Asna
 
AIMA UCSC-SV Leadership_in_the_AI_era 20250628 v16.pptx
home
 
STATEMENT-BY-THE-HON.-MINISTER-FOR-HEALTH-ON-THE-COVID-19-OUTBREAK-AT-UG_revi...
nservice241
 
Light Reflection and Refraction- Activities - Class X Science
SONU ACADEMY
 
Indian Contract Act 1872, Business Law #MBA #BBA #BCOM
priyasinghy107
 
epi editorial commitee meeting presentation
MIPLM
 
Vani - The Voice of Excellence - Jul 2025 issue
Savipriya Raghavendra
 
IMPORTANT GUIDELINES FOR M.Sc.ZOOLOGY DISSERTATION
raviralanaresh2
 
How to Manage Expiry Date in Odoo 18 Inventory
Celine George
 
How to Create a Customer From Website in Odoo 18.pptx
Celine George
 
Android Programming - Basics of Mobile App, App tools and Android Basics
Kavitha P.V
 
How to Configure Re-Ordering From Portal in Odoo 18 Website
Celine George
 
Difference between write and update in odoo 18
Celine George
 

SOLID Ruby SOLID Rails

  • 1. Michael Mahlberg, Consulting Guild AG Jens-Christian Fischer, InVisible GmbH SOLID Ruby - SOLID Rails Establishing a sustainable codebase 1
  • 2. Who? Michael Mahlberg 2
  • 3. Founder of roughly a dozen companies over the last two decades 3
  • 5. Working as A consultant on software processes, architecture & design for > 2 decades 5
  • 6. >> relevance != nil => true 6
  • 7. Who? Jens-Christian Fischer 7
  • 8. Tinkerer, Practician, Author and generally interested in way too many things 8
  • 10. SOLID is not a Law 10
  • 11. PPP (by Robert C. Martin) Agile Software Development, Principles, Patterns, and Practices 11
  • 12. Principles! You know - more like guidelines 12
  • 13. SOLID SRP OCP LSP ISP DIP 13
  • 14. S OL I D SRP OCP LSP ISP DIP 14
  • 15. SRP Single Responsibility Principle A class should have one, and only one, reason to change. 15
  • 16. require  'digest/sha1' class  User  <  ActiveRecord::Base    include  Authentication User Class    include  Authentication::ByPassword    include  Authentication::ByCookieToken    #TODO  Check  login  redirect  if  this  filter  is  skipped    #skip_after_filter  :store_location    #  Virtual  attribute  for  the  unencrypted  password    attr_accessor  :password    belongs_to  :country    has_one  :user_profile,  :dependent  =>  :destroy    has_one  :note            has_many  :queries    has_many  :tags,  :foreign_key  =>  "created_by"    has_many  :taggings,  :as  =>  :tagger    has_many  :organizations,  :through  =>  :affiliations    has_many  :affiliations    has_many  :locations,  :through  =>  :affiliations    has_many  :projects,  :through  =>  :memberships    has_many  :memberships    has_many  :public_assets,  :through  =>  :privileges    has_many  :descriptions,  :through  =>  :privileges    has_many  :assessments,  :through  =>  :privileges    has_many  :description_profiles,  :through  =>  :privileges    has_many  :privileges    has_many  :diaries    has_many  :roles,  :through  =>  :commitments    has_many  :commitments    has_many  :activities    has_many  :messages    has_many  :fellowships    has_many  :user_groups,  :through  =>  :fellowships    has_many  :survey_responses       16
  • 17. So what‘s wrong with this? 17
  • 18. From: user.rb class User < ActiveRecord::Base include Authentication include Authentication::ByPassword include Authentication::ByCookieToken ... belongs_to :country ... has_one :user_profile, :dependent => :destroy has_many :queries has_many :tags, :foreign_key => "created_by" ... validates_presence_of :login, :email, :country_id validates_presence_of :password, :if => :password_required? ... 18
  • 19. From: user.rb acts_as_state_machine :initial => :pending state :pending, :enter => :make_activation_code state :active, :enter => :do_activate ... event :register do transitions :from => :passive, :to => :pending, :guard => Proc.new {|u| !(u.crypted_password.blank? && u.password.blank?) } end ... def message_threads self.message_threads + self.message_threads end 19
  • 20. From: user.rb def forum_nickname self.user_profile.nickname.blank? ? "#{self.first_name} # {self.last_name}" : self.user_profile.nickname end def name "#{self.first_name} #{self.last_name}" rescue 'n/a' end def email_with_name "#{self.first_name} #{self.last_name} <#{self.email}>" end 20
  • 21. From: user.rb def is_admin? self.roles.collect{|role| role.title}.include?('admin') end def countries [self.country] end 21
  • 22. From: user.rb def boards Board.all :conditions => { :user_group_id => self.user_groups.collect{ |g| g.id }} end def discussions Discussion.all :conditions => { :board_id => self.boards.collect{ |b| b.id }} end def organization_roles role_ids = Affiliation.all(:conditions => {:user_id => self.id}).collect{|a| a.role_id}.uniq roles = Role.find(role_ids) end 22
  • 23. From: user.rb def make_password_reset_code self.password_reset_code = Digest::SHA1.hexdigest ( Time.now.to_s.split(//).sort_by {rand}.join ) end def self.published_users User.all(:conditions => ['state = ?', 'published'], :order => 'login ASC', :include => [:user_profile]) end 23
  • 24. Anyone notice a pattern? 24
  • 27. Authentication Roles Mailers State ... 27
  • 28. So how? Mixins 28
  • 29. New User Model class User < ActiveRecord::Base include Authentication include Authentication::ByPassword include Authentication::ByCookieToken include Project::UserStates include Project::UserMailer include Project::UserForum include Project::UserMessages ... end 29
  • 30. UserMessages module Project module UserMessages # to be included in User Model has_many :messages def message_threads MessageThread.all(:conditions => ["sender_id = ? or receiver_id = ?", self.id, self.id]) end end end 30
  • 31. Methods 31
  • 32. def transfer(data, url) h = Net::HTTP.new(self.uri.host, self.uri.port) RAILS_DEFAULT_LOGGER.debug "connecting to CL: #{self.uri}" RAILS_DEFAULT_LOGGER.debug "connecting to CL: #{url}" resp = h.post(url, data, {'Content-Type' => 'application/xml'}) response_code = resp.code.to_i location = if response_code == 201 resp['Location'] else RAILS_DEFAULT_LOGGER.debug "error from CL: #{response_code}" RAILS_DEFAULT_LOGGER.debug "error from CL: #{resp.body}" @error = resp.body nil end [response_code, location] end 32
  • 33. def transfer(data, document) if document.cl_document_url != nil self.uri = URI.parse(document.cl_document_url ) h = Net::HTTP.new(self.uri.host, self.uri.port) response = h.post(self.uri, data, {'Content-Type' => 'application/xml'}) else h = Net::HTTP.new(self.uri.host, self.uri.port) response = h.post("/tasks", data, {'Content-Type' => 'application/xml'}) end response_code = response.code.to_i if response_code == 201 location = response['Location'] document.cl_document_url = location document.save! else nil end [response_code, location] end 33
  • 34. SRP Transfer def transfer data open_connection post data return location end def open_connection @http = Net::HTTP.new(self.uri.host, self.uri.port) end def post data @response = http.post(self.url, data, {'Content-Type' => 'application/xml'}) end 34
  • 35. def location get_location if created? # returns nil if not created? end def response_code @response.code.to_i end def created? response_code == 201 end def get_location @response['Location'] end def error @response.body end 35
  • 36. Add a 16-band equalizer & a BlueRay player to this... 36
  • 37. And now to this... 37
  • 38. S OL I D SRP OCP LSP ISP DIP 38
  • 39. OCP Open Closed Principle You should be able to extend a classes behavior, without modifying it. 39
  • 40. 40
  • 41. 41
  • 42. 42
  • 43. 43
  • 44. def makemove(map) From the Google x, y = map.my_position # calculate a move ... AI Challenge if(valid_moves.size == 0) map.make_move( :NORTH ) (Tronbot) else # choose move ... puts move # debug (like in the old days) map.make_move( move ) end end class Map ... def make_move(direction) $stdout << ({:NORTH=>1, :SOUTH=>3, :EAST=>2, :WEST=>4}[direction]) $stdout << "n" $stdout.flush end end 44
  • 45. From the Google AI Challenge (Tronbot) def puts(*args) $stderr.puts *args end def p(*args) args.map!{|arg| arg.inspect} puts args end def print(*args) $stderr.print *args end 45
  • 47. class Outputter def initialize(io = $stderr) @io = io end def puts(*args) @io.puts *args end ... end out = Outputter.new out.puts "Testing" 47
  • 48. S OL I D SRP OCP LSP ISP DIP 48
  • 49. LSP Liskov Substitution Principle Derived classes must be substitutable for their base classes. 49
  • 50. No Problem in Ruby Or so it seems... 50
  • 51. No Interface... no problem? 51
  • 52. Wrong ! 52
  • 54. A square is a rectangle 54
  • 55. Rectangle setX setY Square setX setY 55
  • 56. Rectange >> class Rectangle >> attr_accessor :width, :height >> end => nil >> ?> shape = Rectangle.new => #<Rectangle:0x10114fad0> >> shape.width => nil >> shape.width=3 >> shape.width => 3 >> shape.height=5 >> shape.height => 5 >> shape.width => 3 56
  • 57. Square >> class Square ?> def width >> @dimension ?> shape = Square.new >> end => #<Square:0x101107e88> ?> def height ?> puts shape.width >> @dimension nil >> end ?> shape.width=3 ?> def width= n => 3 >> @dimension = n ?> shape.width >> end => 3 ?> def height= n ?> shape.height >> @dimension = n => 3 >> end >> end 57
  • 58. A Problem... >> s = [Rectangle.new, Square.new] => [#<Rectangle:0x1005642e8>, #<Square:0x100564298>] >> a_rectangle = s[rand(2)] => #<Square:0x100564298> >> a_rectangle.height=1 => 1 >> a_rectangle.width=3 => 3 Text >> a_rectangle.height => 3 58
  • 59. CCD Common Conceptual Denominator 59
  • 60. dup 60
  • 61. irb 1:0> 5.respond_to? :dup => true irb 2:0> 5.dup TypeError: can't dup Fixnum from (irb):1:in `dup' from (irb):1 irb 3:0> http://blog.objectmentor.com/articles/2007/02/17/ liskov-substitution-principle-and-the-ruby-core-libraries 61
  • 62. S OL I D SRP OCP LSP ISP DIP 62
  • 63. ISP Interface Segregation Principle Make fine grained interfaces that are client specific. 63
  • 64. 64
  • 65. Users Controller class UsersController < ApplicationController ssl_required :new, :create, :edit, :update, :destroy, :activate, :change_passwort, :forgot_password, :reset_password, :make_profile, :my_contacts ssl_allowed :eula, :index, :show access_control [:suspend, :unsuspend, :destroy, :purge, :delete, :admin, :ban, :remove_ban] => 'admin' before_filter :find_user skip_after_filter :store_location def show unless @user == current_user redirect_to access_denied_path(@locale) else respond_to do |format| format.html format.js { render :partial => "users/#{@context.title}/#{@partial}" } end end end ... 65
  • 66. more UsersController def activate logout_keeping_session! user = User.find_by_activation_code(params[:activation_code]) unless params[:activation_code].blank? case when (!params[:activation_code].blank?) && user && !user.active? user.activate! flash[:notice] = t(:message_sign_up_complete) unless params[:context].blank? redirect_to login_path(:context => params[:context]) else redirect_to "/login" end when params[:activation_code].blank? flash[:error] = t(:message_activation_code_missing) redirect_back_or_default("/") else flash[:error] = t(:message_user_with_that_activation_code_missing) redirect_back_or_default("/") end end 66
  • 67. User Class Revisited class User < ActiveRecord::Base ... end class Registration < ActiveRecord::Base set_table_name "users" acts_as_state_machine :initial => :pending state :pending, :enter => :make_activation_code state :active, :enter => :do_activate ... event :activate do transitions :from => :pending, :to => :active end ... end 67
  • 68. class RegistrationController < ApplicationController ... def activate logout_keeping_session! code_is_blank = params[:activation_code].blank? registration = Registration.find_by_activation_code(params [:activation_code]) unless code_is_blank case when (!code_is_blank) && registration && !registratio.active? registration.activate! flash[:notice] = t(:message_sign_up_complete) unless params[:context].blank? redirect_to login_path(:context => params[:context]) else redirect_to "/login" end when code_is_blank flash[:error] = t(:message_activation_code_missing) redirect_back_or_default("/") else flash[:error] = t(:message_user_with_that_activation_code_missing) redirect_back_or_default("/") end end ... end 68
  • 69. S OL I D SRP OCP LSP ISP DIP 69
  • 70. DIP Dependency Inversion Principle Depend on abstractions, not on concretions. 70
  • 71. 71
  • 72. From our OCP example to DIP out = Outputter.new out.puts "Testing" 72
  • 73. The code we wish we had class TronBot def initialize @@out = TRON_ENVIRONMENT[:debugger] end def some_method ... @@out.puts "Testing" ... end end 73
  • 74. TSTTCPW TRON_ENVIRONMENT = { :debugger => Outputter.new ($stderr), :game_engine => Outputter.new ($stdout), :user_io => Outputter.new ($stderr) } 74
  • 75. Later... TRON_ENVIRONMENT = { :debugger => Outputter.new ($stderr), :game_engine => Outputter.new (TCP_OUTPUTTER), :user_io => Outputter.new ($stderr) } 75
  • 76. DIP Violation in Controller format.js do render :update do |page| if @parent_object.class == EspGoal @esp_goal_descriptor = @current_object page.replace_html "descriptor_#{@current_object.id}", :partial => "edit_esp_goal_descriptor", :locals => {:esp_goal_descriptor => @esp_goal_descriptor, :parent_object => @parent_object} else @goal_descriptor = @current_object page.replace_html "descriptor_#{@current_object.id}", :partial => "edit_goal_descriptor", :locals => {:goal_descriptor => @goal_descriptor, :parent_object => @parent_object} end end end 76
  • 77. DIP Violation in Controller format.js do render :update do |page| if @parent_object.class == EspGoal @esp_goal_descriptor = @current_object page.replace_html "descriptor_#{@current_object.id}", :partial => "edit_esp_goal_descriptor", :locals => {:esp_goal_descriptor => @esp_goal_descriptor, :parent_object => @parent_object} else if @parent_object.class == Goal @goal_descriptor = @current_object page.replace_html "descriptor_#{@current_object.id}", :partial => "edit_goal_descriptor", :locals => {:goal_descriptor => @goal_descriptor, :parent_object => @parent_object} else if @parent_object.class == LearningGoal ... ... end end end 77
  • 78. 78
  • 79. 1st Refactoring def show ... format.js do render :update do |page| page.replace_html "descriptor_#{@current_object.id}", @parent_object.page_replacement(@current_object) end end end class EspGoal def page_replacement child { :partial => "edit_esp_goal_descriptor", :locals => {:esp_goal_descriptor => child, :parent_object => self} } end end class Goal def page_replacement child { :partial => "edit_goal_descriptor", :locals => {:goal_descriptor => child, :parent_object => self} } end end 79
  • 80. 80
  • 81. 2nd Refactoring (wiring) class PartialContainer def add class_symbol, partial_replacement @@partinal_replacements.add( class_symbol => partial_replacement) end def self.partial_replacement an_object unless @@partial_replacments self.add( EspGoalReplacement.my_class_sym, EspGoalReplacment.new) self.add( GoalReplacement.my_class_sym, GoalReplacment.new) end @@partial_replacement[an_object.class] end end 81
  • 82. class EspGoalReplacmenent 2nd Refactoring def self.my_class_sym end EspGoal.to_sym (Behaviour) def partial_definition child { :partial => "edit_esp_goal_descriptor", :locals => {:esp_goal_descriptor => child, :parent_object => child.esp_goal} } end end class GoalReplacmenent def self.my_class_sym Goal.to_sym end def partial_definition child { :partial => "edit_goal_descriptor", :locals => {:goal_descriptor => child, :parent_object => child.goal} } end end 82
  • 83. DIP Violation in Controller format.js do render :update do |page| if @parent_object.class == EspGoal @esp_goal_descriptor = @current_object page.replace_html "descriptor_#{@current_object.id}", :partial => "edit_esp_goal_descriptor", :locals => {:esp_goal_descriptor => @esp_goal_descriptor, :parent_object => @parent_object} else @goal_descriptor = @current_object page.replace_html "descriptor_#{@current_object.id}", :partial => "edit_goal_descriptor", :locals => {:goal_descriptor => @goal_descriptor, :parent_object => @parent_object} end end end 83
  • 84. 2nd Refactoring - the Controller - def show ... format.js do render :update do |page| page.replace_html "descriptor_#{@current_object.id}", PartialContainer.partial_replacement(@parent_object). partial_definition(@current_object) end end end 84
  • 85. 85
  • 86. SOLID SRP OCP LSP ISP DIP 86
  • 87. SRP OCP LSP ISP DIP 87
  • 88. Questions? S OL ID SRP OCP LSP ISP DIP 88
  • 93. Jens-Christian Fischer Michael Mahlberg InVisible GmbH Consulting Guild AG @jcfischer @MMahlberg [email protected] [email protected] http://blog.invisible.ch http://agile-aspects.blogspot.com 93 93