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

如何实现单例模型

  •  24
  • gnarf  · 技术社区  · 17 年前

    我在Rails中有一个站点,希望有站点范围的设置。如果发生特定事件,我的应用程序的一部分可以通过短信通知管理员。这是一个我希望通过站点范围设置配置的功能示例。

    所以我想我应该有一个背景模型或者什么的。它必须是一个模型,因为我希望能够有多个:短信息通知的联系人。

    问题是设置模型的数据库中只能有一个日志。所以我考虑使用单例模型,但这只会阻止创建新对象,对吗?

    我是否仍然需要为每个属性创建getter和setter方法,比如:

    def self.attribute=(param)
      Model.first.attribute = param
    end
    
    def self.attribute
      Model.first.attribute
    end
    

    直接使用model.attribute,但始终创建并使用model.attribute的实例,这可能不是最佳实践吗?

    我该怎么办?

    12 回复  |  直到 7 年前
        1
  •  8
  •   Derek P.    17 年前

    我不确定我是否会为了这样一个基本需求而浪费数据库/活动记录/模型开销。这个数据是相对静态的(我假设),不需要进行即时计算(包括数据库查找)。

    既然这样,我建议您用站点范围的设置定义一个yaml文件,并定义一个将设置加载到常量的初始值设定项文件。你不会有那么多不必要的运动部件。

    没有理由数据不能保存在内存中,从而为您节省大量的复杂性。常量在任何地方都可用,不需要初始化或实例化。如果使用类作为单例是绝对关键的,我建议您执行以下两项操作:

    1. 取消初始化/新建方法
    2. 只定义self.*这样的方法是不可能保持状态的
        2
  •  53
  •   Simon L. Brazell    7 年前

    (我同意@user43685,不同意@derek p——有很多很好的理由将站点范围的数据保存在数据库中而不是yaml文件中。例如:您的设置将在所有Web服务器上可用(如果您有多个Web服务器);对设置的更改将是ACID;您不必花费时间实现yaml包装等)。

    在Rails中,这很容易实现,您只需记住您的模型应该是数据库术语中的“单例”,而不是Ruby对象术语中的“单例”。

    最简单的实现方法是:

    1. 添加新模型,每个属性对应一列
    2. 添加一个名为“singleton\u guard”的特殊列,并验证它是否始终等于“0”,并将其标记为唯一(这将强制数据库中此表只有一行)
    3. 向模型类中添加静态助手方法以加载单例行

    所以迁移应该是这样的:

    create_table :app_settings do |t|
      t.integer  :singleton_guard
      t.datetime :config_property1
      t.datetime :config_property2
      ...
    
      t.timestamps
    end
    add_index(:app_settings, :singleton_guard, :unique => true)
    

    模型类应该如下所示:

    class AppSettings < ActiveRecord::Base
      # The "singleton_guard" column is a unique column which must always be set to '0'
      # This ensures that only one AppSettings row is created
      validates_inclusion_of :singleton_guard, :in => [0]
    
      def self.instance
        # there will be only one row, and its ID must be '1'
        begin
          find(1)
        rescue ActiveRecord::RecordNotFound
          # slight race condition here, but it will only happen once
          row = AppSettings.new
          row.singleton_guard = 0
          row.save!
          row
        end
      end
    end
    

    在rails>=3.2.1中,您应该能够用调用替换“instance”getter的主体。 first_or_create! “像这样:

    def self.instance
      first_or_create!(singleton_guard: 0)
    end
    
        3
  •  24
  •   user43685    17 年前

    我不同意大家的看法——从数据库中读取属性没有任何问题。如果愿意的话,您可以读取数据库值并冻结,但是除了简单的冻结之外,还有更灵活的选择。

    yaml与数据库有什么不同?…相同的钻取-应用程序代码持久性设置的外部。

    数据库方法的好处在于,它可以或多或少地以安全的方式动态更改(而不是直接打开和覆盖文件)。另一个好处是它可以在集群节点之间的网络上共享(如果正确实现的话)。

    然而,问题仍然是,使用ActiveRecord实现这种设置的正确方法是什么。

        4
  •  11
  •   hoffm    10 年前

    您还可以强制最多一条记录,如下所示:

    class AppConfig < ActiveRecord::Base
    
      before_create :confirm_singularity
    
      private
    
      def confirm_singularity
        raise Exception.new("There can be only one.") if AppConfig.count > 0
      end
    
    end
    

    这将覆盖 ActiveRecord 方法,以便在类的新实例已存在时尝试创建该实例时,它将爆炸。

    然后,您可以继续只定义作用于一个记录的类方法:

    class AppConfig < ActiveRecord::Base
    
      attr_accessible :some_boolean
      before_create :confirm_singularity
    
      def self.some_boolean?
        settings.some_boolean
      end
    
      private
    
      def confirm_singularity
        raise Exception.new("There can be only one.") if AppConfig.count > 0
      end
    
      def self.settings
        first
      end
    
    end
    
        5
  •  6
  •   alf    13 年前

    我知道这是一条古老的线,但我只需要同样的东西,我发现这条线有一块宝石: acts_as_singleton .

    安装说明适用于Rails 2,但它也适用于Rails 3。

        6
  •  3
  •   JGeiser    17 年前

    很有可能你不需要单身。不幸的是,从模式狂热中产生的最糟糕的设计习惯之一也是最常用的设计习惯之一。我责备这种不幸的简单的外表,但我离题了。如果他们称之为“静态全球”模式,我相信人们会更羞怯地使用它。

    我建议将包装类与要用于单例的类的私有静态实例一起使用。在整个代码中,您不会像使用单例语言那样引入一对紧密的夫妇。

    有些人把这称为单态模式。我倾向于把它看作是对策略/代理概念的另一个扭曲,因为您可以通过实现不同的接口来公开/隐藏功能来实现更大的灵活性。

        7
  •  2
  •   BvuRVKyUVlViVIc7    8 年前

    简单的:

    class AppSettings < ActiveRecord::Base 
      before_create do
        self.errors.add(:base, "already one setting object existing") and return false if AppSettings.exists?      
      end
    
      def self.instance
        AppSettings.first_or_create!(...) 
      end 
    end
    
        8
  •  1
  •   Otto    17 年前

    使用 has_many :contacts 并不意味着你需要一个模特。 has_many 做了一些魔术,但最后它只是用一个指定的契约添加了一些方法。你没有理由不能实现那些方法(或者你需要的子集)来让你的模型表现得像它一样。 有多个联系人 但实际上并没有使用ActiveRecord模型(或模型)进行联系。

        9
  •  1
  •   user44104    17 年前

    您也可以查看configatron:

    http://configatron.mackframework.com/

    configatron使配置应用程序和脚本变得非常容易。不再需要使用常量或全局变量。现在你可以使用一个简单无痛的系统来配置你的生活。而且,因为它是红宝石,你可以做任何你想做的疯狂的事情!

        10
  •  0
  •   araslanov_e    9 年前
    class Constant < ActiveRecord::Base
      after_initialize :readonly!
    
      def self.const_missing(name)
        first[name.to_s.downcase]
      end
    end
    

    常量::字段名

        11
  •  0
  •   user3049407    7 年前

    你可以这样做:

     class Config < ApplicationRecord
      def self.instance
        Config.first || Config.create!
      end
     end
    
        12
  •  0
  •   Кирилл Григорьев    7 年前

    我假设使用继承列 type 有uniq约束。

    # miragtion
    class CreateSingletonRecords < ctiveRecord::Migration[5.2]
      create_table :balance_holders do |t|
        t.string :type
        t.index :type, unique: true
    
        t.timestamps
      end
    end
    

    父类中的几个方法:

    class SingletonRecord < ApplicationRecord
      class << self
        def instance
          @singleton__instance__
        end
    
        def load_record(params = {})
          @singleton__instance__ = find_or_create_by!(params)
        end
      end
    
      load_record
    
      validates :type, uniqueness: true
    end
    

    之后,您可以永远为单例模型类使用单记录。 在加载模型类期间,将一次性加载或创建实例。