Подтвердить что ты не робот

Как реализовать одноэлементную модель

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

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

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

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

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

def self.attribute
  Model.first.attribute
end

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

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

4b9b3361

Ответ 1

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

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

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

  • undef метод initialize/new
  • определить только self. * методы таким образом, чтобы вы не могли поддерживать состояние

Ответ 2

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

В рельсах это достаточно просто реализовать, вам просто нужно помнить, что ваша модель должна быть "singleton" в терминах базы данных, а не в терминах Ruby.

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

  • Добавьте новую модель с одним столбцом для каждого свойства, которое вам нужно
  • Добавьте специальный столбец "singleton_guard" и подтвердите, что он всегда равен "0", и пометьте его как уникальный (это обеспечит соблюдение только одной строки в базе данных для этой таблицы)
  • Добавить статический вспомогательный метод в класс модели для загрузки одиночной строки

Итак, миграция должна выглядеть примерно так:

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 вы должны иметь возможность заменить тело "экземпляра" геттера вызовом " first_or_create!"

Ответ 3

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

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

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

Однако остается вопрос, каким будет правильный способ реализации такой настройки с помощью ActiveRecord.

Ответ 4

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

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

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

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

Ответ 6

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

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

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

Ответ 7

Простой:

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

Использование has_many :contacts не означает, что вам нужна модель. has_many делает некоторую магию, но в итоге он просто добавляет некоторый метод с указанным контрактом. Нет причин, по которым вы не можете реализовать эти методы (или какое-то подмножество, которое вам нужно), чтобы ваша модель вела себя как has_many :contacts, но на самом деле не использует модель ActiveRecord (или вообще модель) для Contact.

Ответ 9

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

http://configatron.mackframework.com/

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

Ответ 10

class Constant < ActiveRecord::Base
  after_initialize :readonly!

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

Constant:: FIELD_NAME