Skip to content

step 3: ruby basics

Alex Naser edited this page Oct 26, 2015 · 2 revisions

MIGRATED

Edit in Compass, not here.

objectives

  • 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

tutorial

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!

fire up Sinatra

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 a get request.
  • '/' refers to the REQUEST PATH. Notice this is a / surrounded by ''. Still quotation marks! That makes '/' a string.
  • do and end 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!

ruby basics

variables

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's avatar_url, not avatarUrl, AvatarUrl, Avatar_url, Avatar_Url, or avatar-url.
  • Some of our variables point to values without quotation marks. We have a few Fixnums, 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.

control flow

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!

methods

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

hashes

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 keys in our hash. :username, :avatar_url are both keys that point to their respective values. 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]

Looks great!