Blog

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 DCI and cleaning up your objects, classes, and code, then drop your email here. This isn't a junk mailing list, it's purely for information about making 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 DCI and cleaning up your objects, classes, and code, then drop your email here. This isn't a junk mailing list, it's purely for information about making 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

Always check in schema.rb

by Jim Gay

Sometimes developers are unsure about Rails' schema.rb file and whether or not it should be checked in to source control. The answer is simple: yes.

As you are familiar, Rails encourages developers to alter the database state through migrations. One migration adds some table, and the following migrations add more, change something, or remove something. And as you well know, this is a great way to keep developers in sync with each other’s changes to the database structure.

With each migration you add and subsequently run, your schema.rb file is altered to represent the final state of your database. Seemingly because this is automatically done for you, some developers think this file should be left out of your project; it should not.

Don’t store it just because David Heinemeier Hansson said you should

First things first, this is a terrible reason to add schema.rb to your source control. Often, in an argument about something like this it’s easy to look to an authority, find a relevant quote and say “See, he said so.” Yes, there is a commit in Rails where he suggests you store schema.rb, but this was done to end an argument, it is not an argument itself.

Authorities are authorities because of their deep understanding of something, not because they command you to do things. But often people will allow authorities to command them to do things because they are authorities. David Heinemeier Hansson is an authority on Rails and his control of the project allowed him to make that commit and force the direction of Rails. If you agree with this commit, agree because of the reasoning, not because DHH said so.

Don’t store generated files, except for schema.rb

As a general rule of thumb in compiled languages, you don’t store generated files. The reason for this is not because an authority said so or because many consider it to be a good rule to follow, but because you want your code to generate the proper files for correct execution of the application.

If you add your generated files to your source control you may in the future find that you’ve altered your code in a way that prevents a particular file from being generated at all. But if that file exists regardless of the bug you introduced you’re likely to find errors in the application’s behavior. Tracking that down may be tough until you realize that you’ve checked-in your generated files.

Additionally, if you store generated file X in source control but change the code to instead generate file Y, you’ll leave unused code in your project possibly giving other developers the misconception that it is important in some way. Worse yet, depending on your application the mere presence of file X could affect the execution of your application. I’ve heard things like this appropriately called “code turds.” If it’s not going to be used, it shouldn’t be there.

Regardless of all of this, schema.rb doesn’t affect the execution of your application so there is no danger in storing it in source control. Avoiding generated files in source control is a good rule to follow, but knowing when to break that rule is important too. Leave the code turds out and schema.rb in.

File churn in source control is not an issue with schema.rb

If you are concerned about the amount of churn (that is, frequent changes) you have with the development of your schema.rb file, then you probably are actively developing your database, or you have a problem elsewhere and need to get your team to work on a clearer picture of what your database should do.

Churn in schema.rb can actually be valuable, however. It’s easy to overlook multiple migration files changing things in your database, but in reviewing code commits the amount of churn in an area of schema.rb can reveal problems with your development team.

Conflicts in schema.rb are valuable

If your team is making dueling commits over the purpose of a database field, your problem is not with resolving conflicts in schema.rb, it’s with resolving conflicts between and among your developers about the structure of your database. Keeping schema.rb in source control will help to reveal this.

Before you commit any of your files to your master/production/whatever branch, you should

  1. run your tests
  2. pull down and merge any other changes
  3. re-run your migrations if any new ones were pulled down
  4. re-run your tests
  5. commit/push your changes (including schema.rb)

Following those steps ensures that your tests are run against the database that everyone else will have and ensures that the schema.rb file you commit is the latest and most up-to-date. Maybe you don’t want to run your tests twice, that’s fine, but be sure to run them after you pull down the latest code for the project and merge your changes.

Store it because schema.rb is a representation of the current state of the database

At any point in development, you can look at schema.rb to give you an accurate representation of your database structure. Other developers can checkout the project and run rake db:schema:load and almost instantly they are ready to develop (barring any sample data they need to load).

For a new team member, there is absolutely no need to change or rename anything in the database as may be done with migrations. Your ultimate goal to begin development is to have a database in a desired state. It doesn’t matter a bit if a field name was changed from “login” to “username” or if your :text field was once a :string field in your first migration. For your database structure, the final state is all that matters, and schema.rb does this for you.

Application code mixed with migrations can cause problems

Sometimes you may have a need to alter your structure and do something like ProductType.reset_column_information in a migration and add some data. For now, I’ll avoid the discussion on whether or not that’s appropriate, but if you are doing this a problem may arise when at some point in time you remove the ProductType model, or rename it to Category. In that case, you’ll need to go back and maintain your migrations… read that again you’ll need to maintain your migrations. This is a pointless exercise: use schema.rb.

This is also an example of why you shouldn’t mix database seeding and migrations.

Migrations are slow

Relying on migrations to get up to speed for development is slow and gets slower as your application’s database changes and as the number of your migrations increases. Because schema.rb skips over changes and represents the final (and desired) state of your database, it’s fast.

Your blank slate production database only needs the final state

A production database only needs the final state assuming, of course, that the database is a blank slate. Running all of the migrations in production to get where schema.rb would be is not necessary. schema.rb weeds out all of the changes for you and gets the job done quickly.

“But what about a database that already has a structure?” you may ask. Then all you need is the migrations that haven’t been run; you’ll never run rake db:schema:load on an existing production database.

Keep schema.rb with your code

schema.rb in your project’s source control adds value for all developers. It loads the desired state quickly, it gives you a clear representation of your database structure, it reveals conflicts and unnecessary churn, it doesn’t affect the execution of your application. Add schema.rb to your source control and add value for your team.

Permalink… Comments: 6

Ruby Metaprogramming is Awesome

by Jim Gay

If you’ve come to read about how wrong I am about metaprogramming, don’t worry, I’m sure I’ll follow-up with a post about how bad it is to do metaprogramming and how it causes you real, physical pain.

If, however, you’ve come to find out about how awesome metaprogramming is, then you can tell by the title of this article that you are in the right place! Now that’s authoritative!

The best part about this article is that it is for you! That’s right, newbie. I’m not going in-depth, I’m merely going to discuss how a dash of metaprogramming solved an annoying problem.

I do a lot of work with Radiant. And Radiant manages pages well, but there are plenty of extensions that allow you to do other things. You can, for example, add the ability to upload files and whatnot. Or you can add the ability to edit stylesheets and javascripts.

That’s pretty cool. Radiant does a good job of managing pages in the pages table. But stylesheets and javascripts are not pages. Some of the samples that Radiant bundles have stylesheets as regular pages in the page tree and are tied to an almost blank layout that sets a particular content-type like text/css. Yuck. So Chris Parrish, the author of SNS, added text_assets to handle other types of content like stylesheets and javascripts.

One of his reasons for creating this extension is the weirdness of having a stylesheet in your page tree: “No more confused users wonder what those fancy pages are.” Saaaweet! Thanks, Chris.

All is well until you find out that the SNS extension doesn’t let you use your typical Radius tags within javascripts or stylesheets like:

#something { background: url('< r:assets:url title="my_image" />'); }

Nooooooo! Why, God, why!?!

Well, with SNS, you’re dealing with a TextAsset and not a Page. But if you’re familiar with Radiant, you know that page tags aren’t only used on pages, they’re used on snippets and layouts too. So what’s the deal with that?

All of the radius tags that you use in Radiant are evaluated in the context of a single page. Pages, snippets, and layouts are all used in reference to rendering a Page. But SNS renders a TextAsset and as a result, it doesn’t have any of the fancy tags added. Afterall, it’s class TextAsset < ActiveRecord::Base and not class TextAsset < Page.

You read that right: text assets are not pages. Bummer, dude.

Well, what if you could include the same modules as are included in the Page model? Then you could use those tags in your stylesheets.

Fire up your console because you’re about to see how to do it. In a rails console (for a Radiant instance), try this:

Page.included_modules

And you’ll get back a bunch that don’t matter for you. Let’s simplify that:

>> Page.included_modules.select{|m| m.to_s =~ /Tags$/}
=> [TextileTags, SmartyPantsTags, MarkdownTags, StandardTags]

Awesome. All we need to do is include all of those modules into TextAsset and we’re done! Almost.

Since we’re dealing with the SNS extension, you’ll also see that it includes Sns::PageTags which adds tags to output stylesheets and javascripts. Those, we don’t need. So we can filter them out and include them into TextAsset:

TextAsset.class_eval {
  Page.included_modules.select{|m| m.to_s =~ /Tags$/}.reject{|m| m == Sns::PageTags }.each do |mod|
    include mod
  end
}

Bingo! Now we’re in business. Except for the fact that it doesn’t work.

So we need to update the context in which these tags are evaluated. Since they are all written expecting to be included in a Page model, the global variables for the tags need to have knowledge of a page.

TextAssetContext.class_eval {
  def initialize(text_asset)
    super()
    globals.page = text_asset # This is the important line that adds the hook for all existing tags for pages.
    globals.text_asset = text_asset
    text_asset.tags.each do |name|
      define_tag(name) { |tag_binding| text_asset.render_tag(name, tag_binding) }
    end
  end
}

There you have it. All it took was some class_eval and some looping over included_modules.

page tags in stylesheets And now you’ll have happy users. Because even though Chris solved the problem of weird stylesheet pages in the page tree, the solution introduced a problem where editors of the site expect the radius tags to simply work everywhere. Now they do.

The above code assumes that the only valuable tags are those whose modules have “Tags” at the end of their names. That’s a reasonable expectation, but we can go even further by inspecting the included modules of the included modules to see if Radiant::Taggable is there:

TextAsset.class_eval {
  Page.included_modules.reject{|m| m == Sns::PageTags }.each do |mod|
    if mod.included_modules.any? {|inc| inc == Radiant::Taggable }
      include mod
    end
  end
}

So go ahead and install the page attachments, or paperclipped, or whatever extension with sns and just do script/extension install sns_page_hook, or better yet: gem install radiant-sns_page_hook-extension. Please let me know if you find any problems with it.

Permalink… Comments: 7

Radiant 0.9 Official Release

by Jim Gay

Finally, after a long, long wait: 0.9 is out.

It’s my first non prerelease gem to push out for Radiant but I’m proud of all the work that I and many, many others did to make it happen. John Long put the UI updates together with the help of a good group. Keith Bingman managed all that went into internationalization and the many contributors that made it happen. William Ross added features such as pagination in the standard Radius tags, and pagination in the admin screens. Josh French gave us the ability to load extensions from gems. John Muhl, our newest core team member increased the general quality in testing for bugs and improving the overall quality of the application.

There are many contributors, I’m just listing some from the top of my head. Thank you to everyone of the contributors, be you bug reporters, committers, or just users who discuss your needs on the mailing list.

While this release is an exciting improvement, I’m even more excited about the future of the project. More to come…

Permalink… Comments: 0

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

Call Jim Gay at 571 403 0338