Chapter 4. Initializing Instances: Off to a Great Start

image with no caption

Right now, your class is a time bomb. Every instance you create starts out as a clean slate. If you call certain instance methods before adding data, an error will be raised that will bring your whole program to a screeching halt.

In this chapter, we’re going to show you a couple of ways to create objects that are safe to use right away. We’ll start with the initialize method, which lets you pass in a bunch of arguments to set up an object’s data at the time you create it. Then we’ll show you how to write class methods, which you can use to create and set up an object even more easily.

Payroll at Chargemore

You’ve been tasked with creating a payroll system for Chargemore, a new chain of department stores. They need a system that will print pay stubs for their employees.

Chargemore employees are paid for two-week pay periods. Some employees are paid a two-week portion of their annual salary, and some are paid for the number of hours they work within the two-week period. For starters, though, we’re just going to focus on the salaried employees.

A pay stub needs to include the following information:

  • The employee name

  • The amount of pay an employee received during a two-week pay period

So...here’s what the system will need to know for each employee:

  • Employee name

  • Employee salary

And here’s what it will need to do:

  • Calculate and print pay for a two-week period

This sounds like the ideal place to create an Employee class! Let’s try it, using the same techniques that we covered back in Chapter 2.

We’ll set up attribute reader methods for @name and @salary instance variables, then add writer methods (with validation). Then we’ll add a print_pay_stub instance method that prints the employee’s name and their pay for the period.

image with no caption
image with no caption

An Employee class

Here’s some code to implement our Employee class...

image with no caption

(Yes, we realize that this doesn’t account for leap years and holidays and a host of other things that real payroll apps must consider. But we wanted a print_pay_stub method that fits on one page.)

Creating new Employee instances

Now that we’ve defined an Employee class, we can create new instances and assign to their name and salary attributes.

amy = Employee.new
amy.name = "Amy Blake"
amy.salary = 50000

Thanks to validation code in our name= method, we have protection against the accidental assignment of blank names.

image with no caption

Our salary= method has validation to ensure that negative numbers aren’t assigned as a salary.

image with no caption

And when an Employee instance is properly set up, we can use the stored name and salary to print a summary of the employee’s pay period.

image with no caption

Hmmm... It’s typical to display two decimal places when showing currency, though. And did that calculation really come out to an even dollar amount?

Before we go on to perfect our Employee class, it looks like we have a bug to fix. And that will require us to go on a couple of brief detours. (But you’ll learn some number formatting skills that you’ll need later—promise!)

image with no caption
  1. Our employee pay is getting its decimal places chopped off. To fix this, we’ll need to look at the difference between Ruby’s Float and Fixnum numeric classes.

  2. We don’t want to display too many decimal places, either, so we’ll need to look at the format method to format our numbers properly.

image with no caption

A division problem

We’re working to make the perfect Employee class to help us calculate payroll for the Chargemore department store. But there’s a little detail we have to take care of first...

image with no caption

That’s true. Doing the math on paper (or launching a calculator app, if that’s your thing) can confirm that Amy should be earning $1917.81, rounded to the nearest cent. So where did that other $13.81 go?

To find out, let’s launch irb and do the math ourselves, step by step.

First, let’s calculate a day’s pay.

image with no caption
image with no caption

That’s nearly a dollar a day missing, compared to doing the math by hand:

50,000 ÷ 365 = 136.9863...

This error is then compounded when we calculate fourteen days’ pay:

image with no caption

Compare that to the answer we’d get if we multiplied the full daily pay:

136.9863 x 14 = 1917.8082...

So we’re nearly $14 off. Multiply that by many paychecks and many employees, and you’ve got yourself an angry workforce. We’re going to have to fix this, and soon...

Division with Ruby’s Fixnum class

image with no caption

The result of our Ruby expression to calculate two weeks of an employee’s pay doesn’t match up with doing the math by hand...

image with no caption

The problem here is that when dividing instances of the Fixnum class (a Ruby class that represents integers), Ruby rounds fractional numbers down to the nearest whole number.

image with no caption

It rounds the number because Fixnum instances aren’t meant to store numbers with decimal places. They’re intended for use in contexts where only whole numbers make sense, like counting employees in a department or the number of items in a shopping cart. When you create a Fixnum, you’re telling Ruby: “I expect to only be working with whole numbers here. If anyone does math with you that results in a fraction, I want you to throw those pesky decimal places away.”

How can we know whether we’re working with Fixnum instances? We can call the class instance method on them. (Remember we talked about the Object class back in Chapter 3? The class method is one of the instance methods inherited from Object.)

image with no caption

Or, if you’d rather save yourself the trouble, just remember that any number in your code that doesn’t have a decimal point in it will be treated as a Fixnum by Ruby.

Any number in your code that does have a decimal point in it gets treated as a Float (the Ruby class that represents floating-point decimal numbers):

image with no caption

If it’s got a decimal point, it’s a Float. If it doesn’t, it’s a Fixnum.

Division with Ruby’s Float class

image with no caption

We loaded up irb and saw that if we divide one Fixnum (integer) instance by another Fixnum, Ruby rounds the result down.

image with no caption

The solution, then, is to use Float instances in the operation, which we can get by including a decimal point in our numbers. If you do, Ruby will give you a Float instance back:

image with no caption

It doesn’t even matter whether both the dividend and divisor are Float instances; Ruby will give you a Float back as long as either operand is a Float.

image with no caption

It holds true for addition, subtraction, and multiplication as well: Ruby will give you a Float if either operand is a Float:

image with no caption

When the first operand is a...

And the second operand is a...

The result is a...

Fixnum

Fixnum

Fixnum

Fixnum

Float

Float

Float

Fixnum

Float

Float

Float

Float

And of course, with addition, subtraction, and multiplication, it doesn’t matter whether both operands are Fixnum instances, because there’s no fractional number to lose in the result. The only operation where it really matters is division. So, remember this rule:

When doing division, make sure at least one operand is a Float.

Let’s see if we can use this hard-won knowledge to fix our Employee class.

Fixing the salary rounding error in Employee

image with no caption

As long as one of the operands is a Float, Ruby won’t truncate the decimals from our division operation.

image with no caption

With this rule in mind, we can revise our Employee class to stop truncating the decimals from employees’ pay:

image with no caption

Now we have a new problem, though: look what happens to the output!

image with no caption

We’re showing a little too much precision! Currency is generally expected to be shown with just two decimal places, after all. So, before we can go back to building the perfect Employee class, we need to go on one more detour...

image with no caption
image with no caption

Formatting numbers for printing

image with no caption

Our print_pay_stub method is displaying too many decimal places. We need to figure out how to round the displayed pay to the nearest penny (two decimal places).

image with no caption

To deal with these sorts of formatting issues, Ruby provides the format method.

Here’s a sample of what this method can do. It may look a little confusing, but we’ll explain it all on the next few pages!

image with no caption

So it looks like format can help us limit our displayed employee pay to the correct number of places. The question is, how? To be able to use this method effectively, we’ll need to learn about two features of format:

  1. Format sequences (the little %0.2f above is a format sequence)

  2. Format sequence widths (that’s the 0.2 in the middle of the format sequence)

Relax

We’ll explain exactly what those arguments to format mean on the next few pages.

We know, those method calls look a little confusing. We have a ton of examples that should clear that confusion up. We’re going to focus on formatting decimal numbers, because it’s likely that will be the main thing you use format for in your Ruby career.

Format sequences

image with no caption

The first argument to format is a string that will be used to format the output. Most of it is formatted exactly as it appears in the string. Any percent signs (%), however, will be treated as the start of a format sequence, a section of the string that will be substituted with a value in a particular format. The remaining arguments are used as values for those format sequences.

image with no caption

Format sequence types

The letter following the percent sign indicates the type of value that’s expected. The most common types are:

%s

string

%i

integer

%f

floating-point decimal

image with no caption

So %f is for floating-point decimal numbers... We can use that sequence type to format the currency in our pay stubs.

By itself, though, the %f sequence type won’t help us. The results still show too many decimal places.

image with no caption

Up next, we’ll look at a fix for that situation: the format sequence width.

Format sequence width

image with no caption

Here’s the useful part of format sequences: they let you specify the width of the resulting field.

Let’s say we want to format some data in a plain-text table. We need to ensure the formatted value fills a minimum number of spaces, so that the columns align properly.

You can specify the minimum width after the percent sign in a format sequence. If the argument for that format sequence is shorter than the minimum width, it will be padded with spaces until the minimum width is reached.

image with no caption

And now we come to the part that’s important for today’s task: you can use format sequence widths to specify the precision (the number of displayed digits) for floating-point numbers. Here’s the format:

image with no caption

The minimum width of the entire number includes decimal places. If it’s included, shorter numbers will be padded with spaces at the start until this width is reached. If it’s omitted, no spaces will ever be added.

The width after the decimal point is the maximum number of digits to show. If a more precise number is given, it will be rounded (up or down) to fit in the given number of decimal places.

Format sequence width with floating-point numbers

image with no caption

So when we’re working with floating-point numbers, format sequence widths let us specify the number of digits displayed before and after the decimal point. Could this be the key to fixing our pay stubs?

Here’s a quick demonstration of various width values in action:

image with no caption

That last format, "%.2f", will let us take floating-point numbers of any precision and round them to two decimal places. (It also won’t do any unnecessary padding.) This format is ideal for showing currency, and it’s just what we need for our print_pay_stub method!

image with no caption

Previously, our calculated pay for our Employee class’s print_pay_stub method was displayed with excess decimal places:

image with no caption

But now we finally have a format sequence that will round a floating-point number to two decimal places:

image with no caption

Let’s try using format in the print_pay_stub method.

image with no caption

Using “format” to fix our pay stubs

image with no caption

We can test our revised print_pay_stub using the same values as before:

image with no caption
image with no caption

We had to make a couple of detours, but we’ve finally got our Employee class printing pay stubs as it should! Next, we’ll get back to the business of perfecting our class...

image with no caption
image with no caption

When we forget to set an object’s attributes...

Now that you have the employee pay printing in the correct format, you’re puttering along, happily using your new Employee class to process payroll. That is, until you create a new Employee instance and forget to set the name and salary attributes before calling print_pay_stub:

image with no caption

What happened? It’s only natural that the name is empty; we forgot to set it. But what’s this “undefined method for nil” error? What the heck is this nil thing?

This sort of error is pretty common in Ruby, so let’s take a few pages to understand it.

Let’s alter the print_pay_stub method to print the values of @name and @salary, so we can figure out what’s going on.

image with no caption

“nil” stands for nothing

Now let’s create a new Employee instance and call the revised method:

image with no caption

Well, that wasn’t very helpful. Maybe we’re missing something, though.

Back in Chapter 1, we learned that the inspect and p methods can reveal information that doesn’t show up in ordinary output. Let’s try again, using p:

image with no caption

We create another new instance, make another call to the instance method, and...

image with no caption

Ruby has a special value, nil, that represents nothing. That is, it represents the absence of a value.

Just because nil represents nothing doesn’t mean it’s actually nothing, though. Like everything else in Ruby, it’s an object, and it has its own class:

image with no caption

But if there’s actually something there, how come we didn’t see anything in the output?

It’s because the to_s instance method from NilClass always returns an empty string.

image with no caption

The puts and print methods automatically call to_s on an object to convert it to a string for printing. That’s why we got two blank lines when we tried to use puts to print the values of @name and @salary; both were set to nil, so we wound up printing two empty strings.

Unlike to_s, the inspect instance method from NilClass always returns the string "nil".

image with no caption

You may recall that the p method calls inspect on each object before printing it. That’s why the nil values in @name and @salary appeared in the output once we called p on them.

“/” is a method

So, when you first create an instance of the Employee class, its @name and @salary instance variables have a value of nil. The @salary variable, in particular, causes problems if you call the print_pay_stub method without setting it first:

image with no caption

It’s obvious from the error that the problem is related to the nil value. But it says undefined method '/'... Is division really a method?

In Ruby, the answer is yes; most mathematical operators are implemented as methods. When Ruby sees something like this in your code:

6 + 2

...it converts it to a call to a method named + on the Fixnum object 6, with the object on the right of the + (that is, 2) as an argument:

image with no caption

Both forms are perfectly valid Ruby, and you can try running them yourself:

image with no caption

The same is true for most of the other mathematical operators.

image with no caption

Even comparison operators are implemented as methods.

image with no caption

But while the Fixnum and Float classes define these operator methods, NilClass does not.

image with no caption

In fact, nil doesn’t define most of the instance methods you see on other Ruby objects.

And why should it? If you’re doing mathematical operations with nil, it’s almost certainly because you forgot to assign a value to one of the operands. You want an error to be raised, to bring your attention to the problem.

It was a mistake when we forgot to set a salary for an Employee, for example. And now that we understand the source of this error, it’s time to prevent it from happening again.

The “initialize” method

We tried to call print_pay_stub on an instance of our Employee class, but we got nil when we tried to access the @name and @salary instance variables.

employee = Employee.new
employee.print_pay_stub
image with no caption

Chaos ensued.

image with no caption

Here’s the method where the nil values caused so much trouble:

image with no caption

Here’s the key problem: at the time we create an Employee instance, it’s in an invalid state; it’s not safe to call print_pay_stub until you set its @name and @salary instance variables.

If we could set @name and @salary at the same time as we create an Employee instance, it would reduce the potential for errors.

Ruby provides a mechanism to help with this situation: the initialize method. The initialize method is your chance to step in and make the object safe to use, before anyone else attempts to call methods on it.

class MyClass
  def initialize
    puts "Setting up new instance!"
  end
end

When you call MyClass.new, Ruby allocates some memory to hold a new MyClass object, then calls the initialize instance method on that new object.

image with no caption

Ruby calls the initialize method on new objects after they’re created.

Employee safety with “initialize”

Let’s add an initialize method that will set up @name and @salary for new Employee instances before any other instance methods are called.

image with no caption

Now that we’ve set up an initialize method, @name and @salary will already be set for any new Employee instance. It’ll be safe to call print_pay_stub on them immediately!

image with no caption

Arguments to “initialize”

Our initialize method now sets a default @name of "Anonymous" and a default @salary of 0.0. It would be better if we could supply a value other than these defaults.

It’s for situations like this that any arguments to the new method are passed on to initialize.

image with no caption

We can use this feature to let the caller of Employee.new specify what the initial name and salary should be. All we have to do is add name and salary parameters to initialize, and use them to set the @name and @salary instance variables.

image with no caption

And just like that, we can set @name and @salary via arguments to Employee.new!

image with no caption

Of course, once you set it up this way, you’ll need to be careful. If you don’t pass any arguments to new, there will be no arguments to forward on to initialize. At that point, you’ll get the same result that happens any time you call a Ruby method with the wrong number of arguments: an error.

image with no caption

We’ll look at a solution for this in a moment.

Using optional parameters with “initialize”

We started with an initialize method that set default values for our instance variables, but didn’t let you specify your own...

image with no caption

Then we added parameters to initialize, which meant that you had to specify your own name and salary values, and couldn’t rely on the defaults...

image with no caption

Can we have the best of both worlds?

Yes! Since initialize is an ordinary method, it can utilize all the features of ordinary methods. And that includes optional parameters. (Remember those from Chapter 2?)

We can specify default values when declaring the parameters. When we omit an argument, we’ll get the default value. Then, we just assign those parameters to the instance variables normally.

image with no caption

With this change in place, we can omit one or both arguments and get the appropriate defaults!

image with no caption

The new method is needed to actually create the object; initialize just sets up the new object’s instance variables.

“initialize” does an end-run around our validation

image with no caption

@name = “Steve Wilson (HR Manager)” @salary = 80000

You remember our name= attribute writer method, which prevents the assignment of an empty string as an Employee name:

image with no caption

There’s also our salary= attribute writer method, which ensures that negative numbers aren’t assigned as a salary:

image with no caption

We have bad news for you: since your initialize method assigns directly to the @name and @salary instance variables, bad data has a new way to sneak in!

image with no caption

“initialize” and validation

We could get our initialize method to validate its parameters by adding the same validation code to the initialize method...

image with no caption

But duplicating code like that is a problem. What if we changed the initialize validation code later, but forgot to update the name= method? There would be different rules for setting the name, depending on how you set it!

Rubyists try to follow the DRY principle, where DRY stands for Don’t Repeat Yourself. It means that you should avoid duplicating code wherever possible, as it’s likely to result in bugs.

What if we called the name= and salary= methods from within the initialize method? That would let us set the @name and @salary instance variables. It would also let us run the validation code, without duplicating it!

Call other methods on the same instance with “self”

We need to call the name= and salary= attribute writer methods from within the initialize method of the same object. That will let us run the writer methods’ validation code before we set the @name and @salary instance variables.

Unfortunately, code like this won’t work...

image with no caption

The code in the initialize method treats name= and salary= not as calls to the attribute writer methods, but as resetting the name and salary local variables to the same values they already contain! (If that sounds like a useless and nonsensical thing to do, that’s because it is.)

What we need to do is make it clear to Ruby that we intend to call the name= and salary= instance methods. And to call an instance method, we usually use the dot operator.

But we’re inside the initialize instance method...what would we put to the left of the dot operator?

We can’t use the amy variable; it would be silly to refer to one instance of the class within the class itself. Besides, amy is out of scope within the initialize method.

image with no caption

We need something to put to the left of the dot operator, so that we can call our Employee class’s name= and salary= attribute accessor methods within our initialize method. The problem is, what do we put there? How do you refer to the current instance from inside an instance method?

image with no caption

Ruby has an answer: the self keyword. Within instance methods, self always refers to the current object.

We can demonstrate this with a simple class:

class MyClass
  def first_method
    puts "Current instance within first_method: #{self}"
  end
end

Within instance methods, the keyword self refers to the current object.

If we create an instance and call first_method on it, we’ll see that inside the instance method, self refers to the object the method is being called on.

image with no caption

The string representations of my_object and self include a unique identifier for the object. (We’ll learn more about this in Chapter 8.) The identifiers are the same, so it’s the same object!

We can also use self with the dot operator to call a second instance method from inside the first one.

image with no caption

Now that we have self to use the dot operator on, we can make it clear to Ruby that we want to call the name= and salary= instance methods, not to set the name and salary variables...

image with no caption

Let’s try calling our new constructor and see if it worked!

image with no caption

Success! Thanks to self and the dot operator, it’s now clear to Ruby (and everyone else) that we’re making calls to the attribute writer methods, not assigning to variables.

And since we’re going through the accessor methods, that means the validation works, without any duplicated code!

image with no caption
image with no caption

When “self” is optional

Right now, our print_pay_stub method accesses the @name and @salary instance variables directly:

class Employee

  def print_pay_stub
    puts "Name: #{@name}"
    pay_for_period = (@salary / 365.0) * 14
    formatted_pay = format("$%.2f", pay_for_period)
    puts "Pay This Period: #{formatted_pay}"
  end

end

But we defined name and salary attribute reader methods in our Employee class; we could use those instead of accessing the instance variables directly. (That way, if you ever change the name method to display last name first, or change the salary method to calculate salary according to an algorithm, the print_pay_stub code won’t need to be updated.)

We can use the self keyword and the dot operator when calling name and salary, and it will work just fine:

image with no caption

But Ruby has a rule that can save us a little typing when calling from one instance method to another... If you don’t specify a receiver using the dot operator, the receiver defaults to the current object, self.

image with no caption

As we saw in the previous section, you have to include the self keyword when calling attribute writer methods, or Ruby will mistake the = for a variable assignment. But for any other kind of instance method call, you can leave self off, if you want.

If you don’t specify a receiver using the dot operator, the receiver defaults to the current object, self.

Implementing hourly employees through inheritance

The Employee class you’ve created for Chargemore is working great! It prints accurate pay stubs that are formatted properly, and thanks to the initialize method you wrote, it’s really easy to create new Employee instances.

But, at this point, it only handles salaried employees. It’s time to look at adding support for employees that are paid by the hour.

The requirements for hourly employees are basically the same as for salaried ones; we need to be able to print pay stubs that include their name and the amount paid. The only difference is the way that we calculate their pay. For hourly employees, we multiply their hourly wage by the number of hours they work per week, then double that amount to get two weeks’ worth.

(salary / 365.0) * 14

Salaried employee pay calculation formula

hourly_wage * hours_per_week * 2

Hourly employee pay calculation formula

Since salaried and hourly employees are so similar, it makes sense to put the shared functionality in a superclass. Then, we’ll make two subclasses that hold the different pay calculation logic.

image with no caption

Let’s start by ensuring the common logic between SalariedEmployee and HourlyEmployee stays in the Employee superclass.

Since pay stubs for both salaried and hourly employees need to include their names, we’ll leave the name attribute in the superclass, for the subclasses to share. We’ll move the code that prints the name into the print_name method in the superclass.

image with no caption

We’ll move the logic to calculate pay for salaried employees to the SalariedEmployee class, but we’ll call the inherited print_name method to print the employee name.

image with no caption

With those changes in place, we can create a new SalariedEmployee instance, set its name and salary, and print a pay stub as before:

image with no caption

Now we’ll build a new HourlyEmployee class. It’s just like SalariedEmployee, except that it holds an hourly wage and number of hours worked per week, and uses those to calculate pay for a two-week period. As with SalariedEmployee, storing and printing the employee name is left up to the Employee superclass.

class HourlyEmployee  <  Employee

  attr_reader :hourly_wage, :hours_per_week

  def hourly_wage=(hourly_wage)
    # Code to validate and set @hourly_wage
  end

  def hours_per_week=(hours_per_week)
    # Code to validate and set @hours_per_week
  end

  def print_pay_stub
    print_name
    pay_for_period = hourly_wage * hours_per_week * 2
    formatted_pay = format("$%.2f", pay_for_period)
    puts "Pay This Period: #{formatted_pay}"
  end

end

And now we can create an HourlyEmployee instance. Instead of setting a salary, we set an hourly wage and number of hours per week. Those values are then used to calculate the pay stub amount.

image with no caption

That wasn’t bad at all! Through the use of inheritance, we’ve implemented pay stubs for hourly employees, kept pay stubs for salaried employees, and minimized code duplication between the two.

We’ve lost something in the shuffle, though—our initialize method. We used to be able to set up an Employee object’s data at the time we created it, and these new classes won’t let us do that. We’ll have to add initialize methods back in.

Restoring “initialize” methods

To make SalariedEmployee and HourlyEmployee objects that are safe to work with as soon as they’re created, we’ll need to add initialize methods to those two classes.

image with no caption

As we did with the Employee class before, our initialize methods will need to accept a parameter for each object attribute we want to set. The initialize method for SalariedEmployee will look just like it did for the old Employee class (since the attributes are the same), but initialize for HourlyEmployee will accept a different set of parameters (and set different attributes).

image with no caption

With our initialize methods added, we can once again pass arguments to the new method for each class. Our objects will be ready to use as soon as they’re created.

image with no caption

Inheritance and “initialize”

There’s one small weakness in our new initialize methods, though: the code to set the employee name is duplicated between our two subclasses.

image with no caption

In all other aspects of our subclasses, we delegate handling of the name attribute to the Employee superclass. We define the reader and writer methods there. We even print the name via the print_name method, which the subclasses call from their respective print_pay_stub methods.

image with no caption

But we don’t do this for initialize. Could we?

Yes! We’ve said it before, and we’ll say it again: initialize is just an ordinary instance method. That means that it gets inherited like any other, that it can be overridden like any other, and that overriding methods can call it via super like any other. We’ll demonstrate on the next page.

Note

* Okay, we realize it’s just one line of duplicated code. But the technique we’re about to show you will also help with much larger amounts of duplication.

“super” and “initialize”

To eliminate the repeated name setup code in our Employee subclasses, we can move the name handling to an initialize method in the superclass, then have the subclass initialize methods call it with super. SalariedEmployee will keep the logic to set up a salary, HourlyEmployee will keep the logic to set up an hourly wage and hours per week, and the two classes can delegate the shared logic for name to their shared superclass.

image with no caption

First, let’s try moving the name handling from the initialize method in SalariedEmployee to the Employee class.

image with no caption

Trying to use this revised initialize method reveals a problem, though...

image with no caption

Oops! We forgot a key detail about super that we learned earlier—if you don’t specify a set of arguments, it calls the superclass method with the same set of arguments that the subclass method received. (This is true when you’re using super in other instance methods, and it’s true when you’re using super within initialize.) The initialize method in SalariedEmployee received two parameters, and super passed them both on to the initialize method in Employee. (Even though it only accepts one argument.)

The fix, then, is to specify which parameter we want to pass on: the name parameter.

image with no caption

Let’s try to initialize a new SalariedEmployee again...

image with no caption

It worked! Let’s make the same changes to the HourlyEmployee class...

image with no caption

Previously, we used super within our print_pay_stub methods in SalariedEmployee and HourlyEmployee to delegate printing of the employee name to the Employee superclass. Now we’ve just done the same thing with the initialize method, allowing the superclass to handle setting of the name attribute.

Why does it work? Because initialize is an instance method just like any other. Any feature of Ruby that you can use with an ordinary instance method, you can use with initialize.

Same class, same attribute values

With your HourlyEmployee class complete, Chargemore is ready to begin a hiring blitz to staff their new stores. Here’s the set of employees they need to create for their first store downtown:

image with no caption

If you look at the above code, you’ll probably notice there are large groups of objects where similar arguments are passed to the new method. There’s a good reason for this: the first group are cashiers for the new store, the second group are janitors, and the third group are security guards.

Chargemore starts all new cashiers off at the same base pay and number of hours per week. Janitors get a different rate and number of hours than cashiers, but it’s the same for all janitors. And the same is true for security guards. (Individuals may get raises later, depending on performance, but they all start out the same.)

image with no caption

The upshot is that there’s a lot of repetition of arguments in those calls to new, and a lot of chances to make a typo. And this is just the first wave of hiring, for the first Chargemore store, so things can only get worse. Seems like we can make this easier.

An inefficient factory method

When you need to make many instances of a class that have similar data, you can often save some repetition by making a factory method to create objects prepopulated with the needed attribute values. (Factory methods are a programming pattern that can be used in any object-oriented language, not just Ruby.)

But if we use only the tools we have now, any factory method we make will be inefficient at best.

To demonstrate what we mean, let’s try making a method to set up new HourlyEmployee objects with the default pay and hours per week for cashiers.

image with no caption

This works, yes. So what’s so inefficient about it? Let’s look at our initialize method (which of course has to run when we create a new HourlyEmployee) again...

image with no caption

We’re setting the hourly_wage and hours_per_week attributes within initialize, then immediately turning around and setting them again within turn_into_cashier!

This is inefficient for Ruby, but there’s potential for it to be inefficient for us, too. What if we didn’t have default parameters for hourly_wage and hours_per_week on initialize? Then, we’d have to specify the arguments we’re throwing away!

image with no caption

That’s the problem with writing factory methods as instance methods: we’re trying to make a new instance of the class, but there has to already be an instance to run the methods on! There must be a better way...

Fortunately, there is! Up next, we’re going to learn about class methods.

Class methods

You don’t have an instance of a class, but you need one. And you need a method to set it up for you. Where do you put that method?

You could stick it off by itself in some little Ruby source file, but it would be better to keep it together with the class that it makes instances of. You can’t make it an instance method on that class, though. If you had an instance of the class, you wouldn’t need to make one, now would you?

It’s for situations like this that Ruby supports class methods: methods that you can invoke directly on a class, without the need for any instance of that class. You don’t have to use a class method as a factory method, but it’s perfect for the job.

A class method definition is very similar to any other method definition in Ruby. The difference: you specify that you’re defining it on the class itself.

image with no caption

Within a class definition (but outside any instance method definitions), Ruby sets self to refer to the class that’s being defined. So many Rubyists prefer to replace the class name with self:

image with no caption

In most ways, class method definitions behave just like you’re used to:

  • You can put as many Ruby statements as you like in the method body.

  • You can return a value with the return keyword. If you don’t, the value of the last expression in the method body is used as the return value.

  • You can optionally define one or more parameters that the method accepts, and you can make the parameters optional by defining defaults.

We’ve defined a new class, MyClass, with a single class method:

class  defined a new class,

  def self.my_class_method(p1, p2)
    puts "Hello from MyClass!"
    puts "My parameters: #{p1}, #{p2}"
  end

end

Once a class method is defined, you can call it directly on the class:

image with no caption

Perhaps that syntax for calling a class method looks familiar to you...

MyClass.new

That’s right, new is a class method! If you think about it, that makes sense; new can’t be an instance method, because you’re calling it to get an instance in the first place! Instead, you have to ask the class for a new instance of itself.

Now that we know how to create class methods, let’s see if we can write some factory methods that will create new HourlyEmployee objects with the pay rate and hours per week already populated for us. We need methods to set up predefined pay and hours for three positions: cashier, janitor, and security guard.

image with no caption

We won’t know the name of the employee in advance, so we accept that as a parameter to each of the class methods. We do know the values for hourly_wage and hours_per_week for each employee position, though. We pass those three arguments to the new method for the class, and get a new HourlyEmployee object back. That new object is then returned from the class method.

Now we can call the factory methods directly on the class, providing only the employee name.

angela = HourlyEmployee.security_guard("Angela Matthews")
edwin = HourlyEmployee.janitor("Edwin Burgess")
ivan = HourlyEmployee.cashier("Ivan Stokes")

The HourlyEmployee instances returned are fully configured with the name we provided, and the appropriate hourly_wage and hours_per_week for the position. We can begin printing pay stubs for them right away!

image with no caption

In this chapter, you’ve learned that there are some pitfalls to creating new objects. But you’ve also learned techniques to ensure your objects are safe to use as soon as you make them. With well-designed initialize methods and factory methods, creating and configuring new objects is a snap!

With well-designed initialize methods and factory methods, creating and configuring new objects is a snap!

Our complete source code

image with no caption
image with no caption

Your Ruby Toolbox

That’s it for Chapter 4! You’ve added the initialize method and class methods to your toolbox.

image with no caption

Up Next...

So far, we’ve been working with objects one at a time. But it’s much more common to work with groups of objects. In the next chapter, we’ll show you how to create a group, with arrays. We’ll also show you how to process each of the items in those arrays, using blocks.

Get Head First Ruby now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.