如何实现单例模型

时间:2022-09-02 08:00:57

I have a site in rails and want to have site-wide settings. One part of my app can notify the admin by SMS if a specific event happens. This is an example of a feature that I want configurable via the site-wide settings.

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

So I was thinking I should have a Setting model or something. It needs to be a model because I want to be able to has_many :contacts for the SMS notification.

所以我在想我应该有一个环境模型或什么的。它需要是一个模型,因为我希望能够使用has_many:SMS通知的联系人。

The problem is that there can only be one post in the database for the settings model. So I was thinking of using a Singleton model but that only prevents new object to be created right?

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

Would I still need to create getter and setter methods for each attribute like so:

我还需要为每个属性创建getter和setter方法,如下所示:

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

def self.attribute
  Model.first.attribute
end

Is it perhaps not best-practice to use Model.attribute directly but always create an instance of it and use that?

直接使用Model.attribute可能不是最佳实践,但总是创建它的实例并使用它吗?

What should I do here?

我该怎么办?

10 个解决方案

#1


8  

I am not sure I'd waste the database/ActiveRecord/Model overhead for such a basic need. This data is relatively static (I am assuming) and on the fly calculations aren't necessary (including database lookups).

我不确定我是否会浪费数据库/ ActiveRecord / Model开销来满足这样的基本需求。这个数据是相对静态的(我假设)并且不需要动态计算(包括数据库查找)。

Having said that, I'd recommend you define a YAML file with your site-wide settings and define an initializer file that loads the settings into a constant. You won't have nearly as many of the unnecessary moving parts.

话虽如此,我建议您使用站点范围的设置定义YAML文件,并定义一个初始化文件,将设置加载到常量中。你不会有那么多不必要的活动部件。

There is no reason that data couldn't just sit in memory and save you a ton of complexity. Constants are available everywhere, and they don't need to be initialized or instantiated. If its absolutely critical that you utilize a class as a singleton, I'd recommend doing these two things:

没有理由说数据不能仅仅存在于内存中并且为您节省大量复杂性。常量随处可用,无需初始化或实例化。如果你将一个类作为一个单独使用是绝对关键的,我建议你做这两件事:

  1. undef the initialize/new method
  2. undef初始化/新方法
  3. define only self.* methods that way it is not possible for you to maintain a state
  4. 只定义self。*方法,这样你就无法维持一个状态

#2


48  

(I agree with @user43685 and disagree with @Derek P -- there are lots of good reasons to keep site-wide data in the database instead of a yaml file. For example: your settings will be available on all web servers (if you have multiple web servers); changes to your settings will be ACID; you don't have to spend time implementing a YAML wrapper etc. etc.)

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

In rails, this is easy enough to implement, you just have to remember that your model should be a "singleton" in database terms, not in ruby object terms.

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

The easiest way to implement this is:

实现此目的的最简单方法是:

  1. Add a new model, with one column for each property you need
  2. 添加一个新模型,为您需要的每个属性添加一列
  3. Add a special column called "singleton_guard", and validate that it is always equal to "0", and mark it as unique (this will enforce that there is only one row in the database for this table)
  4. 添加一个名为“singleton_guard”的特殊列,并验证它始终等于“0”,并将其标记为唯一(这将强制数据库中只有一行用于此表)
  5. Add a static helper method to the model class to load the singleton row
  6. 向模型类添加静态辅助方法以加载单行

So the migration should look something like this:

因此迁移应该如下所示:

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)

And the model class should look something like this:

模型类应该看起来像这样:

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

In Rails >= 3.2.1 you should be able to replace the body of the "instance" getter with a call to "first_or_create!"

在Rails> = 3.2.1中,您应该能够通过调用“first_or_create!”来替换“instance”getter的主体。

#3


24  

I disagree with common opinion - there is nothing wrong with reading a property out of the database. You can read the database value and freeze if you'd like, however there could be more flexible alternatives to simple freezing.

我不同意共同意见 - 从数据库中读取属性没有任何问题。您可以读取数据库值并冻结(如果您愿意),但是可以有更灵活的替代方法来进行简单冻结。

How is YAML different from database? .. same drill - external to application code persistent setting.

YAML与数据库有何不同? ..相同的钻取 - 在应用程序代码持久性设置之外。

Nice thing about the database approach is that it can be changed on the fly in more or less secure way (not opening and overwriting files directly). Another nice thing is it can be shared across the network between cluster nodes (if properly implemented).

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

The question however remains what would be the proper way to implement such a setting using ActiveRecord.

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

#4


10  

You could also enforce a maximum of one record as follows:

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

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

This overrides the ActiveRecord method so that it will blow up if you try to create a new instance of the class when one already exists.

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

You could then go on to define only class methods that act on the one record:

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

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  

I know this is an old thread, but I just needed the same thing and found out that there's a gem for this: acts_as_singleton.

我知道这是一个旧线程,但我只是需要相同的东西,并发现有一个宝石:acts_as_singleton。

Installation instructions are for Rails 2, but it works great with Rails 3 too.

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

#6


3  

Odds are good you don't need a singleton. It's unfortunate that one of the worst design habits to come out of the patterns craze is also one of the most commonly adopted. I blame the unfortunate appearance of simplicity, but I digress. If they had called it the "Static Global" pattern I'm sure people would have been more sheepish about using it.

赔率很高,你不需要单身人士。不幸的是,从模式热潮中走出来的最糟糕的设计习惯之一也是最常采用的习惯之一。我责怪简单的不幸外表,但我离题了。如果他们称之为“静态全球”模式,我相信人们对使用它会更加羞怯。

I suggest using a wrapper class with a private static instance of the class you want to use for the singleton. You won't introduce a tight couple throughout your code like you will with the singleton.

我建议使用一个包装类,该类包含要用于单例的类的私有静态实例。你不会在整个代码中引入紧密的情侣,就像你对单身人士一样。

Some people call this a monostate pattern. I tend to think of it as just another twist on the strategy/agent concept since you can allow for more flexibility by implementing different interfaces to expose/hide functionality.

有些人称之为单一模式。我倾向于将其视为策略/代理概念的另一个转折,因为您可以通过实现不同的接口来实现更多的灵活性来公开/隐藏功能。

#7


2  

Simple:

简单:

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  

Using has_many :contacts doesn't mean you need a model. has_many does some magic, but in the end it's just adds some method with a specified contract. There's no reason why you can't implement those methods (or some subset that you need) to make your model behave like it has_many :contacts yet not actually use an ActiveRecord model (or model at all) for Contact.

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

#9


1  

You might also check out Configatron:

您也可以查看Configatron:

http://configatron.mackframework.com/

http://configatron.mackframework.com/

Configatron makes configuring your applications and scripts incredibly easy. No longer is a there a need to use constants or global variables. Now you can use a simple and painless system to configure your life. And, because it‘s all Ruby, you can do any crazy thing you would like to!

Configatron使您的应用程序和脚本配置变得异常简单。不再需要使用常量或全局变量。现在,您可以使用简单且无痛的系统来配置您的生活。并且,因为它是所有Ruby,你可以做任何你想做的疯狂的事情!

#10


0  

class Constant < ActiveRecord::Base
  after_initialize :readonly!

  def self.const_missing(name)
    first[name.to_s.downcase]
  end
end

Constant::FIELD_NAME

恒:: FIELD_NAME

#1


8  

I am not sure I'd waste the database/ActiveRecord/Model overhead for such a basic need. This data is relatively static (I am assuming) and on the fly calculations aren't necessary (including database lookups).

我不确定我是否会浪费数据库/ ActiveRecord / Model开销来满足这样的基本需求。这个数据是相对静态的(我假设)并且不需要动态计算(包括数据库查找)。

Having said that, I'd recommend you define a YAML file with your site-wide settings and define an initializer file that loads the settings into a constant. You won't have nearly as many of the unnecessary moving parts.

话虽如此,我建议您使用站点范围的设置定义YAML文件,并定义一个初始化文件,将设置加载到常量中。你不会有那么多不必要的活动部件。

There is no reason that data couldn't just sit in memory and save you a ton of complexity. Constants are available everywhere, and they don't need to be initialized or instantiated. If its absolutely critical that you utilize a class as a singleton, I'd recommend doing these two things:

没有理由说数据不能仅仅存在于内存中并且为您节省大量复杂性。常量随处可用,无需初始化或实例化。如果你将一个类作为一个单独使用是绝对关键的,我建议你做这两件事:

  1. undef the initialize/new method
  2. undef初始化/新方法
  3. define only self.* methods that way it is not possible for you to maintain a state
  4. 只定义self。*方法,这样你就无法维持一个状态

#2


48  

(I agree with @user43685 and disagree with @Derek P -- there are lots of good reasons to keep site-wide data in the database instead of a yaml file. For example: your settings will be available on all web servers (if you have multiple web servers); changes to your settings will be ACID; you don't have to spend time implementing a YAML wrapper etc. etc.)

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

In rails, this is easy enough to implement, you just have to remember that your model should be a "singleton" in database terms, not in ruby object terms.

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

The easiest way to implement this is:

实现此目的的最简单方法是:

  1. Add a new model, with one column for each property you need
  2. 添加一个新模型,为您需要的每个属性添加一列
  3. Add a special column called "singleton_guard", and validate that it is always equal to "0", and mark it as unique (this will enforce that there is only one row in the database for this table)
  4. 添加一个名为“singleton_guard”的特殊列,并验证它始终等于“0”,并将其标记为唯一(这将强制数据库中只有一行用于此表)
  5. Add a static helper method to the model class to load the singleton row
  6. 向模型类添加静态辅助方法以加载单行

So the migration should look something like this:

因此迁移应该如下所示:

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)

And the model class should look something like this:

模型类应该看起来像这样:

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

In Rails >= 3.2.1 you should be able to replace the body of the "instance" getter with a call to "first_or_create!"

在Rails> = 3.2.1中,您应该能够通过调用“first_or_create!”来替换“instance”getter的主体。

#3


24  

I disagree with common opinion - there is nothing wrong with reading a property out of the database. You can read the database value and freeze if you'd like, however there could be more flexible alternatives to simple freezing.

我不同意共同意见 - 从数据库中读取属性没有任何问题。您可以读取数据库值并冻结(如果您愿意),但是可以有更灵活的替代方法来进行简单冻结。

How is YAML different from database? .. same drill - external to application code persistent setting.

YAML与数据库有何不同? ..相同的钻取 - 在应用程序代码持久性设置之外。

Nice thing about the database approach is that it can be changed on the fly in more or less secure way (not opening and overwriting files directly). Another nice thing is it can be shared across the network between cluster nodes (if properly implemented).

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

The question however remains what would be the proper way to implement such a setting using ActiveRecord.

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

#4


10  

You could also enforce a maximum of one record as follows:

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

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

This overrides the ActiveRecord method so that it will blow up if you try to create a new instance of the class when one already exists.

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

You could then go on to define only class methods that act on the one record:

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

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  

I know this is an old thread, but I just needed the same thing and found out that there's a gem for this: acts_as_singleton.

我知道这是一个旧线程,但我只是需要相同的东西,并发现有一个宝石:acts_as_singleton。

Installation instructions are for Rails 2, but it works great with Rails 3 too.

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

#6


3  

Odds are good you don't need a singleton. It's unfortunate that one of the worst design habits to come out of the patterns craze is also one of the most commonly adopted. I blame the unfortunate appearance of simplicity, but I digress. If they had called it the "Static Global" pattern I'm sure people would have been more sheepish about using it.

赔率很高,你不需要单身人士。不幸的是,从模式热潮中走出来的最糟糕的设计习惯之一也是最常采用的习惯之一。我责怪简单的不幸外表,但我离题了。如果他们称之为“静态全球”模式,我相信人们对使用它会更加羞怯。

I suggest using a wrapper class with a private static instance of the class you want to use for the singleton. You won't introduce a tight couple throughout your code like you will with the singleton.

我建议使用一个包装类,该类包含要用于单例的类的私有静态实例。你不会在整个代码中引入紧密的情侣,就像你对单身人士一样。

Some people call this a monostate pattern. I tend to think of it as just another twist on the strategy/agent concept since you can allow for more flexibility by implementing different interfaces to expose/hide functionality.

有些人称之为单一模式。我倾向于将其视为策略/代理概念的另一个转折,因为您可以通过实现不同的接口来实现更多的灵活性来公开/隐藏功能。

#7


2  

Simple:

简单:

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  

Using has_many :contacts doesn't mean you need a model. has_many does some magic, but in the end it's just adds some method with a specified contract. There's no reason why you can't implement those methods (or some subset that you need) to make your model behave like it has_many :contacts yet not actually use an ActiveRecord model (or model at all) for Contact.

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

#9


1  

You might also check out Configatron:

您也可以查看Configatron:

http://configatron.mackframework.com/

http://configatron.mackframework.com/

Configatron makes configuring your applications and scripts incredibly easy. No longer is a there a need to use constants or global variables. Now you can use a simple and painless system to configure your life. And, because it‘s all Ruby, you can do any crazy thing you would like to!

Configatron使您的应用程序和脚本配置变得异常简单。不再需要使用常量或全局变量。现在,您可以使用简单且无痛的系统来配置您的生活。并且,因为它是所有Ruby,你可以做任何你想做的疯狂的事情!

#10


0  

class Constant < ActiveRecord::Base
  after_initialize :readonly!

  def self.const_missing(name)
    first[name.to_s.downcase]
  end
end

Constant::FIELD_NAME

恒:: FIELD_NAME