recognition 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Raya Social Media
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # Recognition
2
+
3
+ A fully-fledged reward system for Rails 3.1+.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'recognition'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Then, you need to run the generator:
16
+
17
+ $ rails generate recogintion:install
18
+
19
+ ## Usage
20
+
21
+ Assuming you have two models User and Post and you want to give users points for the following:
22
+
23
+ 5 points for registration
24
+ 7 points for adding new posts, can be earned multiple times with no limit
25
+ 2 point for deleting posts, can be earned twice
26
+ 2 points for visiting their profile, earned once
27
+
28
+ app/models/user.rb:
29
+
30
+ class User < ActiveRecord::Base
31
+ attr_accessible :name
32
+ has_many :posts
33
+ acts_as_recognizable initial: 5
34
+ end
35
+
36
+ app/models/post.rb:
37
+
38
+ class Post < ActiveRecord::Base
39
+ attr_accessible :title, :user_id
40
+ belongs_to :user
41
+ recognize :user, for: :create, gain: 7
42
+ recognize :user, for: :destroy, loss: 2, maximum: 4
43
+ end
44
+
45
+ app/controllers/profiles_controller.rb:
46
+
47
+ class ProfilesController < ApplicationController
48
+ recognize :current_user, for: :index, amount: 6, maximum: 12
49
+ def index
50
+ @user = User.find(params[:id])
51
+ respond_to do |format|
52
+ format.html
53
+ end
54
+ end
55
+ end
56
+
57
+ Note:
58
+ =====
59
+ Due to the way Ruby method aliasing work, if you need to recognize users for
60
+ non-ActiveRecord actions (anything that's not :create, :update and :destroy),
61
+ make sure you add the `recognize` line *after* the method you want to
62
+ recognize the user for.
63
+
64
+ Example:
65
+ -------
66
+ The following won't work:
67
+
68
+ class Post < ActiveRecord::Base
69
+ attr_accessible :title, :user_id
70
+ recognize :user, for: :foo, gain: 2
71
+ def foo
72
+ # do something useful
73
+ end
74
+ end
75
+
76
+ This one will:
77
+
78
+ class Post < ActiveRecord::Base
79
+ attr_accessible :title, :user_id
80
+ def foo
81
+ # do something useful
82
+ end
83
+ recognize :user, for: :foo, gain: 2
84
+ end
85
+
86
+
87
+
88
+ ## Contributing
89
+
90
+ Please see CONTRIBUTING.md for details.
91
+
92
+ ## Credits
93
+ recognition was originally written by Omar Abdel-Wahab.
94
+
95
+ ![RSM](http://rayasocialmedia.com/images/logo.png)
96
+
97
+ recognition is maintained and funded by Raya Social Media.
98
+
99
+ ## License
100
+ recognition is Copyright © 2013 Raya Social Media. It is free software, and may be redistributed under the terms specified in the LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Recognition'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+ require 'rspec/core/rake_task'
28
+ RSpec::Core::RakeTask.new(:spec)
29
+ task :default => :spec
30
+
31
+ Bundler::GemHelper.install_tasks
32
+
@@ -0,0 +1,12 @@
1
+ module Recognition
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../templates", __FILE__)
5
+
6
+ def copy_files
7
+ template "recognition.rb", "config/initializers/recognition.rb"
8
+ template "recognition.yml", "config/recognition.yml"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ rails_root = Rails.root || File.dirname(__FILE__) + '/../..'
2
+ rails_env = Rails.env || 'development'
3
+
4
+ Recognition.setup do |config|
5
+ config.redis = YAML.load_file("#{ rails_root.to_s }/config/recognition.yml")[Rails.env.to_s]
6
+ end
@@ -0,0 +1,4 @@
1
+ development: localhost:6379
2
+ test: localhost:6379:1
3
+ testing: localhost:6379
4
+ production: localhost:6379
@@ -0,0 +1,32 @@
1
+ require "recognition/engine"
2
+ require "recognition/version"
3
+ require "recognition/railtie"
4
+ require "redis"
5
+
6
+ module Recognition
7
+ mattr_accessor :redis
8
+ @@redis = 'localhost:6378'
9
+
10
+ mattr_accessor :backend
11
+ @@backend = nil
12
+
13
+ def self.setup
14
+ yield self
15
+ end
16
+
17
+ def self.backend
18
+ if self.redis['redis://']
19
+ @@backend = Redis.connect(:url => self.redis, :thread_safe => true)
20
+ else
21
+ self.redis, namespace = self.redis.split('/', 2)
22
+ host, port, db = self.redis.split(':')
23
+
24
+ @@backend = Redis.new(
25
+ :host => host,
26
+ :port => port,
27
+ :db => db,
28
+ :thread_safe => true
29
+ )
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ require "recognition/database"
2
+
3
+ module Recognition
4
+ module ActionControllerExtension
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.class_attribute :recognitions
8
+ end
9
+
10
+ module ClassMethods
11
+ def recognize recognizable, condition
12
+ include InstanceMethods
13
+ self.recognitions ||= {}
14
+ self.recognitions[condition[:for]] = { recognizable: recognizable, amount: condition[:amount], maximum: condition[:maximum] || 0 }
15
+ after_filter :recognize_actions
16
+ end
17
+
18
+ end
19
+
20
+ module InstanceMethods
21
+ def recognize_actions
22
+ action = params[:action].to_sym
23
+ if self.class.recognitions.keys.include? action
24
+ # update_points current_user.id, action, self.class.recognitions[action][:amount]
25
+ Database.update_points self, action, self.class.recognitions[action]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,92 @@
1
+ require "recognition/database"
2
+
3
+ module Recognition
4
+ module ActiveRecordExtension
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.class_attribute :recognitions
8
+ base.class_attribute :recognizable
9
+ end
10
+
11
+ module ClassMethods #:nodoc:
12
+ # to be called from user-model
13
+ def acts_as_recognizable options = {}
14
+ include RecognizableInstanceMethods
15
+ self.recognizable = true
16
+ self.recognitions ||= {}
17
+ self.recognitions[:initial] = {
18
+ amount: options[:initial]
19
+ }
20
+ after_save :add_initial_points
21
+ end
22
+
23
+ def recognize recognizable, condition
24
+ include ObjectInstanceMethods
25
+ self.recognitions ||= {}
26
+ self.recognitions[condition[:for]] = {
27
+ recognizable: recognizable,
28
+ bucket: condition[:group],
29
+ gain: condition[:gain],
30
+ loss: condition[:loss],
31
+ maximum: condition[:maximum]
32
+ }
33
+ # Due to the lack of ActiveRecord before_filter,
34
+ # we will have to alias the original method in order to intercept
35
+ if [:create, :update, :destroy].include? condition[:for]
36
+ include ActiveModel::Dirty
37
+ else
38
+ method = condition[:for]
39
+ define_method "#{method}_with_recognition" do |*args|
40
+ if self.send("#{method}_without_recognition", *args)
41
+ Database.update_points self, condition[:for], self.class.recognitions[condition[:for]]
42
+ end
43
+ end
44
+ alias_method_chain method, 'recognition'
45
+ end
46
+ # For actions that can be intercepted using ActiveRecord callbacks
47
+ before_destroy :recognize_destroying
48
+ after_save :recognize_updating
49
+ # We use after_save for creation to make sure all associations
50
+ # have been persisted
51
+ after_save :recognize_creating
52
+ end
53
+ end
54
+
55
+ module RecognizableInstanceMethods #:nodoc:
56
+ def points
57
+ Database.get_user_points self.id
58
+ end
59
+
60
+ def recognition_counter bucket
61
+ Database.get_user_counter self.id, bucket
62
+ end
63
+
64
+ def add_initial_points
65
+ Database.update_points self, :initial, self.class.recognitions[:initial]
66
+ end
67
+
68
+ def update_points amount, bucket
69
+ Database.log(self.id, amount.to_i, bucket)
70
+ end
71
+ end
72
+
73
+ module ObjectInstanceMethods #:nodoc:
74
+ def recognize_creating
75
+ if self.id_changed? # Unless we are creating
76
+ Database.update_points self, :create, self.class.recognitions[:create] unless self.class.recognitions[:create].nil?
77
+ end
78
+ end
79
+
80
+ def recognize_updating
81
+ unless self.id_changed? # Unless we are creating
82
+ Database.update_points self, :update, self.class.recognitions[:update] unless self.class.recognitions[:update].nil?
83
+ end
84
+ end
85
+
86
+ def recognize_destroying
87
+ Database.update_points self, :destroy, self.class.recognitions[:destroy] unless self.class.recognitions[:destroy].nil?
88
+ end
89
+
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,10 @@
1
+ module Recognition
2
+ class Backend
3
+ attr_reader :store
4
+
5
+ def initialize(store)
6
+ @store = store
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module Recognition
2
+ module Condition
3
+ def parse opts
4
+ {
5
+ opts[:for] => {
6
+ recognizable: nil,
7
+ amount: options[:initial]
8
+ }
9
+ }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,97 @@
1
+ require "recognition/transaction"
2
+
3
+ module Recognition
4
+ module Database
5
+ def self.log id, amount, bucket
6
+ hash = Time.now.to_f.to_s
7
+ Recognition.backend.multi do
8
+ # Recognition.backend.incrby "recognition:user:#{ id }:points", amount
9
+ Recognition.backend.hincrby "recognition:user:#{ id }:counters", 'points', amount
10
+ Recognition.backend.hincrby "recognition:user:#{ id }:counters", bucket, amount
11
+ Recognition.backend.zadd "recognition:user:#{ id }:transactions", hash, { hash: hash, amount: amount, bucket: bucket, datetime: DateTime.now.to_s }.to_json
12
+ Recognition.backend.zadd 'recognition:transactions', hash, { hash: hash, id: id, amount: amount, bucket: bucket, datetime: DateTime.now.to_s }.to_json
13
+ end
14
+ end
15
+
16
+ def self.record transaction
17
+ unless transaction.class.to_s == Recognition::Transaction
18
+ raise ArgumentError, 'parameter should be of type Recognition::Transaction'
19
+ end
20
+ hash = Time.now.to_f.to_s
21
+ Recognition.backend.multi do
22
+ # Recognition.backend.incrby "recognition:user:#{ id }:points", amount
23
+ Recognition.backend.hincrby "recognition:user:#{ id }:counters", 'points', transactions.amount
24
+ Recognition.backend.hincrby "recognition:user:#{ id }:counters", transactions.bucket, transactions.amount
25
+ Recognition.backend.zadd "recognition:user:#{ id }:transactions", hash, { amount: transactions.amount, bucket: transactions.bucket, datetime: DateTime.now.to_s }.to_json
26
+ Recognition.backend.zadd 'recognition:transactions', hash, { id: id, amount: transactions.amount, bucket: transactions.bucket, datetime: DateTime.now.to_s }.to_json
27
+ end
28
+ end
29
+
30
+ def self.get key
31
+ Recognition.backend.get key
32
+ end
33
+
34
+ def self.get_user_points id
35
+ get_user_counter id, 'points'
36
+ end
37
+
38
+ def self.get_user_counter id, counter
39
+ counter = Recognition.backend.hget("recognition:user:#{ id }:counters", counter)
40
+ counter.to_i
41
+ end
42
+
43
+
44
+ def self.update_points object, action, condition
45
+ if condition[:bucket].nil?
46
+ bucket = "#{ object.class.to_s.camelize }:#{ action }"
47
+ else
48
+ bucket = condition[:bucket]
49
+ end
50
+ user = parse_user(object, condition)
51
+ if condition[:amount].nil? && condition[:gain].nil? && condition[:loss].nil?
52
+ false
53
+ else
54
+ total = parse_amount(condition[:amount], object) + parse_amount(condition[:gain], object) - parse_amount(condition[:loss], object)
55
+ ground_total = user.recognition_counter(bucket) + total
56
+ if condition[:maximum].nil? || ground_total <= condition[:maximum]
57
+ Database.log(user.id, total.to_i, bucket)
58
+ end
59
+ end
60
+ end
61
+
62
+ def self.parse_user object, condition
63
+ if condition[:recognizable].nil?
64
+ user = object
65
+ else
66
+ case condition[:recognizable].class.to_s
67
+ when 'Symbol'
68
+ user = object.send(condition[:recognizable])
69
+ when 'String'
70
+ user = object.send(condition[:recognizable].to_sym)
71
+ when 'Proc'
72
+ user = object.call(condition[:proc_params])
73
+ else
74
+ user = condition[:recognizable]
75
+ end
76
+ end
77
+ user
78
+ end
79
+
80
+ def self.parse_amount amount, object
81
+ case amount.class.to_s
82
+ when 'Integer'
83
+ when 'Fixnum'
84
+ value = amount
85
+ when 'Symbol'
86
+ value = object.send(amount)
87
+ when 'Proc'
88
+ value = amount.call(object)
89
+ when 'NilClass'
90
+ # Do not complain about nil amounts
91
+ else
92
+ raise ArgumentError, "type mismatch for amount: expecting 'Integer', 'Fixnum', 'Symbol' or 'Proc' but got '#{ amount.class.to_s }' instead."
93
+ end
94
+ value || 0
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,10 @@
1
+ module Recognition
2
+ class Engine < ::Rails::Engine
3
+ config.generators do |g|
4
+ g.test_framework :rspec, :fixture => false
5
+ g.fixture_replacement :factory_girl, :dir => 'spec/factories'
6
+ g.assets false
7
+ g.helper false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ require "recognition/active_record_extension"
2
+ require "recognition/action_controller_extension"
3
+ require "rails"
4
+
5
+ module Recognition
6
+ class Railtie < Rails::Railtie
7
+ initializer 'recognition.initialize' do
8
+ ActiveSupport.on_load(:active_record) do
9
+ ActiveRecord::Base.send :include, ActiveRecordExtension
10
+ end
11
+ ActiveSupport.on_load(:action_controller) do
12
+ ActionController::Base.send(:include, ActionControllerExtension)
13
+ end
14
+ end
15
+ #
16
+ # initializer 'recognition.database' do
17
+ # #TODO: use a Rails initializer
18
+ # $REDIS = Redis.new #(host: 'localhost', port: redis_config[:port])
19
+ # end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module Recognition
2
+ class Transaction
3
+ attr_accessor :id, :gain, :loss, :bucket
4
+
5
+ def create *args
6
+ args.each do |key, val|
7
+ self.new
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Recognition
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :recognition do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: recognition
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Omar Abdel-Wahab
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.9
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.2.9
30
+ - !ruby/object:Gem::Dependency
31
+ name: redis
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: sqlite3
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec-rails
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: database_cleaner
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: capybara
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Recognize users by giving them points and rewards for their actions
111
+ email:
112
+ - owahab@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - lib/generators/recognition/install_generator.rb
118
+ - lib/generators/recognition/templates/recognition.rb
119
+ - lib/generators/recognition/templates/recognition.yml
120
+ - lib/recognition/action_controller_extension.rb
121
+ - lib/recognition/active_record_extension.rb
122
+ - lib/recognition/backend.rb
123
+ - lib/recognition/condition.rb
124
+ - lib/recognition/database.rb
125
+ - lib/recognition/engine.rb
126
+ - lib/recognition/railtie.rb
127
+ - lib/recognition/transaction.rb
128
+ - lib/recognition/version.rb
129
+ - lib/recognition.rb
130
+ - lib/tasks/recognition_tasks.rake
131
+ - LICENSE.txt
132
+ - Rakefile
133
+ - README.md
134
+ homepage: http://github.com/owahab/recognition
135
+ licenses: []
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ segments:
147
+ - 0
148
+ hash: -334073944928313035
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ! '>='
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ segments:
156
+ - 0
157
+ hash: -334073944928313035
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 1.8.23
161
+ signing_key:
162
+ specification_version: 3
163
+ summary: Recognize users by giving them points and rewards for their actions
164
+ test_files: []