-
Notifications
You must be signed in to change notification settings - Fork 1.6k
step 3: ruby basics
Edit in Compass, not here.
- wire up app through Sinatra request cycle
- learn Ruby basics
- variables
- object types
- control flow
- string interpolation
- methods
- apply basics to print posts to screen
We're going to start to template our posts today. This means we're going to create a way for any generic post (any user, photo, comments, whatever) to be displayed reliably on our trusty index.html
we've been working with. The first thing we should do is figure out a way to get our post information into a consistent format or something. Let's use Ruby to make it happen!
First, from your console:
shotgun
Now open your broswer to http://localhost:9393/ and let's see if Sinatra is up and running. Hooray! A specific error message!
A novel idea, let's actually do what the message suggests. Open app/actions.rb
and add the following:
get '/' do
"Hello world!"
end
Refresh your browser and yes! Hello world! Let's talk a bit about this bit of Ruby code in app/actions.rb
.
The "Hello world!"
is a string. We'll talk about a bunch of a different types of objects, but a string is one of the most fundamental. Notice it must be surrounded by quotation marks! That's literally what makes it a string. Important!
Now let's talk about what surrounds that string.
-
get
refers to the HTTP METHOD. We'll dive into more later, but for now, you can remember any time you visit a URL in your browser, that's aget
request. -
'/'
refers to the REQUEST PATH. Notice this is a/
surrounded by''
. Still quotation marks! That makes'/'
a string. -
do
andend
delineate our block.
These two work together to grab get
requests at /
, or the root path, and render the return value of their block. In our case, our block just has one string: "Hello world!"
, so it gets returned. Voila!
Okay, now let's take our data from a single post and Rubyize it! A variable
is just that: a value that could and probably will change. So let's look for all the values in one of the posts in index.html
that could change depending on the post and give them names:
username = "sharky_j"
avatar_url = "http://naserca.com/images/sharky_j.jpg"
photo_url = "http://naserca.com/images/shark.jpg"
time_ago_in_minutes = 15
like_count = 0
comment_count = 1
comments = [
"sharky_j: Out for the long weekend... too embarrassed to show y'all the beach bod!"
]
Before we do anything with all that, let's talk about a few things.
- Our variable names are in
snake_case
: all lowercase, spaces are underscores. This is the norm in Ruby and we'll stick to it. It'savatar_url
, notavatarUrl
,AvatarUrl
,Avatar_url
,Avatar_Url
, oravatar-url
. - Some of our variables point to values without quotation marks. We have a few
Fixnum
s, for example. -
comments
points to another type:Array
. An array is an ordered list of... anything. Strings, fixnums, hashes, even other arrays.
Now that we have a little better idea of what's going on here, let's throw this into our get '/'
block:
get '/' do
username = "sharky_j"
avatar_url = "http://naserca.com/images/sharky_j.jpg"
photo_url = "http://naserca.com/images/shark.jpg"
time_ago_in_minutes = 15
like_count = 0
comment_count = 1
comments = [
"sharky_j: Out for the long weekend... too embarrassed to show y'all the beach bod!"
]
end
Refresh and you can see we're now returning the value of comments
. In Ruby the last line in a block or method (we'll get to this) is what gets implicitly returned unless you explicitly return
something. Try that. Add return
before username
and see that "sharky j" gets rendered and none of the code below it is run.
Now let's talk about control flow. When we want to take conditional action in our code, we can use tools to control the flow. The simplest and one of the most common is an if/else
statement.
get '/' do
username = "sharky_j"
avatar_url = "http://naserca.com/images/sharky_j.jpg"
photo_url = "http://naserca.com/images/shark.jpg"
time_ago_in_minutes = 15
like_count = 0
comment_count = 1
comments = [
"sharky_j: Out for the long weekend... too embarrassed to show y'all the beach bod!"
]
if time_ago_in_minutes > 60
"more than an hour"
else
"less than an hour"
end
end
[refreshes excitedly]
Excellent. Let's annotate that if/else
with some pseudocode:
# if the time_ago_in_minutes is more than 60
if time_ago_in_minutes > 60
# return this string
"more than an hour"
# if it's less than or equal
else
# return this instead
"less than an hour"
end
Play around with your time_ago_in_minutes
value, and how about we try a third option?
if time_ago_in_minutes > 60
"more than an hour"
elsif time_ago_in_minutes == 60
"an hour"
else
"less than an hour"
end
More!!!
if time_ago_in_minutes > 60
"more than an hour ago"
elsif time_ago_in_minutes == 60
"an hour ago"
elsif time_ago_in_minutes <= 1
"just a moment ago"
else
"less than an hour ago"
end
I think you get it. But let's make it a bit more specific:
if time_ago_in_minutes >= 60
"#{time_ago_in_minutes / 60} hours ago"
else
"#{time_ago_in_minutes} minutes ago"
end
Whoa, cool! If we wrap Ruby code in #{}
, we can throw it right inside a string!
You can probably already see how this could get out of hand quickly. We very often while programming need a tool that accepts some input and returns some output. BEHOLD: a method.
def humanized_time_ago(minute_num)
if minute_num >= 60
"#{minute_num / 60} hours ago"
else
"#{minute_num} minutes ago"
end
end
get '/' do
username = "sharky_j"
avatar_url = "http://naserca.com/images/sharky_j.jpg"
photo_url = "http://naserca.com/images/shark.jpg"
time_ago_in_minutes = 15
like_count = 0
comment_count = 1
comments = [
"sharky_j: Out for the long weekend... too embarrassed to show y'all the beach bod!"
]
humanized_time_ago(time_ago_in_minutes)
end
[refreshes so hard the computer is smoking]
Cool! We define our method using the syntax above:
def <methodname>(<argument(s)>)
# do something with arguments and return something else
end
So in our case, we input our time_ago_in_minutes
and output a humanized time ago for eventual use in Finstagram. Note that in the method definition minute_num
is the name of our argument, even though we pass in a variable called time_ago_in_minutes
. Honestly, we could name the argument x
in our method definition and it would still "work." We just want to use good, clear names. In fact, let's change the argument to time_ago_in_minutes
for some #synergy.
def humanized_time_ago(time_ago_in_minutes)
if time_ago_in_minutes >= 60
"#{time_ago_in_minutes / 60} hours ago"
else
"#{time_ago_in_minutes} minutes ago"
end
end
Since our data thus far is referring to a post, let's group it as an object called a Hash
:
get '/' do
post = {
username: "sharky_j",
avatar_url: "http://naserca.com/images/sharky_j.jpg",
photo_url: "http://naserca.com/images/shark.jpg",
time_ago_in_minutes: 15,
like_count: 0,
comment_count: 1,
comments: [
"sharky_j: Out for the long weekend... too embarrassed to show y'all the beach bod!"
]
}
humanized_time_ago(post[:time_ago_in_minutes])
end
Hmm. Okay, now it looks like we only have one variable (true!) with... sub... variables? (Kind of!) Those are called key
s in our hash. :username
, :avatar_url
are both keys that point to their respective value
s. And you can see from our humanized_time_ago
method call that we access those values by using the syntax:
<variable_pointing_to_hash>[<key>]
Try to return the :username
at the bottom of the method instead of the humanized_time_ago
.
post[:username]
All right! Let's also throw our comments into hashes as well. The more parsed our data is, the more we can do with it!
get '/' do
post = {
username: "sharky_j",
avatar_url: "http://naserca.com/images/sharky_j.jpg",
photo_url: "http://naserca.com/images/shark.jpg",
time_ago_in_minutes: 15,
like_count: 0,
comment_count: 1,
comments: [{
username: "sharky_j",
text: "Out for the long weekend... too embarrassed to show y'all the beach bod!"
}]
}
humanized_time_ago(post[:time_ago_in_minutes])
end
Like we mentioned earlier, arrays can hold any kind of object, including a hash!
As a bit of cleanup, let's move our humanized_time_ago method call into our post hash:
get '/' do
post = {
username: "sharky_j",
avatar_url: "http://naserca.com/images/sharky_j.jpg",
photo_url: "http://naserca.com/images/shark.jpg",
humanized_time_ago: humanized_time_ago(15),
like_count: 0,
comment_count: 1,
comments: [{
username: "sharky_j",
text: "Out for the long weekend... too embarrassed to show y'all the beach bod!"
}]
}
end
Now using this post as a guide, let's use the information from our other two posts to create two more hashes. Go!
def humanized_time_ago(time_ago_in_minutes)
if time_ago_in_minutes >= 60
"#{time_ago_in_minutes / 60} hours ago"
else
"#{time_ago_in_minutes} minutes ago"
end
end
get '/' do
post_shark = {
username: "sharky_j",
avatar_url: "http://naserca.com/images/sharky_j.jpg",
photo_url: "http://naserca.com/images/shark.jpg",
humanized_time_ago: humanized_time_ago(15),
like_count: 0,
comment_count: 1,
comments: [{
username: "sharky_j",
text: "Out for the long weekend... too embarrassed to show y'all the beach bod!"
}]
}
post_whale = {
username: "kirk_whalum",
avatar_url: "http://naserca.com/images/kirk_whalum.jpg",
photo_url: "http://naserca.com/images/whale.jpg",
humanized_time_ago: humanized_time_ago(65),
like_count: 0,
comment_count: 1,
comments: [{
username: "kirk_whalum",
text: "#weekendvibes"
}]
}
post_marlin = {
username: "marlin_peppa",
avatar_url: "http://naserca.com/images/marlin_peppa.jpg",
photo_url: "http://naserca.com/images/marlin.jpg",
humanized_time_ago: humanized_time_ago(190),
like_count: 0,
comment_count: 1,
comments: [{
username: "marlin_peppa",
text: "lunchtime! ;)"
}]
}
end
Awesome. Now let's put these into an array, a natural place for a list of things to go:
get '/' do
# ...
[post_shark, post_whale, post_marlin]
end
And we need to cast this to a string to see it in the browser, so let's do that.
get '/' do
# ...
[post_shark, post_whale, post_marlin].to_s
end
Yes! You're correct! to_s
is "to string"!
[refreshes computer into flames]