Ruby
Interpreted vs Compiled
Depending on the programming language you’re using, it will either be a compiled language or an https://desk-fd.zol-img.com.cn/t_s960x600c5/g5/M00/0B/05/ChMkJlcgb6mIY7cbAAg5YZ7Q3FwAAQrzAAWO2sACDl5956.jpg[interpreted language](http://en.wikipedia.org/wiki/Interpreted_language). Compiled programs will first be converted to machine code and then you will be able to run the program. Interpreted languages will be interpreted and converted to machine code at run time.
puts&&print
you need to use the method puts
which is short for “output string.” And because Hello World! is a string, you need to surround your text with ""
.
both “” and ‘’ is ok
differenece
The primary difference between them is that puts
adds a new line after executing, and print
does not.
return
both of print and puts will return nil
example
To both print and return your name, you could write:
def print_and_return_name
puts "Guy Fieri" "Guy Fieri"
end
There is one other way to return a value from a method and that is to use the return
keyword.
Let’s take a look:
def stylish_chef
best_hairstyle = "Guy Fieri"
return "Martha Stewart"
"Guy Fieri"
end
it will just return the first value following
return
Sanitizing User Input: The strip
and chomp
Methods
One thing to know about the #gets
method is that it captures a new line character at the end of whatever input it is storing. We don’t want extra whitespace or new lines to be appended to the user input we are trying to store. So, we can chain a call to the #strip
method to remove any new lines or leading and trailing whitespace.
The #chomp
method works similarly, and you are likely to see #gets.chomp
used in some examples online. The #chomp
method removes any new lines at the end of a string while the #strip
method removes whitespace (leading and trailing) and new lines.
IRB
What is IRB?
IRB stands for “Interactive Ruby.” It’s a Ruby shell or REPL.
How do you use it?
IRB allows you to execute ruby in the terminal .To access IRB, just type irb
in the terminal. IRB allows you to do anything you can do in a Ruby file.use exit to leave IRB.
Four Common Error Types
Name Errors
NameErrors are caused when a given name is invalid or undefined. Whenever the Ruby interpreter encounters a word it doesn’t recognize, it assumes that word is the name of a variable or a method. If that word was never defined as either a variable or a method, it will result in a name error.
Syntax Errors
Syntax errors are pretty self-explanatory: they’re the result of incorrect syntax. Thankfully, they’re usually followed by a guess about the location of the error. For instance:
2.times do puts "hi"
Will result in:
2: syntax error, unexpected end-of-input, expecting keyword_end
Here, Ruby is saying that on line 2, there is a missing end
(every do
keyword must be followed by some code and then an end
keyword). Always read the full details of syntax errors and look for line numbers, which usually appear at the beginning of the error message.
Type Errors
When you try and do a mathematical operation on two objects of a different type, you will receive a TypeError. For example if you try and add a string to an integer, Ruby will complain.
1 + "1"
Will produce the following error:
TypeError: String can't be coerced into Fixnum
Division Errors
DivisionErrors are caused when a given number is divided by 0.
variable
Note: The syntax of #{current_president}
simply injects the value of the variable current_president
into the string. This is called Interpolation .where you are simply adding together multiple strings.
In Ruby, a variable can point to almost any type of value including numbers, strings, arrays, and hashes.
- Variable names should start with a lowercase letter. A variable that begins with an uppercase letter is known as a constant and has different characteristics.
- There is strong convention among Rubyists to use what is known as snake case
this_is_an_example_of_snake_case
words are separated by underscores.
- A Ruby variable cannot start with a number, be a Ruby reserved word, or have punctuation or space characters.
- Ruby is what is known as a dynamically typed language. That means the value of a variable can change its type and does not need to be explicitly and permanently defined.
- It is also a strongly typed language. This means a variable will never be automatically coerced to another type without you explicitly changing the type.
How You Interpolate Variables into Strings
To interpolate, you wrap the variable like #{this}
.
Another Way to Interpolate Variables into Strings
Some Rubyists write this another way, like this:
answer = "Flamboyance"
puts "A group of flamingos is called a " + answer + "."
Single quotes:
''
do not support string interpolation
Ruby Data Types
Creating Strings
There are two ways to create a string. In fact, we’ve already created a string just by typing "hello"
.
Try it out by opening up IRB, and typing "hello".class
You should see a return value of => String
. You can actually call .class
on any object to find out what type of data, i.e. what class, it is.
The Literal Constructor: This is the method through which we created our “hello” string.
The Class Constructor: You can also create a string with String.new
. This will create an empty string.
String.new("hello")
on the other hand, will create this string: "hello"
. For the most part, you will create strings using the first method discussed here––simply by enclosing whatever text you want in quotes.
Because every string is based on Ruby’s String class, there are certain behaviors, or methods, available to us for operating on them. You can learn more about the many String methods by reading the Ruby documentation on Strings. For now, we’ll just take a look at a few examples.
"hello".size => 5
"hello".upcase => "HELLO"
"hello".reverse => "olleh"
Booleans
There are only two values of the Boolean data type: true
and false
. In Ruby, however, there is no such thing as a Boolean class. Instead, every appearance, or instance, of true
and false
in your program are instances of TrueClass and FalseClass respectively.
Unlike strings, there is not a way to create a Boolean value, other than to explicitly write
true
orfalse
. Later, we will see that we can write lines of code that evaluate to or returntrue
andfalse
,
In Ruby only false and nil are falsey. Everything else is truthy (yes, even 0 is truthy).
What are Boolean Operators?
Boolean operators are really methods which means that they have return values. What do they return? true
or false
of course!
In Ruby there are three main boolean operators:
!
(“single-bang”) which represents “NOT”,&&
(“double-ampersand”) which represents “AND”, and||
(“double-pipe”) which represents “OR”
Comparison Operators
Operator | Operation |
---|---|
== |
If the values of the two operands are equal, then the evaluation is true . |
!= |
If the values of the two operands are not equal, then the evaluation is true . |
> |
If the value of the left operand is greater than the value of the right operand, then the evaluation is true . |
< |
If the value of the left operand is less than the value of the right operand, then the evaluation is true . |
>= |
If the value of the left operand is greater than or equal to the value of the right operand, then the evaluation is true . |
<= |
If the left operand is less than or equal to the value of the right operand, then the evaluation is true . |
number
You can read more about Fixnums here and more about Floats here.
7.5.floor => this method will round the float down to the nearest fixnum. Here it will return 7
7.5.ceil => 8
10.next => 11
change:
'5'.to_i
.
Symbols
A symbol is a representation of a piece of data. Symbols look like this :my_symbol
. If I make a symbol, :my_symbol
, and then use that symbol later on in my code, my program will refer to the same area of memory in both cases. This is different from, for example, strings, which take up new areas of memory every time they are used.
You write symbols by placing a
:
in front of the symbol name.:this_is_a_symbol
Arrays
Arrays are collections of Ruby objects. You can store any type of data in an array.
There are a number of ways to create an array. Just like with creating strings, you can use the literal constructor or the class constructor.
The Literal Constructor:[1, 3, 400, 7]
is an array of integers. Any set of comma separated data enclosed in brackets is an array. So, by simply writing something like the above, you can create an array.
The Class Constructor: You can also create an array with the Array.new
syntax. Just typing Array.new
will create an empty array (=> []
).
For now, we’ll preview a few array methods, and you can check out more here.
[5, 100, 234, 7, 2].sort => [2, 5, 7, 100, 234]
[1, 2, 3].reverse => [3, 2, 1]
more ways
The shovel method employs the “shovel” operator <<
and allows you to add (“shovel”) items onto the end of an array:
famous_cats = ["lil' bub", "grumpy cat", "Maru"]
famous_cats << "nala cat"
famous_cats #=> ["lil' bub", "grumpy cat", "Maru", "nala cat"]
The shovel method <<
is the preferred syntax for adding elements to an array, however you might see other methods used in examples online:
The .push
Method
Calling .push
on an array with an argument of the element you wish to add to that array, will also add that element to the end of the array. It has the same effect as the shovel method explained above. However the .push
will also let you add multiple elements to an array, whereas the shovel method will only add one element.
famous_cats = ["lil' bub", "grumpy cat", "Maru"]
famous_cats.push("nala cat")
famous_cats #=> ["lil' bub", "grumpy cat", "Maru", "nala cat"]
The .unshift
Method
To add an element to the front of an array, you can call the .unshift
method on it with an argument of the element you wish to add:
famous_cats = ["lil' bub", "grumpy cat", "Maru"]
famous_cats.unshift("nala cat")
famous_cats.inspect #=> ["nala cat", "lil' bub", "grumpy cat", "Maru"]
Removing Items From an Array
The .pop
Method
Calling .pop
on an array will remove the last item from the end of the array. The .pop
method will also supply the removed element as its return:
famous_cats = ["lil' bub", "grumpy cat", "Maru"]
maru_cat = famous_cats.pop
famous_cats #=> ["lil' bub", "grumpy cat"]
maru_cat #=> Maru
The .shift
Method
Calling .shift
on an array will remove the first item from the front of the array. The .shift
method will also supply the removed element as a return:
famous_cats = ["lil' bub", "grumpy cat", "Maru"]
lil_bub = famous_cats.shift
famous_cats #=> ["grumpy cat", "Maru"]
lil_bub #=> lil' bub
The .reverse
Method
This method reverses an array.
famous_wizards = ["Dumbledore", "Gandalf", "Merlin"]
famous_wizards.reverse #=> ["Merlin", "Gandalf", "Dumbledore"]
The .include?
Method
This method will return a boolean of whether or not the array contains (or includes) the element submitted to it inside the parentheses:
famous_cats = ["lil' bub", "grumpy cat", "Maru"]
famous_cats.include?("Garfield") #=> false
famous_cats.include?("Maru") #=> true
Additional Resources
nested array
A nested, or multidimensional array, is an array whose individual elements are also arrays.
Adding Data to a Nested Array
To add data to a nested array, we can use the same <<
, or shovel, method we use to add data to a one-dimensional array.
To add another student to our students
array:
students = ["Mike", "Tim", "Monique"]
students << "Sarah"students
#=> ["Mike", "Tim", "Monique", "Sarah"]
To add an element to an array that is nested inside of another array, we first use the same bracket notation as above to dig down to the nested array, and then we can use the <<
on it. To illustrate, let’s add another piece of info, "Class President"
, to the nested array that describes Monique.
First, we have to access Monique’s array.
nested_students[2]
Then – bam! – we hit it with the shovel, <<
.
nested_students[2] << "Class President"
Now, our nested_students
array looks like this:
nested_students = [
["Mike", "Grade 10", "A average"],
["Tim", "Grade 10", "C average"],
["Monique", "Grade 11", "B average", "Class President"]
]
Iterating Over Nested Arrays
What if we want to add data to every array that is nested within the parent array? It would be very tedious if we had to calculate the length of the array and then, one-by-one, modify each individual child array using bracket notation and the <<
method.
When we are dealing with a one-dimensional array and want to do something to every element, we iterate, using methods like #each
and #collect
. If, for example, we wanted to puts
out every member of the students
array, we could do so like this:
students.each do |student|
puts student
end
In order to manipulate or operate on each element of a nested array, we must first dig down into that level of the array. For example, run the following code in IRB:
nested_students = [
["Mike", "Grade 10", "A average"],
["Tim", "Grade 10", "C average"],
["Monique", "Grade 11", "B average", "Class President"]
]
nested_students.each do |student_array|
# #inspect returns a human-readable representation of the array
puts student_array.inspect
end
The .each
method prints out each nested array separately and then returns the original array:
["Mike", "Grade 10", "A average"]
["Tim", "Grade 10", "C average"]
["Monique", "Grade 11", "B average", "Class President"]
# => [["Mike", "Grade 10", "A average"], ["Tim", "Grade 10", "C average"], ["Monique", "Grade 11", "B average", "Class President"]]
In the example above, we are iterating through the list of arrays that make up the top level of the nested_students
array. If we want to iterate through the elements inside each child array, we add a second layer of iteration inside the first:
nested_students = [ ["Mike", "Grade 10", "A average"], ["Tim", "Grade 10", "C average"], ["Monique", "Grade 11", "B average", "Class President"]] nested_students.each do |student_array|
student_array.each do |student_detail|
puts student_detail
end
end
Copy and paste the above code into IRB. You should see the following output:
Mike
Grade 10
A average
TimGrade 10
C average
Monique
Grade 11
B average
Class President
album_element.class != Array
Boolean Enumerables
- all?
- none?
- any?
- include?
examples
[1,3].none?{|i| i.even?} #=> true
[1,2,100].any?{|i| i > 99} #=> true
the_numbers = [4,8,15,16,23,42]
the_numbers.include?(42) #=> true
the_numbers.include?(6) #=> false
search enumerables
- select
- detect or find(detect and find are two names for the same method)
- reject
[1,2,3,4,5].select{|i| i.odd?} #=> [1,3,5]
[1,2,3].select{|i| i.is_a?(String)} #=> []
[1,2,3,4].detect{|i| i.even?} #=> 2
[1,2,3,4].detect{|i| i.is_a?(String)} #=> nil
Notice also that
#detect
will always return a single object where#select
will always return an array.
[1,2].reject{|i| i.even?} #=> [1]
Hashes
Hashes also store objects in Ruby. However, they differ from arrays in that they function like dictionaries. Instead of a simple comma separated list, hashes are composed of key/value pairs. Each key points to a specific value––just like a word and a definition in a regular dictionary.
Hashes look like this: {"i'm a key" => "i'm a value!", "key2" => "value2"}
The curly brackets denote the hash and this particular hash has two key/value pairs.
Creating Hashes
Hashes can be created with literal constructors and class constructors.
The Literal Constructor: You can create a hash by simply writing key/value pairs enclosed in curly braces.
The Class Constructor: Or, you can use the Hash.new
syntax, which would create an empty hash, {}
.
Operating on Hashes
There are many methods for operating on hashes and their individual key/value pairs. We will learn much more about them later, but you can preview some methods here.
Method
Defining a Method
You can define a method in Ruby with the def
keyword. A method’s name can begin with any lowercase letter. Here’s a quick example:
def greeting # Method Signature
puts "Hello World" # Method Body
end # Method Closing
That first line, def greeting
, is called the method signature, it defines the basic properties of the method including the name of the method, greeting
.
Once you open a method definition with the def
keyword, all subsequent lines in your program are considered the method’s body, the actual procedure or code that your method will run every time it’s called.
You must terminate every opening def
of a method with a corresponding end
in order to close the method body.The body of a method should be indented two (2) spaces, placing it visually within the method.(just for beauty)
Excute it
greeting
no()
control flow
if
,else
, andelsif
statements,case
statements,- loops.
if
dog = "thirsty"
if dog == "hungry"
puts "Refilling food bowl."
elsif dog == "thirsty"
puts "Refilling water bowl."
else
puts "Reading newspaper."
end
Loops
10.times do
puts "Hi! Welcome to my very repetitive program"
end
counter = 0
loop do
counter += 1
puts "Iteration #{counter} of the loop"
if counter >= 10
break
end
end
while
counter = 0
while counter < 20
puts "The current number is less than 20."
counter += 1
end
until
Until
is simply the inverse of a while
loop. An until
keyword will keep executing a block until a specific condition is true. In other words, the block of code following until
will execute while the condition is false. One helpful way to think about it is to read until
as “if not”.
counter = 0
until counter == 20
puts "The current number is less than 20."
counter += 1
end
Iteration vs. Looping
In previous readings we discussed the four loop types, loop
, times
, while
, and until
. Now we’re going to discuss the difference between looping and iteration. Looping occurs when you tell your program to do something a certain number of times. Iteration occurs when you have a collection of data (for example, an array), and you operate on each member of that collection.
For example, if I tell my program to print out the phrase “I love programming!” five times, that’s looping. If I tell my program to enumerate over the array [1, 2, 3, 4, 5]
and add 10
to each number, that’s iteration.
each - The Most Abstract
Being abstract is something profoundly different from being vague…. The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.
— Edsger Dijkstra
Using #each
The #each
method is a prime example of an iterator. Here’s a boilerplate example of its usage:
primary_colors = ["Red", "Yellow", "Blue"]
primary_colors.each do |color|
puts "Primary Color #{color} is #{color.length} letters long."
end
#each
is called on the collection primary_colors
, which is an array containing 3 individual strings.
A block is passed to #each
, opened by the code that starts with do
and closed by the end
. Every do
needs a closing end
.
primary_colors = ["Red", "Yellow", "Blue"]
primary_colors.each do |color| # do begins a block
# the lines between the do/end are the block's body
puts "Primary Color #{color} is #{color.length} letters long."
end # end terminates the block
The output from this code is:
Primary Color Red is 3 letters long.
Primary Color Yellow is 6 letters long.
Primary Color Blue is 4 letters long.
We can see that the block passed to each
is executed once for each element in the original collection. If there were 5 colors in primary_colors
, the block would have run 5 times. We call each run, each execution, of the block passed to the iterator (#each
in this case), an iteration. It’s a word used to refer to each ‘step’, or each ‘execution’, of a block. An iteration is the singular execution of a sequence of code (that we call a block) within a loop.
When we iterate over a collection of elements using #each
(and also in other iterators and enumerables we’ll soon learn about),** the iterator #each
yields each element one at a time to every iteration via a variable declared with the opening of the block.**
After the opening do
of our code above, we see |color|
. |
is called a pipe. After do
, we declare a local variable color
by enclosing it in | |
pipes. This variable’s value is automatically assigned the element from the array for the current iteration. So on the first iteration of the each
above, the variable color
would be equal to "Red"
. But on the next iteration of the block, color
will be reassigned the value of the next element in the primary_colors
array, "Yellow"
.
The { }
Syntax
Another way of establishing a code block that you may encounter is to use curly brackets, { }
, instead of the do
/end
keywords. Let’s take a look:
brothers = ["Tim", "Tom", "Jim"]
brothers.each{|brother| puts "Stop hitting yourself #{brother}!"}
It is appropriate to use the { }
syntax when the code in the block is short and can fit on one line.
rsepc
rspec --fail-fast
describe
The first thing RSpec allows you to do with its DSL is to define what it is you are describing.
it
Now that we’ve created a structure to group our tests together using the describe
method, we can move on to actually describing the desired functionality. Every specification in RSpec begins with the it
method.
expect
, to
, and eq
.
expect()
is a method that accepts our unknown value or variable, the thing we’re testing. So for instance, in a simple math equation, imagine the following:
x = 1 + 1expect(x)
Since x
is the unknown variable, we’d be testing the expectation of the value of x
, so we pass that value to the expect
method. I can imagine it’s weird to think of the variable x
as an unknown value worth confirming. You’re thinking, “It’s obviously 2!” But the truth is, you’re making the assumption that Ruby has a correct notion of arithmetic. As our programs become more complex and we use more variables, it’s very important to constantly validate our assumptions with expectations and testing. Let’s finish the example.
In addition to the expect(x)
call, we need to communicate what we expect x
to be equal to. To accomplish this, we chain a to()
method to the expect()
, so it simply looks like:
x = 1 + 1expect(x).to
Then finally we use what is known as a matcher, eq
, to specify our expectation: that we expect the value of x
, passed to the expect
method, to equal (to eq
) 2.
x = 1 + 1expect(x).to eq(2)
You won’t have to write your own tests for a while, so don’t worry about mastering the expect
, to
, and eq
usage. The important part is that you can read the DSL and understand what it is trying to suggest.
- RSpec looks in a directory named
spec
for all files that end with the pattern_spec.rb
. Why thespec
folder and the_spec.rb
pattern? No reason, just convention.- RSpec then executes the Ruby code within each
_spec.rb
file.
class
class Dog
end
fido = Dog.new
fido #=> #<Dog:0x007fc52c2d7d20>
snoopy = Dog.new
snoopy #=> #<Dog:0x007fc52c2d4170>
snoopy == fido #=> false - these dogs are not the same.
Implementing Instance Variables
We define an instance variable by prefacing the variable name with an @
symbol.
Instance variables are bound to an instance of a class. That means that the value held by an instance variable is specific to whatever instance of the class it happens to belong to. Instance variables hold information about an instance, usually an attribute of that instance, and can be called on throughout the class, without needing to be passed into other methods as arguments (as would be the case with local variables).
Let’s refactor our Dog
class to use an instance variable instead of a local variable to set and get an individual dog’s name.
Open up dog.rb
and change the Dog
class in the following way:
class Dog
def name=(dogs_name)
@this_dogs_name = dogs_name
end
def name
@this_dogs_name
end
end
lassie = Dog.new
lassie.name = "Lassie"
puts lassie.name
initialize
require certain arguments to be passed when instantiating the class to provide it with initial data
class Person
def initialize(name)
@name = name
end
def name
@name
end
end
kanye = Person.new("Kanye")
kanye.name #=> "Kanye"
Setter vs. Getter Methods
Our Person class’ #name
method is referred to as a “getter” or reader method. It returns information stored in an instance variable. In order to make a person’s name attribute writable, we need to define a “setter” or writer method.
Defining a Setter Method
class Person
def initialize(name)
@name = name
end
def name
@name
end
def name=(new_name)
@name = new_name
end
end
A setter method is defined with an =
, equals sign, appended to the name of the method. The =
is followed by the (argument_name)
. Now that we’ve defined our setter method on the Person class, we can change Kanye’s name.
Calling a Setter Method
To call a setter method, you use the .
notation (dot notation) to call the method and set it equal to a new value.
kanye = Person.new("Kanye")
kanye.name => "Kanye"
kanye.name = "Yeezy"
kanye.name => "Yeezy"
You can also call a setter method like this:
kanye.name=("Yeezy")
But we prefer the first notation.
The Abstraction of Instance Methods
kanye.instance_variable_set(:@name, "Yeezy")
kanye.instance_variable_get(:@name)
=> "Yeezy"
Since this is the case, why do we even use instance setter and getter methods?
, we care about our program’s readability and design. The above method is ugly. It places a verb at the end of the method name.
It exposes it directly to the person executing our code. This is bad practice because it forces our program to rely directly on the
@name
variable.for example:We’ll initialize our
Person
instances with both a first and last name.class Person def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end #... end
With this change, our program has some added functionality. We could imagine collecting all of our instances of
Person
and sorting them by last name, for example.BUT, now, any other part of our program that was calling
instance_variable_get(:@name)
is broken!Let’s create our abstraction: the
#name=
and#name
setter and getter instance methods:class Person def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def name=(full_name) first_name, last_name = full_name.split @first_name = first_name @last_name = last_name end def name "#{@first_name} #{@last_name}".strip end end
Now, even if the content of the #name method changes, for example, Kanye changes his mind again and wants to be referred to only as “Yeezy” (using our interface this change would be kanye.name = “Yeezy”), the interface, how our application uses that content, remains constant. In other words, we can change the content of these methods according to our needs, without needing to hunt down every appearance of them in our program and change them as well, like we would need to do with our
instance_method_set
andinstance_method_get
usages.