Как реализовать одноэлементную модель - PullRequest
28 голосов
/ 30 декабря 2008

У меня есть сайт в рельсах, и я хочу иметь настройки для всего сайта. Одна часть моего приложения может уведомить администратора по SMS, если произойдет определенное событие. Это пример функции, которую я хочу настроить с помощью настроек для всего сайта.

Так что я подумал, что у меня должна быть модель настройки или что-то в этом роде. Это должна быть модель, потому что я хочу иметь возможность has_many: контакты для SMS-уведомления.

Проблема в том, что в модели настроек может быть только один пост в базе данных. Я подумал об использовании модели Singleton, но это только мешает созданию нового объекта, верно?

Нужно ли мне создавать методы получения и установки для каждого атрибута следующим образом:

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

def self.attribute
  Model.first.attribute
end

Возможно, не рекомендуется использовать Model.attribute напрямую, но всегда создавать его экземпляр и использовать его?

Что мне здесь делать?

Ответы [ 12 ]

55 голосов
/ 17 сентября 2012

(Я согласен с @ user43685 и не согласен с @Derek P - есть много веских причин для сохранения данных по всему сайту в базе данных вместо файла yaml. Например: ваши настройки будут доступны на всех веб-серверах (если у вас несколько веб-серверов); изменения в ваших настройках будут ACID; вам не нужно тратить время на внедрение оболочки YAML и т. д. и т. д.)

В rails это достаточно просто реализовать, вы просто должны помнить, что ваша модель должна быть "одноэлементной" в терминах базы данных, а не в терминах ruby-объектов.

Самый простой способ реализовать это:

  1. Добавить новую модель с одним столбцом для каждого необходимого свойства
  2. Добавьте специальный столбец с именем «singleton_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» на вызов « first_or_create! » следующим образом:

def self.instance
  first_or_create!(singleton_guard: 0)
end
24 голосов
/ 14 января 2009

Я не согласен с общим мнением - нет ничего плохого в том, чтобы считать свойство из базы данных. Вы можете прочитать значение базы данных и заморозить, если хотите, однако могут быть более гибкие альтернативы простому замораживанию.

Чем YAML отличается от базы данных? .. та же самая тренировка - внешняя по отношению к постоянной настройке кода приложения.

Хорошая особенность подхода к базе данных состоит в том, что он может быть изменен на лету более или менее безопасным способом (не открывая и не перезаписывая файлы напрямую). Еще одна приятная вещь - это возможность совместного использования по сети между узлами кластера (при правильной реализации).

Однако остается вопрос: как правильно реализовать такую ​​настройку с помощью ActiveRecord?

11 голосов
/ 29 июня 2013

Вы также можете установить максимум одну запись следующим образом:

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
10 голосов
/ 30 декабря 2008

Я не уверен, что потратил бы впустую накладные расходы на базу данных / ActiveRecord / Model для такой базовой потребности. Эти данные относительно статичны (я предполагаю) и на лету расчеты не нужны (включая поиск в базе данных).

Сказав это, я бы порекомендовал вам определить файл YAML с настройками вашего сайта и определить файл инициализатора, который загружает настройки в константу. У вас не будет почти столько ненужных движущихся частей.

Нет причины, по которой данные не могли бы просто храниться в памяти и сэкономить массу сложности. Константы доступны везде, и их не нужно инициализировать или создавать. Если абсолютно важно, чтобы вы использовали класс как синглтон, я бы порекомендовал сделать следующие две вещи:

  1. undef инициализировать / новый метод
  2. определяйте только свои. * Методы таким образом, что вы не сможете поддерживать состояние
6 голосов
/ 26 февраля 2013

Я знаю, что это старая ветка, но мне просто нужно было то же самое, и я обнаружил, что для этого есть драгоценный камень: acts_as_singleton .

Инструкции по установке предназначены для Rails 2, но он прекрасно работает и с Rails 3.

3 голосов
/ 30 декабря 2008

Шансы хорошие, вам не нужен синглтон. К сожалению, одна из наихудших привычек дизайна, возникающая в моде, также является одной из наиболее распространенных. Я обвиняю в неудачном появлении простоты, но я отвлекся. Если бы они назвали это «Статическим глобальным» паттерном, я уверен, что люди были бы более смущены его использованием.

Я предлагаю использовать класс-оболочку с закрытым статическим экземпляром класса, который вы хотите использовать для синглтона. Вы не будете вводить тесную пару в свой код, как в случае синглтона.

Некоторые люди называют это моногосударственным паттерном. Я склонен думать об этом как об очередном повороте концепции стратегии / агента, поскольку вы можете обеспечить большую гибкость, реализуя различные интерфейсы для раскрытия / скрытия функциональности.

2 голосов
/ 11 мая 2017

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
1 голос
/ 13 февраля 2019

Вы можете сделать это так:

 class Config < ApplicationRecord
  def self.instance
    Config.first || Config.create!
  end
 end
1 голос
/ 22 ноября 2016
class Constant < ActiveRecord::Base
  after_initialize :readonly!

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

Constant :: FIELD_NAME

1 голос
/ 02 января 2009

Вы также можете проверить Configatron:

http://configatron.mackframework.com/

Configatron делает настройку ваших приложений и сценариев невероятно простой. Больше нет необходимости использовать константы или глобальные переменные. Теперь вы можете использовать простую и безболезненную систему для настройки своей жизни. И, поскольку все это Ruby, вы можете делать любые безумные вещи, которые хотели бы!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...