qalam_merit 4.0.35

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +84 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +358 -0
  7. data/RELEASING.md +16 -0
  8. data/Rakefile +44 -0
  9. data/TESTING.txt +12 -0
  10. data/config/locales/en.yml +5 -0
  11. data/lib/merit.rb +95 -0
  12. data/lib/merit/badge_rules_methods.rb +30 -0
  13. data/lib/merit/base_target_finder.rb +31 -0
  14. data/lib/merit/class_methods.rb +41 -0
  15. data/lib/merit/controller_extensions.rb +71 -0
  16. data/lib/merit/generators/active_record/install_generator.rb +40 -0
  17. data/lib/merit/generators/active_record/merit_generator.rb +25 -0
  18. data/lib/merit/generators/active_record/remove_generator.rb +30 -0
  19. data/lib/merit/generators/active_record/templates/add_merit_fields_to_model.erb +7 -0
  20. data/lib/merit/generators/active_record/templates/create_badges_sashes.erb +14 -0
  21. data/lib/merit/generators/active_record/templates/create_merit_actions.erb +16 -0
  22. data/lib/merit/generators/active_record/templates/create_merit_activity_logs.erb +12 -0
  23. data/lib/merit/generators/active_record/templates/create_merit_badges.erb +17 -0
  24. data/lib/merit/generators/active_record/templates/create_sashes.erb +8 -0
  25. data/lib/merit/generators/active_record/templates/create_scores_and_points.erb +16 -0
  26. data/lib/merit/generators/active_record/templates/remove_merit_fields_from_model.erb +7 -0
  27. data/lib/merit/generators/active_record/templates/remove_merit_tables.erb +12 -0
  28. data/lib/merit/generators/install_generator.rb +30 -0
  29. data/lib/merit/generators/merit_generator.rb +33 -0
  30. data/lib/merit/generators/remove_generator.rb +23 -0
  31. data/lib/merit/generators/templates/badge.erb +49 -0
  32. data/lib/merit/generators/templates/badge_sash.erb +21 -0
  33. data/lib/merit/generators/templates/merit.erb +18 -0
  34. data/lib/merit/generators/templates/merit_badge_rules.erb +50 -0
  35. data/lib/merit/generators/templates/merit_point_rules.erb +31 -0
  36. data/lib/merit/generators/templates/merit_rank_rules.erb +32 -0
  37. data/lib/merit/generators/templates/sash.erb +15 -0
  38. data/lib/merit/judge.rb +102 -0
  39. data/lib/merit/models/action_concern.rb +50 -0
  40. data/lib/merit/models/active_record/action.rb +11 -0
  41. data/lib/merit/models/active_record/activity_log.rb +11 -0
  42. data/lib/merit/models/active_record/badges_sash.rb +13 -0
  43. data/lib/merit/models/active_record/sash.rb +32 -0
  44. data/lib/merit/models/active_record/score.rb +25 -0
  45. data/lib/merit/models/badges_sash_concern.rb +13 -0
  46. data/lib/merit/models/base/badges_sash.rb +15 -0
  47. data/lib/merit/models/base/sash.rb +55 -0
  48. data/lib/merit/models/sash_concern.rb +53 -0
  49. data/lib/merit/point_rules_methods.rb +33 -0
  50. data/lib/merit/rank_rules_methods.rb +58 -0
  51. data/lib/merit/reputation_change_observer.rb +19 -0
  52. data/lib/merit/rule.rb +35 -0
  53. data/lib/merit/rules_matcher.rb +24 -0
  54. data/lib/merit/sash_finder.rb +15 -0
  55. data/lib/merit/target_finder.rb +43 -0
  56. data/qalam_merit.gemspec +23 -0
  57. data/test/dummy/Rakefile +7 -0
  58. data/test/dummy/app/controllers/admin/users_controller.rb +9 -0
  59. data/test/dummy/app/controllers/api/comments_controller.rb +5 -0
  60. data/test/dummy/app/controllers/api/users_controller.rb +5 -0
  61. data/test/dummy/app/controllers/application_controller.rb +7 -0
  62. data/test/dummy/app/controllers/comments_controller.rb +56 -0
  63. data/test/dummy/app/controllers/registrations_controller.rb +21 -0
  64. data/test/dummy/app/controllers/users_controller.rb +38 -0
  65. data/test/dummy/app/helpers/application_helper.rb +2 -0
  66. data/test/dummy/app/models/address.rb +3 -0
  67. data/test/dummy/app/models/comment.rb +13 -0
  68. data/test/dummy/app/models/dummy_observer.rb +3 -0
  69. data/test/dummy/app/models/merit/badge_rules.rb +66 -0
  70. data/test/dummy/app/models/merit/point_rules.rb +44 -0
  71. data/test/dummy/app/models/merit/rank_rules.rb +24 -0
  72. data/test/dummy/app/models/user.rb +23 -0
  73. data/test/dummy/app/views/admin/users/index.html.erb +26 -0
  74. data/test/dummy/app/views/comments/_form.html.erb +29 -0
  75. data/test/dummy/app/views/comments/edit.html.erb +6 -0
  76. data/test/dummy/app/views/comments/index.html.erb +35 -0
  77. data/test/dummy/app/views/comments/new.html.erb +5 -0
  78. data/test/dummy/app/views/comments/show.html.erb +23 -0
  79. data/test/dummy/app/views/layouts/application.html.erb +24 -0
  80. data/test/dummy/app/views/users/_form.html.erb +22 -0
  81. data/test/dummy/app/views/users/edit.html.erb +6 -0
  82. data/test/dummy/app/views/users/index.html.erb +27 -0
  83. data/test/dummy/app/views/users/new.html.erb +5 -0
  84. data/test/dummy/app/views/users/show.html.erb +18 -0
  85. data/test/dummy/config.ru +4 -0
  86. data/test/dummy/config/application.rb +26 -0
  87. data/test/dummy/config/application_api_only.rb +28 -0
  88. data/test/dummy/config/boot.rb +10 -0
  89. data/test/dummy/config/database.yml +22 -0
  90. data/test/dummy/config/environment.rb +5 -0
  91. data/test/dummy/config/environment_api_only.rb +7 -0
  92. data/test/dummy/config/environments/development.rb +24 -0
  93. data/test/dummy/config/environments/production.rb +51 -0
  94. data/test/dummy/config/environments/test.rb +36 -0
  95. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  96. data/test/dummy/config/initializers/inflections.rb +10 -0
  97. data/test/dummy/config/initializers/merit.rb +47 -0
  98. data/test/dummy/config/initializers/mime_types.rb +5 -0
  99. data/test/dummy/config/initializers/new_framework_defaults.rb +3 -0
  100. data/test/dummy/config/initializers/secret_token.rb +12 -0
  101. data/test/dummy/config/initializers/session_store.rb +8 -0
  102. data/test/dummy/config/locales/en.yml +5 -0
  103. data/test/dummy/config/mongoid.yml +13 -0
  104. data/test/dummy/config/routes.rb +16 -0
  105. data/test/dummy/db/migrate/20110421191249_create_users.rb +12 -0
  106. data/test/dummy/db/migrate/20110421191250_create_comments.rb +16 -0
  107. data/test/dummy/db/migrate/20120318022220_add_fields_to_users.rb +11 -0
  108. data/test/dummy/db/migrate/20130321082817_add_fields_to_comments.rb +11 -0
  109. data/test/dummy/db/migrate/20130329224406_create_merit_actions.rb +18 -0
  110. data/test/dummy/db/migrate/20130329224407_create_merit_activity_logs.rb +15 -0
  111. data/test/dummy/db/migrate/20130329224408_create_sashes.rb +11 -0
  112. data/test/dummy/db/migrate/20130329224409_create_badges_sashes.rb +16 -0
  113. data/test/dummy/db/migrate/20130329224410_create_scores_and_points.rb +20 -0
  114. data/test/dummy/db/migrate/20140211144001_create_addresses.rb +11 -0
  115. data/test/dummy/db/migrate/20140819133931_add_target_data_to_merit_actions.rb +5 -0
  116. data/test/dummy/db/schema.rb +89 -0
  117. data/test/dummy/db/seeds.rb +19 -0
  118. data/test/dummy/public/404.html +26 -0
  119. data/test/dummy/public/422.html +26 -0
  120. data/test/dummy/public/500.html +26 -0
  121. data/test/dummy/public/favicon.ico +0 -0
  122. data/test/dummy/public/rails.js +404 -0
  123. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  124. data/test/dummy/public/stylesheets/scaffold.css +56 -0
  125. data/test/dummy/script/rails +6 -0
  126. data/test/integration/navigation_test.rb +332 -0
  127. data/test/support/integration_case.rb +5 -0
  128. data/test/test_helper.rb +40 -0
  129. data/test/unit/action_test.rb +12 -0
  130. data/test/unit/base_target_finder_test.rb +64 -0
  131. data/test/unit/merit_unit_test.rb +33 -0
  132. data/test/unit/rule_unit_test.rb +57 -0
  133. data/test/unit/rules_matcher_test.rb +37 -0
  134. data/test/unit/sash_finder_test.rb +27 -0
  135. data/test/unit/sash_test.rb +104 -0
  136. data/test/unit/score_test.rb +13 -0
  137. data/test/unit/target_finder_test.rb +98 -0
  138. metadata +360 -0
@@ -0,0 +1,19 @@
1
+ module Merit
2
+ class ReputationChangeObserver
3
+ def update(changed_data)
4
+ ActivityLog.create(
5
+ description: changed_data[:description],
6
+ related_change: related_change(changed_data),
7
+ action_id: changed_data[:merit_action_id]
8
+ )
9
+ end
10
+
11
+ private
12
+
13
+ def related_change(data)
14
+ if data[:merit_object].respond_to?(:activity_logs)
15
+ data[:merit_object]
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/merit/rule.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Merit
2
+ # Rules has a badge name and level, a target to badge, a conditions block
3
+ # and a temporary option.
4
+ # Could split this class between badges and rankings functionality
5
+ class Rule
6
+ attr_accessor :badge_id, :badge_name, :level, :to, :model_name, :level_name,
7
+ :multiple, :temporary, :score, :block, :category
8
+
9
+ # Does this rule's condition block apply?
10
+ def applies?(target_obj = nil)
11
+ return true if block.nil? # no block given: always true
12
+
13
+ case block.arity
14
+ when 1 # Expects target object
15
+ if target_obj.present?
16
+ block.call(target_obj)
17
+ else
18
+ Rails.logger.warn '[merit] no target_obj found on Rule#applies?'
19
+ false
20
+ end
21
+ when 0
22
+ block.call
23
+ end
24
+ end
25
+
26
+ # Get rule's related Badge.
27
+ def badge
28
+ if badge_id
29
+ Merit::Badge.find(badge_id)
30
+ else
31
+ Merit::Badge.find_by_name_and_level(badge_name, level)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ module Merit
2
+ class RulesMatcher
3
+ def initialize(path, action_name)
4
+ @path = path
5
+ @action_name = action_name
6
+ end
7
+
8
+ def select_from(rules)
9
+ rules.select do |glob, _|
10
+ entire_path =~ /^#{Regexp.new(glob)}$/
11
+ end.values.flatten
12
+ end
13
+
14
+ def any_matching?
15
+ select_from(AppBadgeRules).any? || select_from(AppPointRules).any?
16
+ end
17
+
18
+ private
19
+
20
+ def entire_path
21
+ @entire_path ||= [@path, @action_name].join('#')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module Merit
2
+ class SashFinder
3
+ def self.find(rule, action)
4
+ targets(rule, action).map(&:_sash)
5
+ rescue NoMethodError
6
+ Rails.logger.warn "[merit] Couldn't find model to grant reputation to. " \
7
+ "Refer to https://github.com/tute/merit/issues/171#issuecomment-44185696."
8
+ []
9
+ end
10
+
11
+ def self.targets(rule, action)
12
+ TargetFinder.find(rule, action).compact
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ module Merit
2
+ class TargetFinder < Struct.new(:rule, :action)
3
+ def self.find(*args)
4
+ new(*args).find
5
+ end
6
+
7
+ def find
8
+ target = case rule.to
9
+ when :itself then
10
+ base_target
11
+ when :action_user then
12
+ action_user
13
+ else
14
+ other_target
15
+ end
16
+ Array.wrap(target)
17
+ end
18
+
19
+ private
20
+
21
+ def base_target
22
+ BaseTargetFinder.find rule, action
23
+ end
24
+
25
+ def action_user
26
+ user = Merit.user_model.find_by_id action.user_id
27
+ if user.nil?
28
+ user_model = Merit.user_model
29
+ str = "[merit] no #{user_model} found with id #{action.user_id}"
30
+ Rails.logger.warn str
31
+ end
32
+ user
33
+ end
34
+
35
+ def other_target
36
+ base_target.send rule.to
37
+ rescue NoMethodError
38
+ str = "[merit] NoMethodError on `#{base_target.class.name}##{rule.to}`" \
39
+ ' (called from Merit::TargetFinder#other_target)'
40
+ Rails.logger.warn str
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "qalam_merit"
3
+ s.summary = "Reputation engine for Rails apps"
4
+ s.description = "Manage badges, points and rankings (reputation) in your Rails app."
5
+ s.homepage = "https://github.com/merit-gem/merit"
6
+ s.files = `git ls-files`.split("\n").reject{|f| f =~ /^\./ }
7
+ s.test_files = `git ls-files -- test/*`.split("\n")
8
+ s.license = 'MIT'
9
+ s.version = '4.0.35'
10
+ s.authors = ["Tute Costa", "Ahmed Abdelhamid"]
11
+ s.email = ['tutecosta@gmail.com', 'a.hamid@nezam.io']
12
+
13
+ s.required_ruby_version = '>= 2.3.0'
14
+
15
+ s.add_runtime_dependency 'zeitwerk'
16
+
17
+ s.add_development_dependency 'rails', '>= 5.1.6'
18
+ s.add_development_dependency 'capybara'
19
+ s.add_development_dependency 'simplecov'
20
+ s.add_development_dependency 'rubocop'
21
+ s.add_development_dependency 'minitest-rails'
22
+ s.add_development_dependency 'mocha'
23
+ end
@@ -0,0 +1,7 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+ require 'rake'
6
+
7
+ Dummy::Application.load_tasks
@@ -0,0 +1,9 @@
1
+ class Admin::UsersController < ApplicationController
2
+ def index
3
+ @users = User.all
4
+ respond_to do |format|
5
+ format.html # index.html.erb
6
+ format.xml { render :xml => @users }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class Api::CommentsController < ApplicationController
2
+ def show
3
+ render json: {}
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Api::UsersController < ApplicationController
2
+ def index
3
+ render json: User.all.to_json
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class ApplicationController < ActionController::Base
2
+ protect_from_forgery
3
+
4
+ def current_user
5
+ @current_user ||= User.first
6
+ end
7
+ end
@@ -0,0 +1,56 @@
1
+ class CommentsController < ApplicationController
2
+ def index
3
+ @comments = Comment.all
4
+ end
5
+
6
+ def show
7
+ @comment = Comment.find(params[:id])
8
+ end
9
+
10
+ def new
11
+ @comment = Comment.new
12
+ end
13
+
14
+ def edit
15
+ @comment = Comment.find(params[:id])
16
+ end
17
+
18
+ def vote
19
+ @comment = Comment.find(params[:id])
20
+ @comment.votes += params[:value].to_i
21
+ @comment.save
22
+ redirect_to(comments_url, :notice => 'Vote added!')
23
+ end
24
+
25
+ def create
26
+ @comment = Comment.new(comment_params)
27
+ if @comment.save
28
+ redirect_to(@comment, :notice => 'Comment was successfully created.')
29
+ else
30
+ render "new"
31
+ end
32
+ end
33
+
34
+ def update
35
+ @comment = Comment.find(params[:id])
36
+ if @comment.update(comment_params)
37
+ redirect_to(@comment, :notice => 'Comment was successfully updated.')
38
+ else
39
+ render "edit"
40
+ end
41
+ end
42
+
43
+ # DELETE /comments/1
44
+ # DELETE /comments/1.xml
45
+ def destroy
46
+ @comment = Comment.find(params[:id])
47
+ @comment.destroy
48
+ redirect_to(comments_url)
49
+ end
50
+
51
+ private
52
+
53
+ def comment_params
54
+ params.require(:comment).permit!
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ class RegistrationsController < ApplicationController
2
+ def update
3
+ @user = User.find(params[:id])
4
+
5
+ respond_to do |format|
6
+ if @user.update(user_params)
7
+ format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
8
+ format.xml { head :ok }
9
+ else
10
+ format.html { render "edit" }
11
+ format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
12
+ end
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def user_params
19
+ params.require(:user).permit!
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ class UsersController < ApplicationController
2
+ def index
3
+ @users = User.all
4
+ end
5
+
6
+ def show
7
+ @user = User.find(params[:id])
8
+ end
9
+
10
+ def new
11
+ @user = User.new
12
+ end
13
+
14
+ def edit
15
+ @user = User.find(params[:id])
16
+ end
17
+
18
+ def create
19
+ @user = User.new(user_params)
20
+ if @user.save
21
+ redirect_to(@user, :notice => 'User was successfully created.')
22
+ else
23
+ render "new"
24
+ end
25
+ end
26
+
27
+ def destroy
28
+ @user = User.find(params[:id])
29
+ @user.destroy
30
+ redirect_to(users_url)
31
+ end
32
+
33
+ private
34
+
35
+ def user_params
36
+ params.require(:user).permit!
37
+ end
38
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,3 @@
1
+ class Address < ActiveRecord::Base
2
+ belongs_to :user
3
+ end
@@ -0,0 +1,13 @@
1
+ class Comment < ActiveRecord::Base
2
+ def friend
3
+ User.find_by_name('friend')
4
+ end
5
+
6
+ has_merit
7
+
8
+ belongs_to :user
9
+
10
+ validates :name, :comment, :user_id, :presence => true
11
+
12
+ delegate :comments, :to => :user, :prefix => true
13
+ end
@@ -0,0 +1,3 @@
1
+ class DummyObserver
2
+ def update(*); end
3
+ end
@@ -0,0 +1,66 @@
1
+ # +grant_on+ accepts:
2
+ # * Nothing (always grants)
3
+ # * A block which evaluates to boolean (recieves the object as parameter)
4
+ # * A block with a hash composed of methods to run on the target object with
5
+ # expected values (+votes: 5+ for instance).
6
+ #
7
+ # +grant_on+ can have a +:to+ method name, which called over the target object
8
+ # should retrieve the object to badge (could be +:user+, +:self+, +:follower+,
9
+ # etc). If it's not defined merit will apply the badge to the user who
10
+ # triggered the action (:action_user by default). If it's :itself, it badges
11
+ # the created object (new user for instance).
12
+ #
13
+ # The :temporary option indicates that if the condition doesn't hold but the
14
+ # badge is granted, then it's removed. It's false by default (badges are kept
15
+ # forever).
16
+
17
+ module Merit
18
+ class BadgeRules
19
+ include Merit::BadgeRulesMethods
20
+
21
+ def initialize
22
+ # If it creates user, grant badge
23
+ # Should be "current_user" after registration for badge to be granted.
24
+ # Example rule with block with no parameters
25
+ grant_on 'users#create', badge: 'just-registered', to: :itself do
26
+ true
27
+ end
28
+
29
+ # For regression test: if condition doesn't hold, it doesn't check
30
+ # sashes to badge. Useful for polymorphic relations where not every
31
+ # model has reputation. See https://github.com/tute/merit/issues/134.
32
+ grant_on 'users#create', badge: 'just-registered', to: :model_with_no_reputation do |user|
33
+ user.model_with_no_reputation.respond_to?(:_sash)
34
+ end
35
+
36
+ # Example rule for multiple badge granting
37
+ grant_on 'users#index', badge: 'gossip', multiple: true
38
+
39
+ # Example rule for badge granting in namespaced controllers
40
+ grant_on 'admin/users#index', badge: 'visited_admin'
41
+
42
+ # Example rule for testing badge granting in differently namespaced controllers.
43
+ grant_on '.*users#index', badge: 'wildcard_badge', multiple: true
44
+
45
+ # If it has 10 comments, grant commenter-10 badge, find badge by badge_id
46
+ grant_on 'comments#create', badge_id: 1, level: 10 do |comment|
47
+ comment.user.comments.count >= 10
48
+ end
49
+ # Testing badge granting in more than one rule per action with different targets
50
+ grant_on 'comments#create', badge: 'has_commenter_friend', to: :friend do |comment|
51
+ comment.user.comments.count >= 10
52
+ end
53
+
54
+ # If it has at least 10 votes, grant relevant-commenter badge
55
+ grant_on 'comments#vote', badge: 'relevant-commenter', to: :user do |comment|
56
+ comment.votes >= 10
57
+ end
58
+
59
+ # Changes his name by one wider than 4 chars (arbitrary ruby code and custom model_name)
60
+ # This badge is temporary (user may lose it)
61
+ grant_on 'registrations#update', badge: 'autobiographer', temporary: true, model_name: 'User' do |user|
62
+ user.name.length > 4
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,44 @@
1
+ # Points are a simple integer value which are given to "meritable" resources
2
+ # according to rules in +app/models/merit/point_rules.rb+. They are given on
3
+ # actions-triggered.
4
+
5
+ module Merit
6
+ class PointRules
7
+ include Merit::PointRulesMethods
8
+
9
+ def initialize
10
+ # Thanks for voting point. Tests that both rules are called when both
11
+ # apply.
12
+ score 1, on: 'comments#vote'
13
+ score 1, on: 'comments#vote'
14
+
15
+ # All user's comments earn points
16
+ score 2, to: :user_comments, on: 'comments#vote'
17
+
18
+ # Points to voted user
19
+ score 5, to: :user, on: 'comments#vote', category: 'vote'
20
+
21
+ # Example rule for using model_name in the case of namespaced controllers
22
+ score 1, to: :user, model_name: 'Comment', on: 'api/comments#show'
23
+
24
+ score 20, on: [
25
+ 'comments#create',
26
+ 'registrations#update'
27
+ ] do |object|
28
+ if object.class == Comment
29
+ object.name.length > 4
30
+ else
31
+ true
32
+ end
33
+ end
34
+
35
+ score lambda { |comment| comment.comment.to_i }, to: :user, on: 'comments#create' do |object|
36
+ object.comment.to_i > 0
37
+ end
38
+
39
+ score (-5), to: :user, on: 'comments#destroy' do |comment|
40
+ comment.present?
41
+ end
42
+ end
43
+ end
44
+ end