Inheritance vs. Modules vs. Composition (In the context of Rails)
Anyone tried out Rails 4? All sorts of cool things. Turbolinks. HStore. Russian Doll Caching. But while you were busy hyperventilating over whether turbolinks would magically convert your once cutting edge web 2.0 app into a, now much more de mode, single page app, the Rails team snuck one weird little change in under our noses. They added a "models/concerns" and a "controllers/concerns" directory to encourage your pretty little faces to do a little refactoring.
1) What on earth is a concern? See here. It's basically a module with some funny syntax that does some fancy, but most likely unnecessary, dependency management.
2) Why do you care? Well, this is as good of time as any to talk about where code belongs in our Rails apps and when we should include modules.
So you're working on a Rails app. As you work you repeat the Rails mantra to yourself over and over, "Fat Models, Skinny Controllers" and you've even done a pretty good job of sticking to it. Well done! Yet... Things are starting to get a little bit messy. Your models are getting too fat, I'm talking John Pinette fat, and you are beginning to suspect you've been lied to and there is a little bit more to this whole software design thing.
Fear not! Inheritance, inclusion and composition are here to save you! And yes, I know, this has been written about before. The thing is that, for whatever reason, the examples used always involve dogs, cats, Pomeranians and your cousin. In the real world I have never actually written "class Dog". This is a real world example of how to use these concepts. Real world, like straight from the street.
Lets pretend you're building a website called tumblr_ripoff.com. As you may have guessed, it's a ripoff of tumblr. This website has the following requirements
1) Users can post picture or text posts
2) A certain subset of users are notified when a post is added or a comment is made on a post
3) The notifications are stored with relation to the post and the users who were notified should be findable by post
To this end you have created the following ActiveRecord class:
https://gist.github.com/joeljackson/5003095
TBH, it's not half bad, but the road to hell is filled with good intentions
Lets look at the problems with this class
1) You probably also have a class called text which belongs to user (And might do some similar blog posty things) which means repeated code. Any time you change one you have to change the other.
2) Comments also have notifications, so we'll need the notification code there too, once again repeated code that needs to exist in two places.
3) Why does a photo know anything at all about how to send notifications? It's an ActiveRecord class, it's responsibility is to expose data, allow saving of data and define relationships. (God, the poor thing has enough to do already.)
So let's fix this up, each of these problems is solvable by the respective technique mentioned above.
1) Let's create a post class that photo can inherit from to share logic with other types of posts. (Aka. text)
2) Let's create a notifiable module so that it can be mixed into non-post type classes which need notifications. (Aka. comments)
3) Lastly let's make a class that knows how to make notifiable objects then send the notification.
https://gist.github.com/joeljackson/5003155
Now... What have we learned?
When to use inheritance: Pretty rarely to be honest. If the bulk of the responsibility of two classes are the same, and I mean like really most, then it should inherit.
When to use inclusion: Well, this is where "concerns" come in. Just remember, moving code to another file does help share code between your classes, and it does make the class look smaller, BUT once its included in the class, in reality the class is not any smaller. Include code if it falls within the responsibility of a class, and only then and if it needs to be shared between two files.
When to use composition: In short, try and prefer composition. A few strategies? Do you have methods on your models that modify them? Consider moving them into classes that change the models, particularly if they involve multiple classes. Do you have methods all throughout your models to display things on the screen? Move them into decorators. Do you have methods on your models to compare values? Move the values into value classes.
At some point later I'll talk about these and maybe other refactoring methods. Until then take this away from this post, keep your classes small, keep your methods small, and favor composition over inclusion.
Now go forth, refactor and build!