Class A and B are identical:
class A < ActiveRecord::Base
def foo
puts "foo"
end
end
class B < ActiveRecord::Base
def foo
puts "foo"
end
end
What's the difference between refactoring like this with a base class:
class Base < ActiveRecord::Base
def foo
puts "foo"
end
end
class A < Base
end
class B < Base
end
versus like this using a base module:
module Base
def foo
puts "foo"
end
end
class A < ActiveRecord::Base
include Base
end
class B < ActiveRecord::Base
include Base
end
Is one way preferable over another?
There's a fundamental difference between those two methods that all the other answers are missing, and that's rails' implementation of STIs (Single Table Inheritance):
http://api.rubyonrails.org/classes/ActiveRecord/Base.html (Find the "Single Table Inheritance" section)
Basically, if you refactor your Base class like this:
class Base < ActiveRecord::Base
def foo
puts "foo"
end
end
class A < Base
end
class B < Base
end
Then, you are supposed to have a database table called "bases", with a column called "type", which should have a value of "A" or "B". The columns on this table will be the same across all your models, and if you have a column that belongs to only one of the models, your "bases" table will be denormalized.
Whereas, if you refactor your Base class like this:
Module Base
def foo
puts "foo"
end
end
class A < ActiveRecord::Base
include Base
end
class B < ActiveRecord::Base
include Base
end
Then there will be no table "bases". Instead, there will be a table "as" and a table "bs". If they have the same attributes, the columns will have to be duplicated across both tables, but if there are differences, they won't be denomarlized.
So, if one is preferable over the other, yes, but that's specific to your application. As a rule of thumb, if they have the exact same properties or a big overlap, use STI (1st example), else, use Modules (2nd example).
Both of these methods will work. When deciding to use a module or a class, the question I have is does the class fit into the object hierarchy, or are these just methods I am looking to reuse. If I am just trying to factor out common code for DRY reasons, that sounds like a module. If there really is a class that fits into the hierarchy that makes sense on its own, I use a class.
Coming from a Java background, it is refreshing I can choose to make these decisions.
You have more flexibility with the module. The module's intent is to span across different types of classes. With the other method you are locking yourself into Base. Other than that, there isn't much difference.
Ruby's answer to multiple inheritance is mixins. Since your classes are already inheriting from Rails specific classes, they can no longer inherit from your custom classes.
So your choice is to chain together in a long chain, or use a mixin which is much cleaner, and easier to understand.
The module gives you more flexibility in that 1) you can only inherit from one class, but you can include multiple modules, and 2) you can't inherit from a base class without inheriting its superclasses, but you can include a module all by itself (e.g. you might want to add the "foo" method to another class that isn't an active record model).
Another difference is that within the methods in the class Base you could call things from ActiveRecord::Base, but you couldn't do that from the module.
It depends on what you are really trying to do.
- Overriding or adding methods to ActiveRecord::Base: Do this if you want every ActiveRecord model in your app to
respond_to
foo
.
- Subclass ActiveRecord::Base, and have every model inherit from your subclass: Achieves the same as 1, but every model in your app needs to extend an unconventional class, so why go through the trouble.
- include module: This works great if only some number of models need access to
foo
. This is pretty much what all those acts_as_<whatever>
plugins do.
Bottom line, if you want every single model to have a different behavior to what ActiveRecord::Base already provides, use option 1. If only a handful of your models require the behavior, create a module and include it in your models (option 3).