Rails 2.3 STI return all child classes
I am using standard STI and want to create an input select on a form whose options are all child type of the parent开发者_StackOverflow class. So I'd like Parent.select_options to return ['Child1','Child2','Child3']
class Parent < ActiveRecord::Base
# kinda what I'd like except the descendants method is undefined in rails 2.3
def self.select_options
descendants.map{ |c| c.to_s }.sort
end
end
class Child1 < Parent
end
class Child2 < Parent
end
class Child3 < Parent
end
view.html.haml
= f.input :parent_id, :as => :select, :collection => Parent.select_options, :prompt => true
UPDATE
Thanks to @nash and @jdeseno just need to add the following initializer using @jdeseno method:
%w[parent child1 child2 child3].each do |c|
require_dependency File.join("app","models","#{c}.rb")
end
You can add a descendants method by hooking into Class.inherited
:
class Parent
@@descendants = []
def self.inherited(klass)
@@descendants << klass
end
def descendants
@@descendants
end
end
class A < Parent; end
class B < Parent; end
class C < Parent; end
Eg:
irb> Parent.new.descendants
[A, B, C]
Actually, custom-implementing the tracking of subclasses isn't really necessary even in Rails 2.3. There already exists an aptly named method "subclasses" injected into Class by ActiveSupport that returns them for you in lexically sorted order. So you could have written
class Parent < ActiveRecord::Base
def self.select_options
subclasses.map{ |c| c.to_s }
end
end
class Child3 < Parent
end
class Child1 < Parent
end
class Child2 < Parent
end
Or you could have used the same trick as they did there and used
class Parent < ActiveRecord::Base
def self.select_options
Object.subclasses_of(self).map{ |c| c.to_s }.sort
end
end
Just verified this in Rails 2.3.14 (Ruby 1.8.7-p352) and got the expected result in both cases:
>> Parent.select_options
=> ["Child1", "Child2", "Child3"]
The necessity to preload STI child classes in the development environment still applies. Kudos to Alex Reisner for the hint in his blog.
When you invoke your Parent.select_options
method your child models may not be loaded yet. So, you can add something like this:
class Parent < ActiveRecord::Base
Dir[File.join(File.dirname(__FILE__), "*.rb")].each do |f|
Parent.const_get(File.basename(f, '.rb').classify)
end
end
in your Parent
model. Now you can use your method:
ruby-1.9.2-p290 :010 > Parent.descendants.map {|c| c.to_s}.sort
=> ["Child1", "Child2", "Child3"]
精彩评论