Способ, которым системы достижений обычно работают, заключается в том, что существует большое количество различных достижений, которые могут быть инициированы, и есть набор триггеров, которые можно использовать для проверки, нужно ли запускать достижение или нет,
Использование полиморфной ассоциации, вероятно, является плохой идеей, потому что загрузка всех достижений и их проверка могут оказаться сложным упражнением.Есть также тот факт, что вам придется выяснить, как выразить условия успеха или неудачи в какой-то таблице, но во многих случаях вы можете получить определение, которое не отображается так аккуратно.Вы можете получить шестьдесят разных таблиц, представляющих все виды триггеров, и это похоже на кошмарный сон.
Альтернативным подходом может быть определение ваших достижений с точки зрения имени, ценности и так далее,и иметь таблицу констант, которая действует как хранилище ключей / значений.
Вот пример миграции:
create_table :achievements do |t|
t.string :name
t.integer :points
t.text :proc
end
create_table :trigger_constants do |t|
t.string :key
t.integer :val
end
create_table :user_achievements do |t|
t.integer :user_id
t.integer :achievement_id
end
Столбец achievements.proc
содержит код Ruby, который вы оцениваете, чтобы определить, является ли достижениедолжен срабатывать или нет.Обычно это загружается, упаковывается и заканчивается как служебный метод, который вы можете вызвать:
class Achievement < ActiveRecord::Base
def proc
@proc ||= eval("Proc.new { |user| #{read_attribute(:proc)} }")
rescue
nil # You might want to raise here, rescue in ApplicationController
end
def triggered_for_user?(user)
# Double-negation returns true/false only, not nil
proc and !!proc.call(user)
rescue
nil # You might want to raise here, rescue in ApplicationController
end
end
Класс TriggerConstant
определяет различные параметры, которые вы можете настроить:
class TriggerConstant < ActiveRecord::Base
def self.[](key)
# Make a direct SQL call here to avoid the overhead of a model
# that will be immediately discarded anyway. You can use
# ActiveSupport::Memoizable.memoize to cache this if desired.
connection.select_value(sanitize_sql(["SELECT val FROM `#{table_name}` WHERE key=?", key.to_s ]))
end
end
ИмеяНеобработанный код Ruby в вашей БД означает, что легче настроить правила на лету без необходимости повторного развертывания приложения, но это может усложнить тестирование.
Пример proc
может выглядеть следующим образом:
user.max_weight_lifted > TriggerConstant[:brickhouse_weight_required]
Если вы хотите упростить свои правила, вы можете создать что-то, что автоматически расширит $brickhouse_weight_required
в TriggerConstant[:brickhouse_weight_required]
.Это сделало бы его более читабельным для нетехнических людей.
Чтобы не помещать код в вашу БД, что может показаться кому-то плохим, вам придется самостоятельно определять эти процедуры в какой-то массовой процедуре.файл и передать различные параметры настройки по какому-то определению.Этот подход будет выглядеть следующим образом:
module TriggerConditions
def max_weight_lifted(user, options)
user.max_weight_lifted > options[:weight_required]
end
end
Настройте таблицу Achievement
таким образом, чтобы она сохраняла информацию о том, какие параметры следует передавать:
create_table :achievements do |t|
t.string :name
t.integer :points
t.string :trigger_type
t.text :trigger_options
end
В этом случае trigger_options
являетсятаблица отображения, которая хранится сериализовано.Примером может быть:
{ :weight_required => :brickhouse_weight_required }
Комбинируя это, вы получаете несколько упрощенный, менее eval
счастливый результат:
class Achievement < ActiveRecord::Base
serialize :trigger_options
# Import the conditions which are defined in a separate module
# to avoid cluttering up this file.
include TriggerConditions
def triggered_for_user?(user)
# Convert the options into actual values by converting
# the values into the equivalent values from `TriggerConstant`
options = trigger_options.inject({ }) do |h, (k, v)|
h[k] = TriggerConstant[v]
h
end
# Return the result of the evaluation with these options
!!send(trigger_type, user, options)
rescue
nil # You might want to raise here, rescue in ApplicationController
end
end
Вам часто приходится пробираться через целую кучуAchievement
записей, чтобы увидеть, были ли они достигнуты, если у вас нет таблицы сопоставления, которая может определить, в упрощенном виде, какие записи тестируют триггеры.Более надежная реализация этой системы позволит вам определить конкретные классы для наблюдения за каждым достижением, но этот базовый подход должен, по крайней мере, служить основой.