Blog

How to preserve idioms in Ruby subclassing

by Jim Gay

Ever wonder why you call new on a class but define initialize? Let’s take a look.

Leaning on inheritance is a great way to solve related problems in our applications while allowing for some variation in behavior. Inheritance is simple and easy to implement.

Here’s a quick example of where developers often stumble by writing classes that require too much knowledge about their subclasses.

Let’s make a simple class that we’ll use to say hello:

class Greeter
  def initialize(args)
    @name = args.fetch(:name){ 'nobody' }
  end

  def greet
    "I'm #{@name}."
  end
end

Here we have a Greeter class and when you initialize an object from it you’ll be able to tell it to greet with an introduction:

greeter = Greeter.new(:name => 'Jim')
greeter.greet
=> "I'm Jim."

We later find that in some cases we need a variation of this with something more formal. So let’s make a class to support that:

class FormalGreeter < Greeter
  def greet
    "I am your servant, #{@name}."
  end
end

With this FormalGreeter class we have a different introduction:

greeter = FormalGreeter.new(:name => 'Jim')
greeter.greet
=> "I am your servant, Jim."

Adding Default Behavior During Initialization

Later you decide that your FormalGreeter really ought to do something special during the greeting.

greeter = FormalGreeter.new(:name => 'Amy', :movement => 'curtsy')
greeter.greet
=> "I am your servant, Amy."
greeter.action
=> "curtsy"

Here our FormalGreeter is the class that will move during the greeting. So we can just define initialize:

class FormalGreeter < Greeter
  def initialize(args)
    @movement = args.fetch(:movement){ 'bow' }
    super
  end

  def action
    @movement.to_s
  end
end

All you have to do for everything else to work is to remember to call super.

But having to remember things can be a stumbling block in your code. Humans forget things.

One way to do this would be to setup a special initialize method to be called in the base class. There you could define your own behavior in the subclasses:

class Greeter
  def initialize(args)
    @name = args.fetch(:name){ 'nobody' }
    special_initialize(args)
  end

  def special_initialize(args)
    # hook for subclass
  end
end

This is better because we don’t need to remember in what order we should run our code. Does my code go first and then super? Or do I call super first and then do what I need? Now, we don’t need to mentally recall the procedure.

This simplifies our initialization process, but we still have the need to remember that instead of calling super within our initialize method, we need to define our special_initialize method.

Humans forget things.

This should still be easier.

Instead of needing to remember things, you can hook into your initialization in a way that requires less thinking during the development of your subclasses. Fallback to standards like defining initialize.

Here’s how you protect idiomatic code:

class Greeter
  def self.new(args)
    instance = allocate # make memory space for a new object
    instance.send(:default_initialize, args)
    instance.send(:initialize, args)
    instance
  end

  def default_initialize(args)
    @name = args.fetch(:name){ 'nobody' }
  end
  private :default_initialize

  def initialize(args)
    # idiomatic hook!
  end
end

Now our subclasses have the benefit of being able to use their own initialize without the need for us to remember super nor a custom method name.

class FormalGreeter < Greeter
  def initialize(args)
    @movement = args.fetch(:movement){ 'bow' }
  end

  def action
    @movement
  end
end

Now you have some insight into the way that Ruby uses your initialize method.

Clean Up Your Code

If you want more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.

Permalink… Comments: 2

Wroclove.rb Presentation

by Jim Gay

I was very fortunate to present some ideas to a group of developers in Wroclaw, Poland at http://wrocloverb.com/

Slides always leave out interesting things (and they should) but I’ll post the video as soon as it’s available.

Clean Up Your Code

If you want more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.

Permalink… Comments: 0

Triggering the DCI Context

by Jim Gay

There’s been a lot of interest in DCI among Ruby developers, and a lot of good discussion going on among some blogs about application architecture.

I’ve written about basic information on the concept which touched on some sample code. One main point to make is that our class oriented approach to application architecture is that we end up with junk drawer objects; objects which contain methods for any number of interactions that may be performed at some time during the execution of the application.

Mike Pack’s article has been the first to discuss triggers for DCI in Rails (but correct me if I’m wrong). Triggers are the actions that initialize your Context to perform the algorithm.

When you develop your system with DCI one of the goals is to take your business use cases and describe them in your Context. When writing a use case you’ll say that you have a specific preconditions, specific actors, and of course specific goals. But we can look to Alistair Cockburn for the full understanding of use cases.

Here are the main points about what a use case should do and some points about how we can apply this to our executable code. In Cockburn’s words (with my comments afterward) use cases DO:

Hold Functional Requirements in an easy to read, easy to track text format. We can do this in code. Ruby is widely known for being easy to read and the community values beautiful code. As far as holding functional requirements, that should be very simple for executable code to do.

Represents the goal of an interaction between an actor and the system. The goal represents a meaningful and measurable objective for the actor. This is both an important aspect of your business, and an important aspect of DCI. When we’re attempting to achieve our business goals, we should write software that is uniquely designed to do that. The Context is an object that encapsulates this concept.

Records a set of paths (scenarios) that traverse an actor from a trigger event (start of the use case) to the goal (success scenarios). In simple code this is easy enough to do with if/else blocks or case statements, for example.

Records a set of scenarios that traverse an actor from a trigger event toward a goal but fall short of the goal (failure scenarios). An example here might be the above if/else blocks or perhaps a rescue from an exception. A use case describes a complete interaction between the user and your system and it is the responsibility of your Context to implement this.

Are multi-level: one use case can use/extent the functionality of another. This is reflected in DCI in the fact that we want to achieve the vision of Alan Kay, to create a network of interacting objects much like biological cells or computer networks. A Context can trigger other Contexts within.

What you’re attempting to do with DCI is not battle junk drawer objects with other junk drawer objects, but to implement business logic in an organized set. Take a specific need and describe it, including variations, in a single Context to coordinate the Data and Interactions. The Context has real meaning and real value to your business, it’s not just a place to use extend on your objects.

In Rails, your controllers should handle the user actions that trigger these use cases. You might have multiple ways to trigger a use case. For example in a typical view your user can interact with objects in your system but in admin view another user can do the same with perhaps an alternate scenario allowing him to override certain aspects of the scenario. It makes a lot of business sense to look at this use case and scenarios together, so why not put that code into one place? Why not create Context that explains all of this for us in executable code.

Put your use cases in a set of executable code and trigger it from wherever your interface requires it. Before you attempt this, begin first by writing your use cases. Use cases reveal the needs of the program and also show the value of the DCI Context in centralizing your business needs.

I’m writing about this and more in “Clean Ruby” a book about DCI in Ruby that is a collection of research and tips from my experience in working with large and small applications and making them easy to understand and maintain.

Clean Up Your Code

If you want more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.

Permalink… Comments: 6

OOP, DCI and Ruby - what your system is vs. what your system does

by Jim Gay

If you’ve read my previous article on the tip of the iceberg with DCI then you might be curious to find out more about it. You read a very simple example that was only used to make a point. But for sure, you’re left wondering a bit:

  • Why not make some other class to manage this if we want to separate functionality?
  • Is this at odds with the program’s “connascence”
  • And another good question came up in the comments: what about performance?

All of these, and more, are good questions. But first you need to get your head around why you would use a pattern like DCI. What does the approach with Data, Context, and Interaction have to do with Object Oriented Programming?

Your first challenge

You need to unlearn what you know to be OOP.

Not quite all of it, but you need to stop and reconsider what it means to be “object oriented.”

It’s very likely that you’ve been building programs that are “class oriented” and not “object oriented.” Take a moment and look at your latest project and consider if that’s true.

Class Oriented Programming

You might call your program “class oriented” if your classes define both what an object is and what it does. If you need something else to happen to or with that object then what’s your answer?

Do you add more methods to it’s class or do you make a new class of thing to abstract the management of some task?

If you do either of those, it could be called class oriented.

Think about what this comment from Joe Armstrong might mean in Coders at Work: “The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.”

Can that be applied to your objects?

Object Oriented Programming

When we program with OOP we care about objects, not only classes.

What DCI attempts to do is separate what the system is from what the system does. With this type of approach you’ll get a truer mental model of your program. And your code concepts will be far closer to what you experience in the real world.

“But classes represent what things are! And different things behave in certain ways” you might say. This may, on the outset, seem to be true, but if you think about the things in the real world, they don’t do anything unless they are playing some role.

Objects and Roles

The problem with relying upon classes and inheritance to define object behavior is that the behavior is fixed at compile time and your objects are limited in what actions they perform.

For example, you might have a Person class that is the basic model representation of a human being. That person is also a student, so you make a Student subclass. Eventually the student needs to get a job to pay for books and you need to make an Employee class. Should employees inherit from students? No, that’s not right. Should the student inherit from employees? That’s not right either.

Really, what you have is an object, a person, that needs to play many roles. With DCI, the approach is obvious: when the object needs to play a role, it is given that role.

Obvious Code

Yeah, but how often does this happen? Isn’t your example contrived?

This type of inheritance problem may not happen often in your programs, but it does happen and you’re likely to run into it at some point. And when it does happen what will you do?

I’ll make another object to manage the work. Maybe the Person has many tasks and I can just make a TaskPerformer object to handle that.

While making another object to handle behavior may solve the problem with better encapsulation, your code is less representative of how the real world actually works. By making your code less like the real world, it makes it more difficult for you and others to reason about it’s functions. And by introducing an abstract object, you’ve introduced something that doesn’t make sense in the real world. Does a person have a task performer or does a person just perform a task?

The benefit in approaching objects and roles like this is that it makes your code more obvious. In the context of some event, an object is given a role and has methods defined to perform some action. By explicitly assigning roles to objects in a context, your code is instantly more decipherable.

Let’s look at a simple example:

current_user.extend Admin
current_user.grant_permission(other_user)
current_user.extend Notifier
current_user.send_thank_you_to(other_user)

In the example code, we see when an object gets a role, and performs some action. Looking at that code, one could assume that the modules used to extend the objects define the methods used.

Compare that code with this:

current_user.grant_permission(other_user)
current_user.send_thank_you_to(other_user)

Are those methods defined on the class? Are they in another module that’s included in the class? Perhaps. And yet you might need to break out grep to look around for def grant_permission in your project to find out exactly what that method does.

By defining these methods directly in an object’s class, we’re convoluting what the system is with what the system does. By separating the actions in to Roles, we’re drawing a clean line between our representation of data (what it is) and the use cases that our system is designed to implement (what it does).

Separate Object from Role

With Ruby, we can easily just define new methods on an object with extend. This gives you the ability to easily break apart your concerns.

Here’s something to try: the next time you need to implement a feature begin by writing some pseudo-code. Take the details of what needs to be done and add them as comments. Then gather your objects and assign to them some roles that make sense for what needs to be done.

# A user submits a form to request permission to access our system
# If there is an available opening
team.extend AvailabilityChecker
if team.available_opening?
  applicant = User.find(params[:id])
  applicant.extend Applicant
  # and the user has completed the application, send it to processing queue
  if applicant.completed_application?
    applicant.prepare_for_acceptance
  else
    # If the user hasn't completed the application, ask them to complete it
    render 'edit', :notice => 'Oops! Please finish this.'
  end
else
  # If there is no available opening, display a message
  redirect_to :index, :notice => "Bummer, dude! We're all out of space."
end

The sample I gave is completely unreal and off the top of my head; don’t read too much into it. But if you take a glance you’ll see that it’s pretty obvious where I intend these methods to be defined. It’s likely that others on my team would find it obvious too.

Another benefit is that I didn’t add any new methods to my user class. My code won’t infect any other feature or test developed by others and by not adding more methods to the user class I don’t add any overhead to understanding it.

Once I have that pseudo-code that describes the behavior, I can comment it out and start writing tests while I re-implement each tested piece.

Try this approach to see whether the experience is good or bad. (Note that this doesn’t have to happen in your controller. You might implement this in a separate object, a Context which coordinates the knowledge of these objects and roles).

Does it make your code more obvious? Did it make testing any easier? What did others on your development team think when they saw it?

Clean Up Your Code

If you want more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.

Permalink… Comments: 20

4 Simple Steps - Extending Ruby Objects - The Tip of the Iceberg with DCI

by Jim Gay

You’ve got a few Rails applications under your belt, perhaps several. But something is wrong. As you go, your development slows and your classes become bloated and harder to understand.

Keep your program simple

While you’re doing your best to follow the concept of keeping your controllers skinny and your models fat, your models are getting really fat.

The answer is simple: cut the fat.

Your models don’t need to have every method they will ever need defined in the class. The reality is that the objects that your application handles only need those methods when they need them and not at any other time. Read that again if you must, because it’s true.

Step 1: Evaluate your code and look for a place to separate concerns

This is the point where you look at your code and try to realize what object needs what and when.

For example, if users of your system need to approve a friend request they only need to do it in the context of viewing that request. Your User class or @current_user object doesn’t need this ability at any other time.

Step 2: Prepare your test suite

If you want to make your code simpler and easier to understand, write tests. You must do this first.

Even if you’re only intending to change one small thing (just one tiny piece), write a test for that. You need a baseline.

Step 3: Create the Object Roles

Take your friend approval (or whatever it is) method or methods and put them in a module.

You might want to drop this into some namespace such as ObjectRole::FriendApprover or if you know your name won’t clash with anything else, just go with FriendApprover.

Here’s a sample of what this might look like:

module FriendApprover
  def approve(friend_request)
    friend_request.approved = true
    friend_request.save
    increment_friends
    notify_new_buddy(friend_request.user_id)
  end

  def increment_friends
    friend_count += 1
    save
  end

  def notify_new_buddy(buddy_id)
    BuddyMailer.notify_buddy(buddy_id, "We're officially friends!")
  end
end

It doesn’t really matter what my sample code is, you get the picture: take the methods from your User class that do the approval and put them in your FriendApprover module.

The unit tests you had for these methods can now be simplified and applied to the module. The test just needs to check that some object agrees to the contract that the methods expect.

Step 4: Extend your user

Extend your user. Thats little “u” user. Your class doesn’t need this module, your object does.

Open up your controller where you usually call current_user.approve(friend_request) and change it to:

current_user.extend FriendApprover
current_user.approve(friend_request)

That’s it.

What you’ve just done

You’ve made your code more obvious.

It’s only in this context that a user needs to perform this action and this change has limited the scope of those methods to a very concrete area.

  • Your User class is smaller making your cognitive strain easier
  • Your User unit test is smaller
  • You have a clear separation of concerns with your new Object Role module
  • You’ve inherently made these methods reusable

But what about…

Yes, there’s more to it. Of course there’s more you can do, but with this simple concept you can do a lot of cleanup of both your code, and your ability to reason about your code.

What is DCI?

For now, I’ll leave the description of what DCI is to this article but I’ll be writing more about the concepts in Data Context and Interaction.

Clean Up Your Code

If you want more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.

Permalink… Comments: 16

How delete on a Hash works in Ruby... or how you thought it worked

by Jim Gay

Are you new to Ruby? Or were you once? Cool, me too.

When you first bump into Hash#delete you think you might be able to use it and just remove the items you don’t want, but it doesn’t work that way.

You think “I’ll just do this:”

hashy = {'this' => 'that', 'something' => 'else', 'secret' => 'I love NKOTB'}
hashy = hashy.delete('secret')

And bingo you’ve got your hash without that nasty secret. But what you actually found is that delete will remove that key from the hash and return the deleted value and not the altered hash.

Perhaps this is old news to you, but to new developers this is the moment where they say “Oh, right, it gives me the value that I don’t want.”

Well here’s a quick idea to give you what you want:

class Hash
  def except(which)
    self.tap{ |h| h.delete(which) }
  end
end

Now you can use the same code above but run:

hashy = {'this' => 'that', 'something' => 'else', 'secret' => 'I love NKOTB'}
hashy = hashy.except('secret')

That’ll return your hash without the part you want. That could be simplified to this:

hashy = {'this' => 'that', 'something' => 'else', 'secret' => 'I love NKOTB'}
hashy.except('secret')

because the delete will alter the hash. So you need to be aware that if you want to keep the original hash you’ll need to:

hashy = {'this' => 'that', 'something' => 'else', 'secret' => 'I love NKOTB'}
new_hash = hashy.dup.except('secret')

Enjoy!

Permalink… Comments: 0

1999 - 2012 © Saturn Flyer LLC 2127 S. Oxford St. Arlington, VA 22204

Call Jim Gay at 571 403 0338