Permalink
Browse files

Add subclasses plugin, for recording all of a models subclasses and d…

…escendent classes

The Subclasses plugin keeps track of all subclasses of the
current model class.  Direct subclasses are available via the
subclasses method, and all descendent classes are available via the
descendents method.

  c = Class.new(Sequel::Model)
  c.plugin :subclasses
  sc1 = Class.new(c)
  sc2 = Class.new(c)
  ssc1 = Class.new(sc1)
  c.subclasses    # [sc1, sc2]
  sc1.subclasses  # [ssc1]
  sc2.subclasses  # []
  ssc1.subclasses # []
  c.descendents   # [sc1, ssc1, sc2]
  • Loading branch information...
1 parent 6d0611c commit b75bf1940da241f6f44a148fc607c72004f1e682 @jeremyevans jeremyevans committed Sep 7, 2009
Showing with 100 additions and 0 deletions.
  1. +2 −0 CHANGELOG
  2. +45 −0 lib/sequel/plugins/subclasses.rb
  3. +52 −0 spec/extensions/subclasses_spec.rb
  4. +1 −0 www/pages/plugins
View
@@ -1,5 +1,7 @@
=== HEAD
+* Add subclasses plugin, for recording all of a models subclasses and descendent classes (jeremyevans)
+
* Add looser_typecasting extension, for using .to_f and .to_i instead of Kernel.Float and Kernel.Integer when typecasting floats and integers (jeremyevans)
* Catch database errors when preparing statements or setting variable values when using the native MySQL adapter (jeremyevans)
@@ -0,0 +1,45 @@
+module Sequel
+ module Plugins
+ # The Subclasses plugin keeps track of all subclasses of the
+ # current model class. Direct subclasses are available via the
+ # subclasses method, and all descendent classes are available via the
+ # descendents method.
+ #
+ # c = Class.new(Sequel::Model)
+ # c.plugin :subclasses
+ # sc1 = Class.new(c)
+ # sc2 = Class.new(c)
+ # ssc1 = Class.new(sc1)
+ # c.subclasses # [sc1, sc2]
+ # sc1.subclasses # [ssc1]
+ # sc2.subclasses # []
+ # ssc1.subclasses # []
+ # c.descendents # [sc1, ssc1, sc2]
+ module Subclasses
+ # Initialize the subclasses instance variable for the model.
+ def self.apply(model, &block)
+ model.instance_variable_set(:@subclasses, [])
+ end
+
+ module ClassMethods
+ # All subclasses for the current model. Does not
+ # include the model itself.
+ attr_reader :subclasses
+
+ # All descendent classes of this model.
+ def descendents
+ subclasses.map{|x| [x] + x.descendents}.flatten
+ end
+
+ # Add the subclass to this model's current subclasses,
+ # and initialize a new subclasses instance variable
+ # in the subclass.
+ def inherited(subclass)
+ super
+ subclasses << subclass
+ subclass.instance_variable_set(:@subclasses, [])
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,52 @@
+require File.join(File.dirname(__FILE__), "spec_helper")
+
+describe Sequel::Model, "Subclasses plugin" do
+ before do
+ @c = Class.new(Sequel::Model)
+ @c.plugin :subclasses
+ end
+
+ specify "#subclasses should record direct subclasses of the given model" do
+ @c.subclasses.should == []
+
+ sc1 = Class.new(@c)
+ @c.subclasses.should == [sc1]
+ sc1.subclasses.should == []
+
+ sc2 = Class.new(@c)
+ @c.subclasses.should == [sc1, sc2]
+ sc1.subclasses.should == []
+ sc2.subclasses.should == []
+
+ ssc1 = Class.new(sc1)
+ @c.subclasses.should == [sc1, sc2]
+ sc1.subclasses.should == [ssc1]
+ sc2.subclasses.should == []
+ end
+
+ specify "#descendents should record all descendent subclasses of the given model" do
+ @c.descendents.should == []
+
+ sc1 = Class.new(@c)
+ @c.descendents.should == [sc1]
+ sc1.descendents.should == []
+
+ sc2 = Class.new(@c)
+ @c.descendents.should == [sc1, sc2]
+ sc1.descendents.should == []
+ sc2.descendents.should == []
+
+ ssc1 = Class.new(sc1)
+ @c.descendents.should == [sc1, ssc1, sc2]
+ sc1.descendents.should == [ssc1]
+ sc2.descendents.should == []
+ ssc1.descendents.should == []
+
+ sssc1 = Class.new(ssc1)
+ @c.descendents.should == [sc1, ssc1, sssc1, sc2]
+ sc1.descendents.should == [ssc1, sssc1]
+ sc2.descendents.should == []
+ ssc1.descendents.should == [sssc1]
+ sssc1.descendents.should == []
+ end
+end
View
@@ -17,6 +17,7 @@
<li><a href="rdoc-plugins/classes/Sequel/Plugins/Schema.html">schema</a>: Adds backwards compatibility for Model.set_schema and Model.create_table.</li>
<li><a href="rdoc-plugins/classes/Sequel/Plugins/Serialization.html">serialization</a>: Supports serializing column values and storing them as either marshal, yaml, or json in the database.</li>
<li><a href="rdoc-plugins/classes/Sequel/Plugins/SingleTableInheritance.html">single_table_inheritance</a>: Sets up STI by making subclasses of this model only load rows that match the class, and returning instances of the subclasses.</li>
+<li><a href="rdoc-plugins/classes/Sequel/Plugins/Subclasses.html">subclasses</a>: Allows easy access all model subclasses and descendent classes, without using ObjectSpace.</li>
<li><a href="rdoc-plugins/classes/Sequel/Plugins/TacticalEagerLoading.html">tactical_eager_loading</a>: Allows you to eagerly load an association for all objects retreived from the same dataset when calling the association method on any of the objects in the dataset.</li>
<li><a href='rdoc-plugins/classes/Sequel/Plugins/Timestamps.html'>timestamps</a>: Creates hooks for automatically setting create and update timestamps.</li>
<li><a href='rdoc-plugins/classes/Sequel/Plugins/TypecastOnLoad.html'>typecast_on_load</a>: Fixes bad database typecasting when loading model objects.</li>

0 comments on commit b75bf19

Please sign in to comment.