I'm attempting, poorly, to implement an achievement system into my Ruby on Rails application.
我正在尝试将一个成就系统实现到我的Ruby on Rails应用程序中。
I have a long list of achievements I'd like to check for. All are triggered by some create action in various controllers.
我有一长串我想要检查的成就。所有这些都是由各种控制器中的创建操作触发的。
I've had the idea that I'll have a achievement model, which includes the controller and action it responds to. Then do a before filter for the create and check for applicable achievements. I get stuck when it comes to actually defining/executing the achievements. Each achievement may require different data. For example one will want to know how many questions a user has answered, another how many comments they've made, and a third how many people the user invited have responded.
我有一个想法,我将有一个成就模型,包括控制器和它响应的动作。然后做一个前过滤器为创建和检查适用的成就。当涉及到实际定义/执行成就时,我就会陷入困境。每个成就可能需要不同的数据。例如,一个用户想知道一个用户回答了多少问题,另一个用户做了多少评论,第三个用户邀请了多少人来回答。
IS the best thing to do to actually just embed all the necessary ruby code straight into the DB? I could see doing a self contained block that does all the active record finding, etc and returns true/false, though there we are still some issues about knowing what is setup in advance (i.e. current_user, etc).
最好的做法是直接将所有必需的ruby代码嵌入到DB中吗?我可以看到做一个自包含的块,它执行所有的活动记录查找等等,并返回true/false,尽管我们仍然存在一些问题,即预先知道什么是设置(例如current_user,等等)。
Any reasonable best practices out there that don't make me feel dirty? I could see a full on policy/rules engine being one path, though that may scare me more than plan a.
有什么合理的最佳实践不会让我觉得脏吗?我可以看到一个完整的政策/规则引擎是一条路,尽管这可能比计划a更让我害怕。
thanks! Oren
谢谢!奥伦
3 个解决方案
#1
52
I agree with your idea to use an Achievement
model.
我同意你使用成就模型的想法。
You should probably not implement the triggers in your controllers, though. Imagine that you have two ways to post a comment; you will inevitably get code duplication. This sort of behaviour belongs in a model.
不过,您可能不应该在控制器中实现触发器。想象一下,你有两种方式发表评论;您将不可避免地获得代码复制。这种行为属于模型。
Suppose you want to track the number of comments that a user makes, and award an achievement for 100 comments. You could have the following models:
假设您希望跟踪用户做出的评论数量,并奖励100条评论的成果。你可以有以下模型:
class User < ActiveRecord::Base
has_many :comments
has_many :achievements
def award(achievement)
achievements << achievement.new
end
def awarded?(achievement)
achievements.count(:conditions => { :type => achievement }) > 0
end
end
class Achievement < ActiveRecord::Base
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :user
end
class CommentAchievement < Achievement
def self.check_conditions_for(user)
# Check if achievement is already awarded before doing possibly expensive
# operations to see if the achievement conditions are met.
if !user.awarded?(self) and user.comments.size > 100
user.award(self)
end
end
end
The different achievements are all subclasses of Achievement
model, and use single table inheritance so that they are stored in just one table. The subclasses can contain all logic required for each individual achievement. You can also store additional information in this model, such as the date on which the achievement was awarded. To make sure that the database rejects duplicate achievements, you could create a UNIQUE
index on the type
and user_id
columns.
不同的成就都是成就模型的子类,并使用单表继承,以便它们只存储在一个表中。子类可以包含每个个体成就所需的所有逻辑。您还可以在该模型中存储额外的信息,例如获得该成果的日期。为了确保数据库拒绝重复的成就,您可以在类型和user_id列上创建一个惟一的索引。
CommentAchievement.check_conditions_for(user)
can be called whenever you wish to. You may create a background job that runs every now and then, or you could create an observer:
check_conditions_for(用户)可以随时调用。你可以创建一个不时运行的背景作业,或者你可以创建一个观察者:
# app/models/comment_achievement_observer.rb
class CommentAchievementObserver < ActiveRecord::Observer
observe :comment
def after_create(comment)
CommentAchievement.check_conditions_for(comment.user)
end
end
# config/environment.rb
config.active_record.observers = :comment_achievement_observer
The above is just one idea of how to do it, of course there may be others. The code is just an example, I haven't actually tested it. Hopefully it's of some help to you.
以上只是一种方法,当然还有其他的方法。代码只是一个例子,我还没有测试过。希望对你有所帮助。
#2
18
Really nice solution, molf.
很好的解决方案,molf。
I rolled this in to a plugin / gem with generators for new achievements:
我将它提交到一个插件/ gem中以获得新的成就:
http://github.com/paulca/paths_of_glory
http://github.com/paulca/paths_of_glory
Happy achieving!
实现快乐!
#3
7
I wrote a Rails 3 gem for this task, which works for badges, points and rankings. You can find the source code in https://github.com/tute/merit.
我为这个任务编写了Rails 3 gem,它适用于徽章、积分和排名。您可以在https://github.com/tute/merit中找到源代码。
#1
52
I agree with your idea to use an Achievement
model.
我同意你使用成就模型的想法。
You should probably not implement the triggers in your controllers, though. Imagine that you have two ways to post a comment; you will inevitably get code duplication. This sort of behaviour belongs in a model.
不过,您可能不应该在控制器中实现触发器。想象一下,你有两种方式发表评论;您将不可避免地获得代码复制。这种行为属于模型。
Suppose you want to track the number of comments that a user makes, and award an achievement for 100 comments. You could have the following models:
假设您希望跟踪用户做出的评论数量,并奖励100条评论的成果。你可以有以下模型:
class User < ActiveRecord::Base
has_many :comments
has_many :achievements
def award(achievement)
achievements << achievement.new
end
def awarded?(achievement)
achievements.count(:conditions => { :type => achievement }) > 0
end
end
class Achievement < ActiveRecord::Base
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :user
end
class CommentAchievement < Achievement
def self.check_conditions_for(user)
# Check if achievement is already awarded before doing possibly expensive
# operations to see if the achievement conditions are met.
if !user.awarded?(self) and user.comments.size > 100
user.award(self)
end
end
end
The different achievements are all subclasses of Achievement
model, and use single table inheritance so that they are stored in just one table. The subclasses can contain all logic required for each individual achievement. You can also store additional information in this model, such as the date on which the achievement was awarded. To make sure that the database rejects duplicate achievements, you could create a UNIQUE
index on the type
and user_id
columns.
不同的成就都是成就模型的子类,并使用单表继承,以便它们只存储在一个表中。子类可以包含每个个体成就所需的所有逻辑。您还可以在该模型中存储额外的信息,例如获得该成果的日期。为了确保数据库拒绝重复的成就,您可以在类型和user_id列上创建一个惟一的索引。
CommentAchievement.check_conditions_for(user)
can be called whenever you wish to. You may create a background job that runs every now and then, or you could create an observer:
check_conditions_for(用户)可以随时调用。你可以创建一个不时运行的背景作业,或者你可以创建一个观察者:
# app/models/comment_achievement_observer.rb
class CommentAchievementObserver < ActiveRecord::Observer
observe :comment
def after_create(comment)
CommentAchievement.check_conditions_for(comment.user)
end
end
# config/environment.rb
config.active_record.observers = :comment_achievement_observer
The above is just one idea of how to do it, of course there may be others. The code is just an example, I haven't actually tested it. Hopefully it's of some help to you.
以上只是一种方法,当然还有其他的方法。代码只是一个例子,我还没有测试过。希望对你有所帮助。
#2
18
Really nice solution, molf.
很好的解决方案,molf。
I rolled this in to a plugin / gem with generators for new achievements:
我将它提交到一个插件/ gem中以获得新的成就:
http://github.com/paulca/paths_of_glory
http://github.com/paulca/paths_of_glory
Happy achieving!
实现快乐!
#3
7
I wrote a Rails 3 gem for this task, which works for badges, points and rankings. You can find the source code in https://github.com/tute/merit.
我为这个任务编写了Rails 3 gem,它适用于徽章、积分和排名。您可以在https://github.com/tute/merit中找到源代码。