gpt4 book ai didi

ruby-on-rails - RoR成绩系统-多态关联和设计问题

转载 作者:行者123 更新时间:2023-12-04 03:47:05 25 4
gpt4 key购买 nike

我正在尝试在Ruby on Rails中设计成就系统,并且遇到了我的设计/代码的麻烦。

尝试使用多态关联:

class Achievement < ActiveRecord::Base
belongs_to :achievable, :polymorphic => true
end

class WeightAchievement < ActiveRecord::Base
has_one :achievement, :as => :achievable
end

迁移:
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

然后,当我尝试以下一次性单元测试时,它失败了,因为它说成就为空。
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

和我的装置:
Achievement.yml ...
BrickHouse:
id: 1
name: Brick House
description: Squat 500 lbs
achievable: BrickHouseCriteria (WeightAchievement)

weight_achievements.ym ...
 BrickHouseCriteria:
id: 1
weight_required: 500
exercises_id: 1

即使,我也无法运行它,也许在宏伟的计划中,这是一个糟糕的设计问题。我试图做的是拥有一张包含所有成就及其基本信息(名称和描述)的表格。使用该表格和多态关联,我想链接到其他表格,这些表格将包含完成该成就的标准,例如WeightAchievement表将具有所需的体重和运动ID。然后,用户的进度将存储在UserProgress模型中,并链接到实际的成就(与WeightAchievement相对)。

我需要在单独的表格中使用条件的原因是,条件在不同类型的成就之间会发生很大的差异,并且以后会动态添加,这就是为什么我没有为每个成就创建单独的模型的原因。

这有道理吗?我是否应该仅将成就表与特定类型的成就合并,例如WeightAchievement(表是名称,描述,weight_required,exercise_id),然后当用户查询成就时,我只需在我的代码中搜索所有成就? (例如,体重成就,耐力成就,重复成就等)

最佳答案

成就系统通常的工作方式是可以触发大量各种各样的成就,并且有一组触发器可以用来测试是否应触发某个成就。

使用多态关联可能不是一个好主意,因为加载所有要运行的成就并测试所有成就可能最终是一项复杂的工作。还有一个事实,就是您必须弄清楚如何在某种表中表达成功或失败的条件,但是在许多情况下,您可能最终得到的定义并没有那么整齐地映射。您可能最终会有60个不同的表来代表所有不同种类的触发器,这听起来像是一场噩梦,难以维持。

另一种方法是根据名称,值(value)等来定义您的成就,并拥有一个用作键/值存储的常量表。

这是一个示例迁移:

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记录以查看它们是否已实现。此系统的更强大实现将允许您定义每个成就要观察的特定类,但是这种基本方法至少应作为基础。

关于ruby-on-rails - RoR成绩系统-多态关联和设计问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3538282/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com