Как реализовать одноэлементную модель
У меня есть сайт в рельсах и вы хотите иметь настройки на сайте. Одна часть моего приложения может уведомлять администратора по 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, но всегда создавать экземпляр и использовать это?
Что мне здесь делать?
Ответы
Ответ 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