Saturday, January 22, 2011

Dear Lazyweb: How can I put many classes in one file in a Rails app?

Since yesterday, I asked many of my Rails friends the same question:

How can I put many classes in one file in a Rails app?

Unfortunately, I haven't got any answer that would work.

I try to solve this problem every 6 months or so. It's not a thing that I can't live without, but it would help my workflow a lot.

One use case is to put 4-5 model classes in one file. If each class is only 15 lines and they're all conceptually related then why not to put them together?

Another use case is to keep related controllers in one file.

So far I tried a combination of require, require_dependency and autoload and I tried to put them in config/initializers, environment.rb or in application_controller. Each resulted in some kind of error. Either  one of the class was not found, or Rails complained with "A copy of ApplicationController has been removed from the module tree but is still active!".

I tried all of it with Rails 2.3.5 in development mode, however a solution that works with Rails 3 would also be great!

Can anyone help?

Monday, January 17, 2011

Code reuse in Ruby, why composition is so rare?

I've been researching topics related to code reuse and object oriented design in Ruby apps recently.

There are three popular ways of reusing code in the Ruby community:

  1. Inheritance
  2. Modules/mixins
  3. Composition
1. Inheritance

class User < ActiveRecord::Base
  def change_login(new_login)
    login = new_login
It's very popular, mostly because this is the default way you can use ActiveRecord in Rails applications. To be honest, I don't like inheritance. It doesn't fit into my model of thinking about object oriented design. That's a topic for another post, though.
Inheritance let's us access methods available in the base class. The limitation is that a class can only inherit from one base class. Python is better here, allowing multiple inheritance. I don't like inheritance, both from a theoretical and practical point of view.
2. Modules/mixins
module Buyer
  def add_to_cart(product_id, quantity)
    cart.add(find_product(product_id), quantity))

class User
  include Buyer

#usage[:product_id, params[:quantity])
A module is basically a set of methods. You can't instantiate a module and that's the main reason I'm not a big fan of modules. 
You can include as many modules as you want into one class. That's kind of a work around for the lack of multiple inheritance. Modules are extremely popular in the Ruby community - just look at the Rails source code.
One problem with modules is that in order to unit test them you either test every single method or you need to create a dummy class that includes the module and then you test an object of this class. 
Another problem (in my opinion) is that modules introduce hidden dependencies. You can't easily create an object and replace the module with a mock in order to test the collaboration.
The good thing about modules is that it's very easy to use. When you see a common set of methods in two classes, you extract it to a module and the duplication disappears.
My biggest concern with modules is that they break the concept of "Everything is an object", so the theory doesn't fit well with my understanding of object orientation. The practical usage however is very useful, you just extract methods to a module and then you can include it in many classes.
3. Composition
class User
  attr_accessor :buyer
  def add_to_cart(product_id, quantity)
    buyer.add_to_cart(product_id, quantity)
Composition feels most right to me.
You can compose many objects in one place and delegate the work that you specify. Composition uses classes/objects so you can easily test all the classes separately.
When you want to test a class that uses composition you can easily replace one of the collaborators with a mock and just test the communication.
Composition is perfect for a good OO design, but it's very rare to see it in Ruby/Rails projects and here is my question: Is composition so rare because it's hard to use or is there any other reason?

Update: Added code examples, changed modules to modules/mixins

Tuesday, January 4, 2011

Testing emails with Rails, Steak and email_spec

We've started experimenting with switching from Cucumber to Steak.
Here is how you can use email_spec inside Steak scenarios:

gem 'email_spec', "1.1.1"

require "email_spec"

feature "Commenting" do
  include EmailSpec::Helpers
  scenario "email notification about a comment" do
    find_email(email, :with_text => text).should_not == nil