Welcome to Giant Robots Smashing Into Other Giant Robots — a weblog about development, business, design and technology — written by thoughtbot.
it's all about behavior
How do you know when you need a class? What tells you, “I think I need to create another class”? The answer is: behavior. Not state, behavior.
Here’s an example:
1 2 |
class Order < ActiveRecord::Base end |
And our orders table:
orders (id, customer, item, price, created_on)
That customer column is a varchar that contains the customer’s name. I want to keep it simple, so an Order contains an item name and its total price.
Our client then requests a feature, they want to be able to read and update their existing customers’ names. Ok, no problem:
1 2 3 4 5 6 7 8 9 |
class Order < ActiveRecord::Base def self.find_all_customers end def self.update_customer(new_name, old_name) end end |
Now our Order class is dealing with customer related behavior. This will get ugly. Obviously, we’re missing a class: Customer:
1 2 3 4 5 6 7 8 9 10 11 |
class Order < ActiveRecord::Base belongs_to :customer end class Customer < ActiveRecord::Base has_many :orders end |
We get the #find and #update methods for free from ActiveRecord::Base.
And our orders table is now:
orders (id, item, price, created_on, customer_id)
And our new customers table is:
customers (id, name)
This is a very simple example. The key thing is that we didn’t introduce that Customer class until our client asked for a featured related to customers. We had new behavior and instead of making the Order class handle both order and customer responsibilities we introduced a new class. Now our responsibilities are cleanly separated and each of our classes are responsible for 1 thing, Order for orders and Customer for customers.
Another example:
1 2 |
class Company < ActiveRecord::Base end |
And our companies table:
companies (id, street, city, state)
Now our client asked that when viewing a Company, they’d like its address plotted on a Google map, no problem. Google maps, like any mapping API, needs the latitude and longitude of the address to plot. So we create a method on Company to handle that:
1 2 3 4 5 6 7 |
class Company < ActiveRecord::Base def geocode_address # return latitude and longitude end end |
Hmmm, why is the Company class doing address geocoding? Seems like I’m missing something. Of course, an Address class. Let’s change it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Company < ActiveRecord::Base composed_of :address, :mapping => [%w(street street), %w(city city), %w(state state)] end class Address attr_reader :street, :city, :state def initialize(street, city, state) @street, @city, @state = street, city, state end def geocode # return latitude and longitude end end |
That’s better. I don’t want my Company objects doing geocoding, that should be an Address object’s responsibility (even our method names are better company.geocode_address vs. company.address.geocode; 1 word, short and to the point).
Once again by reflecting on our class’s responsibilities we discovered missing key objects. By focusing on behavior we end up with small objects, each doing 1 responsibility and our responsibilities are distributed where they belong. When someone says “hey I think geocoding is not working right”, is a new developer going to look for the geocoding responsibility in the Company class? Of course not, the application is geocoding an address, so its logical to look for it in an Address class.
THIS IS OBJECT-ORIENTED (OO) PROGRAMMING. Objects are all about behavior, discovering objects and distributing responsibilies results in maintainable, easy to understand systems. Systems in which each object has very few methods, each method is short, each class has 1 responsibility and there is very little conditional logic. There are no ‘god’ classes – huge classes that do a large number of totally un-related responsibilities (like our Order handling Customer responsibilities or our Company doing geocoding). Many developers do not realize the fundamental concept of behavior in OO programming and as result spend their time progamming procedurally in languages that support classes and think they’re doing OO programming because they’re creating classes. They never make the ‘jump’ to objects and never get to realize that code can be a lot more elegant and programming can be a lot more enjoyable than wading through nested ‘if’ statements.
There is no problem or no domain complex enough that objects can not simplify. “It’s a complicated problem” is not an excuse for convoluted, long, conditional-logic laden code. You need to challenge yourself and think how to simplify the problem because your code is not going to be maintainable.
A common complaint among people new to objects is that its hard to track down where the actual work takes place. This is true, by distributing responsibilities, several messages may need to be sent to several different objects; this means you may have to open up more than 1 file. This is the beauty of objects, the hardest problem can be solved very easily by a group of objects each doing their 1 simple responsibility but collectively solving a complex problem.
it is not about state
State (i.e. instance variables, attributes, etc.) is not a reason to create a new class. For example:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Patient < ActiveRecord::Base end class Age attr_reader :age def initialize(age) @age = age end end |
And our patients table:
patients (id, name, age)
That Age class has no behavior, just state. This class is un-necessary and adds nothing to the system. These types of classes are usually referred to as “dumb data holders”, they’re just state and getters and setters – they have no interesting behavior.
This style of modeling is common among data-modelers/DBAs. What is in a database? State. That’s it, there’s no behavior in a database. There’s just tables and data. The database is usually controlled by a ‘god’ class called a database management system (DBMS). These are all the functions that handle query parsing and execution, transaction management, concurrency, etc. So a database is a collection of functions (DBMS) reading and writing a bunch of data. This sounds familiar. It’s called procedural programming, the kind you first learned in school using C or BASIC.
So when modeling a system a data-modeler/DBA will probably first attempt to be really DRY and normalize the schema. The schema for our above Order example becomes:
orders (id, item, price, created_on, customer_id) customers (id, name, address_id) addresses (id, street, city_id) cities (id, name, state_id) states (id, name)
Then they’ll turn each of those into classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class Order < ActiveRecord::Base belongs_to :customer end class Customer < ActiveRecord::Base has_many :orders belongs_to :address end class Address < ActiveRecord::Base belongs_to :city def geocode # return latitude and longitude end end class City < ActiveRecord::Base belongs_to :state end class State < ActiveRecord::Base end |
Customer, Order and Address are nice and worth having, however City and State add nothing and are simply “dumb data holders”. “But wait City and State have all the interesting CRUD behavior they get for free from ActiveRecord::Base, they’re not dumb data holders”, they do have the interesting CRUD behavior but our client only asked to geocode an address, they did not ask to CRUD cities and states; therefore its un-necessary.
Data modeling and object modeling are 2 different things. Data modeling comes from the database world and tends to focus on state. Object modeling focuses on behavior.
Although these examples are in Ruby, the language makes no difference. Object modeling is object modeling, if a language supports classes then these examples would be just about the same in any language.
Only create classes when new behavior is asked for and there’s currently no logical place for it. Do not cram more and more behaviors into existing classes, soon these classes will be very large, with many methods and wildly different responsibilities; these ‘god’ classes should be a clear indication that thinking about behavior (i.e. thinking in objects) is not happening.
but what about join models in Rails
So I said not to create classes that don’t have any interesting behavior i.e. “dumb data holders”, so you explain the following example to me:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class User < ActiveRecord::Base has_many :memberships end class Membership < ActiveRecord::Base belongs_to :user belongs_to :group end class Group < ActiveRecord::Base has_many :memberships end |
And our memberships table:
memberships (id, user_id, group_id, active)
active is a boolean allowing a User’s Membership to be activated/deactivated. It’s 1 attribute, no interesting behavior and there’s a class for it. I would argue that this is a limitation of Rails and its inability to update attributes in a join table i.e. if we had a groups_users join table instead of a Membership model. This is a trade-off Rails developers have to deal with.
About this entry
You're reading an entry on GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS, the company weblog of thoughtbot, inc.
- Author:
- Jared Carroll
- Published:
- April 14th 04:25 PM
- Updated:
- September 30th 09:57 AM
- Sections:
- Development
thoughtbot is hiring
We are hiring web developers and web designers in both Boston and New York, NY.
What are we up to?
We built Shoulda, an eclectic set of additions to Test::Unit; Paperclip to manage uploaded files without hassle; factory_girl a replacement for Rails fixtures; Jester, a REST/ActiveResource client written in Javascript, and Squirrel, an enhancement for ActiveRecord's find syntax; — amongst some other projects.

Chad (President) and Jon (CTO) co-authored a technical book titled Pro Active Record: Databases with Ruby and Rails, which explores the ins and outs of the ActiveRecord ruby library. You can buy it today at Amazon.com.
About thoughtbot, inc.
We are a small web application development consulting business, with offices in Boston, MA and New York, NY. If you're looking to find a team for your next web development project or your new web application — get in touch.
5 comments
Jump to comment form