Search code examples
ruby-on-railsrubymodels

How To Associate Rails Model Records With Code?


If I have a Rails model for something like Quests (like in a game) I'd have attributes like name, description, ect. but I'd also need some code associated with each quest to determine if the requirements have been met by a player to complete the quest.

What is the best way to associate some code with records from a model? Should I make a quest_function attribute in the model and write a function for each record in the models/quest.rb file when I create a new quest? This seems clunky and error prone.

How should something like this be done in Rails?


Solution

  • Well, you have several options.

    The first, like you said, is to store a method/function name in your record and then call that method whenever you need to evaluate whether that Quest for that record has been completed. In rough code:

    class Quest < ActiveRecord::Base
        attr_accessible :name, :quest_method
    
        def completed?
          self.send(quest_method)
        end
    
        def end_quest
           Monster.find_by_name('FINAL BOSS').dead?
        end
    end
    
    Quest.create name: 'End Quest', quest_method: 'end_quest'
    

    This isn't quite as bad as you think. This is essentially how single table inheritance works, but instead of storing a method name it stores the class name in the type column.

    class Quest < ActiveRecord::Base
    
        def finished?
            # default value, or throw an error
            false
        end
    
    end
    
    class EndQuest < Quest
    
        def finished?
            Monster.find_by_name('END BOSS').dead?
        end
    end
    
    EndQuest.create # have to do this for all quests
    

    In either case, you're writing the behavior as code, and just storing a reference to the desired behavior in your record. You do have to create all the records by hand with the proper types or method names and make sure everything stays in sync. But at least with single-table inheritance ActiveRecord takes care of some of that.

    There's nothing stopping you, however, from storing code itself into the database.

    class Quest < ActiveRecord::Base
    
        attr_accessible :name, :completed_expr
    
        def completed?
            eval(completed_expr)
        end
    
    end
    
    Quest.create name: 'END QUEST', 
      completed_expr: "Monster.find_by_name('BIG BOSS').dead?"
    

    This, of course is potentially dangerous and error prone unless you know exactly what you're doing—but it does give you complete flexibility to rewrite the quest completion criteria at run-time.

    Finally, if you want the most sophistication and complexity, you might want to look at using a rules engine (or, rolling out your own complete with DSL) to encompass your business (game world) rules and store that.

    class Quest < ActiveRecord::Base
        attr_accessible :name, :completion_rule
    
        def completed?
            RulesEngine.evaluate(completion_rule)
        end
    end
    
    Quest.create name: 'END QUEST', 
      completion_rule: "Monster(name: 'BIG BOSS', state: 'dead')"
    

    The main advantage is by storing your quest-specific behavior or rule in a DSL (which is not raw Ruby code) you get some level of security—you can't just accidentally eval malicious or errant code and bring the house down.

    You can also gain some efficiency if your production rules are modeled in such a way where you can use something like the Rete algorithm to evaluate them.