Ruby

docs https://docs.ruby-lang.org/en/
home https://www.ruby-lang.org/en/
wiki https://en.wikipedia.org/wiki/Ruby_on_Rails
wiki https://en.wikipedia.org/wiki/Ruby_(programming_language)
wiki https://en.wikibooks.org/wiki/Ruby_Programming

Language

#!/usr/bin/env ruby
if $PROGRAM_NAME == __FILE__
  puts "Hello, World!"
end
  • Author: Yukihiro Matsumoto (matz)
  • 1995-
  • Written in C
  • Author wanted an OO language. Didn't like Python, because it wasn't true OO.
  • challenges
  • Typing: duck, dynamic, strong
  • Everything is an object (including primitive data types)
    • not variables, they hold references to an object
      • use #dup when need a copy
      • use #freeze to not admit modifications (numbers and symbols are by default)
    • ergo, everything is a reference
    • arguments are passed as references to functions
  • Optional () on function calls
  • indentation isn't significant
  • most lines that look like statements in Ruby are actually expression that return a value
  • #tap
    • useful for debugging chain of method calls
    • takes a block, pass the receiver, and then return the orignal receiver of the method
    • a "no-op", execept that invokes a block
  • restricted to pure 7-bit ascii for operators, for compatibility across all different source file encodings
  • when we call a method without putting a receiver object before it it refers to the object named "self" aka "puts == self.puts"
  • lib/<gemname>/<class>.rb
  • test/<gemname>/all_tests.rb # require_relative other .rbs
  • test/<gemname>/<class>_test.rb

variables

  • the "nouns" of Ruby

global variables

    description
__FILE__   has the name of the file being run
$stdin   standard input
$stdout   standard output
$stderr   standard error
$ARGV $* array with command line arguments
$ARGF $< the content of all the files whose names were passed
$DEFAULT_OUTPUT $>  
$ENV   Hash that contains environment variables
$ERROR_INFO $!  
$ERROR_POSITION $@  
$FIELD_SEPARATOR $; FS
$OUTPUT_FIELD_SEPARATOR $, OFS
$INPUT_RECORD_SEPARATOR $/ RS
$OUTPUT_RECORD_SEPARATOR $\ ORS
$INPUT_LINE_NUMBER $. NR
$LAST_READ_LINE $_  
$PROCESS_ID $$ PID
$CHILD_STATUS $?  
$MATCH $& regex op, the match (all between //)
$PREMATCH $` regex op, to the left of last match
$POSTMATCH $' regex op, to the right of last match
$LAST_PAREN_MATCH $+ regex op, the last group
  $N regex op, where N is a number of the nth match
  $0 aka $PROGRAM_NAME

testing

  • "do NOT mind code duplication in tests" - PragmaticRuby
  • assert_, refute_
  • _equal, _empty
require_relative "words_from_string"
require "minitest/autorun" # runs all the test_*
class TestWordsFromString < Minitest::Test
  def setup # called before each test_ method in class
    @playlist = Playlist.new("Gonzo")
    @movie1 = Movie.new("A", 10)
    @movie2 = Movie.new("B", 9)
    $stdout = StringIO.new # supress stdout
  end
  def test_high_number
    @playlist.stub(:roll_die, 6) do  # override method to return a constant value
      @playlist.play()
      assert_equal 11, @movie1.rank
    end
  end
  def test_empty_string # methods starting with "test" automatically run
    assert_equal([], words_from_string(""))
    assert_equal([], words_from_string("   "))
  end
end

operators

+ sums, concatenate
=~ match string against regex, returns start pos/nil
%w[] create an array of strings
%w() "
%i[] "
<< appends an element to an array
:: scope resolution operator
#{} string interpolation
¦¦= if LS is falsey, eval RS and assign it to LS
=> "hashrocket", associates k/v in a Hash

require

require "csv"
require_relative "book_in_stock" # for "./book_in_stock.rb"

i/o

style

changelog

1.0 - 1996

  • 1.6 2000
  • 1.8 2003-2013
  • 1.9.3 2011
    • block local variables
    • additional lambda syntax (->(){})
    • additional hash literal syntax (colons after the symbol)
    • per string encoding
    • new socket api
    • require_relative

2.0 - 2013

  • 2.0
    • compatible with 1.9.3
    • method keyword arguments
    • new method "Module#prepend" to extend a class
    • new literal to create array of symbols
    • new api for lazy eval of Enumerables
    • new convention of using "#to_h" for object to hash conversion

3.0 - 2020

  • 3.0
    • "3x faster than 2"
    • introduces MJIT/YJIT
    • new concurrency utils: Fibre, Scheduler, Ractor
    • introduces RBS, for typed ruby
  • 3.1 2021
  • 3.2 2022
    • WASM compatible via WASI
  • 3.3 2023
    • new parser: Prism
    • introduces RJIT

control flow

  • if/elsif/else/end
  • unless/
  • while/end
  • statement modifiers

    return nil if user.nil? # guards clause, usually at the beginning of a method
    
    puts "Danger" if radiation > 3000
    
    square = 4
    square = square * square while square < 1000
    
  • loop/end
    • infinite loop
    • break out of them
    • it will also auto-break if the Enumerator inside runs out of values

      short_enum = [1,2,3].to_enum
      long_enum = ('a'..'z').to_enum
      loop do # loops 3 times
        puts "#{short_enum.next} - #{long_enum.next}"
      end
      

a block {} do/end

  • blocks are closures
  • is a chunk of code you can pass to a method, as it were another parameter
  • assign a block to a variable, all of these return a Proc from the block

    bo = ->(param) { puts "You called me with #{param}" } # stabby lambda, optional parens
    bo = lambda { |param| puts "You called me with #{param}" } # Kernel method "lambda", error on wrong nargs
    bo = proc { |param| puts "You called me with #{param}" } # Kernel method "proc", bubble ups "return"
    bo = Proc.new { |param| puts "You called me with #{param}" } # same but OLD style
    bo.call(99)
    
  • can be passed to a method either
    1. extra IMPLICIT last argument that's passed to a method
    2. (&) EXPLICIT last argument, to call store it and call it later (callbacks)

      class ProcExample
        def pass_in_block(&action)
          @stored_proc = action
        end
        def use_proc(parameter)
          @stored_proc.call(parameter) # .call stored block
        end
      end
      eg = ProcExample.new
      eg.pass_in_block { |param| puts "The parameter is #{param}" }
      eg.use_proc(99)
      
  • parameters to a block are ALWAYS local to that block
  • has access to variables outside his scope
  • Iterator or Enumerator a method that can invoke a block of code repeatedly
  • you can only pass one block per method call greet("dave", "loyal customer") { puts "hi" }
  • invoked in a method using yield
    • block parameters put after a ; are considered locals to that block (awk-ish) (RARE!)

      square = "some shape"
      sum = 0
      [1,2,3,4].each do |value;square| # 2 block parameters
        square = value * value
        sum += square
      end
      puts sum
      puts square
      
    • no argument

      def call_block
        puts "Start of method"
        yield
        yield
        puts "End of method"
      end
      call_block { puts "In the block" }
      
    • with arguments

      def who_says_what
        yield("Dave", "hello")
        yield("Andy", "goodbye")
      end
      who_says_what { |person, phrase| puts "#{person} says #{phrase}" }
      
    • for transactions (ME: using blocks for meta-programming stuff)

      class File
        def self.open_and_process(*args) # class method
          f = File.open(*args)
          yield f
          f.close()
        end
      end
      
      File.open_and_process("testfile", "r") do |file|
        while line = file.gets
          puts line
        end
      end
      

Classes

  • created with NAME.new
  • has/can have
    • each instance has an object_id property
    • instance variables
  • attributes are just methods without arguments
  • the "only easy" way to change an object's state is by calling onf its methods
  • templates for creating objectss
class BookInStock
  attr_accessor :price      # would create a attr_reader/attr_writer
  attr_reader :isbn #, :price   # creates the reader accessor methods #isbn and #price, for @isbn and @price
  def initialize(isbn, price) # called by BookInStock.new
    @isbn = isbn
    @price = Float(price)
  end
  # def price=(new_price) # setter
  #   @price = new_price
  # end
  def price_in_cents
    (price * 100).round # !!!! is valid to refer to it either as "price" or "@price"
  end
  def price_in_cents=(cents) # setter for a "virtual isntance variable", giving a "uniform access principle"
    @price = cents / 100.0
  end
end
book = BookInStock.new("isbn1", 33.80)
book.price = book.price * 0.75 # using the setter and getter
class File
  def self.my_open(*args) # CLASS METHOD, parameter list into array "args"
    file = File.new(*args) # spread array "args", into individual arguments
    return file unless block_given? # guard return if no block_given?
    result = yield file
    file.close
    result
  end
end

Methods (.)

  • private/public/protected def
  • can be redefining (just warns about it)
  • the verbs of ruby
  • attached at the end of variables with by a dot
  • some methods (such as print) are kernel methods, won't use the dot
  • parameters
    • positional foo
    • keyword rank: 10 needs the keyword to be passed
    • default rank = 10 with a possible computed default (ME: aka &optional)
  • kinds
    • module methods (::) self.
    • Class Methods (::) self.
      • attached after variables and constants by a double colon
    • instance methods (.)
      • called by RECEIVER.METHODNAME
      • a message is being send to the object, which contains the method name along with arguments
  • special
    • to_s
    • inspect
    • each
  • arguments, keyword args, *,**splats, &arguments
  • Types
    • public: by default (except initialize which is private) (RARE explicit)
    • protected: can be invoked by class or subclasses (RARE!) can be used on attr_reader/w/a
    • private: cannot be invoked without an explicit receiver

modules

  • to group related constants/methods, or for mixins or namespaces
module Snackbar
  Snack = Data.define(:name, :price)
  SNACKS = [
    Snack.new("popcorn", 3),
    Snack.new("candy", 1)
  ]
  def self.random_snack # self. declares a "module method"
    SNACKS.sample
  end
end
require_relative "snackbar"
Snackbar::SNACKS.each do { |snack| puts snack.name }

types

  • Ruby 2: diagram of classes http://jeromedalbert.com/a-diagram-of-the-ruby-core-object-model/ 10075536704_84aa13676a_o.jpg
  • Constants
    • capitalized
    • proper nouns of Ruby
  • nil
    • is an object, just like any other
    • represents the concept of nothing
  • Numeric https://ruby-doc.org/3.0.7/Numeric.html
  • Strings
    • "",%{},'',%q{} are string literals, from more to less work done
    • "",%{} checks for escape sequences (\) and expression interpolation (#{})
    • Both single and double quotes are used to create strings
    • #each_char returns an Enumerator if not given a block, you can call .each_with_index on it
    • alternatively #each_char.with_index
  • Regexp // https://ruby-doc.org/3.2.2/Regexp.html
    • the match operator =~ can be used to match a string against it returns that starting position or nil
      • also through String#match? or Regex#match?
    • Regexp#sub
    • Regexp#gsub
  • Symbols :foo
    • like lightweight strings
    • immutable
    • same named symbols have the same object_id
    • used
      • as keys and identifiers
      • when you need a string but you won't be printing it
      • when you need to use the same string over and over
  • Struct

    Snack = Struct.new(:name, :price)
    
    • when you have date and no behaviour
    • it creates accessors (r/w) automatically
  • Data

    Snack = Data.define(:name, :price)
    
    • when you have date and no behaviour
    • it creates readers automatically
      • immutable

Enumerator (class)

  • as objects they are Enumerable
  • implements external iterators, where you control the iteration behavior
  • Creating
    • what an iterator method returns if you don't pass it a block
    • #to_enum -> #next
    • #enum_for(:each_slice, 3) - in the case the method used for the enumerator takes params
    • Enumerator.produce - takes an initial value and a block

      triangular_numbers = Enumerator.produce([1,2]) do |number,count|
        [number + count, count + 1]
      end
      5.times { print triangular_numbers.next.first, " " } # => 1 3 6 10 15
      p triangular_numbers.first(5).map { _.first } # => [1,3,6,10,15]
      
    • Enumerator.new {|x| …}

      triangular_numbers = Enumerator.new do |yielder| # called when #next
        number = 0
        count = 1
        loop do
          number += count
          count += 1
          yielder.yield(number) # pausing point
        end
      end
      5.times { print triangular_numbers.next, " " } # 1 3 6 10 15
      

Enumerable (module/mixin)

  • https://ruby-doc.org/3.2.2/Enumerable.html
  • #lazy => Enumerator::Lazy reimplements select/map/… to work with infinite sequences

    class InfiniteStream
      def all
        Enumerator.produce(0) do |number|
          number += 1
        end.lazy
      end
    end
    p InfiniteStream.new.all.first(10)
    p InfiniteStream.new.select { (_1 % 3).zero? }.first(10)
    # Example:
    def palindrome(n)
      n = n.to_s
      n == n.reverse
    end
    p InfiniteStream.new
      .select { (_1 % 3).zero? }
      .select { palindrome?(_1) }
      .first(10)
    # Example: alternative syntax
    multiple_of_three = InfiniteStream.new.all.select { (_1 % 3).zero? }
    p multiple_of_three.first(10)
    m3_palindrome = multiple_of_three.select { palindrome?(_1) }
    p m3_palindrome.first(10)
    # Example: alternative syntax
    multiple_of_three = -> n { (n % 3).zero? }
    palindrome = -> n { n = n.to_s; n == n.reverse }
    p InfiniteStream.new.all
      .select(&multiple_of_three)
      .select(&palindrome)
      .first(10)
    
  • aka a collection of object references
  • #reduce(0) { |sum,ele| sum + ele } #reduce { |sum,ele| sum + ele } #reduce(:+)
  • #dig: helps to dig through nested data structures
    • Returns if not found nil, doesn't raise an exception
    • From: data[:mcu][0][:actors][1] To: data.dig(:mcu, 0, :actors, 1)

Array []

  • Enumerable
  • [0,2,nil][1] Array.new
  • #pop/#push elements from the end
  • #shift/#unshift elements from the beginning
  • #compact remove nil
  • #sample get random element
  • %w[] to create array of strings
  • %i[] to create array of strings
  • [FROM,LENGTH]
  • [FROM..END] [FROM…ENDNOTINC]
  • can growth by displacing elements with [?,?]= or [?..?]
  • [] is a method .[](0) is valid
  • #each #reverse_each (#with_index)
  • Linear list, accessed by index
  • mixed type of objects
  • #tally - counts frequency of elements into a hash

Hash {}

  • {} {"foo" => "bar"}["foo"] { foo: "bar"}[:foo]
  • an indexed collection of key/valuep pairs
  • Enumerable
  • remembers order of insertion
  • supports "punning" for creation, if both are variable names in scope {firstname:, lastname:}
  • key/value
  • An association by an arbitrary key type
  • Hash.new(0) # where 0 is the default value
  • #sort_by - returns an array of arrays of 2 elements

stdlib

Default Gems in Ruby 3.3.3

name description
timeout Auto-terminates code blocks after the time limit is reached
drb Distributed object system for Ruby
nkf Kanji encoding converter. Can also be used via the Kconv.kconv method.
strscan Lexical string scanning
win32ole Windows OLE automation interface
securerandom Provides cryptographical randomness from openssl or the OS
english Readable aliases for special global variables
fiddle Support for FFI (Foreign Function Interface)
erb Templating engine for Ruby
tsort Topological sorting using Tarjan's algorithm, which finds strongly connected components in graphs
pstore Transactional file storage for Ruby objects
cmath Trigonometric and transcendental functions for complex numbers
scanf Pure-Ruby scanf implementation
prism Prism is a portable, error tolerant, and maintainable recursive descent parser for the Ruby language
date The Date and DateTime classes
time Adds more methods to Time
concurrency
mutex_m A mixin that makes any object behave like a mutex
rinda Support fot the Linda distributed computing paradigm in drb
thwait Waits for threads to finish
sync Two-phase lock with a counter for multi-threaded code
io-wait Adds methods to wait until an IO stream becomes readable or writable
io-nonblock Allows to work with IO streams in a non-blocking way
dev / debug
benchmark Benchmark utility: Measures and reports the time used to execute code
bundler Bundler is the local package manager for Ruby applications
e2mmap Abstraction around exception handling
error_highlight Improves error messages with additional debug info
irb Interactive Ruby Console (REPL)
prettyprint Better object formatting and inspection. Also see the pp gem.
pp The pp print debugging helper via require 'pp'. It will format the result object in more readable way.
ruby2_keywords A shim gemspec to indicate that the Ruby implementation includes the ruby2_keywords method
rubygems RubyGems allows you to download, install, and use Ruby software packages on your system
rdoc Ruby documentation generator
syntax_suggest Assists with code recommendation to resolve Ruby syntax errors
tracer Outputs the code execution trace via Kernel#set_trace_func
datastructures
bigdecimal Support for arbitrary-precision floating point decimal arithmetic
delegate Provides three ways to delegate method calls
forwardable Provides a way to delegate method calls. Also see this overview of delegation in Ruby, which contains an example
set Data structure for unordered collections without duplicates. Implemented on top of Hash.
ostruct Wrapper around Hash which lets you read and set attributes with a method-based API
observer Implementation of the observer pattern, away to let interested other objects know o an objetc's updates
weakref Explicitly allow objects to be garbage collected
stringio Makes strings behave like IO objects
singleton Mixin for Ruby classes that should only have one instance
os
etc Access UNIX info from /etc
fcntl Loads values from the OS' fcntl.h to be used for low-level file descriptor manipulation system calls with IO#fcntl and IO.sysopen
find Finds all files in a given directory and its sub-directories
fileutils Utilities for working with the file system, such as copying, moving, or deleting files
open3 Simple spawning of child processes
pathname Wraps File, FileTest, Dir, and FileUtils to ease working with file system paths
logger Logging utility
syslog Interface to the low-level syslog logger
tempfile Simplifies OS independent creation of temporary files
tmpdir Adds a Dir.mktmpdir method for creating temporary directories OS independently
un Utilities to replace common UNIX commands
cli
optparse Command-line option parser
getoptlong GNU getopt_long() style command-line option parsing
io-console Patches IO for simple and portable access to the console
reline Ruby-only implementation of GNU Readline / NetBSD Editline
readline If available, readline-ext will be loaded, or (Ruby-only) default gem reline will be used.
readline-ext Interface to GNU Readline and NetBSD Editline
shellwords Escape and manipulate commands to be run in the shell
shell Provides a Ruby interface for interacting with the shell
net/http
cgi Support for CGI (Common Gateway Interface)
digest Provides common hash functions like MD5, SHA1, SHA2, or RIPEMD-160
net-http Support for HTTP (Hypertext Transfer Protocol)
open-uri Monkeypatches Kernel#open to support remote endpoints via net/http and net/ftp
openssl Wraps OpenSSL for cryptographic functionality
uri URI/URL manipulation
webrick HTTP Server
net
net-protocol Internal class for the other net-* libraries
ipaddr IP address manipulation (IPv4 and IPv6)
resolv Thread-aware DNS resolver
resolv-replace Will replace Socket's DNS with resolv
file formats
csv Support for CSV (Comma-separated Values)
dbm Support for DBM databases
gdbm Support for GDBM databases
json Support for JSON (JavaScript Object Notation)
sdbm Support for SDBM databases
psych Support for YAML (YAML Ain't Markup Language)
yaml yaml will load the psych default gem
- yaml/dbm is a wrapper around DBM using YAML serialization,
- yaml/store lets you use pstore with YAML
zlib Interface to the zlib compression library

Default Libraries

continuation Adds the goto-like Kernel#callcc
coverage Measures code coverage
mkmf Generates Makefiles for native C extensions
monitor Monitors for multi-threaded code
objspace Adds more statistics methods to `ObjectSpace`. Since Ruby 3.1, it also includes the `objspace/trace` debug utility.
pty Manages pseudo terminals. Also includes the IO#expect method via `require 'expect'`, which can be used to wait for a specific pattern to be read.
rbconfig `RbConfig` is a Ruby constant that contains compile time information
ripper Ruby parser that creates a symbolic expression tree
socket Support for unix- and network sockets
unicode_normalize Adds a String#unicode_normalize method which normalizes unicode strings
win32 Let's you use Windows APIs, e.g. call functions in DLLs

Bundled Gems in Ruby 3.3.3

name description
did_you_mean Patches error messages to suggest correct spelling of methods/classes/variables
racc Bindings for Racc, a YACC-like LALR(1) parser generator
rake A Ruby task runner, inspired by make
debug Command-line [debugger](https://en.wikipedia.org/wiki/Debugger)
power_assert Debug tool that displays intermediate results of a method chain
test-unit A xUnit family unit testing framework (compatibility layer for minitest)
minitest Test/spec framework, comes with mocking and benchmark capabilities
rbs Released with Ruby 3.0, RBS is a way to declare types in Ruby. parsing and processing RBS type definitions
typeprof Released with Ruby 3.0, typeprof allows you to automatically generate RBS type annotations
matrix Support for [matrices](https://en.wikipedia.org/wiki/Matrix_%28mathematics%29)
prime Access to prime numbers and prime factorization
rss Support for RSS (Rich Site Summary) and Atom
rexml Support for XML (Extensible Markup Language)
xmlrpc Remote Procedure Calls via XML and HTTP
net-ftp Support for FTP (File Transfer Protocol)
net-imap Support for IMAP (Internet Message Access Protocol)
net-pop Support for POP3 (Post Office Protocol)
net-smtp Support for SMTP (Simple Mail Transfer Protocol)
net-telnet Support for Telnet

concurrency - threads

(1..10).map do |i|
  Thread.new do
    `wget http://example.com/file_00#{i}`
  end
end.each { |thread| thread.join }

codebases

snippets

`xrandr`.scan(/current (\d+) x (\d+)/)
  .flatten
  .map(&:to_i)
1600 900
"FOO Bar Foo".downcase.scan(/[\w']+/)
foo bar foo

tools

implementations


Created: 2024-06-11

Updated: 2024-11-16

Back