33

Rails にサイトがあり、サイト全体の設定が必要です。アプリの一部で、特定のイベントが発生した場合に SMS で管理者に通知できます。これは、サイト全体の設定で構成できるようにしたい機能の例です。

だから私は設定モデルか何かを持っているべきだと思っていました. SMS 通知用に has_many :contacts できるようにしたいので、モデルにする必要があります。

問題は、データベースに設定モデルの投稿が 1 つしか存在できないことです。だから私はシングルトンモデルを使用することを考えていましたが、それは新しいオブジェクトが作成されるのを妨げるだけですか?

次のように、属性ごとに getter および setter メソッドを作成する必要がありますか?

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

def self.attribute
  Model.first.attribute
end

Model.attribute を直接使用するのはおそらくベストプラクティスではありませんが、常にそのインスタンスを作成して使用しますか?

ここで何をすべきですか?

4

13 に答える 13

68

(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.)

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.

The easiest way to implement this is:

  1. Add a new model, with one column for each property you need
  2. 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)
  3. Add a static helper method to the model class to load the singleton row

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!" like so:

def self.instance
  first_or_create!(singleton_guard: 0)
end
于 2012-09-17T16:13:03.137 に答える
25

一般的な意見には同意しません。データベースからプロパティを読み取ることには何の問題もありません。必要に応じてデータベースの値を読み取ってフリーズすることもできますが、単純なフリーズに代わるより柔軟な方法があるかもしれません。

YAML はデータベースとどう違うのですか? .. 同じドリル - アプリケーション コードの永続的な設定の外部。

データベース アプローチの優れた点は、多かれ少なかれ安全な方法で (ファイルを直接開いて上書きするのではなく) その場で変更できることです。もう 1 つの優れた点は、クラスタ ノード間でネットワークを介して共有できることです (適切に実装されている場合)。

ただし、ActiveRecord を使用してそのような設定を実装する適切な方法は何かという問題が残ります。

于 2009-01-13T21:40:38.197 に答える
14

次のように、最大​​ 1 つのレコードを適用することもできます。

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ため、既に存在するクラスの新しいインスタンスを作成しようとすると失敗します。

次に、1 つのレコードに作用するクラス メソッドのみを定義することができます。

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
于 2013-06-28T20:57:55.220 に答える
8

このような基本的なニーズのためにデータベース/ActiveRecord/モデルのオーバーヘッドを無駄にするかどうかはわかりません。このデータは比較的静的であり(私が想定している)、その場で計算する必要はありません(データベースの検索を含む)。

そうは言っても、サイト全体の設定でYAMLファイルを定義し、設定を定数にロードする初期化ファイルを定義することをお勧めします。不要な可動部品はほとんどありません。

データをメモリに保存して、複雑さを大幅に軽減できない理由はありません。定数はどこでも使用でき、初期化またはインスタンス化する必要はありません。クラスをシングルトンとして利用することが絶対に重要な場合は、次の2つのことを行うことをお勧めします。

  1. initialize/newメソッドの定義を解除します
  2. self。*メソッドのみを定義してください。そうすれば、状態を維持することはできません。
于 2008-12-30T05:36:02.070 に答える
6

これが古いスレッドであることは知っていますが、同じことが必要であり、このための gem があることがわかりました: act_as_singleton

インストール手順は Rails 2 向けですが、Rails 3 でもうまく機能します。

于 2013-02-26T17:43:19.963 に答える
3

シングルトンを必要としないオッズは良いです。パターンの流行から生まれる最悪のデザイン習慣の1つが、最も一般的に採用されているものの1つでもあるのは残念です。私は不幸な単純さの外見を非難します、しかし私は逸脱します。もし彼らがそれを「静的グローバル」パターンと呼んでいたとしたら、人々はそれを使うことについてもっと羊っぽいものだっただろうと私は確信しています。

シングルトンに使用するクラスのプライベート静的インスタンスでラッパークラスを使用することをお勧めします。シングルトンの場合のように、コード全体にタイトなカップルを導入することはありません。

これをモノステートパターンと呼ぶ人もいます。機能を公開/非表示にするさまざまなインターフェイスを実装することで柔軟性を高めることができるため、私はこれを戦略/エージェントの概念の単なる別のひねりと考える傾向があります。

于 2008-12-30T04:58:18.403 に答える
2

以前の回答にいくつかのコメントを付けます。

  • 一意のインデックス用に別のフィールドは必要ありませんid。フィールドに制約を設定できます。idとにかくRailsアプリにはフィールドが必要なので、それは良いトレードオフです
  • モデルと明示的な ID 割り当てに関する特別な検証は必要ありません。列にデフォルト値を設定するだけで十分です。

これらの変更を適用すると、ソリューションは非常に簡単になります。

# migration
create_table :settings, id: false do |t|
  t.integer :id, null: false, primary_key: true, default: 1, index: {unique: true}
  t.integer :setting1
  t.integer :setting2
  ...
end

# model
class Settings < ApplicationRecord
  def self.instance
    first_or_create!(...)
  end  
end
于 2019-12-10T13:24:50.743 に答える
2

単純:

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
于 2017-05-11T14:11:15.137 に答える
1

Configatron もチェックしてください。

http://configatron.mackframework.com/

Configatron を使用すると、アプリケーションとスクリプトを非常に簡単に構成できます。定数やグローバル変数を使用する必要はもうありません。シンプルで痛みのないシステムを使用して、生活を構成できるようになりました。そして、それはすべてRubyなので、やりたいことは何でもできます!

于 2009-01-02T01:46:32.890 に答える
1

使用has_many :contactsするからといって、モデルが必要なわけではありません。 has_manyいくつかの魔法を行いますが、最終的には、指定されたコントラクトを持つメソッドを追加するだけです. has_many :contactsモデルをそのように動作させるためにこれらのメソッド (または必要なサブセット) を実装できないのに、実際には Contact に ActiveRecord モデル (またはモデルをまったく使用しない) を使用できない理由はありません。

于 2008-12-30T06:32:14.173 に答える