Mun*_*PhD 6 ruby-on-rails achievements polymorphic-associations
我正试图在Ruby on Rails中设计一个成就系统,并且遇到了我的设计/代码.
试图使用多态关联:
class Achievement < ActiveRecord::Base
belongs_to :achievable, :polymorphic => true
end
class WeightAchievement < ActiveRecord::Base
has_one :achievement, :as => :achievable
end
Run Code Online (Sandbox Code Playgroud)
迁移:
class CreateAchievements < ActiveRecord::Migration
... #code
create_table :achievements do |t|
t.string :name
t.text :description
t.references :achievable, :polymorphic => true
t.timestamps
end
create_table :weight_achievements do |t|
t.integer :weight_required
t.references :exercises, :null => false
t.timestamps
end
... #code
end
Run Code Online (Sandbox Code Playgroud)
然后,当我尝试下面的抛弃单元测试时,它会失败,因为它表示该成就为空.
test "parent achievement exists" do
weightAchievement = WeightAchievement.find(1)
achievement = weightAchievement.achievement
assert_not_nil achievement
assert_equal 500, weightAchievement.weight_required
assert_equal achievement.name, "Brick House Baby!"
assert_equal achievement.description, "Squat 500 lbs"
end
Run Code Online (Sandbox Code Playgroud)
我的灯具:achievement.yml ......
BrickHouse:
id: 1
name: Brick House
description: Squat 500 lbs
achievable: BrickHouseCriteria (WeightAchievement)
Run Code Online (Sandbox Code Playgroud)
weight_achievements.ym ...
BrickHouseCriteria:
id: 1
weight_required: 500
exercises_id: 1
Run Code Online (Sandbox Code Playgroud)
尽管如此,我不能让它运行,也许在宏伟的计划中,这是一个糟糕的设计问题.我正在尝试做的是拥有一张包含所有成就及其基本信息(名称和描述)的表格.使用该表和多态关联,我想链接到其他表,其中包含完成该成就的标准,例如WeightAchievement表将具有所需的权重和运动ID.然后,用户的进度将存储在UserProgress模型中,在该模型中,它链接到实际成就(而不是WeightAchievement).
我需要在单独的表中使用标准的原因是因为标准在不同类型的成就之间会有很大差异,之后会动态添加,这就是为什么我不为每个成就创建单独的模型.
这甚至有意义吗?我是否应该将Achievement表与WeightAchievement等特定类型的成就合并(因此表格是name,description,weight_required,exercise_id),然后当用户查询成就时,在我的代码中我只搜索所有成就?(例如WeightAchievement,EnduranceAchievement,RepAchievement等)
tad*_*man 13
成就系统通常的工作方式是可以触发大量的各种成就,并且有一组触发器可用于测试是否应该触发成就.
使用多态关联可能是个坏主意,因为加载所有要完成的成就并测试它们都可能最终成为一项复杂的练习.还有一个事实是你必须弄清楚如何在某种表格中表达成功或失败条件,但在很多情况下,你最终可能会得到一个没有如此整齐地映射的定义.您最终可能会有六十个不同的表来表示所有不同类型的触发器,这听起来像是一个需要维护的噩梦.
另一种方法是根据名称,值等来定义您的成就,并使用一个常量表作为键/值存储.
这是一个示例迁移:
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
Run Code Online (Sandbox Code Playgroud)
该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
Run Code Online (Sandbox Code Playgroud)
在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
Run Code Online (Sandbox Code Playgroud)
在数据库中使用原始Ruby代码意味着无需重新部署应用程序即可轻松调整规则,但这可能会使测试更加困难.
示例proc可能如下所示:
user.max_weight_lifted > TriggerConstant[:brickhouse_weight_required]
Run Code Online (Sandbox Code Playgroud)
如果你想简化你的规则,你可能会创造一些扩展$brickhouse_weight_required到TriggerConstant[:brickhouse_weight_required]自动.这将使非技术人员更具可读性.
为了避免将代码放入数据库中,某些人可能会觉得这些代码质量很差,您必须在某个批量过程文件中独立定义这些过程,并通过某种定义传递各种调整参数.这种方法看起来像:
module TriggerConditions
def max_weight_lifted(user, options)
user.max_weight_lifted > options[:weight_required]
end
end
Run Code Online (Sandbox Code Playgroud)
调整Achievement表,以便存储有关要传入的选项的信息:
create_table :achievements do |t|
t.string :name
t.integer :points
t.string :trigger_type
t.text :trigger_options
end
Run Code Online (Sandbox Code Playgroud)
在这种情况下,trigger_options是一个存储序列化的映射表.一个例子可能是:
{ :weight_required => :brickhouse_weight_required }
Run Code Online (Sandbox Code Playgroud)
结合这一点,你得到一个简化,不太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
Run Code Online (Sandbox Code Playgroud)
Achievement除非你有一个映射表,可以用松散的术语定义触发器测试的记录类型,否则你经常需要选择一堆记录来查看它们是否已经实现.更加强大的系统实现将允许您定义要为每个成就观察的特定类,但这种基本方法至少应该作为基础.
| 归档时间: |
|
| 查看次数: |
1399 次 |
| 最近记录: |