activerecord-reputation-system 1.3.4 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,13 +1,7 @@
1
- ## Active Record Reputation System [![Build Status](https://secure.travis-ci.org/twitter/activerecord-reputation-system.png)](http://travis-ci.org/twitter/activerecord-reputation-system) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/twitter/activerecord-reputation-system)
1
+ ## ActiveRecord Reputation System [![Build Status](https://secure.travis-ci.org/twitter/activerecord-reputation-system.png)](http://travis-ci.org/twitter/activerecord-reputation-system) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/twitter/activerecord-reputation-system)
2
2
 
3
3
  The Active Record Reputation System helps you discover more about your application and make better decisions. The Reputation System gem makes it easy to integrate reputation systems into Rails applications, decouple the system from the main application and provide guidelines for good design of reputation systems.
4
4
 
5
- ## Concept
6
-
7
- In this gem, the reputation system is described as a network of reputations where updates are triggered by evaluations and reputation values are computed and propagated by the network. In this network, reputations with values directly computed from evaluations are called primary reputations and reputations with values indirectly computed from evaluations are called non-primary reputations. The following is an abstract view of a possible Reputation System:
8
-
9
- ![Alt text](./activerecord-reputation-system/raw/master/abs_rs.png "Abstract view of Reputation System")
10
-
11
5
  ## Installation
12
6
 
13
7
  Add to Gemfile:
@@ -99,19 +93,19 @@ has_reputation :name,
99
93
  :source_of => [{:reputation => name, :of => attribute}, ...],
100
94
  :init_value => initial_value
101
95
  ```
102
- * :name is the name of the reputation.
103
- * :source is a source of the reputation. If it is primary reputation, it takes a class name as input. If it's a non-primary reputation, it takes one or more source definitions, which consist of:
104
- ** :reputation - name of reputation to be used as a source.
105
- ** :of - attribute name (It also accepts a proc as an argument) of the ActiveRecord model which has the source reputation. (default: :self)
106
- ** :weight (optional) - weight value to be used for aggregation (default: 1).
107
- * :aggregated_by is a mathematical process to be used to aggregate reputation or evaluation values. The following processes are available (each value is weighted by a predefined weight):
108
- ** average - averages all values received.
109
- ** sum - sums up all the values received.
110
- ** product - multiplies all the values received.
111
- * :source_of (optional) - just like active record association, you don't need to define this if a name can be derived from class name; otherwise if the reputation is used as a part of a source belonging to other reputations, you must define. It takes one or more source definitions, which consists of:
112
- ** :reputation - name of the reputation to be used as a source.
113
- ** :of - attribute name (It also accepts a proc as an argument) of the ActiveRecord model which has the source reputation. (default: :self)
114
- * :init_value (optional) - initial reputation value assigned to new reputation. It is 0 for average and sum process and 1 for product by default.
96
+ * `:name` is the name of the reputation.
97
+ * `:source` is a source of the reputation. If it is primary reputation, it takes a class name as input. If it's a non-primary reputation, it takes one or more source definitions, which consist of:
98
+ * `:reputation` - name of reputation to be used as a source.
99
+ * `:of` - attribute name (It also accepts a proc as an argument) of the ActiveRecord model which has the source reputation. (default: :self)
100
+ * `:weight` (optional) - weight value to be used for aggregation (default: 1).
101
+ * `:aggregated_by` is a mathematical process to be used to aggregate reputation or evaluation values. The following processes are available (each value is weighted by a predefined weight):
102
+ * average - averages all values received.
103
+ * sum - sums up all the values received.
104
+ * product - multiplies all the values received.
105
+ * `:source_of` (optional) - just like active record association, you don't need to define this if a name can be derived from class name; otherwise if the reputation is used as a part of a source belonging to other reputations, you must define. It takes one or more source definitions, which consists of:
106
+ * `:reputation` - name of the reputation to be used as a source.
107
+ * `:of` - attribute name (It also accepts a proc as an argument) of the ActiveRecord model which has the source reputation. (default: :self)
108
+ * `:init_value` (optional) - initial reputation value assigned to new reputation. It is 0 for average and sum process and 1 for product by default.
115
109
 
116
110
  ## Evaluation
117
111
  ```ruby
@@ -161,6 +155,20 @@ reputations_activated?(reputation_name)
161
155
  ```
162
156
 
163
157
  ## Querying with Reputation
158
+
159
+ ``` ruby
160
+ # Includes the specified reputation value for the given name.
161
+ ActiveRecord::Base.with_reputation(reputation_name, scope)
162
+ # For example:
163
+ User.with_reputation(:karma).where("karma > ?", 3).order("karma")
164
+
165
+ # Includes the specified normalized reputation value for the given name.
166
+ ActiveRecord::Base.with_normalized_reputation(reputation, scope)
167
+ # For example:
168
+ User.with_normalized_reputation(:karma).where("karma > ?" > 0.5).order("karma")
169
+ ```
170
+ Note: Above query methods does not support calcualtion methods such as count, sum and etc yet.
171
+
164
172
  ```ruby
165
173
  # Includes the specified reputation value for the given name via a normal Active Record find query.
166
174
  ActiveRecord::Base.find_with_reputation(reputation_name, find_scope, options)
@@ -15,7 +15,9 @@
15
15
  ##
16
16
 
17
17
  require 'reputation_system/base'
18
- require 'reputation_system/query'
18
+ require 'reputation_system/query_methods'
19
+ require 'reputation_system/finder_methods'
20
+ require 'reputation_system/query_builder'
19
21
  require 'reputation_system/evaluation'
20
22
  require 'reputation_system/network'
21
23
  require 'reputation_system/reputation'
@@ -50,7 +50,9 @@ module ReputationSystem
50
50
  # If it is first time to be called
51
51
  unless ancestors.include?(ReputationSystem::Reputation)
52
52
  has_many :reputations, :as => :target, :class_name => "RSReputation", :dependent => :destroy
53
- include ReputationSystem::Query
53
+ include ReputationSystem::QueryBuilder
54
+ include ReputationSystem::QueryMethods
55
+ include ReputationSystem::FinderMethods
54
56
  include ReputationSystem::Reputation
55
57
  include ReputationSystem::Scope
56
58
  end
@@ -0,0 +1,84 @@
1
+ ##
2
+ # Copyright 2012 Twitter, Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ ##
16
+
17
+ module ReputationSystem
18
+ module FinderMethods
19
+ def self.included(klass)
20
+ klass.extend ClassMethods
21
+ end
22
+
23
+ module ClassMethods
24
+
25
+ def find_with_reputation(*args)
26
+ reputation_name, srn, find_scope, options = parse_query_args(*args)
27
+ options[:select] = build_select_statement(table_name, reputation_name, options[:select])
28
+ options[:joins] = build_join_statement(table_name, name, srn, options[:joins])
29
+ options[:conditions] = build_condition_statement(options[:conditions])
30
+ find(find_scope, options)
31
+ end
32
+
33
+ def count_with_reputation(*args)
34
+ reputation_name, srn, find_scope, options = parse_query_args(*args)
35
+ options[:joins] = build_join_statement(table_name, name, srn, options[:joins])
36
+ options[:conditions] = build_condition_statement(options[:conditions])
37
+ options[:conditions][0].gsub!(reputation_name.to_s, "COALESCE(rs_reputations.value, 0)")
38
+ count(find_scope, options)
39
+ end
40
+
41
+ def find_with_normalized_reputation(*args)
42
+ reputation_name, srn, find_scope, options = parse_query_args(*args)
43
+ options[:select] = build_select_statement(table_name, reputation_name, options[:select], srn, true)
44
+ options[:joins] = build_join_statement(table_name, name, srn, options[:joins])
45
+ options[:conditions] = build_condition_statement(options[:conditions])
46
+ find(find_scope, options)
47
+ end
48
+
49
+ def find_with_reputation_sql(*args)
50
+ reputation_name, srn, find_scope, options = parse_query_args(*args)
51
+ options[:select] = build_select_statement(table_name, reputation_name, options[:select])
52
+ options[:joins] = build_join_statement(table_name, name, srn, options[:joins])
53
+ options[:conditions] = build_condition_statement(options[:conditions])
54
+ if respond_to?(:construct_finder_sql, true)
55
+ construct_finder_sql(options)
56
+ else
57
+ construct_finder_arel(options).to_sql
58
+ end
59
+ end
60
+
61
+ protected
62
+
63
+ def parse_query_args(*args)
64
+ case args.length
65
+ when 2
66
+ find_scope = args[1]
67
+ options = {}
68
+ when 3
69
+ find_scope = args[1]
70
+ options = args[2]
71
+ when 4
72
+ scope = args[1]
73
+ find_scope = args[2]
74
+ options = args[3]
75
+ else
76
+ raise ArgumentError, "Expecting 2, 3 or 4 arguments but got #{args.length}"
77
+ end
78
+ reputation_name = args[0]
79
+ srn = ReputationSystem::Network.get_scoped_reputation_name(name, reputation_name, scope)
80
+ [reputation_name, srn, find_scope, options]
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,73 @@
1
+ ##
2
+ # Copyright 2012 Twitter, Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ ##
16
+
17
+ module ReputationSystem
18
+ module QueryBuilder
19
+ def self.included(klass)
20
+ klass.extend ClassMethods
21
+ end
22
+
23
+ module ClassMethods
24
+ DELTA = 0.000001
25
+ REPUTATION_JOIN_STATEMENT = "LEFT JOIN rs_reputations ON %s.id = rs_reputations.target_id AND rs_reputations.target_type = ? AND rs_reputations.reputation_name = ? AND rs_reputations.active = ?"
26
+
27
+ def build_select_statement(table_name, reputation_name, select=nil, srn=nil, normalize=false)
28
+ select = sanitize_sql_array(["%s.*", table_name]) unless select
29
+ if normalize
30
+ max = RSReputation.max(srn, self.name)
31
+ min = RSReputation.min(srn, self.name)
32
+ range = max - min
33
+ if range < DELTA
34
+ sanitize_sql_array(["%s, (0) AS normalized_%s", select, reputation_name])
35
+ else
36
+ sanitize_sql_array(["%s, ((rs_reputations.value - %s) / %s) AS normalized_%s", select, min, range, reputation_name])
37
+ end
38
+ else
39
+ sanitize_sql_array(["%s, COALESCE(rs_reputations.value, 0) AS %s", select, reputation_name])
40
+ end
41
+ end
42
+
43
+ def build_select_statement_with_reputation_only(table_name, reputation_name, srn=nil, normalize=false)
44
+ if normalize
45
+ max = RSReputation.max(srn, self.name)
46
+ min = RSReputation.min(srn, self.name)
47
+ range = max - min
48
+ if range < DELTA
49
+ sanitize_sql_array(["(0) AS normalized_%s", reputation_name])
50
+ else
51
+ sanitize_sql_array(["((rs_reputations.value - %s) / %s) AS normalized_%s", min, range, reputation_name])
52
+ end
53
+ else
54
+ sanitize_sql_array(["COALESCE(rs_reputations.value, 0) AS %s", reputation_name])
55
+ end
56
+ end
57
+
58
+ def build_condition_statement(conditions=nil)
59
+ conditions ||= [""]
60
+ conditions = [conditions] unless conditions.is_a? Array
61
+ conditions
62
+ end
63
+
64
+ def build_join_statement(table_name, class_name, srn, joins=nil)
65
+ joins ||= []
66
+ joins = [joins] unless joins.is_a? Array
67
+ rep_join = sanitize_sql_array([REPUTATION_JOIN_STATEMENT, class_name.to_s, srn.to_s, true])
68
+ rep_join = sanitize_sql_array([rep_join, table_name])
69
+ joins << rep_join
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,61 @@
1
+ ##
2
+ # Copyright 2012 Twitter, Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ ##
16
+
17
+ module ReputationSystem
18
+ module QueryMethods
19
+ def self.included(klass)
20
+ klass.extend ClassMethods
21
+ end
22
+
23
+ module ClassMethods
24
+ def with_reputation(*args)
25
+ reputation_name, srn = parse_arel_query_args(args)
26
+ select = build_select_statement(table_name, reputation_name)
27
+ joins = build_join_statement(table_name, name, srn)
28
+ self.select(select).joins(joins)
29
+ end
30
+
31
+ def with_reputation_only(*args)
32
+ reputation_name, srn = parse_arel_query_args(args)
33
+ select = build_select_statement_with_reputation_only(table_name, reputation_name)
34
+ joins = build_join_statement(table_name, name, srn)
35
+ self.select(select).joins(joins)
36
+ end
37
+
38
+ def with_normalized_reputation(*args)
39
+ reputation_name, srn = parse_arel_query_args(args)
40
+ select = build_select_statement(table_name, reputation_name, nil, srn, true)
41
+ joins = build_join_statement(table_name, name, srn)
42
+ self.select(select).joins(joins)
43
+ end
44
+
45
+ def with_normalized_reputation_only(*args)
46
+ reputation_name, srn = parse_arel_query_args(args)
47
+ select = build_select_statement_with_reputation_only(table_name, reputation_name, srn, true)
48
+ joins = build_join_statement(table_name, name, srn)
49
+ self.select(select).joins(joins)
50
+ end
51
+
52
+ protected
53
+
54
+ def parse_arel_query_args(args)
55
+ reputation_name = args[0]
56
+ srn = ReputationSystem::Network.get_scoped_reputation_name(name, reputation_name, args[1])
57
+ [reputation_name, srn]
58
+ end
59
+ end
60
+ end
61
+ end
@@ -15,5 +15,5 @@
15
15
  ##
16
16
 
17
17
  module ReputationSystem
18
- VERSION = "1.3.4"
18
+ VERSION = "1.4.0"
19
19
  end
@@ -0,0 +1,238 @@
1
+ ##
2
+ # Copyright 2012 Twitter, Inc
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ ##
16
+
17
+ require 'spec_helper'
18
+
19
+ describe ActiveRecord::Base do
20
+
21
+ before(:each) do
22
+ @user = User.create!(:name => 'jack')
23
+ @question = Question.create!(:text => 'Does this work?', :author_id => @user.id)
24
+ @answer = Answer.create!(:text => 'Yes!', :author_id => @user.id, :question_id => @question.id)
25
+ @phrase = Phrase.create!(:text => "One")
26
+ end
27
+
28
+ describe "#with_reputation" do
29
+ context "Without Scopes" do
30
+ before :each do
31
+ @question.add_evaluation(:total_votes, 3, @user)
32
+ end
33
+
34
+ it "should return result with given reputation" do
35
+ res = Question.with_reputation(:total_votes)
36
+ res.should == [@question]
37
+ res[0].total_votes.should_not be_nil
38
+ end
39
+
40
+ it "should retain conditions option" do
41
+ @question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
42
+ @question2.add_evaluation(:total_votes, 5, @user)
43
+ res = Question.with_reputation(:total_votes).where("total_votes > 4")
44
+ res.should == [@question2]
45
+ end
46
+
47
+ it "should retain joins option" do
48
+ res = Question.with_reputation(:total_votes).
49
+ select("questions.*, users.name AS user_name").
50
+ joins("JOIN users ON questions.author_id = users.id")
51
+ res.should == [@question]
52
+ res[0].user_name.should == @user.name
53
+ end
54
+
55
+ it "should not retain select option" do
56
+ res = Question.with_reputation(:total_votes).select("questions.id")
57
+ res.should == [@question]
58
+ res[0].id.should_not be_nil
59
+ lambda {res[0].text}.should_not raise_error
60
+ end
61
+ end
62
+
63
+ context "With Scopes" do
64
+ before :each do
65
+ @trans_ja = Translation.create!(:text => "Ichi", :user => @user, :locale => "ja", :phrase => @phrase)
66
+ @trans_ja.add_evaluation(:votes, 3, @user)
67
+ @trans_fr = Translation.create!(:text => "Ichi", :user => @user, :locale => "fr", :phrase => @phrase)
68
+ @trans_fr.add_evaluation(:votes, 6, @user)
69
+ end
70
+
71
+ it "should return result with given reputation" do
72
+ res = Phrase.with_reputation(:maturity, :ja)
73
+ res.should == [@phrase]
74
+ res[0].maturity.should == 3
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "#with_reputation_only" do
80
+ context "Without Scopes" do
81
+ before :each do
82
+ @question.add_evaluation(:total_votes, 3, @user)
83
+ end
84
+
85
+ it "should return result with given reputation" do
86
+ res = Question.with_reputation_only(:total_votes)
87
+ res.length.should == 1
88
+ res[0].total_votes.should_not be_nil
89
+ end
90
+
91
+ it "should retain conditions option" do
92
+ @question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
93
+ @question2.add_evaluation(:total_votes, 5, @user)
94
+ res = Question.with_reputation_only(:total_votes).where("total_votes > 4")
95
+ res.length.should == 1
96
+ res[0].total_votes.should > 4
97
+ end
98
+
99
+ it "should retain joins option" do
100
+ res = Question.with_reputation_only(:total_votes).
101
+ select("questions.*, users.name AS user_name").
102
+ joins("JOIN users ON questions.author_id = users.id")
103
+ res[0].user_name.should == @user.name
104
+ end
105
+
106
+ it "should retain select option" do
107
+ res = Question.with_reputation_only(:total_votes).select("questions.id")
108
+ res.should == [@question]
109
+ res[0].id.should_not be_nil
110
+ lambda {res[0].text}.should raise_error
111
+ end
112
+ end
113
+
114
+ context "With Scopes" do
115
+ before :each do
116
+ @trans_ja = Translation.create!(:text => "Ichi", :user => @user, :locale => "ja", :phrase => @phrase)
117
+ @trans_ja.add_evaluation(:votes, 3, @user)
118
+ @trans_fr = Translation.create!(:text => "Ichi", :user => @user, :locale => "fr", :phrase => @phrase)
119
+ @trans_fr.add_evaluation(:votes, 6, @user)
120
+ end
121
+
122
+ it "should return result with given reputation" do
123
+ res = Phrase.with_reputation_only(:maturity, :ja)
124
+ res.length.should == 1
125
+ res[0].maturity.should == 3
126
+ end
127
+ end
128
+ end
129
+
130
+ describe "#with_normalized_reputation" do
131
+ context "Without Scopes" do
132
+ before :each do
133
+ @question.add_evaluation(:total_votes, 3, @user)
134
+ end
135
+
136
+ it "should return result with given normalized reputation" do
137
+ @question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
138
+ @question2.add_evaluation(:total_votes, 6, @user)
139
+ res = Question.with_normalized_reputation(:total_votes)
140
+ res.should == [@question, @question2]
141
+ res[0].normalized_total_votes.should be_within(DELTA).of(0)
142
+ res[1].normalized_total_votes.should be_within(DELTA).of(1)
143
+ end
144
+
145
+ it "should not retain select option" do
146
+ res = Question.with_normalized_reputation(:total_votes).select("questions.id")
147
+ res.should == [@question]
148
+ res[0].id.should_not be_nil
149
+ lambda {res[0].text}.should_not raise_error
150
+ end
151
+
152
+ it "should retain conditions option" do
153
+ @question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
154
+ @question2.add_evaluation(:total_votes, 6, @user)
155
+ res = Question.with_normalized_reputation(:total_votes).where("normalized_total_votes > 0.6")
156
+ res.should == [@question2]
157
+ end
158
+
159
+ it "should retain joins option" do
160
+ res = Question.with_normalized_reputation(:total_votes).
161
+ select("questions.*, users.name AS user_name").
162
+ joins("JOIN users ON questions.author_id = users.id")
163
+ res.should == [@question]
164
+ res[0].user_name.should == @user.name
165
+ end
166
+ end
167
+
168
+ context "With Scopes" do
169
+ before :each do
170
+ @trans_ja = Translation.create!(:text => "Ichi", :user => @user, :locale => "ja", :phrase => @phrase)
171
+ @trans_ja.add_evaluation(:votes, 3, @user)
172
+ @trans_fr = Translation.create!(:text => "Ichi", :user => @user, :locale => "fr", :phrase => @phrase)
173
+ @trans_fr.add_evaluation(:votes, 6, @user)
174
+ end
175
+
176
+ it "should return result with given reputation" do
177
+ res = Phrase.with_normalized_reputation(:maturity, :ja)
178
+ res.should == [@phrase]
179
+ res[0].normalized_maturity.should be_within(DELTA).of(0)
180
+ end
181
+ end
182
+ end
183
+
184
+ describe "#with_normalized_reputation_only" do
185
+ context "Without Scopes" do
186
+ before :each do
187
+ @question.add_evaluation(:total_votes, 3, @user)
188
+ end
189
+
190
+ it "should return result with given normalized reputation" do
191
+ @question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
192
+ @question2.add_evaluation(:total_votes, 6, @user)
193
+ res = Question.with_normalized_reputation_only(:total_votes)
194
+ res.length.should == 2
195
+ res[0].normalized_total_votes.should be_within(DELTA).of(0)
196
+ res[1].normalized_total_votes.should be_within(DELTA).of(1)
197
+ end
198
+
199
+ it "should not retain select option" do
200
+ res = Question.with_normalized_reputation_only(:total_votes).select("questions.id")
201
+ res.length.should == 1
202
+ res[0].id.should_not be_nil
203
+ lambda {res[0].text}.should raise_error
204
+ end
205
+
206
+ it "should retain conditions option" do
207
+ @question2 = Question.create!(:text => 'Does this work?', :author_id => @user.id)
208
+ @question2.add_evaluation(:total_votes, 6, @user)
209
+ res = Question.with_normalized_reputation_only(:total_votes).where("normalized_total_votes > 0.6")
210
+ res.length.should == 1
211
+ res[0].normalized_total_votes.should > 0.6
212
+ end
213
+
214
+ it "should retain joins option" do
215
+ res = Question.with_normalized_reputation_only(:total_votes).
216
+ select("questions.*, users.name AS user_name").
217
+ joins("JOIN users ON questions.author_id = users.id")
218
+ res.length.should == 1
219
+ res[0].user_name.should == @user.name
220
+ end
221
+ end
222
+
223
+ context "With Scopes" do
224
+ before :each do
225
+ @trans_ja = Translation.create!(:text => "Ichi", :user => @user, :locale => "ja", :phrase => @phrase)
226
+ @trans_ja.add_evaluation(:votes, 3, @user)
227
+ @trans_fr = Translation.create!(:text => "Ichi", :user => @user, :locale => "fr", :phrase => @phrase)
228
+ @trans_fr.add_evaluation(:votes, 6, @user)
229
+ end
230
+
231
+ it "should return result with given reputation" do
232
+ res = Phrase.with_normalized_reputation_only(:maturity, :ja)
233
+ res.length.should == 1
234
+ res[0].normalized_maturity.should be_within(DELTA).of(0)
235
+ end
236
+ end
237
+ end
238
+ end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-reputation-system
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 7
5
+ prerelease:
5
6
  segments:
6
7
  - 1
7
- - 3
8
8
  - 4
9
- version: 1.3.4
9
+ - 0
10
+ version: 1.4.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Katsuya Noguchi
@@ -14,87 +15,98 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2012-08-10 00:00:00 +09:00
18
- default_executable:
18
+ date: 2012-09-10 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: activerecord
22
21
  prerelease: false
23
22
  requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
+ hash: 3
27
28
  segments:
28
29
  - 0
29
30
  version: "0"
30
31
  type: :development
32
+ name: activerecord
31
33
  version_requirements: *id001
32
34
  - !ruby/object:Gem::Dependency
33
- name: rake
34
35
  prerelease: false
35
36
  requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
36
38
  requirements:
37
39
  - - ">="
38
40
  - !ruby/object:Gem::Version
41
+ hash: 49
39
42
  segments:
40
43
  - 0
41
44
  - 8
42
45
  - 7
43
46
  version: 0.8.7
44
47
  type: :development
48
+ name: rake
45
49
  version_requirements: *id002
46
50
  - !ruby/object:Gem::Dependency
47
- name: rspec
48
51
  prerelease: false
49
52
  requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
50
54
  requirements:
51
55
  - - ~>
52
56
  - !ruby/object:Gem::Version
57
+ hash: 19
53
58
  segments:
54
59
  - 2
55
60
  - 8
56
61
  version: "2.8"
57
62
  type: :development
63
+ name: rspec
58
64
  version_requirements: *id003
59
65
  - !ruby/object:Gem::Dependency
60
- name: rdoc
61
66
  prerelease: false
62
67
  requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
63
69
  requirements:
64
70
  - - ">="
65
71
  - !ruby/object:Gem::Version
72
+ hash: 3
66
73
  segments:
67
74
  - 0
68
75
  version: "0"
69
76
  type: :development
77
+ name: rdoc
70
78
  version_requirements: *id004
71
79
  - !ruby/object:Gem::Dependency
72
- name: database_cleaner
73
80
  prerelease: false
74
81
  requirement: &id005 !ruby/object:Gem::Requirement
82
+ none: false
75
83
  requirements:
76
84
  - - ~>
77
85
  - !ruby/object:Gem::Version
86
+ hash: 1
78
87
  segments:
79
88
  - 0
80
89
  - 7
81
90
  - 1
82
91
  version: 0.7.1
83
92
  type: :development
93
+ name: database_cleaner
84
94
  version_requirements: *id005
85
95
  - !ruby/object:Gem::Dependency
86
- name: sqlite3
87
96
  prerelease: false
88
97
  requirement: &id006 !ruby/object:Gem::Requirement
98
+ none: false
89
99
  requirements:
90
100
  - - ~>
91
101
  - !ruby/object:Gem::Version
102
+ hash: 17
92
103
  segments:
93
104
  - 1
94
105
  - 3
95
106
  - 5
96
107
  version: 1.3.5
97
108
  type: :development
109
+ name: sqlite3
98
110
  version_requirements: *id006
99
111
  description: ActiveRecord Reputation System gem allows rails apps to compute and publish reputation scores for active record models.
100
112
  email:
@@ -122,8 +134,10 @@ files:
122
134
  - lib/models/rs_reputation_message.rb
123
135
  - lib/reputation_system/base.rb
124
136
  - lib/reputation_system/evaluation.rb
137
+ - lib/reputation_system/finder_methods.rb
125
138
  - lib/reputation_system/network.rb
126
- - lib/reputation_system/query.rb
139
+ - lib/reputation_system/query_builder.rb
140
+ - lib/reputation_system/query_methods.rb
127
141
  - lib/reputation_system/reputation.rb
128
142
  - lib/reputation_system/scope.rb
129
143
  - lib/reputation_system/version.rb
@@ -133,12 +147,11 @@ files:
133
147
  - spec/models/rs_reputation_spec.rb
134
148
  - spec/reputation_system/base_spec.rb
135
149
  - spec/reputation_system/evaluation_spec.rb
136
- - spec/reputation_system/network_spec.rb
137
- - spec/reputation_system/query_spec.rb
150
+ - spec/reputation_system/finder_methods_spec.rb
151
+ - spec/reputation_system/query_methods_spec.rb
138
152
  - spec/reputation_system/reputation_spec.rb
139
153
  - spec/reputation_system/scope_spec.rb
140
154
  - spec/spec_helper.rb
141
- has_rdoc: true
142
155
  homepage: https://github.com/twitter/activerecord-reputation-system
143
156
  licenses: []
144
157
 
@@ -148,23 +161,27 @@ rdoc_options: []
148
161
  require_paths:
149
162
  - lib
150
163
  required_ruby_version: !ruby/object:Gem::Requirement
164
+ none: false
151
165
  requirements:
152
166
  - - ">="
153
167
  - !ruby/object:Gem::Version
168
+ hash: 3
154
169
  segments:
155
170
  - 0
156
171
  version: "0"
157
172
  required_rubygems_version: !ruby/object:Gem::Requirement
173
+ none: false
158
174
  requirements:
159
175
  - - ">="
160
176
  - !ruby/object:Gem::Version
177
+ hash: 3
161
178
  segments:
162
179
  - 0
163
180
  version: "0"
164
181
  requirements: []
165
182
 
166
183
  rubyforge_project:
167
- rubygems_version: 1.3.6
184
+ rubygems_version: 1.8.24
168
185
  signing_key:
169
186
  specification_version: 3
170
187
  summary: ActiveRecord Reputation System gem allows rails apps to compute and publish reputation scores for active record models
@@ -1,102 +0,0 @@
1
- ##
2
- # Copyright 2012 Twitter, Inc
3
- #
4
- # Licensed under the Apache License, Version 2.0 (the "License");
5
- # you may not use this file except in compliance with the License.
6
- # You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- # Unless required by applicable law or agreed to in writing, software
11
- # distributed under the License is distributed on an "AS IS" BASIS,
12
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- # See the License for the specific language governing permissions and
14
- # limitations under the License.
15
- ##
16
-
17
- module ReputationSystem
18
- module Query
19
- def self.included(klass)
20
- klass.extend ClassMethods
21
- end
22
-
23
- module ClassMethods
24
- DELTA = 0.000001
25
-
26
- def find_with_reputation(*args)
27
- reputation_name, srn, find_scope, options = parse_query_args(*args)
28
- options[:select] ||= sanitize_sql_array(["%s.*", self.table_name])
29
- options[:select] = sanitize_sql_array(["%s, COALESCE(rs_reputations.value, 0) AS %s", options[:select], reputation_name])
30
- find_options = get_find_options(srn, options)
31
- find_options[:conditions][0].gsub!(reputation_name.to_s, "COALESCE(rs_reputations.value, 0)")
32
- find(find_scope, find_options)
33
- end
34
-
35
- def count_with_reputation(*args)
36
- reputation_name, srn, find_scope, options = parse_query_args(*args)
37
- find_options = get_find_options(srn, options)
38
- find_options[:conditions][0].gsub!(reputation_name.to_s, "COALESCE(rs_reputations.value, 0)")
39
- count(find_scope, find_options)
40
- end
41
-
42
- def find_with_normalized_reputation(*args)
43
- reputation_name, srn, find_scope, options = parse_query_args(*args)
44
- max = RSReputation.max(srn, self.name)
45
- min = RSReputation.min(srn, self.name)
46
- range = max - min
47
- options[:select] ||= sanitize_sql_array(["%s.*", self.table_name])
48
- if range < DELTA
49
- options[:select] = sanitize_sql_array(["%s, (0) AS normalized_%s", options[:select], reputation_name])
50
- else
51
- options[:select] = sanitize_sql_array(["%s, ((rs_reputations.value - %s) / %s) AS normalized_%s", options[:select], min, range, reputation_name])
52
- end
53
- find_options = get_find_options(srn, options)
54
- find(find_scope, options)
55
- end
56
-
57
- def find_with_reputation_sql(*args)
58
- reputation_name, srn, find_scope, options = parse_query_args(*args)
59
- options[:select] ||= sanitize_sql_array(["%s.*", self.table_name])
60
- options[:select] = sanitize_sql_array(["%s, COALESCE(rs_reputations.value, 0) AS %s", options[:select], reputation_name])
61
- find_options = get_find_options(srn, options)
62
- if respond_to?(:construct_finder_sql, true)
63
- construct_finder_sql(find_options)
64
- else
65
- construct_finder_arel(find_options).to_sql
66
- end
67
- end
68
-
69
- protected
70
- def get_find_options(srn, options)
71
- options[:joins] ||= []
72
- options[:joins] = [options[:joins]] unless options[:joins].is_a? Array
73
- temp_joins = sanitize_sql_array(["LEFT JOIN rs_reputations ON %s.id = rs_reputations.target_id AND rs_reputations.target_type = ? AND rs_reputations.reputation_name = ? AND rs_reputations.active = ?", self.name, srn.to_s, true])
74
- temp_joins = sanitize_sql_array([temp_joins, self.table_name])
75
- options[:joins] << temp_joins
76
- options[:conditions] ||= [""]
77
- options[:conditions] = [options[:conditions]] unless options[:conditions].is_a? Array
78
- options
79
- end
80
-
81
- def parse_query_args(*args)
82
- case args.length
83
- when 2
84
- find_scope = args[1]
85
- options = {}
86
- when 3
87
- find_scope = args[1]
88
- options = args[2]
89
- when 4
90
- scope = args[1]
91
- find_scope = args[2]
92
- options = args[3]
93
- else
94
- raise ArgumentError, "Expecting 2, 3 or 4 arguments but got #{args.length}"
95
- end
96
- reputation_name = args[0]
97
- srn = ReputationSystem::Network.get_scoped_reputation_name(name, reputation_name, scope)
98
- [reputation_name, srn, find_scope, options]
99
- end
100
- end
101
- end
102
- end
@@ -1,27 +0,0 @@
1
- ##
2
- # Copyright 2012 Twitter, Inc
3
- #
4
- # Licensed under the Apache License, Version 2.0 (the "License");
5
- # you may not use this file except in compliance with the License.
6
- # You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- # Unless required by applicable law or agreed to in writing, software
11
- # distributed under the License is distributed on an "AS IS" BASIS,
12
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- # See the License for the specific language governing permissions and
14
- # limitations under the License.
15
- ##
16
-
17
- require 'spec_helper'
18
-
19
- describe ActiveRecord::Base do
20
-
21
- before(:each) do
22
- @user = User.create!(:name => 'jack')
23
- @question = Question.create!(:text => 'Does this work?', :author_id => @user.id)
24
- @answer = Answer.create!(:text => 'Yes!', :author_id => @user.id, :question_id => @question.id)
25
- @phrase = Phrase.create!(:text => "One")
26
- end
27
- end