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

SOLID Ruby -
SOLID Rails
Establishing a sustainable
codebase
Who?

       Michael Mahlberg
Founder of

     roughly a dozen companies
     over the last two decades
>> relevance
=> nil
Working as

     A consultant on software
     processes, architecture &
     design for > 2 decades
>> relevance != nil
=> true
Who?

       Jens-Christian Fischer
Tinkerer, Practician,
      Author
            and generally
          interested in way
           too many things
What is
SOLID?
SOLID
  is
 not
  a
 Law
PPP
(by Robert C. Martin)




                        Agile Software
                        Development,
                        Principles, Patterns,
                        and Practices
Principles!

        You know - more
         like guidelines
SOLID
SOLID

SRP OCP L
S   O   LSP I
            ISP DIP
                D
S OL I D

SRP OCP LSP ISP DIP
SRP
SRP
      Single
      Responsibility
      Principle
SRP
      Single
      Responsibility
      Principle


                   A class should have
                   one, and only one,
                   reason to change.
User Class
require 'digest/sha1'

class User < ActiveRecord::Base
 include Authentication
 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
end

def editable_by?(user=nil)
 self.created_by == user
end

def readable_by?(user=nil)
 self.created_by == user
end

def boards
                               User Class
 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

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

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

def all_roles
 roles = (self.organization_roles + self.user_group_roles + self.project_roles).uniq
end

def tags_of(user)
 taggings = Tagging.all :conditions => {:taggable_type => "User", :taggable_id => self.id, :tagger_type => "User", :tagge
 tags = taggings.collect {|t| t.tag.name}.uniq.sort
end
So what‘s wrong with
        this?
From: user.rb
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?
...
From: user.rb
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
From: user.rb
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
From: user.rb
From: user.rb
def is_admin?
  self.roles.collect{|role| role.title}.include?('admin')
end

def countries
  [self.country]
end
From: user.rb
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
From: user.rb
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
Anyone notice a pattern?
Neither do we
Separation of Concerns
Authentication
    Roles
   Mailers
    State
     ...
So how?


  Mixins
New User Model
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
UserMessages
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
Methods
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
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
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
SRP Transfer
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
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
Add a 16-band
 equalizer & a
   BlueRay®
player to this...
And now to
  this...
S OL I D

SRP OCP LSP ISP DIP
OCP
OCP
  Open
  Closed
  Principle
OCP
  Open
  Closed
  Principle


              You should be able
              to extend a classes
              behavior, without
              modifying it.
From the Google
  AI Challenge
   (Tronbot)
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
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
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
From the Google AI Challenge (Tronbot)
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
Design Sketch
class Outputter

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

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

  ...
end

out = Outputter.new
out.puts "Testing"
S OL I D

SRP OCP LSP ISP DIP
LSP
LSP
      Liskov
      Substitution
      Principle
LSP
      Liskov
      Substitution
      Principle


                     Derived classes
                     must be substitutable
                     for their base
                     classes.
No Problem
  in Ruby

        Or so it seems...
No Interface...

            no problem?
Wrong !
The classic violation
A square is a rectangle
Rectangle

setX
setY




        Square

setX
setY
Rectangle
Rectangle
>>   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
Square

    ?> shape = Square.new
    => #<Square:0x101107e88>
    ?> puts shape.width
    nil
    ?> shape.width=3
    => 3
    ?> shape.width
    => 3
    ?> shape.height
    => 3
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
A Problem...



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




                             Text
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
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
CCD Common Conceptual
     Denominator
dup
http://blog.objectmentor.com/articles/2007/02/17/
liskov-substitution-principle-and-the-ruby-core-libraries
irb 1:0> 5.respond_to? :dup




          http://blog.objectmentor.com/articles/2007/02/17/
          liskov-substitution-principle-and-the-ruby-core-libraries
irb 1:0> 5.respond_to? :dup
=> true




          http://blog.objectmentor.com/articles/2007/02/17/
          liskov-substitution-principle-and-the-ruby-core-libraries
irb 1:0> 5.respond_to? :dup
=> true
irb 2:0> 5.dup




          http://blog.objectmentor.com/articles/2007/02/17/
          liskov-substitution-principle-and-the-ruby-core-libraries
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
S OL I D

SRP OCP LSP ISP DIP
ISP
ISP
      Interface
      Segregation
      Principle
ISP
      Interface
      Segregation
      Principle


                    Make fine grained
                    interfaces that are
                    client specific.
Users Controller
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
...
more UsersController
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
User Class Revisited
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
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
 ...
S OL I D

SRP OCP LSP ISP DIP
DIP
DIP
      Dependency
      Inversion
      Principle
DIP
      Dependency
      Inversion
      Principle


                   Depend on
                   abstractions, not on
                   concretions.
From our OCP example to DIP
From our OCP example to DIP



out = Outputter.new
out.puts "Testing"
The code we wish we had
The code we wish we had
class TronBot
  def initialize
    @@out = TRON_ENVIRONMENT[:debugger]
  end

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

end
TSTTCPW
TSTTCPW


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


TRON_ENVIRONMENT = {
        :debugger => Outputter.new ($stderr),
        :game_engine => Outputter.new (TCP_OUTPUTTER),
        :user_io => Outputter.new ($stderr)
        }
DIP Violation in Controller
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
DIP Violation in Controller
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
1st Refactoring
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
2nd Refactoring
(Behaviour)
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
2nd Refactoring
(wiring)
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
DIP Violation in Controller
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
2nd Refactoring
- the Controller -
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
SOLID
SOLID

SRP OCP LSP ISP DIP
SRP OCP LSP ISP DIP
S OL ID

SRP OCP LSP ISP DIP
S OL ID

SRP OCP LSP ISP DIP
S OL ID

SRP OCP LSP ISP DIP
S OL ID

SRP OCP LSP ISP DIP
S OL ID

SRP OCP LSP ISP DIP
S O L I D Questions?


SRP OCP LSP ISP DIP
Domo Arigato
Coding Dojo
           Wednesday 11:10
              Salon 3
The Way of the carpenter is to
become proficient in the use of
his tools, first to lay his plans with a
true measure and then perform his
work according to plan.

                   – Go Rin No Sho
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/


                                                                        91
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/




                                                                  92
Lizense


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




                                         93
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




                                                             94
Get the Presentation
http://github.com/MichaelMahlberg/RailsWayCon2010




                                                    95
96

More Related Content

What's hot

Java Hibernate Programming with Architecture Diagram and Example
Java Hibernate Programming with Architecture Diagram and ExampleJava Hibernate Programming with Architecture Diagram and Example
Java Hibernate Programming with Architecture Diagram and Examplekamal kotecha
 
Manipulating Android tasks and back stack
Manipulating Android tasks and back stackManipulating Android tasks and back stack
Manipulating Android tasks and back stackRan Nachmany
 
[DL輪読会]Autonomous Reinforcement Learning: Formalism and Benchmarking
[DL輪読会]Autonomous Reinforcement Learning: Formalism and Benchmarking[DL輪読会]Autonomous Reinforcement Learning: Formalism and Benchmarking
[DL輪読会]Autonomous Reinforcement Learning: Formalism and BenchmarkingDeep Learning JP
 
[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기
[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기
[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기NAVER Engineering
 
Introduction to Koltin for Android Part I
Introduction to Koltin for Android Part I Introduction to Koltin for Android Part I
Introduction to Koltin for Android Part I Atif AbbAsi
 
object detection with lidar-camera fusion: survey (updated)
object detection with lidar-camera fusion: survey (updated)object detection with lidar-camera fusion: survey (updated)
object detection with lidar-camera fusion: survey (updated)Takuya Minagawa
 
RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)Tracy Lee
 
R-CNNの原理とここ数年の流れ
R-CNNの原理とここ数年の流れR-CNNの原理とここ数年の流れ
R-CNNの原理とここ数年の流れKazuki Motohashi
 
【DL輪読会】Novel View Synthesis with Diffusion Models
【DL輪読会】Novel View Synthesis with Diffusion Models【DL輪読会】Novel View Synthesis with Diffusion Models
【DL輪読会】Novel View Synthesis with Diffusion ModelsDeep Learning JP
 
Testing Spring Boot application in post-JUnit 4 world
Testing Spring Boot application in post-JUnit 4 worldTesting Spring Boot application in post-JUnit 4 world
Testing Spring Boot application in post-JUnit 4 worldYura Nosenko
 
React + Redux + TypeScript === ♥
React + Redux + TypeScript === ♥React + Redux + TypeScript === ♥
React + Redux + TypeScript === ♥Remo Jansen
 
画像生成AI stable diffusionの紹介
画像生成AI stable diffusionの紹介画像生成AI stable diffusionの紹介
画像生成AI stable diffusionの紹介iPride Co., Ltd.
 
React for Re-use: Creating UI Components with Confluence Connect
React for Re-use: Creating UI Components with Confluence ConnectReact for Re-use: Creating UI Components with Confluence Connect
React for Re-use: Creating UI Components with Confluence ConnectAtlassian
 
Unreal Engine Basics 03 - Gameplay
Unreal Engine Basics 03 - GameplayUnreal Engine Basics 03 - Gameplay
Unreal Engine Basics 03 - GameplayNick Pruehs
 
SP1: Exploratory Network Analysis with Gephi
SP1: Exploratory Network Analysis with GephiSP1: Exploratory Network Analysis with Gephi
SP1: Exploratory Network Analysis with GephiJohn Breslin
 
Gitflow - Branching and Merging Flow for Git
Gitflow - Branching and Merging Flow for GitGitflow - Branching and Merging Flow for Git
Gitflow - Branching and Merging Flow for GitMaulik Shah
 
私は如何にして心配するのを止めてPyTorchを愛するようになったか
私は如何にして心配するのを止めてPyTorchを愛するようになったか私は如何にして心配するのを止めてPyTorchを愛するようになったか
私は如何にして心配するのを止めてPyTorchを愛するようになったかYuta Kashino
 
[db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by 株式会社HPCソリューションズ ...
[db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by 株式会社HPCソリューションズ ...[db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by 株式会社HPCソリューションズ ...
[db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by 株式会社HPCソリューションズ ...Insight Technology, Inc.
 

What's hot (20)

react-slides.pptx
react-slides.pptxreact-slides.pptx
react-slides.pptx
 
Java Hibernate Programming with Architecture Diagram and Example
Java Hibernate Programming with Architecture Diagram and ExampleJava Hibernate Programming with Architecture Diagram and Example
Java Hibernate Programming with Architecture Diagram and Example
 
Manipulating Android tasks and back stack
Manipulating Android tasks and back stackManipulating Android tasks and back stack
Manipulating Android tasks and back stack
 
[DL輪読会]Autonomous Reinforcement Learning: Formalism and Benchmarking
[DL輪読会]Autonomous Reinforcement Learning: Formalism and Benchmarking[DL輪読会]Autonomous Reinforcement Learning: Formalism and Benchmarking
[DL輪読会]Autonomous Reinforcement Learning: Formalism and Benchmarking
 
[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기
[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기
[12]MVVM과 Grab Architecture : MVVM에 가기 위한 여행기
 
Introduction to Koltin for Android Part I
Introduction to Koltin for Android Part I Introduction to Koltin for Android Part I
Introduction to Koltin for Android Part I
 
object detection with lidar-camera fusion: survey (updated)
object detection with lidar-camera fusion: survey (updated)object detection with lidar-camera fusion: survey (updated)
object detection with lidar-camera fusion: survey (updated)
 
RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)RxJS Operators - Real World Use Cases (FULL VERSION)
RxJS Operators - Real World Use Cases (FULL VERSION)
 
Recycler view
Recycler viewRecycler view
Recycler view
 
R-CNNの原理とここ数年の流れ
R-CNNの原理とここ数年の流れR-CNNの原理とここ数年の流れ
R-CNNの原理とここ数年の流れ
 
【DL輪読会】Novel View Synthesis with Diffusion Models
【DL輪読会】Novel View Synthesis with Diffusion Models【DL輪読会】Novel View Synthesis with Diffusion Models
【DL輪読会】Novel View Synthesis with Diffusion Models
 
Testing Spring Boot application in post-JUnit 4 world
Testing Spring Boot application in post-JUnit 4 worldTesting Spring Boot application in post-JUnit 4 world
Testing Spring Boot application in post-JUnit 4 world
 
React + Redux + TypeScript === ♥
React + Redux + TypeScript === ♥React + Redux + TypeScript === ♥
React + Redux + TypeScript === ♥
 
画像生成AI stable diffusionの紹介
画像生成AI stable diffusionの紹介画像生成AI stable diffusionの紹介
画像生成AI stable diffusionの紹介
 
React for Re-use: Creating UI Components with Confluence Connect
React for Re-use: Creating UI Components with Confluence ConnectReact for Re-use: Creating UI Components with Confluence Connect
React for Re-use: Creating UI Components with Confluence Connect
 
Unreal Engine Basics 03 - Gameplay
Unreal Engine Basics 03 - GameplayUnreal Engine Basics 03 - Gameplay
Unreal Engine Basics 03 - Gameplay
 
SP1: Exploratory Network Analysis with Gephi
SP1: Exploratory Network Analysis with GephiSP1: Exploratory Network Analysis with Gephi
SP1: Exploratory Network Analysis with Gephi
 
Gitflow - Branching and Merging Flow for Git
Gitflow - Branching and Merging Flow for GitGitflow - Branching and Merging Flow for Git
Gitflow - Branching and Merging Flow for Git
 
私は如何にして心配するのを止めてPyTorchを愛するようになったか
私は如何にして心配するのを止めてPyTorchを愛するようになったか私は如何にして心配するのを止めてPyTorchを愛するようになったか
私は如何にして心配するのを止めてPyTorchを愛するようになったか
 
[db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by 株式会社HPCソリューションズ ...
[db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by 株式会社HPCソリューションズ ...[db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by 株式会社HPCソリューションズ ...
[db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by 株式会社HPCソリューションズ ...
 

Similar to SOLID Ruby, SOLID Rails

Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Railsrstankov
 
Desarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosDesarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosEdgar Suarez
 
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Coupa Software
 
Venturing Into The Wild: A .NET Developer's Experience As A Ruby Developer
Venturing Into The Wild: A .NET Developer's Experience As A Ruby DeveloperVenturing Into The Wild: A .NET Developer's Experience As A Ruby Developer
Venturing Into The Wild: A .NET Developer's Experience As A Ruby DeveloperJon Kruger
 
Tame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapperTame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapperGiordano Scalzo
 
Ruby on rails
Ruby on rails Ruby on rails
Ruby on rails Mohit Jain
 
Introduction to Active Record at MySQL Conference 2007
Introduction to Active Record at MySQL Conference 2007Introduction to Active Record at MySQL Conference 2007
Introduction to Active Record at MySQL Conference 2007Rabble .
 
Intro to Ruby - Twin Cities Code Camp 7
Intro to Ruby - Twin Cities Code Camp 7Intro to Ruby - Twin Cities Code Camp 7
Intro to Ruby - Twin Cities Code Camp 7Brian Hogan
 
Beyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the codeBeyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the codeWim Godden
 
Extreme Swift
Extreme SwiftExtreme Swift
Extreme SwiftMovel
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelpauldix
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelpauldix
 
Rails World 2023: Powerful Rails Features You Might Not Know
Rails World 2023: Powerful Rails Features You Might Not KnowRails World 2023: Powerful Rails Features You Might Not Know
Rails World 2023: Powerful Rails Features You Might Not KnowChris Oliver
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weiboshaokun
 
Beyond php it's not (just) about the code
Beyond php   it's not (just) about the codeBeyond php   it's not (just) about the code
Beyond php it's not (just) about the codeWim Godden
 
Introduction to Active Record - Silicon Valley Ruby Conference 2007
Introduction to Active Record - Silicon Valley Ruby Conference 2007Introduction to Active Record - Silicon Valley Ruby Conference 2007
Introduction to Active Record - Silicon Valley Ruby Conference 2007Rabble .
 
Building Better Applications with Data::Manager
Building Better Applications with Data::ManagerBuilding Better Applications with Data::Manager
Building Better Applications with Data::ManagerJay Shirley
 
Um roadmap do Framework Ruby on Rails, do Rails 1 ao Rails 4 - DevDay 2013
Um roadmap do Framework Ruby on Rails, do Rails 1 ao Rails 4 - DevDay 2013Um roadmap do Framework Ruby on Rails, do Rails 1 ao Rails 4 - DevDay 2013
Um roadmap do Framework Ruby on Rails, do Rails 1 ao Rails 4 - DevDay 2013Joao Lucas Santana
 

Similar to SOLID Ruby, SOLID Rails (20)

SOLID Ruby SOLID Rails
SOLID Ruby SOLID RailsSOLID Ruby SOLID Rails
SOLID Ruby SOLID Rails
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 
Why ruby
Why rubyWhy ruby
Why ruby
 
Desarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosDesarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutos
 
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
 
Venturing Into The Wild: A .NET Developer's Experience As A Ruby Developer
Venturing Into The Wild: A .NET Developer's Experience As A Ruby DeveloperVenturing Into The Wild: A .NET Developer's Experience As A Ruby Developer
Venturing Into The Wild: A .NET Developer's Experience As A Ruby Developer
 
Tame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapperTame Accidental Complexity with Ruby and MongoMapper
Tame Accidental Complexity with Ruby and MongoMapper
 
Ruby on rails
Ruby on rails Ruby on rails
Ruby on rails
 
Introduction to Active Record at MySQL Conference 2007
Introduction to Active Record at MySQL Conference 2007Introduction to Active Record at MySQL Conference 2007
Introduction to Active Record at MySQL Conference 2007
 
Intro to Ruby - Twin Cities Code Camp 7
Intro to Ruby - Twin Cities Code Camp 7Intro to Ruby - Twin Cities Code Camp 7
Intro to Ruby - Twin Cities Code Camp 7
 
Beyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the codeBeyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the code
 
Extreme Swift
Extreme SwiftExtreme Swift
Extreme Swift
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
 
Rails World 2023: Powerful Rails Features You Might Not Know
Rails World 2023: Powerful Rails Features You Might Not KnowRails World 2023: Powerful Rails Features You Might Not Know
Rails World 2023: Powerful Rails Features You Might Not Know
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weibo
 
Beyond php it's not (just) about the code
Beyond php   it's not (just) about the codeBeyond php   it's not (just) about the code
Beyond php it's not (just) about the code
 
Introduction to Active Record - Silicon Valley Ruby Conference 2007
Introduction to Active Record - Silicon Valley Ruby Conference 2007Introduction to Active Record - Silicon Valley Ruby Conference 2007
Introduction to Active Record - Silicon Valley Ruby Conference 2007
 
Building Better Applications with Data::Manager
Building Better Applications with Data::ManagerBuilding Better Applications with Data::Manager
Building Better Applications with Data::Manager
 
Um roadmap do Framework Ruby on Rails, do Rails 1 ao Rails 4 - DevDay 2013
Um roadmap do Framework Ruby on Rails, do Rails 1 ao Rails 4 - DevDay 2013Um roadmap do Framework Ruby on Rails, do Rails 1 ao Rails 4 - DevDay 2013
Um roadmap do Framework Ruby on Rails, do Rails 1 ao Rails 4 - DevDay 2013
 

More from Jens-Christian Fischer

More from Jens-Christian Fischer (9)

Beyond HTML Internet Briefing
Beyond HTML Internet BriefingBeyond HTML Internet Briefing
Beyond HTML Internet Briefing
 
Beyond HTML - Scriptsprachen, Frameworks, Templatesprachen und vieles mehr
Beyond HTML - Scriptsprachen, Frameworks, Templatesprachen und vieles mehrBeyond HTML - Scriptsprachen, Frameworks, Templatesprachen und vieles mehr
Beyond HTML - Scriptsprachen, Frameworks, Templatesprachen und vieles mehr
 
Architektur der kleinen Bausteine
Architektur der kleinen BausteineArchitektur der kleinen Bausteine
Architektur der kleinen Bausteine
 
Taking your Web App for a walk
Taking your Web App for a walkTaking your Web App for a walk
Taking your Web App for a walk
 
Mobino at Webmondy Frankfurt, Mai 2011
Mobino at Webmondy Frankfurt, Mai 2011Mobino at Webmondy Frankfurt, Mai 2011
Mobino at Webmondy Frankfurt, Mai 2011
 
Testing distributed, complex web applications
Testing distributed, complex web applicationsTesting distributed, complex web applications
Testing distributed, complex web applications
 
Ruby Coding Dojo
Ruby Coding DojoRuby Coding Dojo
Ruby Coding Dojo
 
Synology Workshop07 06
Synology Workshop07 06Synology Workshop07 06
Synology Workshop07 06
 
Offline Arbeiten
Offline ArbeitenOffline Arbeiten
Offline Arbeiten
 

Recently uploaded

ERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage IntacctERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage IntacctBrainSell Technologies
 
JavaScript Usage Statistics 2024 - The Ultimate Guide
JavaScript Usage Statistics 2024 - The Ultimate GuideJavaScript Usage Statistics 2024 - The Ultimate Guide
JavaScript Usage Statistics 2024 - The Ultimate GuidePixlogix Infotech
 
Working together SRE & Platform Engineering
Working together SRE & Platform EngineeringWorking together SRE & Platform Engineering
Working together SRE & Platform EngineeringMarcus Vechiato
 
WebRTC and SIP not just audio and video @ OpenSIPS 2024
WebRTC and SIP not just audio and video @ OpenSIPS 2024WebRTC and SIP not just audio and video @ OpenSIPS 2024
WebRTC and SIP not just audio and video @ OpenSIPS 2024Lorenzo Miniero
 
Microsoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - QuestionnaireMicrosoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - QuestionnaireExakis Nelite
 
AI in Action: Real World Use Cases by Anitaraj
AI in Action: Real World Use Cases by AnitarajAI in Action: Real World Use Cases by Anitaraj
AI in Action: Real World Use Cases by AnitarajAnitaRaj43
 
Design Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptxDesign Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptxFIDO Alliance
 
Google I/O Extended 2024 Warsaw
Google I/O Extended 2024 WarsawGoogle I/O Extended 2024 Warsaw
Google I/O Extended 2024 WarsawGDSC PJATK
 
AI mind or machine power point presentation
AI mind or machine power point presentationAI mind or machine power point presentation
AI mind or machine power point presentationyogeshlabana357357
 
ADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptxADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptxFIDO Alliance
 
Introduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDMIntroduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDMKumar Satyam
 
How to Check CNIC Information Online with Pakdata cf
How to Check CNIC Information Online with Pakdata cfHow to Check CNIC Information Online with Pakdata cf
How to Check CNIC Information Online with Pakdata cfdanishmna97
 
Event-Driven Architecture Masterclass: Challenges in Stream Processing
Event-Driven Architecture Masterclass: Challenges in Stream ProcessingEvent-Driven Architecture Masterclass: Challenges in Stream Processing
Event-Driven Architecture Masterclass: Challenges in Stream ProcessingScyllaDB
 
Cyber Insurance - RalphGilot - Embry-Riddle Aeronautical University.pptx
Cyber Insurance - RalphGilot - Embry-Riddle Aeronautical University.pptxCyber Insurance - RalphGilot - Embry-Riddle Aeronautical University.pptx
Cyber Insurance - RalphGilot - Embry-Riddle Aeronautical University.pptxMasterG
 
2024 May Patch Tuesday
2024 May Patch Tuesday2024 May Patch Tuesday
2024 May Patch TuesdayIvanti
 
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)Samir Dash
 
CORS (Kitworks Team Study 양다윗 발표자료 240510)
CORS (Kitworks Team Study 양다윗 발표자료 240510)CORS (Kitworks Team Study 양다윗 발표자료 240510)
CORS (Kitworks Team Study 양다윗 발표자료 240510)Wonjun Hwang
 
The Zero-ETL Approach: Enhancing Data Agility and Insight
The Zero-ETL Approach: Enhancing Data Agility and InsightThe Zero-ETL Approach: Enhancing Data Agility and Insight
The Zero-ETL Approach: Enhancing Data Agility and InsightSafe Software
 
ChatGPT and Beyond - Elevating DevOps Productivity
ChatGPT and Beyond - Elevating DevOps ProductivityChatGPT and Beyond - Elevating DevOps Productivity
ChatGPT and Beyond - Elevating DevOps ProductivityVictorSzoltysek
 
Top 10 CodeIgniter Development Companies
Top 10 CodeIgniter Development CompaniesTop 10 CodeIgniter Development Companies
Top 10 CodeIgniter Development CompaniesTopCSSGallery
 

Recently uploaded (20)

ERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage IntacctERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage Intacct
 
JavaScript Usage Statistics 2024 - The Ultimate Guide
JavaScript Usage Statistics 2024 - The Ultimate GuideJavaScript Usage Statistics 2024 - The Ultimate Guide
JavaScript Usage Statistics 2024 - The Ultimate Guide
 
Working together SRE & Platform Engineering
Working together SRE & Platform EngineeringWorking together SRE & Platform Engineering
Working together SRE & Platform Engineering
 
WebRTC and SIP not just audio and video @ OpenSIPS 2024
WebRTC and SIP not just audio and video @ OpenSIPS 2024WebRTC and SIP not just audio and video @ OpenSIPS 2024
WebRTC and SIP not just audio and video @ OpenSIPS 2024
 
Microsoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - QuestionnaireMicrosoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - Questionnaire
 
AI in Action: Real World Use Cases by Anitaraj
AI in Action: Real World Use Cases by AnitarajAI in Action: Real World Use Cases by Anitaraj
AI in Action: Real World Use Cases by Anitaraj
 
Design Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptxDesign Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptx
 
Google I/O Extended 2024 Warsaw
Google I/O Extended 2024 WarsawGoogle I/O Extended 2024 Warsaw
Google I/O Extended 2024 Warsaw
 
AI mind or machine power point presentation
AI mind or machine power point presentationAI mind or machine power point presentation
AI mind or machine power point presentation
 
ADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptxADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptx
 
Introduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDMIntroduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDM
 
How to Check CNIC Information Online with Pakdata cf
How to Check CNIC Information Online with Pakdata cfHow to Check CNIC Information Online with Pakdata cf
How to Check CNIC Information Online with Pakdata cf
 
Event-Driven Architecture Masterclass: Challenges in Stream Processing
Event-Driven Architecture Masterclass: Challenges in Stream ProcessingEvent-Driven Architecture Masterclass: Challenges in Stream Processing
Event-Driven Architecture Masterclass: Challenges in Stream Processing
 
Cyber Insurance - RalphGilot - Embry-Riddle Aeronautical University.pptx
Cyber Insurance - RalphGilot - Embry-Riddle Aeronautical University.pptxCyber Insurance - RalphGilot - Embry-Riddle Aeronautical University.pptx
Cyber Insurance - RalphGilot - Embry-Riddle Aeronautical University.pptx
 
2024 May Patch Tuesday
2024 May Patch Tuesday2024 May Patch Tuesday
2024 May Patch Tuesday
 
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
 
CORS (Kitworks Team Study 양다윗 발표자료 240510)
CORS (Kitworks Team Study 양다윗 발표자료 240510)CORS (Kitworks Team Study 양다윗 발표자료 240510)
CORS (Kitworks Team Study 양다윗 발표자료 240510)
 
The Zero-ETL Approach: Enhancing Data Agility and Insight
The Zero-ETL Approach: Enhancing Data Agility and InsightThe Zero-ETL Approach: Enhancing Data Agility and Insight
The Zero-ETL Approach: Enhancing Data Agility and Insight
 
ChatGPT and Beyond - Elevating DevOps Productivity
ChatGPT and Beyond - Elevating DevOps ProductivityChatGPT and Beyond - Elevating DevOps Productivity
ChatGPT and Beyond - Elevating DevOps Productivity
 
Top 10 CodeIgniter Development Companies
Top 10 CodeIgniter Development CompaniesTop 10 CodeIgniter Development Companies
Top 10 CodeIgniter Development Companies
 

SOLID Ruby, SOLID Rails

  • 1. Michael Mahlberg, Consulting Guild AG Jens-Christian Fischer, InVisible GmbH SOLID Ruby - SOLID Rails Establishing a sustainable codebase
  • 2. Who? Michael Mahlberg
  • 3. Founder of roughly a dozen companies over the last two decades
  • 4.
  • 6. Working as A consultant on software processes, architecture & design for > 2 decades
  • 7.
  • 8. >> relevance != nil => true
  • 9. Who? Jens-Christian Fischer
  • 10. Tinkerer, Practician, Author and generally interested in way too many things
  • 12. SOLID is not a Law
  • 13. PPP (by Robert C. Martin) Agile Software Development, Principles, Patterns, and Practices
  • 14. Principles! You know - more like guidelines
  • 15. SOLID
  • 16. SOLID SRP OCP L S O LSP I ISP DIP D
  • 17. S OL I D SRP OCP LSP ISP DIP
  • 18. SRP
  • 19. SRP Single Responsibility Principle
  • 20. SRP Single Responsibility Principle A class should have one, and only one, reason to change.
  • 21. User Class require 'digest/sha1' class User < ActiveRecord::Base include Authentication 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
  • 22. end def editable_by?(user=nil) self.created_by == user end def readable_by?(user=nil) self.created_by == user end def boards User Class 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 def user_group_roles role_ids = Fellowship.all(:conditions => {:user_id => self.id}).collect{|a| a.role_id}.uniq roles = Role.find(role_ids) end def project_roles role_ids = Membership.all(:conditions => {:user_id => self.id}).collect{|a| a.role_id}.uniq roles = Role.find(role_ids) end def all_roles roles = (self.organization_roles + self.user_group_roles + self.project_roles).uniq end def tags_of(user) taggings = Tagging.all :conditions => {:taggable_type => "User", :taggable_id => self.id, :tagger_type => "User", :tagge tags = taggings.collect {|t| t.tag.name}.uniq.sort end
  • 23. So what‘s wrong with this?
  • 25. 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? ...
  • 27. 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
  • 29. 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
  • 31. From: user.rb def is_admin? self.roles.collect{|role| role.title}.include?('admin') end def countries [self.country] end
  • 33. 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
  • 35. 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
  • 36. Anyone notice a pattern?
  • 39. Authentication Roles Mailers State ...
  • 40. So how? Mixins
  • 42. 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
  • 44. 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
  • 46.
  • 47. 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
  • 48.
  • 49. 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
  • 50. 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
  • 52. 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
  • 53.
  • 54. 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
  • 55. Add a 16-band equalizer & a BlueRay® player to this...
  • 56. And now to this...
  • 57. S OL I D SRP OCP LSP ISP DIP
  • 58. OCP
  • 59. OCP Open Closed Principle
  • 60. OCP Open Closed Principle You should be able to extend a classes behavior, without modifying it.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65. From the Google AI Challenge (Tronbot)
  • 66. 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
  • 67. 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
  • 68. 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
  • 69. From the Google AI Challenge (Tronbot)
  • 70. 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
  • 72.
  • 73. class Outputter def initialize(io = $stderr) @io = io end def puts(*args) @io.puts *args end ... end out = Outputter.new out.puts "Testing"
  • 74. S OL I D SRP OCP LSP ISP DIP
  • 75. LSP
  • 76. LSP Liskov Substitution Principle
  • 77. LSP Liskov Substitution Principle Derived classes must be substitutable for their base classes.
  • 78. No Problem in Ruby Or so it seems...
  • 79. No Interface... no problem?
  • 82. A square is a rectangle
  • 83. Rectangle setX setY Square setX setY
  • 85. Rectangle >> 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
  • 86. Square ?> shape = Square.new => #<Square:0x101107e88> ?> puts shape.width nil ?> shape.width=3 => 3 ?> shape.width => 3 ?> shape.height => 3
  • 87. 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
  • 88. A Problem... Text
  • 89. A Problem... >> s = [Rectangle.new, Square.new] => [#<Rectangle:0x1005642e8>, #<Square:0x100564298>] >> a_rectangle = s[rand(2)] Text
  • 90. 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
  • 91. 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
  • 92. CCD Common Conceptual Denominator
  • 93. dup
  • 95. irb 1:0> 5.respond_to? :dup http://blog.objectmentor.com/articles/2007/02/17/ liskov-substitution-principle-and-the-ruby-core-libraries
  • 96. irb 1:0> 5.respond_to? :dup => true http://blog.objectmentor.com/articles/2007/02/17/ liskov-substitution-principle-and-the-ruby-core-libraries
  • 97. irb 1:0> 5.respond_to? :dup => true irb 2:0> 5.dup http://blog.objectmentor.com/articles/2007/02/17/ liskov-substitution-principle-and-the-ruby-core-libraries
  • 98. 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
  • 99. S OL I D SRP OCP LSP ISP DIP
  • 100. ISP
  • 101. ISP Interface Segregation Principle
  • 102. ISP Interface Segregation Principle Make fine grained interfaces that are client specific.
  • 103.
  • 105. 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 ...
  • 107. 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
  • 109. 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
  • 110.
  • 111. 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 ...
  • 112. S OL I D SRP OCP LSP ISP DIP
  • 113. DIP
  • 114. DIP Dependency Inversion Principle
  • 115. DIP Dependency Inversion Principle Depend on abstractions, not on concretions.
  • 116.
  • 117. From our OCP example to DIP
  • 118. From our OCP example to DIP out = Outputter.new out.puts "Testing"
  • 119. The code we wish we had
  • 120. The code we wish we had class TronBot def initialize @@out = TRON_ENVIRONMENT[:debugger] end def some_method ... @@out.puts "Testing" ... end end
  • 122. TSTTCPW TRON_ENVIRONMENT = { :debugger => Outputter.new ($stderr), :game_engine => Outputter.new ($stdout), :user_io => Outputter.new ($stderr) }
  • 124. Later... TRON_ENVIRONMENT = { :debugger => Outputter.new ($stderr), :game_engine => Outputter.new (TCP_OUTPUTTER), :user_io => Outputter.new ($stderr) }
  • 125. DIP Violation in Controller
  • 126. 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
  • 127. DIP Violation in Controller
  • 128. 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
  • 129.
  • 131. 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
  • 132.
  • 134. 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
  • 136. 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
  • 137. DIP Violation in Controller
  • 138. 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
  • 139. 2nd Refactoring - the Controller -
  • 140. 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
  • 141.
  • 142. SOLID
  • 143. SOLID SRP OCP LSP ISP DIP
  • 144. SRP OCP LSP ISP DIP
  • 145. S OL ID SRP OCP LSP ISP DIP
  • 146. S OL ID SRP OCP LSP ISP DIP
  • 147. S OL ID SRP OCP LSP ISP DIP
  • 148. S OL ID SRP OCP LSP ISP DIP
  • 149. S OL ID SRP OCP LSP ISP DIP
  • 150. S O L I D Questions? SRP OCP LSP ISP DIP
  • 152. Coding Dojo Wednesday 11:10 Salon 3 The Way of the carpenter is to become proficient in the use of his tools, first to lay his plans with a true measure and then perform his work according to plan. – Go Rin No Sho
  • 156. 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 94
  • 158. 96

Editor's Notes