开发者

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"] 
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜