Ruby / Classes, objects, modules, and methods
From WhyNotWiki
[edit] Object-orientedness
[edit] Object-orientedness: Classes and objects
[edit] Class hierarchy of fundamental classes
I think it's pretty important to get this much clear:
irb -> Class.superclass
=> Module
irb -> Module.superclass
=> Object
irb -> Object.superclass
=> nil
irb -> Class <= Module and Module <= Object
=> true
Class is subclass of Module
I have frequently been confused because I forgot that Class is a subclass of Module. So when I find some method in Module, I fail to realize that I can use that same method in Class as well!
So don't be alarmed, for example, that you can't find Class.class_method_set in the rdoc. Instead, you'll find that there is Module.class_method_set. You can still do this:
irb -> String.send(:class_variable_set, :@@what, 3)
=> 3
Okay, I'm confused. String.superclass is Object, not Class. So how does it inherit class_variable_set from Module?? (Or does it not?)
[edit] Class is the only known subclass of Module
If there is any other subclass of Module, please tell me!!
irb -> class Example end
=> nil
irb -> Example.class <= Module
=> true
irb -> Example <= Module
=> nil
irb -> Example.class
=> Class
irb -> "Another".class
=> String
irb -> "Another".class <= Module
=> nil
So you can check if an argument is either a Class or a Module by asking arg.class <= Module.
I think that's exactly what they were doing in the Pickaxe Ruby book, p. 389 in this example:
class Module
...
def Module::doc(aClass)
# If we're passed a class or module, convert to string
aClass = aClass.name if aClass.class <= Module
@@docs[aClass]
end
end
puts Module::doc(Example) # Example is a class, so aClass.class is Class, and Class <= Module
puts Module::doc("Another") # "Another" is not a class and is not a module, so it fails that test
(I wonder why they didn't just check aClass.is_a?(Class) or aClass.is_a?(Module)...wouldn't that have been easier to read?)
[edit] Reopening a class that has/had a superclass specified
Which of these is legal?:
class B < A end class B < C end # no! class B < A end class B end class B end class B < A end
[edit] Inheriting from a ... function (dynamically-determined class)???
/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/rails_generator/commands.rb
class Base < DelegateClass(Rails::Generator::Base)
[edit] Methods
[edit] How do you call a class method?
[edit] from an instance method of the same class
Here's one way I've found that works:
class Klass
def my_instance_method
self.class.my_class_method
end
I like it only marginally better than:
class Klass
def my_instance_method
MyClass.my_class_method
end
[edit] from another class method of the same class
class Klass
def self.my_instance_method
self.my_class_method
end
[edit] Overriding methods
"Inside a subclass, calling super is sufficient by itself — you don’t have to specify the method name or arguments. It figures them out automagically from the current method and arguments. It can be overridden if necessary." (http://woss.name/2006/05/07/notes-from-a-rails-course/)
"Mixins (using include) behave as if they are inherited methods. So you can override them and call super and so on, as usual." (http://woss.name/2006/05/07/notes-from-a-rails-course/)
[edit] [Obscure useless tip] Can't set self= ? Try replace.
Can't do:
class Array
def stupid
self = [self[0]]
end
end
Will give "Can't change the value of self"
Instead:
class Array
def stupid
self.replace [self[0]]
end
end
replace doesn't necessarily exist in every class, but it exists in Array, Hash, String, ...
[edit] Singleton classes: Object-specific classes
See Pickaxe book, Chapter 24. Classes and Objects: How Classes and Objects Interact, p. 382-383
Ruby allows you to create a class tied to a particular object. In the following example, we create two String objects. We then associate an anonymous class with one of them, overriding one of the methods in the object's base class and adding a new method.
a = "hello" b = a.dup class <<a def to_s "The value is '#{self}" end #... end a.to_s #=> "The value is 'hello'" [uses to_s from anonymous class that we just created for a] b.to_s #=> "hello" [uses to_s from String class]This example uses the
class <<objnotation, which basically says "build me a new class just for the object obj." We could also have written it as#... def a.to_s #... end #...The effect is the same in both cases: a class is added to the object
a. This gives us a strong hint about the Ruby implementation: a virtual class is created and inserted asa's direct class.a's original class,String, is made this virtual class's superclass. [...]
[edit] Virtual classes / meta classes / singleton classes
Technically, according to the Pickaxe book p. 380-382, each object has a meta/virtual/singleton class associated with it. I'm kind of confused by what difference (if any) there exists between those 3 terms.
Pickaxe p. 382 (Matz):
- "Every object in Ruby has its own attributes (methods, constants, and so on) that in other languages are held by classes. It's just like each object having its own class."
- "To handle per-object attributes, Ruby provides a classlike something for each object that is sometimes called a singleton class."
[edit] "Singleton methods": Adding methods to objects as opposed to classes
When would you want to do that?
- Whenever you only plan on calling the method on that particular method...
- When you don't want to clutter up your base classes with your ["domain-specific"] methods...
[edit] [Real-world sightings (category)]
[edit] TkVariable example
Pickaxe p. ??:
require 'tk'
...
checked = TkVariable.new
def checked.status
value == "1" ? "Yes" : "No"
end
status = TkLabel.new do
text checked.status
...
end
...
TkCheckButton.new do
variable checked
...
end
TkButton.new do
text "Show status"
command { status.ext(checked.status) }
end
In this example, we add behavior to a single TkVariable object: we add a status method to the checked object. Why do we do that? Because we want the text of a label to be tied to the current result of that method. And we don't want the label to display the non-user-friendly valus "1" or "0"; rather, we want to show something user friendly like "Yes" or "No".
We only use it for this one object, so there's no use in re-opening the TkVariable class and adding the method to all TkVariable objects. Nor do we really want to go through all the work of creating a special container class just to contain a TkVariable object and provide a status method.
[edit] Mocha::Expectation#with
http://mocha.rubyforge.org/classes/Mocha/Expectation.html#M000017
# File lib/mocha/expectation.rb, line 169
169: def with(*arguments, ¶meter_block)
170: @parameters, @parameter_block = arguments, parameter_block
171: class << @parameters; def to_s; join(', '); end; end
172: self
173: end
Here a to_s method is added to a singleton class created just for the @parameters object.
[edit] Problem: you have a method that you want available to Strings objects used by your class but you don't want to litter the base String class with them.
(Very often in the context of: Adding a presentation layer to your data layer.)
Subclassing won't get you anywhere, since you can't control the type of objects returned by other methods (they'll return Strings, not "CustomStrings" (or whatever), so we'll just have to deal with it.)
Also, we don't want to go the procedural, non-OOP way of just calling a global/class-method...
number_to_currency(1234567890.50) => $1,234,567,890.50
colorize_svn_diff(svn("diff file1"))
Yuck.
We want it to be object-oriented. So it should look closer to this (although not necessarily exactly), with a method being called on the object, rather than the object being passed to some method from another class:
(1234567890.50).to_currency
svn("diff file1").colorize
Rails has a bunch of "helper" methods like this that (I believe) cause your view code to be very un-object-oriented. (See f.i. NumberHelper)
[edit] Solution 1: Add these methods to the base class.
Add the methods directly to Float or to String.
Sure that would work...some of the time. But it would be very prone to name collisions. Other libraries that you're using might have had the same idea about adding methods with those names to Float or String. Even within your own application, you may want to have more than one method called "to_currency" or "colorize". You may want to display currencies one way in this report or quite another way over somewhere else.
[edit] Solution 2: Singleton methods.
Attempt 1
irb -> def svn(args); "output from `#{args}`"; end
=> nil
irb -> module Svn; module Diff; def colorize; self + " (colorized)"; end; end; end
=> nil
irb -> svn("diff file1").extend(Svn::Diff).colorize
=> "output from `diff file1` (colorized)"
[edit] Attempt 2: singleton_send: Create a singleton class with the method you need and then call it, with a single command
irb -> class Object; def singleton_send(mod, *args); self.extend(mod); self.send(*args); end; end
=> nil
irb -> svn("diff file1").singleton_send(Svn::Diff, :colorize)
=> "output from `diff file1` (colorized)"
Published at http://qualitysmithext.rubyforge.org/classes/Object.html#M000027
[edit] Duck typing
Pickaxe book p. 367: "In Ruby, the class is never (OK, almost never) the type. Instead, the type of an object is defined more by what the object can do. In Ruby, we call this duck typing. If an object walks like a duck and talks like a duck, then the interpreter is happy to treat it as if it were a duck."
[edit] [Example (category)] StringIO
Can use it interchangeably any place that expects a file IO object! It doesn't care! As long as it responds to all the same messages, the users of the object don't care.
[edit] [Monkey patching (category)]
Bruce Tate (2007-03-13). Crossing borders: Extensions in Rails: The anatomy of an acts_as plug-in (http://www-128.ibm.com/developerworks/java/library/j-cb03137/index.html).
([reopening classes (category)])(Warning)
In Ruby, you can open up any class and redefine it quickly.
This capability is one of Ruby's greatest strengths because of the increased flexibility. But the capability is a weakness too. Too much flexibility can lead to code that's difficult to understand and maintain, so be careful.
[edit] [Monkey patching (category)]: If you override attribute my_attr, will it also override my_attr= for you?
alias_method :date_placed, :creation_date
Will that also override :date_placed= as well?
Nope, you need to override that separately:
alias_method :date_placed=, :creation_date=
Is there/should there be another method that does both of these for you? Hmm...
[edit] Object-orientedness: Modules and mixins
[edit] How do I define class methods in my module that should be mixed into the includer class?
How not to do it:
module ActsAsCat
def self.acts_as_cat(a)
...
end
end
class ActiveRecord::Base
include ActsAsCat
end
That doesn't work because when you include ActsAsCat, it although it mixes in all constants, variables, and methods from ActsAsCat into ActiveRecord::Base, acts_as_cat is defined as a class method, not a normal method. Class methods are not mixed in. (This is unfortunate and unintuitive in my opinion. I wonder why it can't be fixed by a future version of Ruby. Discuss)
This works:
module ActsAsCat
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def acts_as_cat(a)
...
end
end
end
class ActiveRecord::Base
include ActsAsCat
end
What does it do? base.extend mixes in all the instance methods of ActsAsCat::ClassMethods (acts_as_cat in this case) are mixed into the class represented by base (in this case, ActiveRecord::Base). So they become class methods.
Actually, an easier way to make class methods be mixed in when including is to use Facets' class_extension method.
[edit] append_features or included?
You can also define append_features instead of included. What's the difference? Well, the Ruby docs say ...
- (http://ruby-doc.org/core/classes/Module.html#M000743) included(mod) is a "Callback invoked whenever the receiver is included in another module or class. This should be used in preference to Module.append_features if your code wants to perform some action when a module is included in another."
- (http://ruby-doc.org/core/classes/Module.html#M000719) append_features(mod): "When this module is included in another, Ruby calls append_features in this module, passing it the receiving module in mod. Ruby’s default implementation is to add the constants, methods, and module variables of this module to mod if this module has not already been added to mod or one of its ancestors."
This seems like a more fitting place to mix in ClassMethods.
- I think we should heed the manual's advice and use "included" "in preference to Module.append_features". Why? (1) It's a nicer-sounding, more intuitive name. (2) The manual says to. (3) Because then we don't have to remember to call super like we would if we overrode append_features.
[edit] What does it mean to define a class/module within a class?
Also: [the difference between extend/include (category)] [extended/included (category)]
And why would you want to do such a thing?
class Command
def initialize(subcommand)
self.extend(self.class.const_get(:"Subcommand#{subcommand}"))
end
module SubcommandA
def self.extended(base)
puts "#{self.name} got extended into #{base.class.name}" end
def self.included(base)
puts "#{self.name} got included into #{base.class.name}" end
end
class SubcommandC
def initialize
puts "SubcommandC#initialize"
end
end
end
puts Module.constants.grep(/^Command/)
puts Command.constants.grep(/^Sub/).sort.map{|a| '\\ ' + a}
puts
puts "Including/extending at outermost level"
include Command::SubcommandA
self.extend Command::SubcommandA
puts
puts "Command.new(:A)"
Command.new(:A)
puts
puts "What about the *class* within a class?"
Command::SubcommandC.new
It looks like the class SubcommandC defined within the class Command behaves just as if it had been defined within a module named Command. In other words, our class behaved like a module.
That sort of makes sense that a class can be used like a module. It (Class) is, after all, a subclass of Module. (I'm not sure why I was so surprised to see modules defined within classes then!)
irb -> Class.superclass
=> Module
That's actually really nice that Ruby gives you that flexibility. It doesn't arbitrarily say "No, you can't put classes inside of classes! You can only put them in modules." It helps keep things clean because you can use your class for namespacing as well as for data storage (instance variables) and behavior (methods).
[edit] What is the outermost scope, actually? What is the receiver then? To what does an include apply?
If every method call has a receiver (an object), then what is the receiver if you just do a "bare word" (not qualified at all) method, like this?:
some_method
irb will tell that it is operating on an instance of (and I don't mean subclass of) Object:
irb -> self
=> main
irb -> self.class
=> Object
So any method calls you make will be directed to that Object object (or Kernel, but that's another topic) unless you tell it otherwise.
You can tell irb to point to a different object, by the way, ...
irb -> irb s
Welcome!
irb#1 -> self
=> "test"
irb#1 -> self[0..0]
=> "t"
Okay, that's nice and all, but what does it mean for me? Well, it would be nice if we understood what happened when we did include at the outer scope.
module SubcommandA
def self.extended(base)
puts "#{self.name} got extended into #{base.class.name}" end
def self.included(base)
puts "#{self.name} got included into #{base.class.name}" end
end
end
puts "Including/extending at outermost level"
include Command::SubcommandA
self.extend Command::SubcommandA
yields:
Including/extending at outermost level Command::SubcommandA got included into Class Command::SubcommandA got extended into Object
So it looks like [class methods](?) like include will be received by Class.
(Yes, the Class constant does refer to an object, in case you were wondering...
irb -> Class.object_id
=> -604440958
)
[edit] How does Kernel work then?
Question: Kernel is a module. So if the outer scope is Object, how come we're able to call the methods of Kernel (like "puts") as bare names?
It's almost as if Kernel is mixed into Object.
Indeed, that seems to be what this is telling us:
irb -> self.class.ancestors
=> [Object, PP::ObjectMixin, Kernel]
Yet its methods don't show up in Object...
irb -> self.class
=> Object
irb -> self.methods.grep /puts/
=> []
irb -> self.instance_methods.grep /puts/
NoMethodError: undefined method `instance_methods' for main:Object
from (irb):13
irb -> Object.methods.grep /puts/
=> []
irb -> Object.instance_methods.grep /puts/
=> []
irb -> Kernel.methods(false).grep /puts/
=> ["puts"]
irb -> Kernel.instance_methods.grep /puts/
=> []
..., so was it really mixed in or does Ruby handle Kernel specially (internally)?
The RDoc for Kernel lists puts (and most other methods) under the heading "Public Instance methods". And yet...
If Kernel were actually mixed into Object, we should see behavior more like this...
irb -> module Whatever; def foo; end; end
=> nil
irb -> include Whatever
=> Object
irb -> self.methods.grep /foo/
=> ["foo"]
irb -> Object.methods.grep /foo/
=> ["foo"]
That is, we should see it listed in the Object.methods array. But it isn't.
So again I wonder, how does Kernel work?
Could the Kernels methods (puts at least) actually be class methods?
These are the only instance methods that Kernel claims to have:
irb -> Kernel.instance_methods(false).sort
=> ["==", "===", "=~", "__id__", "__send__", "class", "clone", "display", "dup", "eql?", "equal?", "extend", "freeze", "frozen?", "gem", "hash", "id", "inspect", "instance_eval", "instance_of?", "instance_variable_get", "instance_variable_set", "instance_variables", "is_a?", "kind_of?", "method", "methods", "nil?", "object_id", "pretty_inspect", "private_methods", "protected_methods", "public_methods", "require", "require_gem", "respond_to?", "send", "singleton_methods", "taint", "tainted?", "to_a", "to_s", "type", "untaint"]
So either the RDocs are lying by listing puts in the "Public Instance Methods" section, or I'm not understanding something.
Either way, I'm confused.
Here are some of the class methods provided by Kernel (I only included the interesting/familiar methods in this list, because it's a long list, but you can see that they're all class methods):
irb -> puts (Kernel.methods - Object.methods - Kernel.instance_methods).sort irb -> require 'qualitysmith_extensions/module/class_methods' irb -> puts Kernel.class_methods ` at_exit binding block_given? caller catch eval exec exit fork getc gets global_variables lambda load p pp print proc putc puts raise rand require sleep system throw
So puts is a class method! ... right?
So when we use these methods as bare names, somehow it knows to find them in Kernel.
Still don't understand how that works, unless Object inherits from Kernel or something, which I doubt they do...
irb -> Object.superclass
=> nil
I know that it's mixed into Object...
irb -> self.class.ancestors
=> [Object, PP::ObjectMixin, Kernel]
but I didn't think that mixing in a module caused the class methods to be added to the includer class. Am I wrong here?
set_trace_func doesn't tell us if the method called was a class method or instance method... Otherwise the following trace might have been more informative...
require 'rubygems'
require 'unroller'
Unroller::trace(:include_c_calls => true) { puts 'hi' }
#Identical output if we do this instead:
#Unroller::trace(:include_c_calls => true) { Kernel.puts 'hi' }
| - ( c-call) Kernel block_given? (/usr/lib/ruby/gems/1.8/gems/unroller-0.0.13/lib/unroller.rb:323)
| - (c-return) Kernel block_given? (/usr/lib/ruby/gems/1.8/gems/unroller-0.0.13/lib/unroller.rb:323)
| Unroller::trace(:include_c_calls => true) { puts 'hi' } | t.rb:3
| - ( c-call) Kernel puts (t.rb:3 )
| - ( c-call) IO write (t.rb:3 )
hi | - (c-return) IO write (t.rb:3 )
| - ( c-call) IO write (t.rb:3 )
| - (c-return) IO write (t.rb:3 )
| - (c-return) Kernel puts (t.rb:3 )
So if we "override" puts by defining a local method of that name, what actually happens? Why can't it find Kernel#puts ?
irb -> def puts; 'nothing'; end
=> nil
irb -> puts
=> "nothing"
And yet we can still access it as a module (class) method!...
irb -> Kernel.puts
=> nil
So it is a module method ... right? It's both? I'm confused.
Actually, it only finds them in Kernel if they don't exist as local methods (in a more local scope). But maybe that's nothing unusually as far as the resolution of instance/class methods goes...
[edit] Local methods that match the same name as a Kernel method will be used instead of the Kernel method
So we can "override" methods sort of like this...
irb -> def exec; end
=> nil
irb -> exec
=> nil
We're not actually overriding the method when we do this however -- the Kernel method is still callable if we qualify the method call with some scope information -- we're just taking advantage of the [method call resolution order] of Ruby: instance methods, then class methods (?).
irb -> Kernel.exec('date')
Wed Feb 21 09:48:26 PST 2007
(To actually override a Kernel method, just re-open the Kernel class.)
Question: If we override `, however, how do we call the Kernel version???
irb -> def `(*args); p args; end
=> nil
irb -> `date`
["date"]
=> nil
irb -> Kernel.`hi`
(irb):19: warning: parenthesize argument(s) for future version
SyntaxError: compile error
(irb):19: unterminated string meets end of file
from (irb):19
from :0
irb -> Kernel.`(hi)
" `
SyntaxError: compile error
(irb):24: unterminated string meets end of file
from (irb):24
from :0
Well, when lacking a more elegant solution, the send method usually gets the job done!
irb -> Kernel.send(:`, 'date')
=> "Wed Feb 21 10:07:11 PST 2007\n"
[edit] What does/can a mixin do?
http://www.rubycentral.com/book/classes.html
When a class includes a module, that module's instance methods become available as instance methods of the class. It's almost as if the module becomes a superclass of the class that uses it. Not surprisingly, that's about how it works. When you include a module, Ruby creates an anonymous proxy class that references that module, and inserts that proxy as the direct superclass of the class that did the including. The proxy class contains references to the instance variables and methods of the module.
(This means you can call super from a class and it will call the corresponding method of (one of) the modules that it has mixed in.)
[edit] Mixins: What's a mixin?
Bruce Tate (2007-03-13). Crossing borders: Extensions in Rails: The anatomy of an acts_as plug-in (http://www-128.ibm.com/developerworks/java/library/j-cb03137/index.html).
A module has method definitions, but no base inheritance hierarchy. Instead, you can attach modules to any existing Ruby class. If the concept is new to you, think of a module as an interface plus the implementation for that interface. The nice thing about a module is that you can attach its functionality to any existing Ruby class, and you can attach as many as you want. You can also leverage a class's existing capabilities. This technique is called mixing in. C++ uses multiple inheritance to provide a similar capability, but with ugly complications. The Java founders eliminated multiple inheritance to address those complications. With modules, you can get some of the benefits of multiple inheritance without the sticky complications. Languages such as Smalltalk and Python also support mix-in inheritance.
[edit] Mixins: who needs multiple inheritance?
Brown, Gregory (2007-01-11). inheritance concept in ruby (http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/aac34ed1e5ac40d1/c7b7d59e35c8ff25?lnk=gst&q=inheritance).
The real complication with multiple inheritance is that you end up inheriting the ancestors of both parent classes.
When you use a mixin, the additional methods are tacked on to a class in the heirarchy, rather than creating another set of ancestors.
Since modules live outside of the tree of inheritance, they can be used to provide functionality without complicating the ancestry chain.
See Comparable and Enumerable for excellent and practical uses of modules.
In which cases do the two scenarious (M.I. and mixins) cause different behaviors? Is it only related to member visibility? If yes, how exactly?
if:
A < B < C
and module D is mixed into C, there is still a single path back to A.
Had we used multiple inheritence (if it were possible), perhaps
F < E < D
so now, C has two distinct roots, A and F.
Now imagine circular dependencies and other complications. Scary! :)
So modules allow you to avoid the verbosity of interfaces without complicating the chain of ancestors.
Brown, Gregory (2007-01-12 12:13). inheritance concept in ruby (http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/aac34ed1e5ac40d1/c7b7d59e35c8ff25?lnk=gst&q=inheritance).
That is, the exact same thing happens when you have a class include two modules and both implement the 'hello' method. There is no self-evident way of choosing, so you have to come up with an arbitrary choice like that of order of call to 'include'.
Yes, this is true.
However the issue is rarely with immediate dependencies, but dependency chains.
I.e., with mixins,
If I have A->B->C and C mixes in D, then E
the lookup is C,E,D,B,A
which logically is quick intuitive if you think about it.
If I have A->B->C
and ?->D->C
and
?->E->C
Those two question marks represent two independent dependency chains that you cannot be sure what they implement without knowing the ancestors of each.
Since modules live outside of the hierarchy, you will only be likely to run into this problem with bad design. The point of mixins (and multiple inheritance) really, is to address orthogonal concerns. Name collision is a sign of either a code smell, or just a really complex system that is going to need a lot of thought to begin with.
Brown, Gregory (2007-01-19 20:20). Digging Deep: Mixing it up (or in) with Modules (http://www.oreillynet.com/ruby/blog/2007/01/digging_deep_mixing_it_up_or_i_1.html).
So this post will focus on a recent topic on RubyTalk, the merit of using Mixins as a suitable replacement for multiple inheritance.
[edit] Overview
If you haven’t worked with multiple inheritance before, or aren’t familiar with object oriented programming, this article isn’t going to be super helpful. However, as a quick review, the core concept of multiple inheritance is that an object can have more than one parent class. For example, perhaps you have a Student class, and you want to say Joe is a student, but he’s also a musician and a rubyist. Single inheritance would not let you have this kind of structure without awkward hackery, so multiple inheritance would make life easier.
Though I’m sure there are folks out there who can explain why Java’s interfaces help simplify things in some cases, this sort of thing is a notorious source of annoyance in Java. A lot of times single inheritance just doesn’t do the trick. So often when folks hear that Ruby is single inheritance, they get sort of nervous.
On the gripping hand, there are plenty of languages in which incorrect use of MI can really bite you. C++ and Perl would be common culprits [...].
Ruby I suppose is lucky enough to be able to happily steal good ideas and throw away the bad ones. With this in mind, I think the Mix-in idea really fits the language.
[edit] But isn’t a Module just a container / a crippled Class ?
A short definition of a Module would likely be ‘a ruby class that cannot be instantiated and does not live within the class hierarchy’. It wouldn’t be 100% complete, but it’s enough to work with.
This at first sounds inconsequential. But the semantic power that such a construct provides is rather vast. David Black mentioned that he likes the idea of having both a noun like construct as well as an adjective like construct available for modeling.
That’s is a pretty good way to put it. This object is an Array. It is Enumerable.
If you look closely, you’ll see the distinction. The relationship is not really an ‘is a’ for the latter, but more describes some behavior the object has. It’s noun vs. adjective, when it all boils down.
...
[edit] I see the point, but I still don’t get how to use these things in my modeling
Basically, if you’re already familiar with modeling multiple inheritance in a sane way, the leap to mix-ins will be relatively small. However, if you’re new to the concept, it might take a little extra leap. Going back to David’s analogy, classes are our Noun component and modules our adjective.
Perhaps we have a Record construct and we want to be able to add tags to these records( a la folksonomy ). This is an ideal time to say an object with the ability to be tagged is Taggable, which is a nice adjective, and makes a nice module.
[edit] Subclasses vs. mixins
Hodel, Eric (2006-08-23). Subclassing vs include (http://blog.segment7.net/articles/2006/08/23/subclassing-vs-include).
In memcached Basics for Rails Rob Sanheim asked:
[W]hy make the cached model a class to extend instead of a module? Whether or not a model is cached should be an implementation detail, and shouldn’t define the hierarchy for a class. I know I would rather not use the power of (single!) inheritance just to cache something, when a mix-in should be plenty powerful to do it.The short answer is:
Using a class the correct way to overlay features on top of another class.
Here’s my long answer:
When you use a class you get super, and super is a beautiful thing. It automatically walks your class’ ancestors and calls the right method in the right order.
A module doesn’t have this property so you can’t use it to overlay features on top of a class. The class’ implementation will always be called before the module’s implementation.
Now you’re going to say something about using alias to shuffle methods around. You could do that, but you’ll have to do this for each method you want to overlay (five in CachedModel) which involves lots of extra typing “do_the_thingy_without_the_stuff” that you could have had for free (and more-prettily) with “super”.
You also get another problem that may cause subtle bugs. When you use alias to overlay features the order of execution is dependent upon the order the files are required in! Intentionally writing code where the behavior may change file load order gives me the heebie-jeebies.
Having to do all that work to get the benefits of a subclass tells me that a module isn’t powerful enough to do what a subclass can, so a module isn’t the right way to add a cache.
I avoid* using modules to overlay features of a class, but do use them to add orthogonal or complementary features. Typically when I write a module it ends up being used like Comparable, Enumerable or Singleton. When I need to do something invasive a subclass is better.
Finally, making the argument that adding caching to ActiveRecord::Base via a subclass shouldn’t define the inheritance argument is very subjective. I could justify using a subclass by saying that ActiveRecord is a data storage class, and CachedModel is just another data store. If you want caching, inherit from CachedModel. If you don’t want caching, inherit from ActiveRecord::Base.
Jim Weirich said 1 day later:Hi Eric,
I’m not sure I’m following your reasoning.
Eric: When you use a class you get super, and super is a beautiful thing.Agree about the beauty thing. But modules participate in super chaining as well. [How?]
Eric: A module doesn't have this property so you can't use it to overlay features on top of a class. The class's implementation will always be called before the module's implementation.The current class's methods will be called before both the module and the parent class. The module functions will be called before the parent class.
Which seems to be the right thing to do.
Unless you are talking about something more subtle where the module is mixed-in in more than one place in the inheritance heirarchy.
Thanks.
Rob Sanheim said 1 day later:
Hi Eric,
Thanks for the detailed response. I’m curious as to why a module wouldn’t work wrt the point Jim made. I’m aware of the amount of magic going on in the finder methods, so I agree 100% things could get very nasty if you had to alias and chain things…
Regarding the inheritance issue – it just seems to me that having a “Cacheable” module makes much more sense (assuming the implementation isn’t horrible). In a perfect world, I’d also prefer including ActiveRecord::Base so my business models could extend from anything, and possibly also mixin ActiveCVS, ActiveLDAP, etc. I see the models main concern being the business logic and data, and how that data gets persisted is secondary.
That said, I’ll admit my real world code hasn’t yet seen the need for abstract business superclasses, so Ruby’s power might allow metaprogramming to take the place of deeper class hiearchies.
Eric Hodel said 2 days later:
Jim: Lately I’ve seen lots of use of modules for layering functionality on top of classes implemented so that you don’t have to add include Blah into every subclass, particularly from rails. (For example flash.rb, from an older revision so its more clear.) I find this particularly messy.
CachedModel needs to override both instance and class methods, and include only adds instance methods. To work around this Rails adds a ClassMethods module and automatically includes it as appropriate. I find a separate module for extending class methods isn’t nearly as readable or organized as having everything in one place. I also can’t remember when which callbacks get called when, but super is simple and easy to understand.
...
James Mead said 11 days later:
Perhaps the more pertinent question is – should ActiveRecord::Base be a class? It could be argued that both persistence and caching are orthogonal concerns relative to the business logic of your domain.
[edit] [extension/non-core] Passing parameters along with your include
/usr/lib/ruby/gems/1.8/gems/facets-1.8.51/lib/facets/more/paramix.rb (Should be documented on the Module class page, but it seems that dependency.rb's documentation "trumped" that)
module Mixin
def hello
puts "Hello from #{Mixin(:name)}"
end
end
class MyClass
include Mixin, :name => 'Ruby'
end
m = MyClass.new
m.hello -> 'Hello from Ruby'
