Friday, November 23, 2018

Getting Started with GraphQL and Express

Interested in learning a lot more about GraphQL? Get a deep dive with The Road to GraphQL 

You can employ GraphQL in your Express server using a middleware. If you get a request that goes to the endpoint POST /graphql, it will pass through the GraphQL Express middleware and handle the response for you.

Installing Packages


npm install express express-graphql graphql

The first package is for the Express server. You might already have that.

The second package is the Express GraphQL middleware.

The third package allows us to use GraphQL constructs in Nodejs.

Setting Up


const express = require('express');
const graphqlHTTP = require('express-graphql');

const schema = require('./schema');

const app = express();
const PORT = 3000;

app.use('/graphql', graphqlHTTP({
  schema: schema
}));

app.listen(PORT, () => {
  console.log(`Server running at port ${PORT}`);
});

The express-graphql middleware requires an Express app. You apply the middleware using app.use(). The first argument is the endpoint where GraphQL will be listening. The code above uses the standard /graphql route. The second argument calls on the middleware with a schema property that can be defined in a separate file:

// schema.js
const {
  GraphQLObjectType,
  GraphQLSchema,
  GraphQLString,
} = require('graphql');

const RootQueryType = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    fruit: {
      type: GraphQLString,
      resolve() {
        return 'Banana';
      }
    }
  }
});

module.exports = new GraphQLSchema({
  query: RootQueryType
});

The GraphQL schema is defined using an object with the query property. That will be the entry point for the queries requested by the client. Under fields, you can specify different types of resources you can query. In the example, we add a field called "fruit" that corresponds to a resource for a fruit and will always return the string "Banana."

You will likely start off creating a new GraphQLObjectType. Then, you define the name and fields properties. For the name, you can call it after the resource type. Each field has a type and a resolve() method that tells GraphQL how to find that resource and return it to the client. There are different types depending on the data type you are using. For example: GraphQLInt, GraphQLList, GraphQLString, etc.

Testing Queries with GraphiQL

GraphiQL Running on Localhost 3000 graphql

It might be helpful, especially if you don't have a frontend to work with, to use the GraphiQL tool to test queries on the browser. To enable it, add the graphiql property when you apply the middleware:

app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true
}));

With that, you can now access http://localhost:3000/graphql in your browser and it will open up a GraphiQL interface. You can try queries out there. For example, try the following:

query {
  fruit
}

You should expect the following response:

{
  "data": {
    "fruit": "Banana"
  }
}


Creating a Custom Type


Having only one field is uninteresting and does not show the true power of GraphQL. Let us turn out fruit into an object with the following properties: id, name, description, isTasty, calories. You can isolate that FruitType in a different variable:

const {
  GraphQLBoolean,
  GraphQLID,
  GraphQLInt,
  GraphQLObjectType,
  GraphQLSchema,
  GraphQLString,
} = require('graphql');

const FruitType = new GraphQLObjectType({
  name: 'FruitType',
  fields: {
    id: {
      type: GraphQLID
    },
    name: {
      type: GraphQLString
    },
    description: {
      type: GraphQLString
    },
    isTasty: {
      type: GraphQLBoolean
    },
    calories: {
      type: GraphQLInt
    }
  }
});

You can create the new type using a new GraphQLObjectType. Then you define a name property. Then you define a list of fields the object will have. For each field, the property is the field name and the value an object with the type property. Notice how we used different kinds of GraphQL built-in types.

Updating the Root Query


Once you defined your own FruitType, your RootQueryType then becomes like this:

const RootQueryType = new GraphQLObjectType({
  name: 'RootQueryType',
  fields: {
    fruit: {
      type: FruitType,
      resolve() {
        return {
          id: '507f1f77bcf86cd799439011',
          name: 'Banana',
          description: 'This fruit is delicious',
          isTasty: true,
          calories: 121
        };
      }
    }
  }
});

Notice how the type of fruit is no longer just a GraphQLString. It is now a FruitType, which happens to be a GraphQLObjectType.

The resolver method is also changed to reflect the new data structure for a Fruit. Typically, you would make a database call (e.g. via ORM) or an API call with an HTTP request in the resolve() method; then the data value for the specific resource would be returned. The resolve() method takes care to unwrap promises if you do provide them as the return value, so do not worry about doing that yourself.

One interesting thing to notice is the GraphQLID assumes the id property is a string, so be aware of that. You can check out the documentation for all the different GraphQL types here.

Trying a New Query


Now you can try the following query in GraphiQL in your browser:

query {
  fruit {
    id
    name
    description
    isTasty
    calories
  }
}

To which you will get the following response:

{
  "data": {
    "fruit": {
      "id": "507f1f77bcf86cd799439011",
      "name": "Banana",
      "description": "This fruit is delicious",
      "isTasty": true,
      "calories": 121
    }
  }
}

With GraphQL, you do not need to always get back all the fields from a resource. GraphQL allows you to ask only for the data you really need.

For example, the fruit object above does not have many fields, but a real world example would likely contains many, many fields.

Many times the client ends up not using all of the fields, so there is a lot of bandwidth waste. Users with a slow mobile connection would have to wait longer to download the larger set of data, only to end up using only part of it.

Other times, you end up not having enough data, so have to make additional requests to retrieve the missing parts.

GraphQL allows you to ask specifically for all the fields you are going to need to use in the client side. No more, no less. So you could just ask for the fruit id and name if you do not care about its description, nor if it is tasty, nor the calorie count:

query {
  fruit {
    id
    name
  }
}

And GraphQL will give you just that:

{
  "data": {
    "fruit": {
      "id": "507f1f77bcf86cd799439011",
      "name": "Banana"
    }
  }
}

Isn't that wonderful? :)

Read The Road to GraphQL to deep dive into the world of GraphQL!

Sunday, December 11, 2016

JavaScript: Stop Using var with ES6

Want to learn more about JavaScript? Get the Eloquent JavaScript.

With the advent of ES6, two new keywords for the definition of variables have come forth: const and let. Now, whenever you need to define a variable, don't use var -- start off with const.

// The old way
var name = "Angela";

// The new way
const name = "Angela";

Once you assign the value of a const variable once, it can not be changed later. In case you are going to modify the variable's value again, you can use let. This is very common with, say, a for loop:

for (let i = 0; i < someArray.length; i++) {
  // do something
}

In the loop above, the index variable i will have to be changed at every iteration, so we cannot just use const.

Keep in mind things like an array can be added elements even though you use const:

const pets = [];

pets.push("cat");
pets.push("dog");

console.log(pets); // => ["cat", "dog"]

The above is totally fine using const because the array reference was assigned only once. Adding elements to the array does not change the location of that reference. However, reassigning pets to a totally new array would give you an error using const. In that case, you would need to use let:

const pets = [];
pets = ["hamster"]; // error: Assignment to constant variable

let pets = [];
pets = ["hamster"]; // fine because pets was declared with let

Another very interesting aspect of using let in a for loop is that the variable declared within the loop header will only be available to the instructions in the loop body. To illustrate that, consider the following example, using var:

for (var j = 0; j < 3; j++) {
  console.log(j);

}

console.log('-----'); // separator


console.log(j); // no problem here!

The code output will be the numbers 0, 1, 2; then the console.log will print 3. Note that the value of j was available even outside of the loop body that is between the curly braces for the for loop. Now, if you use let, things change:

for (let i = 0; i < 3; i++) {
  console.log(i);
}

console.log('-----'); // separator

console.log(i); // error: i is not defined

The output will be 0, 1, 2, and then an error will occur. That is because i is not defined outside the loop body. Using the let keyword, your variable will have local block scope, but not outside the area defined by the curly braces!

In conclusion, you should always start off declaring your variables with const. Then, if you later need to modify its value more than once, you can easily change the keyword const to let and everything will be fine! Also watch out for the scope rules regarding the new ES6 keywords, since they have block-level scope and will not be available anywhere outside the block. :)

For further reference, see:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

Learn more about JavaScript with the Eloquent JavaScript.

Thursday, July 7, 2016

Intro to RSpec and Test-Driven Development

Need a reference text for the Ruby programming language? Get the Well-Grounded Rubyist.

RSpec has become a popular choice as a testing framework for Ruby. Let us learn how to make unit tests and use a test-driven development approach to implement a program to calculate the factorial of a number.

Factorial Review

The factorial of a number is defined as the number repeatedly multiplied by itself MINUS one until we reach the base case of 1:

4! = 4 * 3 * 2 * 1 = 24

In essence, you can define it recursively as such: the factorial of n is always that number times the factorial of (n - 1).

4! = 4 * 3! = 4 * 3 * 2! = 4 * 3 * 2 * 1! = 4 * 3 * 2 * 1 = 24

The base cases are:

1! = 1
0! = 1

Test-Driven Development

In this approach of development, we FIRST write TESTS; then, we write some code to make the tests PASS. We repeat that approach repeatedly until the development process has been complete. In summary:


1) Write tests
2) Run the tests
3) Make first test pass
4) Run the tests
5) Make second test pass
...
AND SO ON until all the tests have succeeded.

Getting Started

Create a file to hold the definition for our factorial method and call it factorial.rb:

touch factorial.rb

Now, create a folder called spec/ to hold all the unit tests:

mkdir spec

RSpec will look for tests in that directory, so it is important you follow this naming convention.

All the unit tests should be named with a suffix _spec.rb, so let us create the following test file:

touch spec/factorial_spec.rb

Writing Our First Test

We will be writing the tests inside the spec file. Let us get started and type the following into factorial_rspec.rb:

require_relative '../factorial'

describe "Factorial" do
  it "computes the factorial of 0" do
    expect(factorial(0)).to eq 1
  end
end

We need the first line to import the contents of the factorial.rb file one level up in the tree hierarchy. You need not add the .rb extension when you do that.

Next, we use a describe construct to define the block of tests for the factorial method. You typically create a describe for each method that you have. In our case, we will only be writing one method (factorial), so one describe block will suffice.

Inside the describe block, you add many unit tests using an "it" statement. These are the actual constructs that perform the tests. You will have many of these for each unit test.

When doing test-driven development, you can start off testing for the most essential elements: in the this case, we have a base case for the factorial, which is factorial(0) being equal to 1. You use the expect method to take a look at the return value of the method that was called as its argument. It will expect that to equal the integer 1.

Keep in mind that although RSpec reads a lot like English, there is no magic here: all those constructs are just Ruby methods. In our case, describe is just a method that takes on a string argument and is being passed a Ruby block (that contains the it statements). The same is true for it: it is just a method and takes a string argument and also accepts a Ruby block.

Do a web search on "RSpec Cheatsheet" or just look a this one to see all the cool things you can do with RSpec.

Running the Tests

You can run the tests from the same directory as factorial.rb using the rspec command:

rspec

RSpec will look for files in the spec/ folder and will run them. Make sure to run the rspec command one level up from the spec directory in the filesystem tree!

You will get an output similar to the following:

$ rspec
F

Failures:

  1) Factorial computes the factorial of 0
     Failure/Error: expect(factorial(0)).to eq 1

     NoMethodError:
       undefined method `factorial' for #<RSpec::ExampleGroups::Factorial:0x007ff503b68398>
     # ./spec/factorial_spec.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.00064 seconds (files took 0.13099 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/factorial_spec.rb:4 # Factorial computes the factorial of 0

So the test failed because we have not defined the factorial method. Let's go ahead and do that inside factorial.rb:

def factorial
end

If you run the tests again, you will get:

$ rspec
F

Failures:

  1) Factorial computes the factorial of 0
     Failure/Error: expect(factorial(0)).to eq 1

     ArgumentError:
       wrong number of arguments (1 for 0)
     # ./factorial.rb:16:in `factorial'
     # ./spec/factorial_spec.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.00093 seconds (files took 0.18702 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/factorial_spec.rb:4 # Factorial computes the factorial of 0

Now, there is an ArgumentError: we passed in one argument, but the method definition does not take any parameters. Let us fix that in factorial.rb:

def factorial(n)
end

Run the tests again:

$ rspec
F

Failures:

  1) Factorial computes the factorial of 0
     Failure/Error: expect(factorial(0)).to eq 1

       expected: 1
            got: nil

       (compared using ==)
     # ./spec/factorial_spec.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.05403 seconds (files took 0.1312 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/factorial_spec.rb:4 # Factorial computes the factorial of 0

Now, we got nil instead of 1 for the factorial of 0 because our method is not defined with anything in particular. Let us fix the problem by simply returning 1 from the method:

def factorial(n)
  1
end

Run the tests:

$ rspec
.

Finished in 0.0014 seconds (files took 0.18038 seconds to load)
1 example, 0 failures

Great! We finally got the test to pass. Now, we can write more tests and keep on going in the same fashion: make a test, run the tests, fix the problem, run the test again and see the test pass.

Formatting RSpec Output

You might want to format the RSpec output to display colors and more detailed information about the tests (for example, which tests actually passed). To do that, create a .rspec file in the same directory as factorial.rb:

touch .rspec

Add the following contents to the file:

--color
--format documentation

Now RSpec is going to show us a more detailed output with colors :)

Writing More Tests

Let us go back to writing tests. Write another test, now for the factorial of 1:

describe "Factorial" do
  it "computes the factorial of 0" do
    expect(factorial(0)).to eq 1
  end
  it "computes the factorial of 1" do
    expect(factorial(1)).to eq 1
  end
end

If you run the tests, you will see you are still passing them. Now, write another test:

it "computes the factorial of 2" do
  expect(factorial(2)).to eq 2
end

Run the tests again:

$ rspec

Factorial
  computes the factorial of 0
  computes the factorial of 1
  computes the factorial of 2 (FAILED - 1)

Failures:

  1) Factorial computes the factorial of 2
     Failure/Error: expect(factorial(2)).to eq 2

       expected: 2
            got: 1

       (compared using ==)
     # ./spec/factorial_spec.rb:11:in `block (2 levels) in <top (required)>'

Finished in 0.02178 seconds (files took 0.13048 seconds to load)
3 examples, 1 failure

Failed examples:

rspec ./spec/factorial_spec.rb:10 # Factorial computes the factorial of 2

Now it is your job to go to the definition of the factorial method and add some code to make the next test pass. You keep on doing that and eventually you will get to write the correct method definition. You could keep tweaking the code on and on, but as soon as you reach, say, a test that checks the factorial of 4, you can just starting writing the factorial method for real.

Here is the code for more tests:

require_relative '../factorial'

describe "Factorial" do
  it "computes the factorial of 0" do
    expect(factorial(0)).to eq 1
  end
  it "computes the factorial of 1" do
    expect(factorial(1)).to eq 1
  end
  it "computes the factorial of 2" do
    expect(factorial(2)).to eq 2
  end
  it "computes the factorial of 3" do
    expect(factorial(3)).to eq 6
  end
  it "computes the factorial of 4" do
    expect(factorial(4)).to eq 24
  end
  it "computes the factorial of 5" do
    expect(factorial(5)).to eq 120
  end
  it "computes the factorial of 6" do
    expect(factorial(6)).to eq 720
  end
  it "computes the factorial of 7" do
    expect(factorial(7)).to eq 5040
  end
end

Make sure to start with the first test that is failing and fix that one. Then, proceed to the next one, and so on. Here is the completed factorial method:

# Given an integer greater than of equal to zero
def factorial(n)
  return 1 if n <= 1

  product = 1
  index = n

  while index > 1
    product *= index
    index -= 1
  end

  product
end

In the example definition above, we return 1 for n less than or equal to 1 to take care of the base cases (0! = 1 and 1! = 1). Assume the given input is greater than or equal to zero. Then, to keep track of the continously multiplication by the number minus one, a product variable is used. The index variable is set to the given number at first and will decrease by one in every iteration. The method finally returns product, which holds the factorial of the given input, at the end.

All Tests Pass :)

$ rspec

Factorial
  computes the factorial of 0
  computes the factorial of 1
  computes the factorial of 2
  computes the factorial of 3
  computes the factorial of 4
  computes the factorial of 5
  computes the factorial of 6
  computes the factorial of 7

Finished in 0.00362 seconds (files took 0.13484 seconds to load)
8 examples, 0 failures

Conclusion

Unit testing has never been easier with RSpec for Ruby. It reads much like English and provides a nice framework to perform test-driven development. With the use of describe blocks and it blocks, you can get started writing your first tests. Make sure to write the tests first before actually implementing your code. Don't forget! :)

Looking for a reference text for Ruby? Get the Well-Grounded Rubyist.

Tuesday, June 28, 2016

Ruby Hashes

Need a reference text for the Ruby programming language? Get the Well-Grounded Rubyist.

An important data structure in Ruby is the hash. Hashes is a data structure of key-value pairs. In other programming languages, it might be called a dictionary or an associative array. If you know JavaScript, Ruby hashes are very similar to Objects. Let us take a look at the most basic aspects of Ruby hashes.

Creating a Hash

You can create a hash using literal notation, with curly braces:

hash = {}

The above is very similar to creating empty arrays, except for hashes you have to use curly braces.

Populating the Hash

To populate the hash, you can use bracket notation. Let us say we want to keep track of a todo list and use the key as the day of the week and the value as what we need to do. Populate the hash for Monday as so:

todo = {}
todo[:monday] = "clean the dishes"

Verify the contents of the hash:

=> {:monday=>"clean the dishes"}

A hash is a data structure of key-value pairs, so in the code above, we are associating the key :monday with the value "clean the dishes." One thing to notice is that we are using a symbol as the key. You could have used a string as the key, but it is common to use symbols as the key to Ruby hashes. Generally, using symbols will speed things up, as ultimately a string key will eventually (and internally) be converted to a symbol anyway.

Let us add one more todo to the hash:

todo[:wednesday] = "karate practice"

Now, the contents of the hash are:

=> {:monday=>"clean the dishes", :wednesday=>"karate practice"}

Hashes are represented using curly braces, with each key-value pair separated by a comma. The mapping between a key and a value is represented using the rocket or arrow symbol =>.

In general, you could have the key or value as any kind of object, not just a string or a symbol. For instance, you could use an integer as the key, but this would seem much like an array. You could also use, say, an array as the value to a certain key. That is perfectly fine in Ruby.

Accessing Nonexistent Key-Value Pairs

Now, what happens if you try to access a key for which the key-value pair does not exist?

=> {:monday=>"clean the dishes", :wednesday=>"karate practice"}

todo[:friday]
=> nil

The answer is nil. If you try to access a key for which there is not value associated with it, you will get nil. This behavior, however, can be changed if you create a new hash using new and then give it an argument for the default value. For instance:

todo = Hash.new
=> {}
todo[:tuesday]
=> nil

The above creates a new hash using Hash.new instead of using just {}. That results in the same outcome. When you try to refer to a key that points to no value, you get nil. Now, if you give new a default value:

todo = Hash.new("DOES NOT EXIST")
=> {}
todo[:tuesday]
=> "DOES NOT EXIST"

todo
=> {}

In the case above, whenever you try to access some key that does not have an associated value, you will get the string "DOES NOT EXIST", because you told Hash.new that that would be the default value in that situation. Mind, however, that the actual hash is still empty even though you got back "DOES NOT EXIST."

Creating a Hash with Initial Key-Value Pairs

So far we had to create a hash from scratch and add key-value pairs one by one. You could also have defined a hash with initial key-value pairs like so:

todo = { :monday => "clean the dishes", :wednesday => "karate practice" }

You can access the value to which a key points to using bracket notation and passing the key as the parameter:

todo[:monday]
=> "clean the dishes"

todo[:wednesday]
=> "karate practice"

Alternative Notation

There is an alternative notation to write hashes. If you are familiar with JavaScript, this will look just like writing JavaScript objects. Given the following definition:

todo = { :monday => "clean the dishes", :wednesday => "karate practice" }

Remove the arrow => and move the colon from the left-hand side of the symbol name to its right-hand side:

todo = { monday: "clean the dishes", wednesday: "karate practice" }

One thing to keep in mind is that you can only use that notation if the key is a symbol. Also, internally, hashes are still represented using the => notation:

todo
=> {:monday=>"clean the dishes", :wednesday=>"karate practice"}

Hash Size and Searching the Hash for Existence of Key/Values

You can find out how many key-value pairs there are in the hash using the size method:

todo
=> {:monday=>"clean the dishes", :wednesday=>"karate practice"}

todo.size
=> 2

Two useful methods to determine the existence of a certain key or certain value are: has_key? and has_value?

Here is an example:

todo.has_key? :monday
=> true

todo.has_key? :tuesday
=> false

Because we have :monday as one of the keys in the todo hash, we get true. However, the hash does not have the key :tuesday, so we get false for that.

Similarly, you can check whether some value is present in the hash:

todo.has_value? "wash the car"
=> false

todo.has_value? "clean the dishes"
=> true

There are many other useful methods that you can find in the Ruby documentation. So check it out

Hashes as the Last Argument to Methods

Say we have a method like so:

def greeting(name, hash)
  puts "Hello, #{name} !"
end

greeting("James", {})

Output:

Hello, James !

The method will simply say Hello followed by whatever name you give as the first argument. For the second argument, I just passed in an empty hash. Let us work with that hash next:

def greeting(name, hash)
  puts "Hello, #{name} !"
  puts "It seems that you have to #{hash[:monday]} on Monday"
end

greeting("James", { :monday => "wash the car" })

Output:

Hello, James !
It seems that you have to wash the car on Monday

So we passed a hash as an argument to greeting. That method then took the value in the hash whose key is :monday and used it to display a message about what the person needs to do.

Now that you understand what the method does, let us get to the point: if the last argument to a method call is a hash, you can omit the curly braces:

greeting("James", :monday => "wash the car")

You will see that a lot. Furthermore, you can also use the alternative notation:

greeting("James", monday: "wash the car")

To me, that looks a lot better. But you have to watch out for what it really means! That is just a hash in disguise. Keep in mind that because the hash is the last argument to the method call, you can omit the curly braces. And then you can also use the alternative notation, because the key is a symbol. It does not matter how many key-value pairs the hash has, the following would be totally okay too:

greeting("James", monday: "wash the car", tuesday: "do the laundry")

Fetch versus Bracket Notation to Retrieve a Value

Given the example:

todo
=> {:monday=>"clean the dishes", :wednesday=>"karate practice"}

You already know that to access a hash's value, you have to give the key within the square brackets:

todo[:monday]
=> "clean the dishes"

You can achieve the same outcome using the fetch method:

todo.fetch(:monday)
=> "clean the dishes"

But is there any difference at all? Consider the case where the key does not exist (i.e. no such key-value pair exists in the hash)

todo[:friday]
=> nil

todo.fetch(:friday)
KeyError: key not found: :friday
from (irb):63:in `fetch'
from (irb):63
from /usr/bin/irb:12:in `<main>'

While using bracket notation returns nil for a key that does not map to any value, using the fetch method will actually raise an exception! Let us go back to our example with the greeting method:

def greeting(name, hash)
  puts "Hello, #{name} !"
  puts "It seems that you have to #{hash[:friday]} on Monday"
  p hash[:friday]
end

greeting("James", { :monday => "wash the car" })

I changed the hash key in the greeting method to :friday (which does not have an associated value) and added a p statement to check the value of hash[:friday]. The output is:

Hello, James !
It seems that you have to  on Monday
nil

Now, if we use fetch instead:

def greeting(name, hash)
  puts "Hello, #{name} !"
  puts "It seems that you have to #{hash.fetch(:friday)} on Monday"
  p hash[:friday]
end

greeting("James", { :monday => "wash the car" })

Output:

Hello, James !
KeyError: key not found: :friday
from (irb):80:in `fetch'
from (irb):80:in `greeting'
from (irb):84
from /usr/bin/irb:12:in `<main>'

When we used bracket notation, the program kept executing and whenever we tried to access an unexistent key, we just got nil and things were just fine. But when we used fetch, it raised a KeyError exception and halted program execution right away. That is the difference between fetch and [] notation: the former raises an exception while the latter only returns nil. If you use fetch, make sure to rescue the exception and do something about it; otherwise, your program will come to a halt and stop. With bracket notation, the program will still keep going, even though having nil in certain places might have undesired effects in what you are trying to do. So handle the nil as well! :)

Conclusion

Ruby hashes are an important data structure that allows you to store data in key-value pairs. Make sure to understand them well, play with irb and make your own hashes. Try doing crazy things with it. Give it different kinds of keys and values and see what you get. Try accessing something that does not exist. Have fun! :)

Looking for a reference text for Ruby? Get the Well-Grounded Rubyist.