Programming languages cheat sheetRuby logo

Ruby cheat sheet

Ruby syntax on one page, grouped by what you are trying to do: strings, hashes, blocks, classes, pattern matching and Bundler, checked against Ruby 4.0.

Last updated

Ruby is built to read like prose: everything is an object, blocks do the looping, and methods end in ? or ! to tell you what they do. The reference below is grouped by what you are trying to do, and the filter box searches all of it at once. Type hash and every hash method on the page comes to you, or type 3.2 to see what arrived in that release.

Every snippet is checked against Ruby 4.0, the current stable release. Anything that needs a version newer than 2.7 says so in the notes column. Names like user and nums are placeholders for your own. If you do not have a current Ruby installed, the official Docker image gets you a shell in one line: docker run --rm -it ruby:4.0 irb, and the Docker cheat sheet has the rest. Coming from PHP? The PHP cheat sheet is grouped the same way, so the two read side by side.

Running Ruby

TaskCommand
Check which version you haveruby -v
Run a scriptruby script.rb
Open an interactive shellirb
Run one line without a fileruby -e 'puts RUBY_VERSION'
Check a file for syntax errorsruby -c file.rb
Run with warnings turned onruby -w script.rb
Run with the YJIT compilerruby --yjit script.rb
Read the docs for a methodri Array#map

Most systems ship an old Ruby or none at all. A version manager such as rbenv, chruby or mise installs the current one next to it without touching the system copy.

Variables and symbols

TaskCodeNotes
Print with a newlineputs "Hello"
Print without a newlineprint "Hello"
Dump a value while debuggingp valuePrints value.inspect and returns the value
Pretty-print a big hashpp config
Assign a variablename = "Ada"No keyword, no type. snake_case by convention
Define a constantMAX_USERS = 100Starts with a capital. Reassigning only warns
Instance variable@name = "Ada"One per object, nil until set
Global variable$stdoutAvoid your own globals
A symbol:activeAn immutable name, one object per spelling
String to symbol and back"active".to_sym :active.to_s
Comment# one line
Is it nilvalue.nil?
Call a method only if not niluser&.address&.cityReturns nil instead of raising
Default when nil or falsename = input || "Anonymous"
Assign only if nil or false@cache ||= {}
Assign several at oncea, b = 1, 2
Swap two variablesa, b = b, a
First item and the restfirst, *rest = [1, 2, 3]
What class is itvalue.classEverything is an object, even 1 and nil
Is it a kind ofvalue.is_a?(Numeric)True for subclasses too
Same valuea == b
Same value and typea.eql?(b)1 == 1.0 is true, 1.eql?(1.0) is false
Same objecta.equal?(b)
Whole-number division7 / 2Gives 3. Use 7.fdiv(2) or 7 / 2.0 for 3.5
Raise to a power2 ** 10
Readable big numbers1_000_000
String to number, lenient"42".to_i "3.5".to_f"abc".to_i is 0
String to number, strictInteger("42")Raises ArgumentError on junk
Strict, but nil on junkInteger(input, exception: false)
Make a value read-onlyROLES = %w[admin member].freeze

Only nil and false are falsy. 0, an empty string and an empty array are all truthy, which trips up anyone arriving from JavaScript, Python or PHP.

Strings

TaskCodeNotes
Interpolate"Hello, #{name}"Double quotes only. Any expression works
No interpolation'Hello, #{name}'Prints #{name} literally
Join stringsfirst + " " + last
Append in placehtml << "</ul>"Changes the string, so not on a frozen one
A copy you can change+'text' str.dup
Length in characterss.length"café".length is 4
Length in bytess.bytesize"café".bytesize is 5
Change cases.upcase s.downcase s.capitalize
Strip whitespace from both endss.striplstrip and rstrip for one end
Remove the trailing newlinegets.chomp
Is it emptys.empty?blank? is Rails, not Ruby
Does it contains.include?("cat")
Does it start or end withurl.start_with?("https") file.end_with?(".rb")
Position of a substrings.index("cat")nil when missing
Replace the first matchs.sub("cat", "dog")
Replace every matchs.gsub("cat", "dog")
Replace by patterns.gsub(/\s+/, " ")
Part of a strings[0, 5] s[0..4] s[-3..]Start and length, or a range
Split into an arraycsv.split(",")
Split into characterss.chars
Join an array into a stringnames.join(", ")
Repeat"-" * 20
Pad to a lengthn.to_s.rjust(3, "0")7 becomes 007. ljust and center too
Format with placeholdersformat("%s has %d items", name, count)
Format a decimal"%.2f" % price
Does it match a patterns.match?(/\d+/)Just true or false, and the fastest
Capture part of a matchs.match(/(?<year>\d{4})/)[:year]match returns nil when nothing matches
Every matchs.scan(/\d+/)
Reverses.reverse
Parse JSONJSON.parse(text, symbolize_names: true)require "json" first. Without the option keys are strings
Encode to JSONdata.to_json

Arrays

TaskCodeNotes
Create an arraynums = [1, 2, 3]
Array of words%w[apple banana cherry]
Array of symbols%i[draft published]
Read by positionnums[0] nums[-1]Negative counts from the end
First or lastnums.first nums.last(2)
Read, raising if out of rangenums.fetch(99)nums[99] quietly returns nil
Appendnums << 4push takes several
Remove from the end or startnums.pop nums.shift
Add to the startnums.unshift(0)
Insert at a positionnums.insert(1, 99)
Remove every copy of a valuenums.delete(3)
Remove at a positionnums.delete_at(0)
How manynums.lengthsize is the same. count takes a block
Does it containnums.include?(3)
Is it emptynums.empty?
Position of a valuenums.index(3)nil when missing
A slicenums[1, 2] nums[1..]
Join two arraysa + b
Union, intersection, differencea | b a & b a - b
Remove duplicatesnums.uniq
Remove nilsvalues.compact
Flatten nested arraysnested.flatten
Sortnums.sort nums.sort.reverse
Sort by a fieldusers.sort_by(&:age)
Sort by two fieldsusers.sort_by { |u| [u.last_name, u.first_name] }
Add up, smallest, largestnums.sum nums.min nums.max
Item with the largest fieldusers.max_by(&:score)min_by too
Random item or ordernums.sample nums.shuffle
Pair up two arraysnames.zip(scores)
Split into groups of nnums.each_slice(2).to_a
A range as an array(1..10).to_a1...10 leaves out the 10
n separate empty arraysArray.new(3) { [] }Array.new(3, []) shares one array
Change in placenums.sort! nums.map! { |n| n * 2 }A trailing ! means it changes the receiver

Hashes

A hash maps keys to values and remembers insertion order. Symbol keys are the norm in Ruby code, string keys are what you get from JSON and web params.

TaskCodeNotes
Create with symbol keysuser = { name: "Ada", age: 36 }
Create with string keys{ "name" => "Ada" }user[:name] will not find it
Build from variables of the same name{ name:, age: }Ruby 3.1+
Read a valueuser[:name]nil when the key is missing
Read, raising if missinguser.fetch(:email)KeyError names the key
Read with a defaultuser.fetch(:role, "member")
Read a nested valueparams.dig(:user, :address, :city)nil if any level is missing
Set a valueuser[:role] = "admin"
Remove a keyuser.delete(:age)Returns the removed value
Does a key existuser.key?(:name)
Does a value existuser.value?("Ada")
Just the keys or valuesuser.keys user.values
Loop over pairsuser.each { |key, value| puts "#{key}: #{value}" }
Merge, right side winsdefaults.merge(options)
Merge, deciding clashes yourselfa.merge(b) { |_key, old, new| old + new }
Keep some keysuser.slice(:name, :email)
Drop some keysuser.except(:password)Ruby 3.0+
Keep pairs that pass a testprices.select { |_, price| price > 10 }reject for the opposite
Change every valueprices.transform_values { |p| p * 2 }
String keys to symbolsdata.transform_keys(&:to_sym)
Index a list by a fieldusers.to_h { |u| [u.id, u] }
Group a list by a fieldwords.group_by(&:size)
Count how often each value appearswords.tally
Hash where missing keys read as 0counts = Hash.new(0)counts[:x] += 1 then just works
Hash where missing keys get their own arrayHash.new { |h, k| h[k] = [] }
Sort by value, largest firstprices.sort_by { |_, v| -v }.to_h
Pair with the largest valueprices.max_by { |_, v| v }Returns [key, value]

Blocks and iterators

A block is the chunk of code between braces or do and end that you hand to a method. Braces for one line, do and end for several. Most of these come from Enumerable, so they work on arrays, hashes, ranges and anything else with an each method.

TaskCodeNotes
Loop over itemsnums.each { |n| puts n }
Loop with the positionnames.each_with_index { |name, i| }
Transform every itemnums.map { |n| n * 2 }
Call one method on every itemnames.map(&:upcase)
Short block parameternums.map { it * 2 }Ruby 3.4+. _1 before that
Keep items that pass a testnums.select(&:even?)filter is the same method
Drop items that pass a testnums.reject(&:zero?)
First item that passes a testusers.find { |u| u.admin? }nil when none do
Transform and drop nils in one passnums.filter_map { |n| n * 10 if n.even? }
Map, then flatten one levelposts.flat_map(&:tags)
Fold into one valuenums.reduce(0) { |sum, n| sum + n }nums.reduce(:+) for short
Build up a hash or arraywords.each_with_object(Hash.new(0)) { |w, counts| counts[w] += 1 }
Split into passing and failingevens, odds = nums.partition(&:even?)
Do all, any or none passnums.all?(&:positive?) nums.any? nums.none?
Count items that pass a testnums.count(&:even?)
Add up a fieldorders.sum(&:total)
Number items from 1names.map.with_index(1) { |name, i| "#{i}. #{name}" }
Neighbouring pairsnums.each_cons(2) { |a, b| }
Repeat n times3.times { |i| }
Count up in steps0.step(100, 10) { |n| }
Work through an endless sequence(1..).lazy.select(&:even?).first(3)Only computes what it needs
Hand a block to your own methoddef twice = (yield; yield)
Was a block passedblock_given?
Keep a block to call laterdef on(event, &handler)
Lambdasquare = ->(x) { x * x }Call with square.(4) or square.call(4)
Pass a method as a blockitems.each(&method(:puts))

Control flow

TaskCodeNotes
If, elsif, elseif a ... elsif b ... else ... endelsif, not else if
One-line ifputs "Adult" if age >= 18
Unlessreturn unless userSkip unless with else, it reads backwards
Pick one of two valueslabel = count == 1 ? "item" : "items"
Match a value to a resultcase code when 200, 201 then "OK" when 404 then "Not found" else "Error" endwhen compares with ===
Match a range or a classcase age when 0...13 then "child" when 13...18 then "teen" else "adult" endString, Integer and regexes work too
Match on shapecase response in { status: 200, body: } then body endPattern matching, Ruby 3.0+
Pull values out of a nested hashconfig => { db: { host: } }Raises if the shape does not match
Does it match a patternuser in { role: "admin" }true or false
While loopwhile line = file.gets ... end
Loop until something is trueuntil queue.empty? ... end
Loop until breakloop do ... break if done ... end
Skip to the next iterationnext if n.odd?
Leave a loop with a valuefound = nums.each { |n| break n if n > 2 }
Chain a value through a blockinput.then { |s| s.strip }
Both must be truea && band and or have much lower precedence

Methods

TaskCodeNotes
Define a methoddef add(a, b) ... endReturns the last expression
One-line methoddef add(a, b) = a + bRuby 3.0+
Default parameterdef greet(name = "world")
Keyword argumentsdef connect(host:, port: 5432)host is required, port is optional
Call with keywordsconnect(host: "db", port: 5433)
Any number of argumentsdef sum(*nums)
Any keyword argumentsdef log(msg, **meta)
Spread an array or hash into argumentssum(*numbers) connect(**options)
Pass every argument ondef wrapper(...) = target(...)
Pass the block on without naming itdef each(&) = @items.each(&)Ruby 3.1+. * and ** work the same from 3.2
Return earlyreturn if list.empty?
Return two valuesdef bounds(list) = [list.min, list.max]Then low, high = bounds(nums)
Method that answers yes or nodef admin?A trailing ? by convention
Dangerous or in-place versiondef save!A trailing ! by convention
Does an object have a methodobj.respond_to?(:each)
Call a method by nameobj.public_send(:name)
Parentheses are optionalputs "Hi" puts("Hi")Keep them when nesting calls

Classes and modules

TaskCodeNotes
Define a classclass User ... end
Constructordef initialize(name) ... endCalled by User.new
Create an objectuser = User.new("Ada")
Read-only attributeattr_reader :nameGenerates def name = @name
Read and write attributeattr_accessor :emailattr_writer for write-only
Class methoddef self.find(id)Called as User.find(1)
Constant on a classUser::ROLES
Inherit from a classclass Admin < User
Call the parent's versionsuperBare super passes the same arguments, super() passes none
Methods only the class can callprivateEvery def below it is private
Group classes under a namemodule Billing ... endThen Billing::Invoice
Mix a module's methods into a classinclude Comparable
Add a module's methods to the class itselfextend Forwardable
Make objects sortable and comparableinclude Comparable def <=>(other)
Make your collection Enumerableinclude Enumerable def each
Immutable value objectPoint = Data.define(:x, :y)Ruby 3.2+
Copy with a changepoint.with(x: 5)
Quick mutable recordPerson = Struct.new(:name, :email)Keyword arguments work from 3.2
Is it an instance ofuser.is_a?(User)
Class and its ancestorsAdmin.ancestors
Text for puts and interpolationdef to_s = "#{name} <#{email}>"
Copy an objectuser.dupShallow copy. clone keeps frozen state
Stop changes to an objectuser.freeze

Classes are open: reopening String or Integer and adding a method works anywhere in the program. It is how Rails adds 3.days.ago, and it is also the fastest way to break someone else's code. Prefer a module or a plain method in your own code.

Exceptions

TaskCodeNotes
Rescue an errorbegin ... rescue ArgumentError => e ... end
Rescue more than one typerescue Timeout::Error, IOError => e
Run code only if nothing was raisedbegin ... rescue ... else ... end
Run code whatever happensbegin ... ensure file.close end
Rescue across a whole methoddef load ... rescue JSON::ParserError ... endNo begin needed. Works in do blocks too
Raise an errorraise ArgumentError, "Bad id"
Raise a plain RuntimeErrorraise "Something broke"
Re-raise the current errorraiseInside a rescue
Define your own errorclass PaymentError < StandardError; end
The message and where it came frome.message e.backtrace.first(5)
Try the begin block againretryCount attempts, or it can loop forever
Default when anything goes wrongvalue = Integer(input) rescue 0Hides every StandardError, so use it sparingly
Jump out of deep loopscatch(:done) { throw :done, result }Control flow, not an error
Close a file even on errorFile.open("log.txt", "a") { |f| f.puts line }The block form closes it for you

A bare rescue catches StandardError and its subclasses, which is almost always what you want. Rescuing Exception also catches Interrupt and SystemExit, so Ctrl+C and exit stop working.

Gems and Bundler

A gem is a Ruby package. Bundler reads the Gemfile, writes the exact versions it resolved to Gemfile.lock, and makes sure the app loads those versions and no others.

TaskCommand
Install a gem globallygem install rails
List installed gemsgem list
Remove a gemgem uninstall rails
Start a Gemfilebundle init
Add a gem to the Gemfile and install itbundle add rack
Add with a version constraintbundle add rack --version "~> 3.1"
Add to a groupbundle add rspec --group test
Remove a gem from the Gemfilebundle remove rspec
Install what the Gemfile.lock saysbundle install
Skip a group on this machinebundle config set --local without test
Upgrade one gem within its constraintbundle update rack
See which gems have newer versionsbundle outdated --only-explicit
Run a command with the bundled versionsbundle exec rspec
Where a gem is and which versionbundle info rack
Check everything is installedbundle check
Add a platform for Linux serversbundle lock --add-platform x86_64-linux
Load the bundle in plain Rubyrequire "bundler/setup"

bundle install and bundle update are not interchangeable. install reproduces Gemfile.lock, so it is what you run after cloning and in CI. update resolves newer versions and rewrites the lock file, so it is a change you commit and review.

A class, start to finish

Most of the class syntax above in one place: a module as a namespace, keyword arguments, attr_reader, Comparable, a class method, a private helper and a custom error.

module Billing
  class InvalidAmount < StandardError; end
 
  class Money
    include Comparable
 
    SYMBOLS = { gbp: "£", usd: "$" }.freeze
 
    attr_reader :pence, :currency
 
    def initialize(pence, currency: :gbp)
      raise InvalidAmount, "pence must be an Integer" unless pence.is_a?(Integer)
 
      @pence = pence
      @currency = currency
    end
 
    def self.from_pounds(pounds, **options)
      new((pounds * 100).round, **options)
    end
 
    def +(other)
      check_currency!(other)
      Money.new(pence + other.pence, currency:)
    end
 
    def <=>(other)
      check_currency!(other)
      pence <=> other.pence
    end
 
    def to_s = format("%s%.2f", SYMBOLS.fetch(currency), pence / 100.0)
 
    private
 
    def check_currency!(other)
      return if other.currency == currency
 
      raise ArgumentError, "cannot mix #{currency} and #{other.currency}"
    end
  end
end
 
price = Billing::Money.new(1999)
puts price + Billing::Money.from_pounds(5) # £24.99
puts [price, Billing::Money.new(500)].max  # £19.99

Defining <=> and including Comparable gives you <, >, between?, clamp, and lets max, min and sort work on a list of Money. Money is held in pence as an integer on purpose: floats cannot represent most decimal fractions exactly, so 0.1 + 0.2 == 0.3 is false in Ruby, as it is in almost every language.

Blocks, procs and lambdas

A method can call the block it was given with yield, or capture it as an object with &. Procs and lambdas are both block objects, but they disagree about arguments and return.

def with_timing(label)
  started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  result = yield
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
  puts format("%s took %.3fs", label, elapsed)
  result
end
 
total = with_timing("sum") { (1..1_000_000).sum }
 
# A lambda checks its arguments, like a method does.
add = ->(a, b) { a + b }
add.(1, 2)  # 3
add.(1)     # ArgumentError
 
# A proc fills gaps with nil and ignores extras.
loose = proc { |a, b| [a, b] }
loose.call(1)       # [1, nil]
loose.call(1, 2, 3) # [1, 2]
 
# Any object with to_proc can stand in for a block.
%w[ada grace].map(&:capitalize) # ["Ada", "Grace"]

Pattern matching

case with in matches on the shape of a value rather than its equality, and binds the parts you name as local variables. It is at its best on parsed JSON.

require "json"
 
response = JSON.parse(body, symbolize_names: true)
 
message =
  case response
  in { status: 200, data: { user: { name: String => name } } }
    "Welcome back, #{name}"
  in { status: 404 }
    "No such user"
  in { status: 500.. => status, error: }
    "Server error #{status}: #{error}"
  in { status: }
    "Unexpected status #{status}"
  end

Hash patterns match when the listed keys are present and ignore any others. Array patterns must match the whole array unless you add *rest. If no branch matches and there is no else, Ruby raises NoMatchingPatternError, which is usually what you want: a response you did not plan for fails loudly.

Data and Struct

Data.define makes a small immutable value class with a constructor, equality, to_h and pattern matching support. Struct is the older, mutable sibling.

Point = Data.define(:x, :y) do
  def +(other) = with(x: x + other.x, y: y + other.y)
  def to_s = "(#{x}, #{y})"
end
 
a = Point.new(x: 1, y: 2)
b = Point.new(3, 4)      # positional arguments work too
puts a + b               # (4, 6)
a == Point.new(x: 1, y: 2) # true, compared by value
a.to_h                   # {x: 1, y: 2}
 
case a
in { x: 0 } then "on the y axis"
in Point(x:, y:) then "at #{x}, #{y}"
end
 
Person = Struct.new(:name, :email)
ada = Person.new(name: "Ada")
ada.email = "ada@example.com" # Struct members can be changed

Strings across several lines

The squiggly heredoc, <<~, strips the indentation of its least indented line, so the text can sit at the same depth as the code around it. It interpolates like a double-quoted string. Put the marker in single quotes to turn interpolation off.

def welcome_email(user)
  <<~TEXT
    Hi #{user.name},
 
    Thanks for signing up. Your username is #{user.username}.
  TEXT
end
 
query = <<~'SQL'
  SELECT * FROM users WHERE name LIKE '#{not interpolated}'
SQL

A Gemfile, start to finish

bundle add writes these lines for you, but it helps to read them. ~> 3.1 means at least 3.1 and below 4.0, so minor updates come in and major ones do not.

# Gemfile
source "https://rubygems.org"
 
ruby "~> 4.0"
 
gem "rack", "~> 3.1"
gem "puma", "~> 8.0"
 
group :development, :test do
  gem "rspec", "~> 3.13"
end

Commit Gemfile and Gemfile.lock together, and run your tools through bundle exec so they use the locked versions. The Git cheat sheet covers the rest of that workflow. For a one-file script, bundler/inline installs and loads gems without a separate Gemfile:

require "bundler/inline"
 
gemfile do
  source "https://rubygems.org"
  gem "rack", "~> 3.1"
end
 
puts Rack.release

Gotchas

The mistakes that turn up in almost every Ruby codebase at some point.

Looks rightWhat actually happensDo this instead
if count when count is 0The branch runs: only nil and false are falsyif count.positive?, or count.zero?
Array.new(3, [])All three slots are the same arrayArray.new(3) { [] }
Hash.new([]) then h[:a] << 1Every key shares one default array, and h stays emptyHash.new { |h, k| h[k] = [] }
params[:name] on parsed JSONnil, because JSON keys are stringsparams["name"], or parse with symbolize_names: true
7 / 23, integer division7.fdiv(2) or 7 / 2.0
users.sort_by(&:age) for a stable orderTies can come out in any orderusers.sort_by.with_index { |u, i| [u.age, i] }
rescue ExceptionAlso catches Ctrl+C and exitrescue => e, or a specific class
ok = valid? and saved?ok gets valid? alone, because = binds tighter than andok = valid? && saved?
"abc".to_i0, no errorInteger("abc"), which raises
name << "!" on a literalFrozenError under frozen_string_literal: truename + "!", or start from +""

Ruby's sort is a quick sort variant, which is why equal items are not guaranteed to keep their order. A stable algorithm such as merge sort keeps them. Both are on the site as step-through visualisations.

Common questions

Which version of Ruby does this cheat sheet cover?

Ruby 4.0, the current stable release, which came out in December 2025. Almost everything on the page also runs on Ruby 3, and anything newer than 2.7 says so in the notes column, for example the it block parameter needs 3.4, Data.define needs 3.2 and hash shorthand needs 3.1. Run ruby -v to see which version you have.

What is the difference between a symbol and a string in Ruby?

A symbol, like :name, is an immutable identifier, and every use of :name is the same object. A string, like "name", is text you can change, and each literal can be a separate object. Use symbols for names the program uses internally, such as hash keys and method names, and strings for text that comes from or goes to people, files and the network.

What is the difference between a proc and a lambda?

Both are blocks turned into objects. A lambda checks its argument count and raises if it is wrong, and return inside it returns from the lambda. A proc ignores missing or extra arguments, and return inside it returns from the method the proc was defined in. Lambdas behave like small methods, so reach for them unless you specifically want proc behaviour.

What is the difference between puts, print and p?

puts prints a value's to_s followed by a newline, and prints each element of an array on its own line. print does the same without the newline. p prints the inspect version, so strings keep their quotes and nil shows as nil, and it returns the value, which makes it handy for debugging in the middle of an expression. All three are methods on Kernel.

What does the ! and ? at the end of a Ruby method mean?

Nothing to the interpreter, a lot to readers. A trailing question mark marks a method that answers yes or no, like empty? or admin?. A trailing exclamation mark marks the more dangerous of a pair: sort returns a new array and sort! changes the one it is called on, save returns false on failure and save! raises.

What is the difference between include and extend?

include adds a module's methods to instances of the class, so every object gets them. extend adds them to the one object you call it on, and inside a class body that object is the class, so they become class methods. Enumerable and Comparable are meant to be included, Forwardable is meant to be extended.

Should I commit Gemfile.lock?

Yes, for an application. The lock file records the exact version of every gem, so bundle install gives every developer, CI run and server the same code. For a gem you publish it is optional: the apps that install your gem ignore your lock file and resolve their own versions, though committing it keeps your own CI reproducible.

Why does my string raise FrozenError?

Something froze it. A file that starts with the comment frozen_string_literal: true makes every string literal in it immutable, and many projects turn that on everywhere. Ruby 3.4 and 4.0 go further towards frozen literals and warn when you change one, with deprecation warnings enabled. Build the string with +"" or call dup to get a copy you can change.

See all cheat sheets

Want this explained by a cat?

The videos cover the same ground in sixty seconds. If there is a tool you want a cheat sheet for next, ask.