recognition 0.2.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/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: []