Variables are placeholders for data. They're useful for storing objects for use later.
Creating a variable is called defining and giving it a value is called assignment. In Ruby, defining happens when we assign a variable, so we usually only talk about assigning a variable.
# Assignment
>> x = 4
>> y = 5
You can find out what type of variable something is with the .class
method.
>> 4.class
=> Fixnum
>> "hello".class
=> String
The nil
keyword is a placeholder for "nothing". It is different from "undefined".
We can think of a variable as a box that we put data in.
# what's in the box? Nothing! aka `nil`
>> box = nil
>> box
=> nil
# what's inside of a non-existent box? That's impossible to know, so it's an error.
>> defined? box2
=> false
>> box2
NameError: undefined local variable or method `box2' for main:Object
Booleans represent whether something is either true
or false
.
A boolean expression is an expression that evaluates to true
or false
.
>> 1 < 3
=> true
>> 5 > 6
=> false
>> 5 == 5
=> true
>> "asdf" == 'asdf'
=> true
Comparing two values using comparison operators in a boolean expression results in either true
or false
.
operator | description |
---|---|
== |
equality |
!= |
not equal |
> |
greater than |
< |
less than |
>= |
greater than or equal to |
<= |
less than or equal to |
.eql? |
equal values AND same types |
.equal? |
identical objects (by object id) |
Though they exist, we almost never use .eql?
or .equal?
.
Booleans can be combined using logical operators.
operator | english | example | description |
---|---|---|---|
&& |
AND | (5 > 3) && (6 > 4) |
true only if both sides are true |
|| |
OR | (5 > 3) || (3 > 5) |
true if any expression evaluates as true |
! |
NOT | !(5 > 3) |
negates (reverses) a boolean expression's result |
Everything in Ruby evaluates to true
except for nil
and false
. This is very useful for writing simple if statements.
>> "yes" if 0
=> "yes"
>> name = "Darth Vader"
>> "yes" if name
=> "yes"
>> "yes" if false
=> nil
>> "yes" if nil
=> nil
Strings are variables that store text.
Concatenation - combining two strings with addition operator +
or the append operator <<
.
"Hello " + "World"
=> "Hello World"
Interpolation - using #{}
to interpolate variables into a string.
name = "Darth Vader"
"Hello #{name}!"
=> "Hello Darth Vader"
# Interpolation allows more complex statements
"Hello #{name.upcase.reverse}"
=> "Hello REDAV HTRAD"
# However, for clarity's sake, it is usually better to assign it to a variable first
Remember, interpolation is generally faster than concatenation and should be prefered if you need to programmatically create a string.
Single quotes - Do not allow for string interpolation or special characters.
Double quotes - Support string interpolation and special characters.
# 'single quotes' does not interpolate
>> 'Hi #{name}!'
=> "Hi \#{name}!"
# "double quotes" does interpolate
>> "Hi #{name}!"
=> "Hi Darth Vader!"
Recent optimizations in the Ruby compiler has reduce the performance difference between single and double quotes to a negligible amount. You can use either one as long as you understand the difference.
Want to know a lot more about it? Read the blog post Rubyists: Just use double-quoted strings.
(each method links name to the Ruby source document)
''
All numbers have a parent class called Numeric
, which shares basic numeric methods. Many mathematical methods can be found on `Numeric.
There are two common subclasses of Numeric
, the Fixnum
class which represents whole number integers (1, 10, 85), and the Float
class, representing real numbers (1.4, 10.75, 3.1415).
# Fixnum
>> 4.class
>> 1000.class
=> Fixnum
# Float
>> 10.4.class
>> 1984.189848.class
=> Float
Why Fixnum
instead of Integer
? The Fix
in Fixnum
stands for "fixed point" numbers, in contrast to "floating point" numbers. Fixnum
is a subclass kind of Integer
!
Watch out! Ruby treats integer division differently than most languages. It will not implicitly convert Integer
types to Floats
!
# Integer division always rounds and returns an Integer
>> 21 / 5
=> 4
>> (21 / 5).class
=> Fixnum
# Using floats return Floats
>> 21 / 5.0 # => 4.2
>> 21.0 / 5 # => 4.2
>> 21.0 / 5.0 # => 4.2
>> (21 / 5.0).class
=> Float
# Explicitly convert integer to float
>> age = 21
>> age.to_f / 5
=> 4.2
Other than the basic mathematical operators addition +
, subtraction -
, multiplication *
, division /
, modulo %
, and power **
, there are a few useful numeric methods.
(each method links name to the Ruby source document)
.0
decimal.Methods are tools for grouping lines of code together with a single common purpose.
Ruby uses the def
keyword to define methods.
Methods can take arguments, which are variables in the scope of the method.
def scream(string)
"#{string.upcase}!!!"
end
scream('howzit')
=> "HOWZIT!!!"
Ruby has syntactic sugar that allows you to drop the ()
on a method call if it's not required.
>> 10.to_s()
=> "10"
# No () needed
>> 10.to_s
=> "10"
Sometimes you do need them, most often when you're passing in the return value of one method into another method call. Ruby doesn't know which argument belongs to which method!
# Does the 3 belong to "puts" or "include?", no way to know!
puts "hello", [1, 2, 3].include? 3
SyntaxError: unexpected tINTEGER, expecting end-of-input
# Now we have to make it clear and it will work
puts "hello", [1, 2, 3].include?(3)
hello
true
=> nil
Every method returns a value. The return value of a method is the last evaluated statement.
def give_me_something_good
"this won't be returned"
"this WILL BE returned!"
end
give_me_something_good
=> "this WILL BE returned!"
Other statements, like if
or blocks, will return a value too!
# If statements work too
def check_truthiness(item)
if item
true
else
false
end
end
# Notice that the return value of the if statement is the last
# evaluated statement!
>> check_truthiness(0)
=> true
>> check_truthiness(nil)
=> false
Variables outside of the method are not available inside of the method.
some_var = 100
def some_method
some_var
end
>> some_method
NameError: undefined local variable or method `some_var' for main:Object
Variables inside of the method are not available outside of the method.
def some_method
some_var = 'hello'
end
>> some_method
=> "hello"
>> some_var
NameError: undefined local variable or method `some_var' for main:Object
Variables defined as method arguments not available outside of the method.
def some_method(some_var)
"#{some_var} hello"
end
>> some_method
=> "hello"
>> some_var
NameError: undefined local variable or method `some_var' for main:Object
Variables declared outside of a method are not accessible within the method (and vice versa), even if they have the same name!
some_var = "ORIGINAL VALUE"
def some_method
some_var = "secret value"
"this is some_method and my some_var is #{some_var}"
end
some_method
=> "this is some_method and my some_var is secret value"
some_var
=> "ORIGINAL VALUE"
Arrays are collections of things!
# It can contain anything in Ruby
>> array = ['zero', 1, 'two', :three, nil, {four: 5.0}, 6]
# Each slot of an array is accessed with an "index"
# Indeces of an array start at 0
>> array[0]
=> "zero"
# The items in an array are called "elements"
>> array[0] # index
=> "zero" # element
# Array elements can be accessed with methods also
>> array.first
=> "zero"
# And negative indeces, which count back from the end
>> array[-1]
=> 6
>> chores = ["laundry", "pay rent"]
# Adding an element with the append operator
>> chores << "dishes"
=> ["laundry", "pay rent", "dishes"]
# Removing an element by it's value (probably slow)
>> chores.delete("laundry")
=> ["pay rent", "dishes"]
# Removing by an index (much faster)
>> chores.delete_at(1)
=> ["pay rent"]
# Combining arrays
>> ian_chores = ["grade quizzes", "create slides"]
>> kevin_chores = ["take photos", "say lekker"]
>> ix_chores = ian_chores + kevin_chores
=> ["grade quizzes", "create slides", "take photos", "say lekker"]
# What about the append operator?
>> ix_chores << ["grade quizzes", "make popcorn"]
=> ["grade quizzes", "create slides", "take photos", "say lekker", ["buy staples", "make popcorn"]]
# Oops! It inserts it, not appends it!
# What if we only want unique elements?
>> ix_chores = ["grade quizzes", "grade quizzes", "create slides", "create slides"]
>> ix_chores.uniq
=> ["grade quizzes", "create slides"]
Symbols are just a simple way of using strings as keys. Strings are human readable, but they're slow and using a lot of them is not memory efficient.
Symbols act as human-readable ways of using strings effectively.
:this_is_a_symbol
Rails uses symbols a lot!
Hashes are a list like arrays, except that they don't use indeces for their elements. They use keys, usually symbols.
A hash maps a set of unique keys to values. The elements of a hash are also referred to as a key/value pair.
# Hashes usually use symbols
>> bill = {:name=>"Bill Door", :height=>150, :eyes=>"blue", :hair=>"red"}
# Ruby programmers prefer shorthand symbols because it's easier to type and read
>> bill = {name: "Bill Door", height: 150, eyes: "blue", hair: "red"}
# Accessing elements works like Arrays, except with keys
# Keys are ALWAYS unique! Anything you do to a key changes only that key.
# Hashes don't allow duplicate keys.
>> bill[:name]
=> "Bill Door"
# a String and a Symbol are different!
>> bill[:name]
=> "Bill Door"
>> bill["name"]
=> nil
# In RAILS though, they are the same!
# begin Rails only
>> bill[:name]
=> "Bill Door"
>> bill["name"]
=> "Bill Door"
# end Rails only
# Changing elements is the same as an Array
>> bill[:eyes] = "green"
>> bill[:eyes]
=> "green"
# Adding new elements is the same
>> bill[:shoes] = "Air Jordans"
>> bill[:shoes]
=> "Air Jordans"
# Deleting elements *always* searches by key
>> bill.delete :shoes
=> {:name=>"Bill Door", :height=>150, :eyes=>"blue", :hair=>"red"}
# Accessing list of keys
>> bill.keys
=> [:name, :height, :eyes, :hair, :shoes]
# Check if a hash has a key
>> bill.key? :eyes
=> true
# You SHOULD NEVER search a hash by value, that's EXTREMELY SLOW
# Hashes are designed when you need very fast access to data, using keys.
# But you can get a list of values of a hash! This is rarely useful.
>> bill.values
=> ["Bill Door", 150, "green", "red", "air jordans"]
Ruby allows you to ignore the curly brackets if you are passing a hash as the last argument to a method.
def feed_me_hashes(hash)
"YUM! I ate all these keys: #{hash.keys}"
end
# This is technically correct
feed_me_hashes({cheese: 'gruyere', meat: 'ostrich'})
=> "YUM! I ate all these keys: [:cheese, :meat]"
# But you can skip the curly brackets because it's the last argument.
# Rails uses this a lot!
feed_me_hashes(salad: 'Caesar', soda: 'Pepsi')
=> "YUM! I ate all these keys: [:salad, :soda]"
When Ruby allows you to be lazy, this is called syntactic sugar.
A block is code that acts like an unnamed method, as if you were passing it to a method as an argument.
Let's see an example in JavaScript to understand why blocks are so useful in Ruby.
// This is JavaScript
function console_print(element) {
console.log(element)
}
var cars = ["Saab", "Volvo", "BMW"];
cars.forEach(console_print);
In JavaScript, you have to create a separate function (method) away from the context of your code. That means someone reading your code has to understand what the function is, away from where they're reading it. This can be error prone, as someone might come along and change the function, or various other challenges.
Ruby allows you to create a block, which acts like you are passing in a function. Your code is in the same place as where you're using it, instead of somewhere else. This allows you to write code more easily, and read it more easily later.
cars = ["Saab", "Volvo", "BMW"];
# Instead of this (not valid Ruby)
def console_print(car)
puts car
end
cars.each(console_print)
# Use a block to print all the cars, all in one place
# |car| is the arguments
cars.each do |car|
puts car
end
This is most commonly used with enumeration.
Enumeration is when you loop through all of the elements of a list and do something with them.
each
loops through a list and does something to each element, then returns the original list.
cars = ["Saab", "Volvo", "BMW"];
cars.each do |car|
puts car
end
each_with_index
works just like each
, except that it also passes in the index of the element.
cars = ["Saab", "Volvo", "BMW"];
cars.each_with_index do |car, index|
puts "Car #{index}: #{car}"
end
map
loops through a list and returns a new list based on the return value of the block. This allows you to transform lists into something different.
cars = ["Saab", "Volvo", "BMW"];
reversed_cars = cars.map do |car|
car.upcase.reverse
end
=> ["BAAS", "OVLOV", "WMB"]
Classes are used for logically grouping together related data and behavior. They are blueprints for objects, much like an arcitect's plans are a blueprint for a house.
class Employee
end
Creating an Object from a Class (like creating a House from a blueprint) is called instantiating.
You use the new
class method to instantiate an Object from a class.
bill = Employee.new
=> #<Employee:0x007fa542ae4360>
You can use the initialize
method to require that an object passes in arguments when creating an instance. You use this when your objects must have data to be valid.
When you call the new
method on a class object, Ruby will instantiate a new object behind the scenes, and then call the object's initialize
method. You cannot call initialize
manually or you will get an error.
Additionally, unlike all other methods, the return value of initialize
is always the object's instance.
class Employee
def initialize(first_name, last_name)
end
end
>> bill = Employee.new
ArgumentError: wrong number of arguments (0 for 2)
>> bill = Employee.new("Bill", "Door")
=> #<Employee:0x007fa547116ae0>
What if we want to save data within the instance of a class? Our Employee object should keep track of the first and last name that we pass in. We do that with instance variables.
Instances do not share data. Each instance will only have access to it's own data.
class Employee
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
>> bill = Employee.new("Bill", "Door")
=> #<Employee:0x007fa5439f3590 @first_name="Bill", @last_name="Door">
Instance methods are methods that are accessible from any object instance of a class.
class Employee
# initialize from last example
def full_name
"#{@first_name} #{@last_name}"
end
end
>> bill = Employee.new("Bill", "Door")
=> #<Employee:0x007fa5439f8744 @first_name="Bill", @last_name="Door">
>> bill.full_name
=> "Bill Door"
"Attributes" is a special Ruby shortcut that allows you to create a get/set method for variables. Remember, everything after a .
in Ruby is a method, so we have to create methods to access our instance variables.
If we wanted to do this for every instance variable, our class definitions would be really big! This is what it would look like.
class Employee
# initialize method
def first_name
@first_name
end
def first_name=(value)
@first_name = value
end
end
Instead, Ruby gives us attributes, so we can do this more easily. Isn't Ruby good at helping us be lazy? This is the exact same code as above, just nicer!
class Employee
attr_accessor :first_name
# initialize method
end
>> bill = Employee.new("Bill", "Door")
=> #<Employee:0x007fa542591b10 @first_name="Bill", @last_name="Door">
>> bill.first_name
=> "Bill"
>> bill.first_name = "Eric"
=> "Eric"
Attributes can be put anywhere, but by convention are are usually at the top of the class definition.
Self is a special Ruby variable that always holds the object instance of the current scope. Because a Class is just a blueprint, it can't know anything about the instance of an object from the blueprint. self
allows instances to access themselves in the class blueprint.
Self is most often used for class methods (next section) but you will see it sometimes in Rails as well.
class Employee
# attributes and initialize
def mirror
self
end
end
>> bill = Employee.new("Bill", "Door")
=> #<Employee:0x007fa54253b3c8 @first_name="Bill", @last_name="Door">
>> bill.mirror
=> #<Employee:0x007fa54253b3c8 @first_name="Bill", @last_name="Door">
>> bill == bill.mirror
=> true
Each Class blueprint is also an object. When you define a class, Ruby instantiates an object that represents that Class—not an instance of that class. This is a very useful object that allows you to have methods that are related to a particular behavior but might not be used for instances.
It is most often used for two use cases: utility methods and Rails database queries.
# Utility methods
>> Time.now
>> RestClient.get
>> Random.rand
# Rails database queries
>> Person.where(first_name: "Bill", last_name: "Door")
>> Movie.find(100)
>> Movie.find_or_initialize_by(title: "Godfather 2", year: 1904)