代码之家  ›  专栏  ›  技术社区  ›  pvf

Rails STI类自动初始化

  •  3
  • pvf  · 技术社区  · 10 年前

    我正在尝试创建一个STI Base模型,它会自动更改为继承类,如下所示:

    #models/source/base.rb
    class Source::Base < ActiveRecord::Base
      after_initialize :detect_type
    
      private
      def detect_type
        if (/(rss)$/ ~= self.url)
          self.type = 'Source::RSS'
        end
      end
    end
    
    #models/source/rss.rb
    class Source::RSS < Source::Base
      def get_content
        puts 'Got content from RSS'
      end
    end
    

    我想要这样的行为:

    s = Source::Base.new(:url => 'http://stackoverflow.com/rss')
    s.get_content #=> Got content from RSS
    
    s2 = Source::Base.first # url is also ending rss
    s2.get_content #=> Got content from RSS
    
    3 回复  |  直到 10 年前
        1
  •  3
  •   James Mason    10 年前

    有(至少)三种方法可以做到这一点:

    1.使用 Factory method

    @亚历杭德罗·巴比奥的回答就是这种模式的一个很好的例子。它的缺点很少,但你必须记住始终使用工厂方法。如果第三方代码正在创建您的对象,这可能是一个挑战。

    2.覆盖 Source::Base.new

    Ruby(因为它的所有罪过)会让你超越 new .

    class Source::Base < ActiveRecord::Base
      def self.new(attributes)
        base = super
        return base if base.type == base.real_type
        base.becomes(base.real_type)
      end
    
      def real_type
        # type detection logic
      end
    end
    

    这是“魔法”,有着所有可以带来的超级酷和超级迷惑的包袱。

    3.包装 becomes 在转换方法中

    class Source::Base < ActiveRecord::Base
      def become_real_type
        return self if self.type == self.real_type
        becomes(real_type)
      end
    
      def real_type
        # type detection logic
      end
    end
    
    thing = Source::Base.new(params).become_real_type
    

    这与工厂方法非常相似,但它允许您进行转换 之后 对象创建,如果有其他人正在创建对象,这会很有帮助。

        2
  •  2
  •   davetapley    10 年前

    另一种选择是使用 polymorphic association ,您的类可能如下所示:

    class Source < ActiveRecord::Base
      belongs_to :content, polymorphic: true
    end
    
    class RSS < ActiveRecord::Base
      has_one :source, as: :content
      validates :source, :url, presence: true
    end
    

    创建实例时,需要创建源,然后创建并分配一个具体的 content 例如,因此:

    s = Source.create
    s.content = RSS.create url: exmaple.com
    

    你可能想 accepts_nested_attributes_for 让事情更简单。

    你的 detect_type 逻辑要么位于控制器中,要么位于 service object 。它可以为内容返回正确的类,例如。 return RSS if /(rss)$/ ~= self.url .


    使用这种方法,您可以要求 Source.all includes: :content ,当您加载 所容纳之物 每个 Source 例如,Rails的多态性会将其初始化为正确的类型。

        3
  •  1
  •   Alejandro Babio    10 年前

    如果我是你,我会添加一个返回正确实例的类方法。

    class Source::Base < ActiveRecord::Base
      def self.new_by_url(params)
        type = if (/(rss)$/ ~= params[:url])
          'Source::RSS'
        end
        raise 'invalid type' unless type
        type.constantize.new(params)
      end
    end
    

    然后您将获得所需的行为:

    s = Source::Base.new_by_url(:url => 'http://stackoverflow.com/rss')
    s.get_content #=> Got content from RSS
    

    s 将是 Source::RSS .

    注意:在阅读您的评论后,变成:它 code 使用 klass.new new 是类方法。初始化后,对象就完成了,它是一个 Source::Base ,而且没有办法改变它。

    推荐文章