Good morning!
Open a terminal window. Type “irb” and hit enter.
Try entering a few things:
1
4 + 3
"hat"
[1, 2, 3, "happy"].each { |i| puts i }
>>
is your prompt to enter text and =>
will appear at the start of each response.
Use the up and down arrow keys to navigate back and forth through the previous commands you’ve entered.
Numbers | 1 , 2 , 3 |
Strings | "chicken" |
Booleans | true , false |
Symbols | :chicken |
Arrays | [1, 7, "cake"] |
Hashes | {:chicken => "chicken", :sillyexample => :chicken} |
Type these at the command line! What happens?
That’s the data, where are the computations?
2 + 2 |
||
3 * 3 |
||
"chicken".reverse |
||
"chicken".length |
||
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
||
"chicken".reverse |
||
"chicken".length |
||
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
||
"chicken".length |
||
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
||
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
7 |
|
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
7 |
|
2 + 2 == 4 |
true |
|
2 + 2 == 5 |
||
[1, 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
7 |
|
2 + 2 == 4 |
true |
|
2 + 2 == 5 |
false |
|
[1, 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
7 |
|
2 + 2 == 4 |
true |
|
2 + 2 == 5 |
false |
|
[1, 2, 3].include?(2) |
true |
What do you think "chicken".reverse.length
does?
What about "puppy".include?('p')
?
var1 = 4
var1
var2 = "chicken"
var2
var2 = var1
var2
Something you might want to do is print out a string that has a value contained within it.
For example, try typing
"Our array is #{[1,2,3]}"
Most useful when dealing with variables!
var = [1,2,3,4,5,6]
"Our array is #{var}"
Now back to variables!
Try typing the following, noting what happens at each step
thing = "chicken"
thing
thing.reverse
thing
thing = thing.reverse
thing.reverse
didn’t permanently reverse the string!Try
awesomelist = [5,2,1,8]
awesomelist.sort!
awesomelist
How did that happen? Actions that end with a !
change the data!
This is a Ruby convention, but a good one to follow.
Let’s consider a book rating system:
Fill in a hash with some books, e.g.
books = { "Left Hand of Darkness" => 5,
"The Word for World Is Forest" => 5,
"Nevermind the Pollacks" => 0,
"Only Revolutions" => :not_rated }
We can retrieve the rating for a particular book like so
books["Left Hand of Darkness"]
We can also set values like so
books["Only Revolutions"] = 3
How can we add a rating for a new book? Any guesses?
We can retrieve the rating for a particular book like so
books["Left Hand of Darkness"]
We can also set values like so
books["Only Revolutions"] = 3
How can we add a rating for a new book? Any guesses?
We set the value of the book just like before!
books["White Teeth"] = 4
Now type books
to see the whole hash.
40.reverse
What happens?
40.reverse
What happens?
Ruby just reported NoMethodError: undefined method `reverse' for 40:Fixnum
That means that reverse
is not something you can do to the number 40.
Most computations in Ruby are performed by methods.
Type 40.methods
to see which methods are available for basic numbers.
Notice +
, -
, et al. in the list?
What about "chicken".methods
?
An object is data along with the methods, computations, that can operate on it.
Everything in Ruby is an object: numbers, strings, hashes, etc.
How do you know what kind of object something is? Ask!
40.class
What is a class?
Let’s learn the syntax by example:
class Counter
end
What can we do with Counter
?
c = Counter.new
Let’s learn the syntax by example:
class Counter attr_accessor :value
end
Now try
c = Counter.new
c.value
c.value = 10
c.value
What happened? What does attr_accessor :value
do?
Let’s learn the syntax by example:
class Counter attr_accessor :value
def initialize @value = 0 end
end
Now, again, try
c = Counter.new
c.value
initialize
gives new
instructions on how to create an object
Let’s learn the syntax by example:
class Counter attr_accessor :value
def initialize @value = 0 end
def increment @value = @value + 1 end
end
Let’s use our Counter class:
count = Counter.new
count.increment
count.increment
count.value
count.class
count.methods
Try count.respond_to?("increment")
. What did you see?
Let’s add a Counter.increment_by
method that takes an argument for how much to increment.
Don’t need to start all over – we open the class instead
class Counter
def increment_by(n) # fill in here end
end
Test your code as follows:
c = Counter.new
c.increment_by(5)
c.value
You can add methods to existing classes as well:
class String
def chicken?
self == "chicken"
end
end
"chicken".chicken?
"puppy".chicken?
self
is a way of referring to the object that the method is being called on.
In "puppy".chicken?
, self
is "puppy"
.
Add a method to String that will test to see if a string is a palindrome.
A palindrome is any string that is the same read forwards or backwards.
To get you started type the following at the command line:
class String
def palindrome?
and finish the rest! Test it on "abba".palindrome?
and "puppy".palindrome?
http://rubydoc.info/stdlib/core/
Good places to start: Array, Enumerable, Fixnum, Float, Hash, NilClass, String, Time
Some methods take blocks.
list.each {|p| code}
runs code
on every element of list
list = [1,2,3,4]
list.each { |n| puts (n + 1) }
{ |arg1, arg2, ..| code }
{ |v| v.palindrome? }
{ |x, y| x * y }
A more complicated example of using each:
reviews = Hash.new(0)
books.values.each { |rate| reviews[rate] = reviews[rate] + 1 }
reviews
reviews
is a count of how many reviews you gave with a particular rating
reviews[5]
There’s another way to write blocks. This is commonly used for multi-line expressions.
reviews = Hash.new(0)
books.values.each do |rate|
reviews[rate] = reviews[rate] + 1
# more code can go here...
end
Ruby provides control structures for writing more complicated code.
If statements are a switch on whether the argument is true or false.
if true
1
else
2
end
if false
1
else
2
end
Case statements evaluate a statement and execute the code in the corresponding branch:
case favorite_color
when "green"
puts "Grass is green"
when "blue"
puts "Skies are blue"
when "red"
puts "Tomatoes are red"
else
puts "Are you sure that's a color?"
end
For-in statements allow iteration over a structure such as a list
list = [1,2,3,4,5]
sum = 0
for n in list
sum = sum + n
end
sum
To get started:
bundle install
rails server
rails console
Rant
and User
classesUser
class has id
and name
attributesRant
class has id
, message
, and user_id
attributesHow can we look up a rant after it’s been created?
r = Rant.find(1)
r.message
r.user_id
What about modifying a rant?
r.message = "RANTING!"
r.save
We need to save
the changes when we’re done editing.
Rails models have built-in finder methods that help you retrieve records from the database.
Try:
Rant.all
Rant.order("created_at DESC")
Rant.first
Rant.where("user_id = 1")
Rant.where(:user_id => 1)
Let’s try creating a new Rant, for user_id
1
user = User.find(1)
rant = user.rants.build
rant.message = "WHAT IS TH"
rant.save
Note that we didn’t need to set the id
field of the message!
That was automatically set by new
.
We save
the record when we’re done, in order to create it, just like when we were editing a rant before.
Creating a rant that way was verbose. Is there a better way?
User.find(1).rants.create(:message => "E POINT!?")
Notice that you don’t need to save when you use create
—it combines new
and save
into a single action.
Open up the user.rb and rant.rb model files.
class Rant < ActiveRecord::Base
belongs_to :user
validates_presence_of :user, :message validates_length_of :message, :maximum => 10
end
class User < ActiveRecord::Base
has_many :rants
validates_presence_of :name
end
Some people are writing in lowercase! This won’t do!
Let’s write a method that can convert all lowercase rants to uppercase. First, go find the rant.rb file in your Rails models directory.
class Rant < ActiveRecord::Base
before_save :convert_message_to_uppercase
def convert_message_to_uppercase
...
Rails provides many extensions to Ruby classes to make common tasks easier. Look in http://api.rubyonrails.org/classes/ActiveSupport/Multibyte/Chars.html to find the helper you need for this exercise.
Once you’ve added the code, try creating a lowercase rant and see what happens.
Sometimes you want to work with your data in a different format. Go back to the Rails console and write a program that creates a hash that contains rant messages paired with user names.
How do you create a new Hash again?
Hash.new
, but {}
is a simpler way.new_hash = {}
The result should look like:
=> {"RANTING!"=>"caylee", "WTF"=>"audrey"}
Can you use an iterator to print this out as a series of formatted strings?
The before_save
code we wrote earlier uses a callback. Rails defines several callbacks that allow you to change the input to a model before or after actions like save, create, update, and delete.
before_save :geocode_address
after_create :notify_admins
before_validate :check_over_some_other_details
Validations provide an easy wrapper for checking that a particular attribute is present, in the right format, or other requirements before the application will save the data. The microblog application already contains some of these. Look at the Rant and User models to see the ones we’ve included.
Can you think of other validations you’d like to add? Try changing the length of a valid Rant message.
Rails will automatically create views with content when you use the scaffold
command. Go to /app/views and have a look around.
These files are making use of a templating system called ERB that converts Ruby statements in the view into static HTML to display. A typical ERB statement might look like:
Documentation on ERB syntax: http://ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
The scaffolding system is quick to use, but not always pretty. Our list of rants lists the user_id
for the person who said it, and not their name. Let’s change that.
Go to /app/views/rants/ and open the index.html file. By default, the scaffolder makes the index view show a list of all records of the related model’s type. Find the line that displays the user id and change it to show the user’s name instead.
On the users index page, let’s add a column to the table to show how many rants each person has written. ActiveRecord makes this easy by providing a method called count
that you can add to a collection of records to get the database count instead of the full records. For example, User.count
or Rants.count
.
These also work through has_many associations, so given a particular user record, you could try user.kittens.count
or user.books.count
(if those associations existed) or …
How does Rails know what page to go to when you visit http://localhost:3000/rants/new ?
Open /config/routes.rb to see how this works.
Now that we’ve seen and edited models and views, let’s take a quick look at the controller files in our project.
What does a controller do?
microrant
directoryrails new betterrant
bundle install
rails generate scaffold User name:string
rails generate scaffold Rant user_id:integer message:string
rake db:migrate
At this point you can start the server and take a look around.