paths_of_glory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG ADDED
@@ -0,0 +1,7 @@
1
+ Mon Feb 22 2010
2
+ --------------
3
+ - Added "options" type for list
4
+
5
+ Mon Feb 8 2010
6
+ --------------
7
+ - Added "integer" and "decimal" types
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ Paths of Glory
2
+ ==============
3
+
4
+ Paths of Glory sets you on the road to achievements!
5
+
6
+ In short, it's a re-usable model for creating an achievement system.
7
+
8
+ It's extracted from http://peertester.com and inspired by: http://stackoverflow.com/questions/885277/how-to-implement-an-achievement-system-in-ror
9
+
10
+
11
+
12
+ Installation
13
+ ============
14
+
15
+ Paths of Glory is easy to install.
16
+
17
+ As a plugin:
18
+
19
+ ./script/plugin install git://github.com/paulca/paths_of_glory.git
20
+
21
+ Or as a gem. Add this to your environment.rb:
22
+
23
+ gem install paths_of_glory
24
+
25
+ config.gem 'paths_of_glory'
26
+
27
+ Then generate the migration:
28
+
29
+ ./script/generate paths_of_glory
30
+
31
+ And run the migration:
32
+
33
+ rake db:migrate
34
+
35
+ This creates the tables.
36
+
37
+ Then, in your User model:
38
+
39
+ class User < ActiveRecord::Base
40
+ include Achievements
41
+ end
42
+
43
+ Basic Usage
44
+ ===========
45
+
46
+ Paths of Glory gives you access to a DSL to generate achievements. Achievements can have multiple levels or can just be standalone achievements.
47
+
48
+ The pattern is basically:
49
+
50
+ Create an achievement:
51
+
52
+ ./script/generate achievement Glory
53
+
54
+ This gives you `app/models/achievements/glory.rb` and `app/models/achievements/glory_observer.rb` with some bootstrapped code.
55
+
56
+ You're on your own after that. Have fun!
57
+
58
+
59
+ Running the tests
60
+ =================
61
+
62
+ You can run the tests by checking out the code into vendor/plugins of a Rails app and running:
63
+
64
+ rake
65
+
66
+ About me
67
+ ========
68
+
69
+ I'm Paul Campbell. I'm an avid Ruby on Rails web developer. Follow my ramblings at [http://www.pabcas.com](http://www.pabcas.com)
70
+
71
+ Follow me on Twitter [http://twitter.com/paulca](http://twitter.com/paulca)
72
+
73
+ Copyright (c) 2010 Paul Campbell, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ desc 'Default: run specs.'
5
+ task :default => :spec
6
+
7
+ desc 'Run the specs'
8
+ Spec::Rake::SpecTask.new(:spec) do |t|
9
+ t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
10
+ t.spec_files = FileList['spec/**/*_spec.rb']
11
+ end
12
+
13
+ PKG_FILES = FileList[
14
+ '[a-zA-Z]*',
15
+ 'app/**/*',
16
+ 'generators/**/*',
17
+ 'config/*',
18
+ 'lib/**/*',
19
+ 'rails/**/*',
20
+ 'spec/**/*',
21
+ 'features/**/*'
22
+ ]
23
+
24
+ begin
25
+ require 'jeweler'
26
+ Jeweler::Tasks.new do |s|
27
+ s.name = "paths_of_glory"
28
+ s.version = "0.1.0"
29
+ s.author = "Paul Campbell"
30
+ s.email = "paul@rslw.com"
31
+ s.homepage = "http://www.github.com/paulca/paths_of_glory"
32
+ s.platform = Gem::Platform::RUBY
33
+ s.summary = "Getting you started on the road to achievements."
34
+ s.files = PKG_FILES.to_a
35
+ s.require_path = "lib"
36
+ s.has_rdoc = false
37
+ s.extra_rdoc_files = ["README.md"]
38
+ end
39
+ rescue LoadError
40
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
41
+ end
42
+
43
+ Jeweler::GemcutterTasks.new
@@ -0,0 +1,65 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{achievement_system}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Paul Campbell"]
12
+ s.date = %q{2010-05-20}
13
+ s.email = %q{paul@rslw.com}
14
+ s.extra_rdoc_files = [
15
+ "README.md"
16
+ ]
17
+ s.files = [
18
+ "CHANGELOG",
19
+ "README.md",
20
+ "Rakefile",
21
+ "app/models/achievement.rb",
22
+ "app/models/achievements.rb",
23
+ "generators/achievement/USAGE",
24
+ "generators/achievement/achievement_generator.rb",
25
+ "generators/achievement/templates/achievement.rb",
26
+ "generators/achievement/templates/achievement_observer.rb",
27
+ "generators/paths_of_glory/USAGE",
28
+ "generators/paths_of_glory/paths_of_glory_generator.rb",
29
+ "generators/paths_of_glory/templates/20100311101933_create_achievements.rb",
30
+ "rails/init.rb",
31
+ "spec/achievement_generator_spec.rb",
32
+ "spec/achievement_spec.rb",
33
+ "spec/blueprints.rb",
34
+ "spec/database.yml",
35
+ "spec/debug.log",
36
+ "spec/paths_of_glory_generator_spec.rb",
37
+ "spec/schema.rb",
38
+ "spec/spec_helper.rb"
39
+ ]
40
+ s.has_rdoc = false
41
+ s.homepage = %q{http://www.github.com/paulca/achievement_system}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.3.6}
45
+ s.summary = %q{Getting you started on the road to achievements.}
46
+ s.test_files = [
47
+ "spec/achievement_generator_spec.rb",
48
+ "spec/achievement_spec.rb",
49
+ "spec/blueprints.rb",
50
+ "spec/paths_of_glory_generator_spec.rb",
51
+ "spec/schema.rb",
52
+ "spec/spec_helper.rb"
53
+ ]
54
+
55
+ if s.respond_to? :specification_version then
56
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
60
+ else
61
+ end
62
+ else
63
+ end
64
+ end
65
+
@@ -0,0 +1,73 @@
1
+ class Achievement < ActiveRecord::Base
2
+
3
+ belongs_to :user
4
+
5
+ named_scope :not_notified, :conditions => {:notified => false}
6
+ named_scope :recent, :order => "created_at desc"
7
+ named_scope :kind_of, lambda { |type| {:conditions => {:type => type.to_s}}} do
8
+ def current
9
+ order("level desc").limit(1).first
10
+ end
11
+ end
12
+
13
+ named_scope :order, lambda { |order| {:order => order} }
14
+ named_scope :limit, lambda { |limit| {:limit => limit} }
15
+
16
+ class << self
17
+ def levels
18
+ @levels ||= []
19
+ end
20
+
21
+ def level(level, options = {})
22
+ levels << {:level => level, :quota => options[:quota]}
23
+ end
24
+
25
+ def set_thing_to_check(&block)
26
+ @thing_to_check = block
27
+ end
28
+
29
+ def thing_to_check(object)
30
+ @thing_to_check.call(object)
31
+ end
32
+
33
+ def select_level(level)
34
+ levels.select { |l| l[:level] == level }.first
35
+ end
36
+
37
+ def quota_for(level)
38
+ select_level(level)[:quota] if select_level(level)
39
+ end
40
+
41
+ def has_level?(level)
42
+ select_level(level).present?
43
+ end
44
+
45
+ def current_level(user)
46
+ if current_achievement = user.achievements.kind_of(self).current
47
+ current_achievement.level
48
+ else
49
+ 0
50
+ end
51
+ end
52
+
53
+ def next_level(user)
54
+ current_level(user) + 1
55
+ end
56
+
57
+ def current_progress(user)
58
+ thing_to_check(user) - quota_for(current_level(user)).to_i
59
+ end
60
+
61
+ def next_level_quota(user)
62
+ quota_for(next_level(user)) - quota_for(current_level(user)).to_i
63
+ end
64
+
65
+ def progress_to_next_level(user)
66
+ if(has_level?(next_level(user)))
67
+ return [(current_progress(user) * 100) / next_level_quota(user), 95].min
68
+ else
69
+ return nil
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,36 @@
1
+ module Achievements
2
+
3
+ def self.included(base)
4
+ base.class_eval do
5
+ has_many :achievements do
6
+ def include?(achievement, level = nil)
7
+ all.select { |a| a.type.to_s == achievement.to_s and a.level == level }.any?
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ def award_achievement(achievement, level = nil)
14
+ achievement.create!(:user => self, :level => level)
15
+ end
16
+
17
+ def has_achievement?(achievement, level = nil)
18
+ conditions = {:type => achievement.to_s, :user_id => id}
19
+ conditions[:level] = level if level
20
+ achievements.first(:conditions => conditions).present?
21
+ end
22
+
23
+ def get_badges_in_progress(badges)
24
+ badges.collect do |achievement|
25
+ {
26
+ :type => achievement,
27
+ :level => achievement.next_level(self),
28
+ :progress => achievement.progress_to_next_level(self),
29
+ :next_level_quota => achievement.next_level_quota(self),
30
+ :current_progress => achievement.current_progress(self),
31
+ :next_level => achievement.next_level(self)
32
+ }
33
+ end.sort_by { |achievement| achievement[:progress] }.reverse[0,3]
34
+ end
35
+
36
+ end
File without changes
@@ -0,0 +1,32 @@
1
+ class AchievementGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ m.class_collisions class_name, "#{class_name}"
5
+ m.class_collisions class_name, "#{class_name}Observer"
6
+
7
+ m.directory 'app/models/achievements'
8
+ m.template "achievement.rb", "app/models/achievements/#{file_name}.rb"
9
+ m.template "achievement_observer.rb", "app/models/achievements/#{file_name}_observer.rb"
10
+ end
11
+ end
12
+
13
+ def after_generate
14
+ puts "
15
+
16
+ Your new achievement, '#{class_name}' has been created.
17
+
18
+ Now you need to edit:
19
+
20
+ app/models/achievements/#{file_name}.rb
21
+
22
+ and
23
+
24
+ app/models/achievements/#{file_name}_observer.rb
25
+
26
+ Then you need to activate the observer, by adding ':#{file_name}_observer' to environment.rb, eg:
27
+
28
+ config.active_record.observers = :#{file_name}_observer
29
+
30
+ "
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ class <%= class_name %> < Achievement
2
+
3
+ # level 1, :quota => 5
4
+ # level 2, :quota => 10
5
+ # level 3, :quota => 30
6
+ # level 4, :quota => 100
7
+ # level 5, :quota => 500
8
+
9
+ # set_thing_to_check { |user| ... }
10
+
11
+ def self.award_achievements_for(user)
12
+ return unless user
13
+ levels.each do |level|
14
+ if user.not.has_achievement?(self, level[:level]) and thing_to_check(user) >= level[:quota]
15
+ user.award_achievement(self, level[:level])
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ class <%= class_name %>Observer < ActiveRecord::Observer
2
+ observe :user
3
+
4
+ def after_save(user)
5
+ Dedicated.award_achievements_for(user) if ('your conditions here')
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ Description:
2
+ Creates a migration to create a table to store achievements called "achievements"
3
+
4
+ Examples:
5
+ `./script/generate paths_of_glory`
@@ -0,0 +1,30 @@
1
+ class PathsOfGloryGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.directory 'db/migrate'
5
+ m.file "20100311101933_create_achievements.rb", "db/migrate/20100311101933_create_achievements.rb"
6
+ end
7
+ end
8
+
9
+ def after_generate
10
+ puts "
11
+
12
+ You're on the Path to Glory
13
+
14
+ Now, add 'include Achievements' to your User model:
15
+
16
+ class User < ActiveRecord::Base
17
+ include Achievements
18
+ end
19
+
20
+ and run:
21
+
22
+ rake db:migrate,
23
+
24
+ To generate a new Achievement, run:
25
+
26
+ ./script/generate achievement Glory
27
+
28
+ where 'Glory' is the name of the achievement."
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ class CreateAchievements < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :achievements do |t|
4
+ t.string :type
5
+ t.integer :level
6
+ t.integer :user_id
7
+ t.notified :boolean, :default => false
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :achievements
15
+ end
16
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'behavior'
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'rails_generator'
3
+ require 'rails_generator/scripts/generate'
4
+
5
+ describe AchievementGenerator do
6
+
7
+ before do
8
+ @dirs = {}
9
+ @dirs['achievements'] = fake_rails_root + '/app/models/achievements'
10
+ @dirs.each do |key, dir|
11
+ FileUtils.mkdir_p(dir)
12
+ end
13
+ @original_files = {}
14
+ @original_files['achievements'] = file_list(@dirs['achievements'])
15
+ Rails::Generator::Scripts::Generate.new.run(["achievement", 'glory'], :destination => fake_rails_root, :backtrace => true)
16
+
17
+ @files = (file_list(@dirs['achievements']) - @original_files['achievements'])
18
+ end
19
+
20
+ let(:achievement_file) { @files.first }
21
+ let(:observer_file) { @files.last }
22
+
23
+ it "should create the achievement file" do
24
+ File.basename(achievement_file).should == "glory.rb"
25
+ File.basename(observer_file).should == "glory_observer.rb"
26
+ end
27
+
28
+ it "should make them beautiful" do
29
+ File.read(achievement_file).should match(/Glory < Achievement/)
30
+ end
31
+
32
+ it "should make them beautiful" do
33
+ File.read(observer_file).should match(/GloryObserver < ActiveRecord::Observer/)
34
+ end
35
+
36
+
37
+ after do
38
+ FileUtils.rm_r(fake_rails_root)
39
+ end
40
+
41
+
42
+ def fake_rails_root
43
+ File.join(File.dirname(__FILE__), 'spec', 'rails_root')
44
+ end
45
+
46
+ def file_list(dir)
47
+ Dir.glob(File.join(dir, "*"))
48
+ end
49
+
50
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe Achievement do
4
+ class Thing < Achievement
5
+ level 1, :quota => 1
6
+ level 2, :quota => 2
7
+
8
+ set_thing_to_check {|u| u.sign_in_count }
9
+ end
10
+
11
+ class Dedicated < Achievement
12
+
13
+ level 1, :quota => 5
14
+ level 2, :quota => 10
15
+ level 3, :quota => 30
16
+ level 4, :quota => 100
17
+ level 5, :quota => 500
18
+
19
+ set_thing_to_check { |user| user.sign_in_count.to_i }
20
+
21
+ def self.award_achievements_for(user)
22
+ return unless user
23
+ levels.each do |level|
24
+ if !user.has_achievement?(self, level[:level]) and thing_to_check(user) >= level[:quota]
25
+ user.award_achievement(self, level[:level])
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ class User < ActiveRecord::Base
32
+ include Achievements
33
+
34
+ def after_save
35
+ Dedicated.award_achievements_for(self) if sign_in_count_changed?
36
+ end
37
+ end
38
+
39
+ User.blueprint do
40
+ email 'joe@king.com'
41
+ encrypted_password 'blah'
42
+ password_salt 'are you joking?'
43
+ end
44
+
45
+ it "should have 2 levels" do
46
+ Thing.levels.size.should == 2
47
+ end
48
+
49
+ it "should have a levels array" do
50
+ Thing.levels.should == [{:quota=> 1, :level =>1}, {:quota=>2, :level=>2}]
51
+ end
52
+
53
+ describe ".quota_for" do
54
+ it "should give me back the quota" do
55
+ Dedicated.quota_for(2).should == 10
56
+ end
57
+ end
58
+
59
+ describe ".has_level?" do
60
+ it "should tell me if it has a level" do
61
+ Dedicated.has_level?(1).should be_true
62
+ end
63
+
64
+ it "should tell me if it doesn't have a level" do
65
+ Dedicated.has_level?(6).should be_false
66
+ end
67
+ end
68
+
69
+ describe ".progress_to_next_level" do
70
+ before do
71
+ @user = User.make(:sign_in_count => 2)
72
+ end
73
+
74
+ it "should tell me the thing to check" do
75
+ Thing.thing_to_check(@user).should == 2
76
+ end
77
+
78
+ it "should show me that I'm 40% the way to dedicated" do
79
+ Dedicated.progress_to_next_level(@user).should == 40
80
+ end
81
+
82
+ it "should update the progress as things happen" do
83
+ @user.update_attribute(:sign_in_count, 4)
84
+ Dedicated.progress_to_next_level(@user).should == 80
85
+ end
86
+
87
+ it "should work for more than one level" do
88
+ @user.sign_in_count = 5
89
+ @user.save!
90
+ Dedicated.thing_to_check(@user).should == 5
91
+ Dedicated.progress_to_next_level(@user).should == 0
92
+ @user.update_attribute(:sign_in_count, 6)
93
+ Dedicated.progress_to_next_level(@user).should == 20
94
+ end
95
+
96
+ it "should return nil finally" do
97
+ @user.sign_in_count = 500
98
+ @user.save!
99
+ Dedicated.progress_to_next_level(@user).should be_nil
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,6 @@
1
+ require 'machinist/active_record'
2
+ require 'faker'
3
+ require 'sham'
4
+
5
+ Achievement.blueprint do
6
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,21 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :database: spec/paths_of_glory.sqlite.db
4
+
5
+ sqlite3:
6
+ :adapter: sqlite3
7
+ :database: spec/paths_of_glory.sqlite3.db
8
+
9
+ postgresql:
10
+ :adapter: postgresql
11
+ :username: postgres
12
+ :password: postgres
13
+ :database: paths_of_glory_test
14
+ :min_messages: ERROR
15
+
16
+ mysql:
17
+ :adapter: mysql
18
+ :host: localhost
19
+ :username: root
20
+ :password: password
21
+ :database: paths_of_glory_test