Typically, success systems are that there are many different achievements that can be triggered, and there must be a set of triggers that can be used to test or achieve success.
Using polymorphic association is probably a bad idea, because downloading all the achievements you need to go through and test them can be a difficult exercise. There is also the fact that you will need to figure out how to express the conditions of success or failure in some table, but in many cases you can get a definition that is not so well displayed. You can have sixty different tables to represent all the different triggers, and that sounds like a nightmare to maintain.
An alternative approach is to determine your achievements in terms of name, meaning, etc. and have a persistent table that acts as a key / value store.
Here's a migration example:
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
The achievements.proc column contains the Ruby code that you evaluate to determine whether the achievement should run or not. This usually loads, wrapped, and exits as a utility method that you can call:
class Achievement < ActiveRecord::Base def proc @proc ||= eval("Proc.new { |user| #{read_attribute(:proc)} }") rescue nil
The TriggerConstant class defines various parameters that you can configure:
class TriggerConstant < ActiveRecord::Base def self.[](key)
Having the Ruby source code in your database means that it is easier to adjust the rules on the fly without having to redeploy the application, but this can make testing difficult.
A sample proc might look like this:
user.max_weight_lifted > TriggerConstant[:brickhouse_weight_required]
If you want to simplify your rules, you can create something that automatically extends $brickhouse_weight_required to TriggerConstant[:brickhouse_weight_required] . This would make it more understandable to non-technical people.
In order not to put the code in your database, which, according to some people, may be bad, you will have to independently determine these procedures in some kind of volumetric procedure file and transfer various settings using a specific definition. This approach would look like this:
module TriggerConditions def max_weight_lifted(user, options) user.max_weight_lifted > options[:weight_required] end end
Adjust the Achievement table so that it stores information about which parameters should be passed:
create_table :achievements do |t| t.string :name t.integer :points t.string :trigger_type t.text :trigger_options end
In this case, trigger_options is a mapping table that is stored in serialized form. An example would be:
{ :weight_required => :brickhouse_weight_required }
Combining this, you get a slightly simplified, less eval happy result:
class Achievement < ActiveRecord::Base serialize :trigger_options
You often have to draw the entire stack of Achievement records to make sure they are reached if you do not have a mapping table that can freely determine which records the triggers check. A more robust implementation of this system will allow you to define specific classes for monitoring each achievement, but this basic approach should at least serve as the basis.