Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby Mindset

Ruby Mindset

Deep dive into Ruby language's features and write idiomatic code.

Avatar for Fabio Pitino

Fabio Pitino

February 04, 2016

Other Decks in Technology

Transcript

  1. A typical Ruby script # test.rb p self.class p self.public_methods

    Object [:to_s, :inspect, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] Objects
  2. In Ruby EVERYTHING is an Object p nil.class.ancestors #=> [NilClass,

    Object, Kernel, BasicObject] p true.class.ancestors #=> [TrueClass, Object, Kernel, BasicObject] p [].class.ancestors #=> [Array, Enumerable, Object, Kernel, BasicObject] p 1.class.ancestors #=> [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject] Objects
  3. Classes • A Class in Ruby is a template Object

    used to initialize other objects p String.kind_of? Class # => true p String.kind_of? Object # => true p Object.kind_of? Class # => true p Class.kind_of? Object # => true Classes
  4. Class as an interface • What you can do with

    a class must be dictated by its public methods • Public methods must be stable to allow other objects to depend on them • Methods are public by default • Apply Information Hiding principle to anything that the public should not see • Use private and protected sections Classes
  5. Use Duck-Typing (a.k.a Dynamic Typing) If it looks like a

    duck and quacks like a duck, it’s a duck! Rely on the object’s messages instead of the object type class Car attr_accessor :model, :owner def initialize(model, owner) @model = model @owner = owner end def label "this is #{owner.name} #{owner.surname}'s #{model}" end end batman = Person.new('Bruce', 'Wayne') Car.new('Batmobile', batman).label # => this is Bruce Wayne's Batmobile Dynamic Typing
  6. Protected methods class Car # code omitted protected def engine

    :diesel end end class Car # code omitted def engine :diesel end protected :engine end • Any instance of the class can call protected methods on any other instances of the same class or subclass Methods
  7. Private methods class Car # code omitted private def engine

    :turbo end end class Car # code omitted def engine :diesel end private :engine end • Can ‘only’ be called from within the class or any subclasses • In reality private and protected are not real limitation but ‘suggestions’ instead • Ruby still allows you to use :send method or alter the class at runtime > ferrari = Car.new > ferrari.send(:engine) # => :turbo Methods
  8. Singleton Methods • Remember stubs? ferrari = Car.new #=> <Car:0x007f846408>

    ferrari.stubs(:color).returns(:red) ferrari.color #=> :red Stubs are a typical example of Singleton Methods • A Singleton Methods is a method that is defined for only one object instance ferrari = Car.new #=> <Car:0x001a901245> ferrari.color #=> undefined method ‘color’ for <Car:0x001a901245> Singleton Methods
  9. How can we define Singleton Methods? # Option 1 ferrari

    = Car.new def ferrari.color :red end puts ferrari.color #=> :red # Option 2 ferrari = Car.new class << ferrari def color :red end end puts ferrari.color #=> :red Singleton Methods
  10. Singleton Methods in objects vs classes (option #1) class Car;

    end my_car = Car.new def my_car.describe_me "I'm #{self}, an instance of #{self.class}" end p my_car.describe_me #=> "I'm #<Car:0x007fc4dd21c6c8>, an instance of Car" class Car; end my_car = Car def my_car.describe_me "I'm #{self}, an instance of #{self.class}" end p my_car.describe_me #=> "I’m Car, an instance of Class" Singleton Methods
  11. Refining SM with classes (option #1) class Car; end def

    Car.describe_me "I'm #{self}, an instance of #{self.class}" end p Car.describe_me #=> "I’m Car, an instance of Class" class Car; end my_car = Car def my_car.describe_me "I'm #{self}, an instance of #{self.class}" end p my_car.describe_me #=> "I’m Car, an instance of Class" Singleton Methods
  12. Singleton Methods in objects vs classes (option #2) class Car;

    end my_car = Car.new class << my_car def describe_me "I'm #{self}, an instance of #{self.class}" end end p my_car.describe_me #=> "I'm #<Car:0x007fc4dd21c6c8>, an instance of Car" class Car; end my_car = Car class << my_car def describe_me "I'm #{self}, an instance of #{self.class}" end end p my_car.describe_me #=> "I’m Car, an instance of Class" Singleton Methods
  13. Refining SM with classes (1) (option #2) class Car; end

    class << Car def describe_me "I'm #{self}, an instance of #{self.class}" end end p Car.describe_me #=> "I’m Car, an instance of Class" class Car; end my_car = Car class << my_car def describe_me "I'm #{self}, an instance of #{self.class}" end end p my_car.describe_me #=> "I’m Car, an instance of Class" Singleton Methods
  14. Refining SM with classes (2) (option #2) class Car class

    << self def describe_me "I'm #{self}, an instance of #{self.class}" end end end p Car.describe_me #=> "I’m Car, an instance of Class" class Car; end class << Car def describe_me "I'm #{self}, an instance of #{self.class}" end end p Car.describe_me #=> "I’m Car, an instance of Class" Singleton Methods
  15. Refining SM with classes (3) (option #2) class Car def

    self.describe_me "I'm #{self}, an instance of #{self.class}" end end end p Car.describe_me #=> "I’m Car, an instance of Class" class Car class << self def describe_me "I'm #{self}, an instance of #{self.class}" end end end p Car.describe_me #=> "I’m Car, an instance of Class" Singleton Methods
  16. attr_accessor (instance vs class) methods class Car @default_engine = :petrol

    class << self attr_accessor :default_engine end attr_reader :name, :engine def initialize(name) @name = name @engine = Car.default_engine end end p Car.new(:bmw) #=> #<Car:0x007f995181b770 @name=:bmw, @engine=:petrol> Car.default_engine = :diesel p Car.new(:ferrari) #=> #<Car:0x007f995181b568 @name=:bmw, @engine=:diesel> class Car attr_accessor :engine end bmw = Car.new bmw.engine = :petrol p bmw.engine #=> :petrol Singleton Methods
  17. Treat everything like an object… Typical usage of serialized Hash

    data… # Scan result data scan_data = { start_time: '2016-03-01 12:30:23', end_time: '2016-03-01 12:35:45', files: [ { filename: 'setup.exe', detection: 'Trojan.Gen', }, { filename: 'dfrg.dll.', detection: 'Suspicious.Cloud.5', } ... ], scanner: 'NIS', version: '20.345' } Class Operators
  18. …to represent the various roles Object Oriented representation of scan

    results class ScanResult attr_reader :start_time, :end_time, :files, :product def initialize(params = {}) # some params initialization end # some code omitted... def total_time end_time - start_time end def detections_found? files.any?{ |f| f.detected? } end end Class Operators class ScannedFile attr_accessor :filename, :detection def initialize(params) @filename = params[:filename] @detection = params[:detection] end def detected? !detection.empty? end def remediated? # code omitted end # other code omitted... end class Product attr_reader :name, :version def initialize(params) @name = params[:name] @version = params[:version] end end
  19. Define operators for classes class Playlist attr_accessor :name def initialize(name)

    @name = name @songs = [] end def <<(song) @songs << song end def [](index) @songs[index] end end my_playlist = Playlist.new(‘Rock’) my_playlist << Song.new(‘Paranoid’) p my_playlist[0] # => Paranoid Other examples: def +(other); end def -(other); end Beware of the pitfalls… • A + B != B + A (e.g. Array – unless sorted) • A + B == B + A (e.g. Fixnum) Class Operators
  20. Equality car_1 = Car.new(:engine => :diesel) car_2 = Car.new(:engine =>

    :diesel) car_1.equal?(car_2) car_1 == car_2 car_1 === car_2 car_1.eql?(car_2) Equality ?
  21. x.equal?(y) equal? is the method each object inherits from Object

    class car_1 = Car.new(engine: :diesel) #<Car:0x007fee8b083d98 @engine=:diesel> car_2 = Car.new(engine: :diesel) #<Car:0x007fee8b083d70 @engine=:diesel> Equality
  22. x == y Uses default implementation of equal? class Car

    attr_accessor :engine, :color def initialize(engine) @engine = engine @color = :black end def ==(other) return false unless other.instance_of?( self.class) engine == other.engine && color == other.color end end car_1 = Car.new(engine: :diesel) #=> #<Car:0x007fee8b083d98 @engine=:diesel, @color=:black> car_2 = Car.new(engine: :diesel) #=> #<Car:0x007fee8b083d70 @engine=:diesel, @color=:black> p car_1 == car_2 #=> true Equality
  23. x === y class Car attr_accessor :engine, :color def initialize(engine)

    @engine = engine @color = :black end def ==(other) return false unless other.instance_of?(self.class) engine == other.engine && color == other.color end def ===(other) puts 'in ===' self == other end end a = Car.new(engine: :diesel) b = Car.new(engine: :diesel) case a when b then puts “actually it matches ‘b’!” when nil then puts ‘it is nil’ else puts ‘it must be something else' end #=> in === #=> actually it matches ‘b’! • The use of === is generally rare to see in Ruby code • Mainly used for case statements Equality
  24. x.eql?(y) class Car attr_accessor :engine, :color def initialize @engine =

    :diesel @color = :black end # code omitted... end catalog = {} car_1 = Car.new(engine: :diesel) car_id_1 = CarIdentifier.new(2016, :sport) catalog[car_id_1] = car_1 car_id_2 = CarIdentifier.new(2016, :sport) car_1 = catalog[car_id_2] #=> nil p car_id_1.hash #=> 3707453709054309147 P car_id_2.hash #=> 4335809772422022143 • Used in Hash objects • Hash uses the hash method to create an object identifier • Hash uses also eql? method to compare object identifiers Equality
  25. x.eql?(y) class CarIdentifier attr_accessor :year, :category def initialize(year, category) @year

    = year @category = category end # code omitted... def hash year.hash ^ category.hash end def eql?(other) return false unless other.instance_of?(self.class) year == other.year && category == other.category end end • E.g. we have to override hash to return XOR of year and category hash values • We have to override eql? tell the Hash object which comparison algorithm to use catalog = {} car_1 = Car.new(engine: :diesel) car_id_1 = CarIdentifier.new(2016, :sport) catalog[car_id_1] = car_1 car_id_2 = CarIdentifier.new(2016, :sport) car_2 = catalog[car_id_2] #=> now it will return car_1 object! p car_id_1.hash #=> 9905875239901056 P car_id_2.hash #=> 9905875239901056 Equality
  26. Watch out for the pitfalls! class Car attr_reader :engine def

    initialize(params) @engine = params[:engine] end def ==(other) return false unless other.kind_of?(self.class) self.engine == other.engine end end class RacingCar < Car # more specifications here... end Equality car_1 = Car.new(engine: :diesel) p car_1 car_2 = RacingCar.new(engine: :diesel) p car_2 p car_1 == car_2 #=> true p car_2 == car_1 #=> false
  27. Modules as namespaces • To group classes and modules in

    a single file module CarParts class Body attr_accessor :color, :model # code omitted... end class Engine attr_accessor :type, :capacity # code omitted... end class Wheel attr_accessor :size, :rim # code omitted... end end engine = CarParts::Engine.new • Or in separate files (recommended) # file: car_parts/body.rb module CarParts class Body attr_accessor :color, :model # code omitted... end end # file: car_parts/engine.rb module CarParts class Engine attr_accessor :type, :capacity # code omitted... end end # file: car_parts/wheel.rb module CarParts class Wheel attr_accessor :size, :rim # code omitted... end end Modules
  28. Modules as namespaces • Not only make the code more

    organized but also help distinguish between homonym classes • Representing completely different entities car.body = CarParts::Body.new document.body = Document::Body.new • Representing same entity with different characteristics if OS.is_windows? Windows::ButtonRenderer else OSX::ButtonRenderer end os = if OS.is_windows? Windows else OSX end os::ButtonRenderer Modules
  29. Modules as containers module Converter def self.cm_to_inches(n) n * 0.393701

    end def self.inches_to_cm(n) n * 2.54 end end size_cm = Converter.inches_to_cm(size_inches) Modules
  30. Modules as mixins module Shareable def share(person) # send me

    to somebody end end module Searcheable def search(phrase) # search in content end # maybe other types of search end module Binary def metadata # extract metadata end # other binary stuff end class HtmlFile include Shareable include Searcheable # other html-stuff... end class PdfFile include Shareable include Searcheable include Binary # other pdf stuff... end class ImageFile include Shareable include Binary # other image stuff... end Modules
  31. Extending modules within classes module Searcheable def search(phrase) # search

    in content end # maybe other types of search end class HtmlFile extend Searcheable # other html-stuff... end # inherits methods as class methods HtmlFile.search(phrase) Modules
  32. Extending modules with objects module FlowerPower def fire_ball # kill

    enemies with fire balls end end class Hero attr_reader :name, :energy def initialize(name) @name = name @energy = 100 end # other code omitted... end mario = Hero.new(‘Super Mario’) # Mario eats the magic flower... mario.extends(FlowerPower) mario.fire_ball Modules
  33. Code blocks • Code blocks are kind-of an inverse of

    a method ⚫ When calling a method we may pass in some parameters and we don’t know what it does ⚫ When using a block we are in control of the flow with the parameters (if any) the block gives us – Although some logic may still be hidden by the block def do_nothing yield if block_given? end do_nothing do puts "Calling a block" end # or this do_nothing { puts "Calling a block" } def greetings_from_block yield("Mr. Block") if block_given? end greetings_from_block do |name| puts "Hello I’m #{name}" end #=> Hello I’m Mr. Block Blocks
  34. Blocks to iterate objects class Team attr_reader :name def initialize(name)

    @name = name @members = [] @projects = [] end def add_member(member) @members << member end def add_project(project) @projects << project end def each_member @members.each do |member| yield(member) end end # other stuff omitted... end qa_team = Team.new('Ops QA') qa_team.add_member('Mark Dy') qa_team.add_member('Marc LeDeroff' ) qa_team.add_member('Jenniffer SeeGo' ) qa_team.add_project('FPIR') qa_team.add_project('Eraser') qa_team.add_project('GVM') qa_team.each_member do |member| puts "#{member} is an #{qa_team.name} " end #=> Mark Dy is an Ops QA #=> Marc LeDeroff is an Ops QA #=> Jenniffer SeeGo is an Ops QA Blocks
  35. Or using Enumerable class Team include Enumerable attr_reader :name def

    initialize(name) @name = name @members = [] end def add_member(member) @members << member end # define method each to get the power # of Enumerable def each @members.each do |member| yield(member) end end # other stuff omitted... end qa_team = Team.new('Ops QA') qa_team.add_member('Mark Dy') qa_team.add_member('Marc LeDeroff' ) qa_team.add_member('Jenniffer SeeGo' ) p qa_team.count p qa_team.map{ |m| m.upcase } p qa_team.select{ |m| m.match(/^M/) } #=> 3 #=> ["MARK DY", "MARC LEDEROFF", "JENNIFFER SEEGO"] # => ["Mark Dy", "Marc LeDeroff"] Blocks
  36. Execute around blocks class DocumentEditor # some stuff omitted... def

    edit(document_name) with_logging('load') do @doc = Document.load(document_name) end # edit the content of document with_logging('save') { @doc.save } end def with_logging(event) begin @logger.debug("#{event} starting") yield @logger.debug("#{event} completed") rescue @logger.error("#{event} failed") raise end end end Blocks
  37. Using blocks to initialize objects class Car attr_accessor :name, :color,

    :engine def initialize(name, params = {}) @name = name @color = params[:color] @engine = params[:engine] yield(self) if block_given? end end car = Car.new('ferrari', { color: 'red', engine: 'turbo' }) p car #=> #<Car:0x007f97d881b0d8 @name="ferrari", @color="red", @engine="turbo"> car = Car.new('ferrari') do |c| c.color = 'red' c.engine = 'turbo' end p car #=> #<Car:0x007f97d881aed0 @name="ferrari", @color="red", @engine="turbo"> Blocks
  38. Explicit code blocks def run_a_block(&block) puts "before the block" block.call

    if block puts "after the block" end run_a_block do puts "a test block that does nothing" end #=> before the block #=> a test block that does nothing #=> after the block Blocks
  39. Use blocks for delayed execution class Car def initialize(name) @name

    = name @before_drive_listeners = [] @after_park_listeners = [] end def before_drive(&block) @before_drive_listeners << block end def after_park(&block) @after_park_listeners << block end def drive(destination) @before_drive_listeners.each do |b| b.call end puts "driving towards #{destination}" end def park puts "parking" @after_park_listeners.each do |b| b.call end end end car = Car.new('VW Beetle') car.before_drive { puts 'check tires pressure' } car.before_drive { puts 'set navigator' } car.after_park { puts 'lock car' } car.drive('New York') car.park #=> check tires pressure #=> set navigator #=> driving towards New York #=> parking #=> lock car Blocks
  40. Use lambda for delayed execution class Car DEFAULT_BEFORE_DRIVE = lambda

    do puts 'check insurance validity' end def initialize(name) @name = name @before_drive_listeners = [DEFAULT_BEFORE_DRIVE] @after_park_listeners = [] end def before_drive(&block) @before_drive_listeners << block end def after_park(&block) @after_park_listeners << block end def drive(destination) @before_drive_listeners.each do |b| b.call end puts "driving towards #{destination}" end def park puts "parking" @after_park_listeners.each do |b| b.call end end end car = Car.new('VW Beetle') car.before_drive { puts 'check tires pressure' } car.before_drive { puts 'set navigator' } car.after_park { puts 'lock car' } car.drive('New York') car.park #=> check insurance validity #=> check tires pressure #=> set navigator #=> driving towards New York #=> parking #=> lock car Blocks
  41. Proc.new vs Lambda • Lambda blocks will return a Proc

    object which is slightly different than the Proc object generated by Proc.new (or &block) • What are the differences between lambda and Proc.new? • Parameters • Proc.new is more forgiving in the number of arguments we pass in • If too few will fill up the remaining arguments with nil • If too many will quietly ignore the arguments in excess • Lambda • Acts like a regular method → it will shout on wrong number of arguments • Behavior • Proc.new runs within the context of the caller (method/code) • Using next, return, break will have direct repercussion into the caller • Lambda runs in an isolated context exactly like a standard method • Using next, return, break will return from the block and no further Blocks