askesis

Ruby essentials: 50 cards on blocks, symbols, Enumerable and mixins

Ruby fundamentals with short snippets: blocks, procs and lambdas, symbols, Enumerable, mixins, truthiness, method_missing, gems and Bundler.

ProgrammingBeginner50 cardsFree

Practice now

Study without an account

0 of 50 seen0 graduated
What does “everything is an object” mean in Ruby?

Scheduled with the default Askesis timings. Progress is saved in this browser.

All 50 cards

  1. 1
    What does “everything is an object” mean in Ruby?
    Every value, including numbers, nil, true and classes themselves, is an object with methods: 5.even?, nil.to_a, 3.times { … }
    5.class is Integer, and Integer.class is Class.
  2. 2
    What does this print?
    ruby
    puts 0 ? "yes" : "no"
    yes: only nil and false are falsy in Ruby; 0, "" and [] are all truthy
  3. 3
    puts vs p
    puts prints the to_s form with a newline and returns nil; p prints the inspect form (strings keep their quotes) and returns its argument, which makes it handy for debugging
  4. 4
    Interpolate a value into a string
    ruby
    puts "Hello, #{name}! You have #{items.size} items."
    Interpolation only works in double-quoted strings; single quotes are literal.
  5. 5
    What does a Ruby method return when there is no return?
    The value of the last expression it evaluated
    ruby
    def full_name
      "#{first} #{last}"
    end
  6. 6
    What do ? and ! at the end of a method name signal?
    ? marks a predicate that returns a boolean (empty?); ! marks a “dangerous” version of a method, usually one that mutates the receiver (sort! vs sort)
  7. 7
    What does "ABC".upcase! return?
    nil: many bang methods return nil when they make no change, so don’t chain them
    "abc".upcase! returns "ABC".
  8. 8
    Generate a getter and setter for @name
    ruby
    class User
      attr_accessor :name
    end
    attr_reader makes only the getter, attr_writer only the setter.
  9. 9
    What do the prefixes @, @@ and $ mean?
    @name is an instance variable, @@count a class variable shared with subclasses, and $stdout a global; names starting with a capital letter are constants
  10. 10
    What is 7 / 2 in Ruby?
    3: dividing two integers is integer division, rounding down
    7 / 2.0 and 7.fdiv(2) give 3.5. Note that -7 / 2 is -4.
  11. 11
    if is an expression
    ruby
    label = if score >= 50 then "pass" else "fail" end
    So are case and begin blocks: they all return their last value.
  12. 12
    Write a one-line conditional
    ruby
    return if user.nil?
    puts "Welcome back" unless first_visit?
    Modifier if and unless go after the statement; unless means “if not”.
  13. 13
    Memoize a value with ||=
    ruby
    def config
      @config ||= load_config
    end
    Gotcha: if the result is nil or false, it is recomputed on every call.
  14. 14
    Define a method with keyword arguments
    ruby
    def greet(name:, greeting: "Hello")
      "#{greeting}, #{name}!"
    end
    
    greet(name: "Ada") # => "Hello, Ada!"
    name: has no default, so it is required.
  15. 15
    Call a method only if the receiver isn’t nil
    ruby
    city = user&.address&.city
    &. is the safe navigation operator: it returns nil instead of raising NoMethodError.
  16. 16
    What is a symbol?
    An immutable, interned name like :status: every use of the same symbol is the same object, which makes symbols cheap identifiers for hash keys and method names
  17. 17
    What does this print?
    ruby
    puts :a.equal?(:a)
    puts "a".equal?("a")
    true, then (without frozen string literals) false: there is only one :a object, but each string literal creates a new string
    equal? checks identity; "a" == "a" is true.
  18. 18
    What does this print?
    ruby
    h = { name: "Ada" }
    p h["name"]
    nil: name: creates a symbol key, so the value is at h[:name], not h["name"]
    { name: "Ada" } is shorthand for { :name => "Ada" }.
  19. 19
    Are Ruby strings mutable?
    Yes: methods like << and upcase! modify a string in place. The magic comment # frozen_string_literal: true freezes every string literal in a file
    Ruby 3.4 can warn (with deprecation warnings enabled) when code mutates a string literal, a step toward frozen literals by default.
  20. 20
    What is a block?
    A chunk of code passed to a method call with { … } or do … end; the method runs it with yield
    ruby
    [1, 2, 3].each { |n| puts n }
    
    File.open("log.txt") do |f|
      puts f.readline
    end
  21. 21
    What does this print?
    ruby
    def twice
      yield 1
      yield 2
    end
    
    twice { |n| puts n * 10 }
    10 and 20 on separate lines: each yield calls the block with an argument
    block_given? tells a method whether it received a block.
  22. 22
    What does this print?
    ruby
    puts [1, 2].map do |x|
      x * 2
    end
    Something like #<Enumerator: …>, not 2 and 4: a do … end block binds to the outermost call (puts), so map gets no block
    { … } binds to the nearest call: puts [1, 2].map { |x| x * 2 } prints 2 and 4.
  23. 23
    Proc vs lambda
    A lambda checks its argument count, and return exits only the lambda; a proc ignores missing or extra arguments, and return returns from the method where the proc was defined
  24. 24
    Create and call a lambda
    ruby
    square = ->(x) { x * x }
    square.call(3) # => 9
    square.(3)     # => 9
    square[3]      # => 9
  25. 25
    What does names.map(&:upcase) mean?
    Shorthand for names.map { |n| n.upcase }: & turns the symbol into a block via Symbol#to_proc
    In a method definition, a &block parameter does the reverse, capturing the passed block as a Proc.
  26. 26
    Refer to a block’s parameter without naming it
    ruby
    [1, 2, 3].map { it * 2 } # Ruby 3.4+
    [1, 2, 3].map { _1 * 2 } # Ruby 2.7+
  27. 27
    Can a block use local variables from where it was written?
    Yes. Blocks, procs and lambdas are closures: they capture the surrounding local variables and can change them
    ruby
    total = 0
    [1, 2, 3].each { |n| total += n }
    total # => 6
  28. 28
    each vs map
    each runs the block for its side effects and returns the original collection; map returns a new array of the block’s results
  29. 29
    Common Enumerable one-liners
    ruby
    (1..10).select(&:even?)       # => [2, 4, 6, 8, 10]
    [1, 2, 3].reject(&:odd?)      # => [2]
    [1, 2, 3].reduce(:+)          # => 6
    %w[a bb c].find { _1.size > 1 } # => "bb"
  30. 30
    Loop with an index, or build a result while iterating
    ruby
    names.each_with_index { |name, i| puts "#{i}: #{name}" }
    
    lengths = names.each_with_object({}) { |n, h| h[n] = n.size }
  31. 31
    How do you get map, select, sort and friends for your own collection class?
    Include the Enumerable module and define each; Enumerable builds dozens of methods on top of it
    ruby
    class Playlist
      include Enumerable
    
      def initialize(songs)
        @songs = songs
      end
    
      def each(&block)
        @songs.each(&block)
      end
    end
  32. 32
    Count how often each word appears
    ruby
    %w[a b a c a].tally # => {"a" => 3, "b" => 1, "c" => 1}
    tally arrived in Ruby 2.7. group_by { … } instead groups elements into arrays by the block’s result.
  33. 33
    1..5 vs 1...5
    Two dots include the end (1 to 5); three dots exclude it (1 to 4)
  34. 34
    Take the first three even squares from an infinite range
    ruby
    (1..).lazy.map { _1 * _1 }.select(&:even?).first(3)
    # => [4, 16, 36]
    lazy processes elements one at a time, so infinite or huge sequences work.
  35. 35
    What is a module?
    A container for methods and constants that can’t be instantiated: used as a namespace (Net::HTTP) and as a mixin added to classes
  36. 36
    include vs prepend vs extend
    include adds a module’s methods as instance methods, looked up after the class’s own; prepend puts them before the class’s own, so they can wrap them with super; extend adds them to a single object, e.g. as class methods
  37. 37
    Make objects comparable with <, > and between?
    ruby
    class Version
      include Comparable
      attr_reader :major, :minor
    
      def <=>(other)
        [major, minor] <=> [other.major, other.minor]
      end
    end
    The Comparable mixin builds <, <=, ==, >, >=, between? and clamp from <=>.
  38. 38
    Inheritance and class methods
    ruby
    class Dog < Animal
      def self.create(name) = new(name)
    
      def speak
        super + " Woof"
      end
    end
    A class has one superclass; def self.x defines a class method; super calls the parent’s version of the current method.
  39. 39
    How does Ruby find a method?
    It walks the receiver’s ancestor chain (its class, included modules, the superclass and so on) until it finds the method, calling method_missing if none matches
    Dog.ancestors shows the chain, e.g. [Dog, Animal, Object, Kernel, BasicObject].
  40. 40
    What are open classes (monkey patching)?
    Any class, including built-ins like String, can be reopened to add or replace methods. Powerful but risky; refinements (refine and using) limit a patch to the files that opt in
  41. 41
    What is duck typing?
    Caring about what an object can do rather than its class: call the method, or check respond_to?(:each), instead of checking is_a?
  42. 42
    == vs equal? vs eql?
    == compares values, equal? checks object identity, and eql? is the stricter equality used for hash keys
    1 == 1.0 is true, but 1.eql?(1.0) is false.
  43. 43
    What is method_missing?
    A hook Ruby calls when no method matches a call, letting an object respond to dynamic method names. Pair it with respond_to_missing?, and call super for names you don’t handle
    ruby
    class Settings
      def initialize(data)
        @data = data
      end
    
      def method_missing(name, *args)
        @data.key?(name) ? @data[name] : super
      end
    
      def respond_to_missing?(name, include_private = false)
        @data.key?(name) || super
      end
    end
  44. 44
    Call a method whose name is in a variable
    ruby
    user.public_send(:name)    # respects visibility
    user.send(:secret_helper)  # can call private methods too
    define_method(:name) { … } defines methods dynamically, which is often clearer than method_missing.
  45. 45
    What is a gem?
    A packaged Ruby library or tool, published on rubygems.org and installed with gem install <name>
  46. 46
    How does Bundler manage a project’s gems?
    You list dependencies in a Gemfile; bundle install resolves compatible versions and records them in Gemfile.lock; bundle exec <cmd> runs a command with exactly those versions
    bundle add <gem> adds a gem to the Gemfile and installs it.
  47. 47
    What does gem "rails", "~> 7.1" allow?
    Versions >= 7.1 and < 8.0. With three parts, "~> 7.1.3" allows >= 7.1.3 and < 7.2
    ~> is the “pessimistic” version constraint.
  48. 48
    Should you commit Gemfile.lock?
    Yes, for applications: it guarantees that every developer, CI run and server uses the same gem versions
  49. 49
    Handle an exception
    ruby
    begin
      data = JSON.parse(text)
    rescue JSON::ParserError => e
      warn "Bad JSON: #{e.message}"
      data = {}
    ensure
      log_attempt
    end
    Raise with raise ArgumentError, "message". Inside a method body, rescue works without begin.
  50. 50
    Match on the shape of a hash
    ruby
    case response
    in { status: 200, body: }
      puts body
    in { status: 404 }
      puts "Not found"
    end
    Pattern matching with case/in is stable since Ruby 3.0; body: binds the value to a local variable.

Frequently asked questions

Which Ruby version does the deck use?

Ruby 3. Recent features are marked, such as pattern matching (3.0) and the it block parameter (3.4).

Should I learn Ruby before Rails?

It helps a lot. Rails code leans on blocks, symbols, mixins and metaprogramming, which is exactly what this deck covers.

What is the difference between a proc and a lambda?

A lambda checks how many arguments it receives, and return leaves only the lambda. A proc is lenient with arguments, and return exits the method that defined it.

Intermediate

JavaScript

Master the JavaScript every developer uses daily: scope, closures, equality, array methods, promises, async/await and the event loop.

50 cards
Intermediate

Advanced JS

Fifty advanced JavaScript cards: event-loop ordering puzzles, generators and async iteration, this binding, Proxy and Reflect, modules, AbortController and coercion edge cases.

50 cards
Intermediate

TypeScript

Learn the TypeScript you use every day: narrowing, generics, keyof, mapped and conditional types, utility types, satisfies and strict mode, with code on every card.

50 cards
Beginner

Python

Learn idiomatic Python fast: comprehensions, f-strings, args and kwargs, generators, decorators, context managers and the standard library.

50 cards
Beginner

Git & terminal

The Git commands and shell skills developers use every day: branching, rebasing, undoing mistakes, pipes, redirects, permissions and processes.

50 cards
Beginner

Java

Core Java with short snippets: equals vs ==, records, sealed types, collections, generics, exceptions, streams, lambdas and JVM basics.

50 cards
Beginner

C#

C# and .NET fundamentals with short snippets: value vs reference types, properties, records, nullable reference types, LINQ, async/await and DI.

50 cards
Beginner

Kotlin

Kotlin fundamentals with short snippets: null safety, val vs var, data and sealed classes, extension functions, scope functions and coroutines.

50 cards
Beginner

Swift

Swift fundamentals with short snippets: optionals, structs vs classes, protocols, enums with associated values, closures, ARC, async/await and SwiftUI.

50 cards
Beginner

Flutter

Flutter and Dart basics: null safety, futures and streams, stateless vs stateful widgets, setState, hot reload, keys, layout and state management.

50 cards
Beginner

PHP

Modern PHP 8 with short snippets: strict types, arrays, match, nullsafe and named arguments, enums, readonly, Composer, PDO and password_hash.

50 cards
Beginner

Go

Go (Golang) fundamentals with short, idiomatic snippets: slices vs arrays, maps, defer, error wrapping, interfaces, goroutines, channels, select and context.

50 cards
Intermediate

Rust

Learn the Rust that the compiler keeps asking about: ownership, borrowing, lifetimes, Option and Result, traits, closures, smart pointers and async.

50 cards
Intermediate

C++

Modern C++ in short snippets: references vs pointers, RAII, smart pointers, move semantics, STL containers and algorithms, const, templates and UB.

50 cards
Intermediate

Haskell

Haskell fundamentals with short code: types and type classes, pattern matching, laziness, Maybe and Either, folds, Functor, Applicative and Monad.

50 cards
Intermediate

Elixir

Elixir fundamentals with short snippets: immutability, pattern matching, the pipe operator, Enum vs Stream, processes, GenServer, supervisors and mix.

50 cards
Intermediate

x86-64 assembly

x86-64 assembly in Intel syntax: registers and sub-registers, flags, mov and lea, jumps, the stack frame, the System V calling convention and Linux syscalls.

50 cards
Beginner

Regex

Learn regular expressions for good: anchors, classes, greedy vs lazy quantifiers, groups, lookarounds, flags, replacements and the backtracking trap.

50 cards