qalam_merit 4.0.4

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.
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 +69 -0
  32. data/lib/merit/generators/templates/merit.erb +18 -0
  33. data/lib/merit/generators/templates/merit_badge_rules.erb +50 -0
  34. data/lib/merit/generators/templates/merit_point_rules.erb +31 -0
  35. data/lib/merit/generators/templates/merit_rank_rules.erb +32 -0
  36. data/lib/merit/generators/templates/qalam_badge_sash.erb +20 -0
  37. data/lib/merit/generators/templates/qalam_sash.erb +14 -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,14 @@
1
+ ### QALAM_MERIT ###
2
+ class Merit::QalamSash < ActiveRecord::Base
3
+ self.table_name = "sashes"
4
+ has_many :badges_sashes, class_name: 'Merit::QalamBadgeSash'
5
+ has_many :badges, through: :badges_sashes, source: :badge
6
+
7
+ class << self
8
+ def user(sash_id = nil)
9
+ return [] unless sash_id
10
+
11
+ User.where(sash_id: sash_id)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,102 @@
1
+ require 'observer'
2
+
3
+ module Merit
4
+ class Judge
5
+ include Observable
6
+
7
+ def initialize(rule, options = {})
8
+ @rule = rule
9
+ @action = options[:action]
10
+ Merit.observers.each do |class_name|
11
+ add_observer class_name.constantize.new
12
+ end
13
+ end
14
+
15
+ # Grant badge if rule applies. If it doesn't, and the badge is temporary,
16
+ # then remove it.
17
+ def apply_badges
18
+ if rule_applies?
19
+ grant_badges
20
+ else
21
+ remove_badges if @rule.temporary
22
+ end
23
+ end
24
+
25
+ def apply_points
26
+ return unless rule_applies?
27
+ sashes.each do |sash|
28
+ point = sash.add_points points, category: category
29
+ notify_observers(
30
+ description: I18n.t("merit.granted_points", points: points),
31
+ merit_object: point,
32
+ sash_id: point.sash_id
33
+ )
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def grant_badges
40
+ sashes.each do |sash|
41
+ next unless new_or_multiple?(sash)
42
+ badge_sash = sash.add_badge badge.id
43
+ notify_observers(
44
+ description: I18n.t("merit.granted_badge", badge_name: badge.name),
45
+ merit_object: badge_sash,
46
+ sash_id: badge_sash.sash_id
47
+ )
48
+ end
49
+ end
50
+
51
+ def remove_badges
52
+ sashes.each do |sash|
53
+ sash.rm_badge badge.id
54
+ notify_observers(
55
+ description: I18n.t("merit.removed_badge", badge_name: badge.name),
56
+ sash_id: sash.id
57
+ )
58
+ end
59
+ end
60
+
61
+ def new_or_multiple?(sash)
62
+ !sash.badge_ids.include?(badge.id) || @rule.multiple
63
+ end
64
+
65
+ def rule_object
66
+ BaseTargetFinder.find(@rule, @action)
67
+ end
68
+
69
+ def rule_applies?
70
+ @rule.applies? rule_object
71
+ end
72
+
73
+ def points
74
+ if @rule.score.respond_to?(:call)
75
+ @rule.score.call(rule_object)
76
+ else
77
+ @rule.score
78
+ end
79
+ end
80
+
81
+ def category
82
+ @rule.category
83
+ end
84
+
85
+ def sashes
86
+ @sashes ||= SashFinder.find @rule, @action
87
+ end
88
+
89
+ def badge
90
+ @rule.badge
91
+ end
92
+
93
+ def notify_observers(changed_data = {})
94
+ changed
95
+ hash = {
96
+ granted_at: @action.created_at,
97
+ merit_action_id: @action.id
98
+ }.merge(changed_data)
99
+ super(hash)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,50 @@
1
+ # Merit::Action general schema
2
+ # ______________________________________________________________
3
+ # source | action | target
4
+ # user_id | method,value | model,id | processed
5
+ # ______________________________________________________________
6
+ # 1 | comment nil | List 8 | true
7
+ # 1 | vote 3 | List 12 | true
8
+ # 3 | follow nil | User 1 | false
9
+ # X | create nil | User #{generated_id} | false
10
+ # ______________________________________________________________
11
+ #
12
+ # Rules relate to merit_actions by action name ('controller#action' string)
13
+ module Merit::Models
14
+ module ActionConcern
15
+ extend ActiveSupport::Concern
16
+
17
+ class_methods do
18
+ def check_unprocessed
19
+ where(processed: false).find_each(&:check_all_rules)
20
+ end
21
+ end
22
+
23
+ # Check rules defined for a merit_action
24
+ def check_all_rules
25
+ mark_as_processed!
26
+ return if had_errors
27
+
28
+ check_rules rules_matcher.select_from(Merit::AppBadgeRules), :badges
29
+ check_rules rules_matcher.select_from(Merit::AppPointRules), :points
30
+ end
31
+
32
+ private
33
+
34
+ def check_rules(rules_array, badges_or_points)
35
+ rules_array.each do |rule|
36
+ judge = Merit::Judge.new(rule, action: self)
37
+ judge.public_send(:"apply_#{badges_or_points}")
38
+ end
39
+ end
40
+
41
+ def mark_as_processed!
42
+ self.processed = true
43
+ save
44
+ end
45
+
46
+ def rules_matcher
47
+ @rules_matcher ||= ::Merit::RulesMatcher.new(target_model, action_method)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,11 @@
1
+ module Merit::Models::ActiveRecord
2
+ class Action < ActiveRecord::Base
3
+ include Merit::Models::ActionConcern
4
+
5
+ self.table_name = :merit_actions
6
+
7
+ has_many :activity_logs, class_name: 'Merit::ActivityLog'
8
+ end
9
+ end
10
+
11
+ class Merit::Action < Merit::Models::ActiveRecord::Action; end
@@ -0,0 +1,11 @@
1
+ module Merit::Models::ActiveRecord
2
+ class ActivityLog < ActiveRecord::Base
3
+ self.table_name = :merit_activity_logs
4
+
5
+ belongs_to :action, class_name: 'Merit::Action'
6
+ belongs_to :related_change, polymorphic: true, optional: true
7
+ has_one :sash, through: :related_change
8
+ end
9
+ end
10
+
11
+ class Merit::ActivityLog < Merit::Models::ActiveRecord::ActivityLog; end
@@ -0,0 +1,13 @@
1
+ module Merit::Models::ActiveRecord
2
+ class BadgesSash < ActiveRecord::Base
3
+ include Merit::Models::BadgesSashConcern
4
+
5
+ has_many :activity_logs,
6
+ class_name: 'Merit::ActivityLog',
7
+ as: :related_change
8
+
9
+ validates_presence_of :badge_id, :sash
10
+ end
11
+ end
12
+
13
+ class Merit::BadgesSash < Merit::Models::ActiveRecord::BadgesSash; end
@@ -0,0 +1,32 @@
1
+ module Merit::Models::ActiveRecord
2
+ # Sash is a container for reputation data for meritable models. It's an
3
+ # indirection between meritable models and badges and scores (one to one
4
+ # relationship).
5
+ #
6
+ # It's existence make join models like badges_users and scores_users
7
+ # unnecessary. It should be transparent at the application.
8
+ class Sash < ActiveRecord::Base
9
+ include Merit::Models::SashConcern
10
+
11
+ has_many :badges_sashes, dependent: :destroy
12
+ has_many :scores, dependent: :destroy, class_name: 'Merit::Score'
13
+
14
+ after_create :create_scores
15
+
16
+ # Retrieve all points from a category or none if category doesn't exist
17
+ # By default retrieve all Points
18
+ # @param category [String] The category
19
+ # @return [ActiveRecord::Relation] containing the points
20
+ def score_points(options = {})
21
+ scope = Merit::Score::Point
22
+ .joins(:score)
23
+ .where('merit_scores.sash_id = ?', id)
24
+ if (category = options[:category])
25
+ scope = scope.where('merit_scores.category = ?', category)
26
+ end
27
+ scope
28
+ end
29
+ end
30
+ end
31
+
32
+ class Merit::Sash < Merit::Models::ActiveRecord::Sash; end
@@ -0,0 +1,25 @@
1
+ module Merit::Models::ActiveRecord
2
+ class Score < ActiveRecord::Base
3
+ self.table_name = :merit_scores
4
+ belongs_to :sash
5
+ has_many :score_points,
6
+ dependent: :destroy,
7
+ class_name: 'Merit::Score::Point'
8
+
9
+ def points
10
+ score_points.group(:score_id).sum(:num_points).values.first || 0
11
+ end
12
+
13
+ class Point < ActiveRecord::Base
14
+ belongs_to :score, class_name: 'Merit::Score'
15
+ has_one :sash, through: :score
16
+ has_many :activity_logs,
17
+ class_name: 'Merit::ActivityLog',
18
+ as: :related_change
19
+ delegate :sash_id, to: :score
20
+ end
21
+ end
22
+ end
23
+
24
+ class Merit::Score < Merit::Models::ActiveRecord::Score; end
25
+ class Merit::Score::Point < Merit::Models::ActiveRecord::Score::Point; end
@@ -0,0 +1,13 @@
1
+ module Merit::Models
2
+ module BadgesSashConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ belongs_to :sash
7
+ end
8
+
9
+ def badge
10
+ Merit::Badge.find(badge_id)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Merit
2
+ module Models::Base
3
+ module BadgesSash
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ belongs_to :sash
8
+ end
9
+
10
+ def badge
11
+ Merit::Badge.find(badge_id)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ module Merit
2
+ module Models::Base
3
+ module Sash
4
+ def badges
5
+ badge_ids.map { |id| Merit::Badge.find id }
6
+ end
7
+
8
+ def badge_ids
9
+ badges_sashes.map(&:badge_id)
10
+ end
11
+
12
+ def add_badge(badge_id)
13
+ bs = Merit::BadgesSash.new(badge_id: badge_id.to_i)
14
+ badges_sashes << bs
15
+ bs
16
+ end
17
+
18
+ def rm_badge(badge_id)
19
+ badges_sashes.where(badge_id: badge_id.to_i).first.try(:destroy)
20
+ end
21
+
22
+ # Retrieve the number of points from a category
23
+ # By default all points are summed up
24
+ # @param category [String] The category
25
+ # @return [Integer] The number of points
26
+ def points(options = {})
27
+ if (category = options[:category])
28
+ scores.where(category: category).first.try(:points) || 0
29
+ else
30
+ scores.reduce(0) { |sum, score| sum + score.points }
31
+ end
32
+ end
33
+
34
+ def add_points(num_points, options = {})
35
+ point = Merit::Score::Point.new
36
+ point.num_points = num_points
37
+ scores
38
+ .where(category: options[:category] || 'default')
39
+ .first_or_create
40
+ .score_points << point
41
+ point
42
+ end
43
+
44
+ def subtract_points(num_points, options = {})
45
+ add_points(-num_points, options)
46
+ end
47
+
48
+ private
49
+
50
+ def create_scores
51
+ scores << Merit::Score.create
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,53 @@
1
+ module Merit::Models
2
+ module SashConcern
3
+ def badges
4
+ badge_ids.map { |id| Merit::Badge.find id }
5
+ end
6
+
7
+ def badge_ids
8
+ badges_sashes.map(&:badge_id)
9
+ end
10
+
11
+ def add_badge(badge_id)
12
+ bs = Merit::BadgesSash.new(badge_id: badge_id.to_i)
13
+ badges_sashes << bs
14
+ bs
15
+ end
16
+
17
+ def rm_badge(badge_id)
18
+ badges_sashes.where(badge_id: badge_id.to_i).first.try(:destroy)
19
+ end
20
+
21
+ # Retrieve the number of points from a category
22
+ # By default all points are summed up
23
+ # @param category [String] The category
24
+ # @return [Integer] The number of points
25
+ def points(options = {})
26
+ if (category = options[:category])
27
+ scores.where(category: category).first.try(:points) || 0
28
+ else
29
+ scores.reduce(0) { |sum, score| sum + score.points }
30
+ end
31
+ end
32
+
33
+ def add_points(num_points, options = {})
34
+ point = Merit::Score::Point.new
35
+ point.num_points = num_points
36
+ scores
37
+ .where(category: options[:category] || 'default')
38
+ .first_or_create
39
+ .score_points << point
40
+ point
41
+ end
42
+
43
+ def subtract_points(num_points, options = {})
44
+ add_points(-num_points, options)
45
+ end
46
+
47
+ private
48
+
49
+ def create_scores
50
+ scores << Merit::Score.create
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ module Merit
2
+ # Points are a simple integer value which are given to "meritable" resources
3
+ # according to rules in +app/models/merit/point_rules.rb+. They are given on
4
+ # actions-triggered.
5
+ module PointRulesMethods
6
+ # Define rules on certaing actions for giving points
7
+ def score(points, *args, &block)
8
+ options = args.extract_options!
9
+ options_to = options.fetch(:to) { :action_user }
10
+
11
+ actions = Array.wrap(options[:on])
12
+
13
+ Array.wrap(options_to).each do |to|
14
+ rule = Rule.new
15
+ rule.score = points
16
+ rule.to = to
17
+ rule.block = block
18
+ rule.category = options.fetch(:category) { :default }
19
+ rule.model_name = options[:model_name] if options[:model_name]
20
+
21
+ actions.each do |action|
22
+ defined_rules[action] ||= []
23
+ defined_rules[action] << rule
24
+ end
25
+ end
26
+ end
27
+
28
+ # Currently defined rules
29
+ def defined_rules
30
+ @defined_rules ||= {}
31
+ end
32
+ end
33
+ end