stacked 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +6 -0
- data/Gemfile +1 -0
- data/LICENSE +20 -0
- data/README.markdown +37 -0
- data/README.rdoc +18 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/cleaner.rb +29 -0
- data/doc/Stacked.html +155 -0
- data/doc/Stacked/Answer.html +1394 -0
- data/doc/Stacked/Badge.html +480 -0
- data/doc/Stacked/Base.html +1124 -0
- data/doc/Stacked/Comment.html +1037 -0
- data/doc/Stacked/NotImplemented.html +162 -0
- data/doc/Stacked/Posttimeline.html +543 -0
- data/doc/Stacked/Question.html +1763 -0
- data/doc/Stacked/Reputation.html +606 -0
- data/doc/Stacked/Tag.html +267 -0
- data/doc/Stacked/User.html +2787 -0
- data/doc/Stacked/Usertimeline.html +630 -0
- data/doc/_index.html +246 -0
- data/doc/file.README.html +54 -0
- data/doc/index.html +54 -0
- data/doc/method_list.html +1203 -0
- data/doc/top-level-namespace.html +87 -0
- data/genddoc.sh +12 -0
- data/lib/stacked.rb +46 -0
- data/lib/stacked/answer.rb +43 -0
- data/lib/stacked/badge.rb +18 -0
- data/lib/stacked/base.rb +159 -0
- data/lib/stacked/comment.rb +37 -0
- data/lib/stacked/posttimeline.rb +10 -0
- data/lib/stacked/question.rb +85 -0
- data/lib/stacked/reputation.rb +15 -0
- data/lib/stacked/tag.rb +9 -0
- data/lib/stacked/user.rb +195 -0
- data/lib/stacked/usertimeline.rb +14 -0
- data/spec/sorted_by_spec.rb +24 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/stacked/answer_spec.rb +31 -0
- data/spec/stacked/badge_spec.rb +17 -0
- data/spec/stacked/base_spec.rb +27 -0
- data/spec/stacked/comment_spec.rb +41 -0
- data/spec/stacked/question_spec.rb +110 -0
- data/spec/stacked/reputation_spec.rb +18 -0
- data/spec/stacked/tag_spec.rb +21 -0
- data/spec/stacked/user_spec.rb +187 -0
- data/spec/stacked/usertimeline_spec.rb +5 -0
- data/spec/support/aliases.rb +7 -0
- data/spec/support/sorted_by.rb +31 -0
- data/spec/support/within.rb +24 -0
- data/spec/within_spec.rb +19 -0
- data/stacked.gemspec +115 -0
- data/stylesheet.css +41 -0
- metadata +157 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
module Stacked
|
2
|
+
class Reputation < Base
|
3
|
+
attr_accessor :negative_rep,
|
4
|
+
:on_date,
|
5
|
+
:positive_rep,
|
6
|
+
:post_id,
|
7
|
+
:post_type,
|
8
|
+
:title
|
9
|
+
|
10
|
+
# Return the cumulative score of this reputation.
|
11
|
+
def score
|
12
|
+
positive_rep - negative_rep
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/stacked/tag.rb
ADDED
data/lib/stacked/user.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
module Stacked
|
2
|
+
class User < Base
|
3
|
+
attr_accessor :about_me,
|
4
|
+
:accept_rate,
|
5
|
+
:age,
|
6
|
+
:answer_count,
|
7
|
+
:creation_date,
|
8
|
+
:display_name,
|
9
|
+
:down_vote_count,
|
10
|
+
:email_hash,
|
11
|
+
:last_access_date,
|
12
|
+
:location,
|
13
|
+
:question_count,
|
14
|
+
:reputation,
|
15
|
+
:up_vote_count,
|
16
|
+
:user_id,
|
17
|
+
:user_type,
|
18
|
+
:view_count,
|
19
|
+
:website_url
|
20
|
+
|
21
|
+
collection :newest, :oldest, :name
|
22
|
+
|
23
|
+
class << self
|
24
|
+
alias_method :reputation, :all
|
25
|
+
|
26
|
+
# Returns users matching the given filter.
|
27
|
+
def filter(filter, options={})
|
28
|
+
options.reverse_merge!(:filter => filter)
|
29
|
+
records(path, options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
###########
|
34
|
+
# Answers #
|
35
|
+
###########
|
36
|
+
|
37
|
+
# Paginated answers for this user.
|
38
|
+
def answers(options={})
|
39
|
+
parse_answers(request(singular(id) + "answers", options))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Paginated answers by views for this user.
|
43
|
+
def answers_by_views(options={})
|
44
|
+
parse_answers(request(singular(id) + "answers/views", options))
|
45
|
+
end
|
46
|
+
|
47
|
+
# Paginated answers by votes for this user.
|
48
|
+
def answers_by_votes(options={})
|
49
|
+
parse_answers(request(singular(id) + "answers/votes", options))
|
50
|
+
end
|
51
|
+
|
52
|
+
# Newest answers for this user.
|
53
|
+
def newest_answers(options={})
|
54
|
+
parse_answers(request(singular(id) + "answers/newest", options))
|
55
|
+
end
|
56
|
+
|
57
|
+
############
|
58
|
+
# Comments #
|
59
|
+
############
|
60
|
+
|
61
|
+
# Paginated comments for this user.
|
62
|
+
def comments(options={})
|
63
|
+
parse_comments(request(singular(id) + "comments", options))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Find a comment directed at +other_user_id+.
|
67
|
+
# Works only if you specify it the correct range.
|
68
|
+
# This method's a bit funny. http://dev.meta.stackoverflow.com/questions/34747/comments-api-method
|
69
|
+
def directed_at(other_user_id, options={})
|
70
|
+
parse_comments(request(singular(id) + "comments" + other_user_id.to_s, options))
|
71
|
+
end
|
72
|
+
|
73
|
+
# All comments directed at +other_user_id+ ordered by creation date in descending order.
|
74
|
+
def directed_at_by_date(other_user_id, options={})
|
75
|
+
parse_comments(request(singular(id) + "comments" + other_user_id.to_s + "recent", options))
|
76
|
+
end
|
77
|
+
|
78
|
+
# All comments directed at +other_user_id+ ordered by votes.
|
79
|
+
def directed_at_by_score(other_user_id, options={})
|
80
|
+
parse_comments(request(singular(id) + "comments" + other_user_id.to_s + "score", options))
|
81
|
+
end
|
82
|
+
|
83
|
+
# Paginated comments for this user ordered by score.
|
84
|
+
def comments_by_score(options={})
|
85
|
+
parse_comments(request(singular(id) + "comments/score", options))
|
86
|
+
end
|
87
|
+
|
88
|
+
alias_method :popular_comments, :comments_by_score
|
89
|
+
|
90
|
+
# Paginated comments for this user ordered by date created.
|
91
|
+
def recent_comments(options={})
|
92
|
+
parse_comments(request(singular(id) + "comments/recent", options))
|
93
|
+
end
|
94
|
+
|
95
|
+
##############
|
96
|
+
# Favourites #
|
97
|
+
##############
|
98
|
+
|
99
|
+
# Paginated favorites in the order they were added.
|
100
|
+
def added_favorites(options={})
|
101
|
+
parse_questions(request(singular(id) + "favorites/added", options))
|
102
|
+
end
|
103
|
+
|
104
|
+
# Paginated favorites.
|
105
|
+
def favorites(options={})
|
106
|
+
parse_questions(request(singular(id) + "favorites", options))
|
107
|
+
end
|
108
|
+
|
109
|
+
def newest_favorites(options={})
|
110
|
+
parse_questions(request(singular(id) + "favorites/newest", options))
|
111
|
+
end
|
112
|
+
|
113
|
+
def popular_favorites(options={})
|
114
|
+
parse_questions(request(singular(id) + "favorites/views", options))
|
115
|
+
end
|
116
|
+
|
117
|
+
alias_method :favorites_by_views, :popular_favorites
|
118
|
+
alias_method :favourites_by_views, :popular_favorites
|
119
|
+
|
120
|
+
def recent_favorites(options={})
|
121
|
+
parse_questions(request(singular(id) + "favorites/recent", options))
|
122
|
+
end
|
123
|
+
|
124
|
+
# Silly Americans.
|
125
|
+
alias_method :added_favourites, :added_favorites
|
126
|
+
alias_method :favourites, :favorites
|
127
|
+
alias_method :popular_favourites, :popular_favorites
|
128
|
+
alias_method :recent_favourites, :favorites
|
129
|
+
alias_method :newest_favourites, :newest_favorites
|
130
|
+
|
131
|
+
#############
|
132
|
+
# Questions #
|
133
|
+
#############
|
134
|
+
|
135
|
+
def newest_questions(options={})
|
136
|
+
parse_questions(request(singular(id) + "questions/newest", options))
|
137
|
+
end
|
138
|
+
|
139
|
+
def questions(options={})
|
140
|
+
parse_questions(request(singular(id) + "questions", options))
|
141
|
+
end
|
142
|
+
|
143
|
+
def questions_by_views(options={})
|
144
|
+
parse_questions(request(singular(id) + "questions/views", options))
|
145
|
+
end
|
146
|
+
|
147
|
+
def questions_by_votes(options={})
|
148
|
+
parse_questions(request(singular(id) + "questions/votes", options))
|
149
|
+
end
|
150
|
+
|
151
|
+
alias_method :popular_questions, :questions_by_votes
|
152
|
+
|
153
|
+
def recent_questions(options={})
|
154
|
+
parse_questions(request(singular(id) + "questions/recent", options))
|
155
|
+
end
|
156
|
+
|
157
|
+
###############
|
158
|
+
# Other stuff #
|
159
|
+
###############
|
160
|
+
|
161
|
+
def reputations(options={})
|
162
|
+
parse_reputations(request(singular(id) + "reputation", options))
|
163
|
+
end
|
164
|
+
|
165
|
+
def mentioned(options={})
|
166
|
+
parse_comments(request(singular(id) + "mentioned", options))
|
167
|
+
end
|
168
|
+
|
169
|
+
def badges(options={})
|
170
|
+
parse_badges(request(singular(id) + "badges", options))
|
171
|
+
end
|
172
|
+
|
173
|
+
def tags(options={})
|
174
|
+
parse_tags(request(singular(id) + "tags", options))
|
175
|
+
end
|
176
|
+
|
177
|
+
alias_method :mentions, :mentioned
|
178
|
+
|
179
|
+
def timeline(options={})
|
180
|
+
parse_user_timeline(request(singular(id) + "timeline", options))
|
181
|
+
end
|
182
|
+
|
183
|
+
alias_method :created_at, :creation_date
|
184
|
+
alias_method :down_votes, :down_vote_count
|
185
|
+
alias_method :gravatar, :email_hash
|
186
|
+
alias_method :id, :user_id
|
187
|
+
alias_method :name, :display_name
|
188
|
+
alias_method :recent_answers, :answers
|
189
|
+
alias_method :type, :user_type
|
190
|
+
alias_method :up_votes, :up_vote_count
|
191
|
+
alias_method :views, :view_count
|
192
|
+
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Stacked
|
2
|
+
# I would really *love* to call this UserTimeline but alas, the API fails me.
|
3
|
+
class Usertimeline < Base
|
4
|
+
attr_accessor :action,
|
5
|
+
:comment_id,
|
6
|
+
:creation_date,
|
7
|
+
:description,
|
8
|
+
:detail,
|
9
|
+
:post_id,
|
10
|
+
:timeline_type
|
11
|
+
|
12
|
+
alias_method :created_at, :creation_date
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Spec::Matchers::Sortedby do
|
4
|
+
before do
|
5
|
+
@questions = []
|
6
|
+
@questions << Stacked::Question.new(:last_edit_date => Time.now.to_i)
|
7
|
+
@questions << Stacked::Question.new(:last_edit_date => (Time.now + 5.days).to_i)
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { @questions }
|
11
|
+
|
12
|
+
it "sorted by" do
|
13
|
+
subject.should be_sorted_by(:last_edit_date)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "sorted by reverse" do
|
17
|
+
subject.reverse.should be_sorted_by(:last_edit_date, :desc)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "fails with an understandable error message" do
|
21
|
+
lambda { subject.reverse.should be_sorted_by(:last_edit_date, :asc) }.should raise_error("Expected result set to be sorted by last_edit_date in ascending order, but was not.")
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'stacked'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
require 'fakeweb'
|
7
|
+
|
8
|
+
Dir["spec/support/**/*.rb"].each { |f| require f }
|
9
|
+
|
10
|
+
# Uncomment only for debugging as we only ever want to test against the real API.
|
11
|
+
# At least, until it becomes stable.
|
12
|
+
# FakeWeb.allow_net_connect = false
|
13
|
+
|
14
|
+
Spec::Runner.configure do |config|
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Stacked::Answer do
|
4
|
+
context "class methods" do
|
5
|
+
it "raises an error if all is called" do
|
6
|
+
lambda { Stacked::Answer.all }.should raise_error(Stacked::NotImplemented, "The requested action is not available in the API.")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context "instance methods" do
|
11
|
+
subject { Stacked::Answer.find(1237127, :body => true) }
|
12
|
+
it "finds the user for an answer" do
|
13
|
+
subject.user.display_name.should eql("Daniel Vandersluis")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "finds the question for an answer" do
|
17
|
+
subject.question.should be_is_a(Stacked::Question)
|
18
|
+
end
|
19
|
+
|
20
|
+
aliases(:views => :view_count,
|
21
|
+
:up_votes => :up_vote_count,
|
22
|
+
:answer_id => :id,
|
23
|
+
:created_at => :creation_date,
|
24
|
+
:updated_at => :last_edit_date)
|
25
|
+
|
26
|
+
it "has a body" do
|
27
|
+
subject.body.should_not be_blank
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Stacked::Badge do
|
4
|
+
subject { Stacked::Badge }
|
5
|
+
it "finds all badges" do
|
6
|
+
subject.all.first.should be_is_a(Stacked::Badge)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "finds all badges ordered by name" do
|
10
|
+
subject.name.first.should be_is_a(Stacked::Badge)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "finds all badges based on tags" do
|
14
|
+
subject.tags.first.name.should eql(".net")
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Stacked::Base do
|
4
|
+
context "common methods" do
|
5
|
+
# We cannot use Stacked::Base here obviously because SO does not have Bases!
|
6
|
+
# So we use User instead.
|
7
|
+
subject { Stacked::User }
|
8
|
+
|
9
|
+
it "retrieves a list of a resource" do
|
10
|
+
subject.all.all? { |u| u.is_a?(subject) }.should be_true
|
11
|
+
end
|
12
|
+
|
13
|
+
it "paginates through the resource" do
|
14
|
+
subject.all.first.should_not eql(subject.all(:page => 2).first)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "limits the results" do
|
18
|
+
subject.all(:pagesize => 1).size.should eql(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "stats" do
|
22
|
+
Stacked::Base.stats.keys.sort.should eql(["answers_per_minute", "api_version", "badges_per_minute", "questions_per_minute", "total_answers", "total_badges", "total_comments", "total_questions", "total_unanswered", "total_users", "total_votes"])
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Stacked::Comment do
|
4
|
+
subject { Stacked::Comment }
|
5
|
+
|
6
|
+
context "class methods" do
|
7
|
+
|
8
|
+
it "raises an error if all is called" do
|
9
|
+
lambda { Stacked::Comment.all }.should raise_error(Stacked::NotImplemented, "The requested action is not available in the API.")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "finds a single comment" do
|
13
|
+
comment = Stacked::Comment.find(2561833)
|
14
|
+
comment.should be_is_a(Stacked::Comment)
|
15
|
+
comment.owner_user_id.should eql(22656)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "instance methods" do
|
20
|
+
subject { Stacked::Comment.find(2561833) }
|
21
|
+
|
22
|
+
aliases(:comment_id => :id,
|
23
|
+
:created_at => :creation_date,
|
24
|
+
:owner => :user,
|
25
|
+
:edits => :edit_count)
|
26
|
+
|
27
|
+
it "finds the related post for this comment" do
|
28
|
+
subject.post.should be_is_a(Stacked::Answer)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "finds who the comment was directed at, or nobody if nobody" do
|
32
|
+
Stacked::Comment.find(2561833).reply_to.should be_is_a(Stacked::User)
|
33
|
+
Stacked::Comment.find(1063043).reply_to.should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "finds the user who wrote the comment" do
|
37
|
+
subject.owner.should be_is_a(Stacked::User)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Stacked::Question do
|
4
|
+
context "class methods" do
|
5
|
+
subject { Stacked::Question }
|
6
|
+
it "gets a list of all questions" do
|
7
|
+
subject.all.all? { |q| q.is_a?(subject) }.should be_true
|
8
|
+
end
|
9
|
+
|
10
|
+
it "gets a list of questions with comments" do
|
11
|
+
pending("Cannot currently get a list of questions with comments")
|
12
|
+
subject.all(:comments => true)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "active" do
|
16
|
+
subject.active(:pagesize => 1).first.last_activity_date.should be_within(1.day)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "newest" do
|
20
|
+
subject.newest(:pagesize => 1).first.creation_date.should be_within(1.day)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "featured" do
|
24
|
+
(subject.featured(:pagesize => 1).first.bounty_closes_date > Time.now.to_i).should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "hot" do
|
28
|
+
# It was 3555 as of 3rd April 2010.
|
29
|
+
(subject.hot(:pagesize => 1).first.view_count > 3555).should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
it "month" do
|
33
|
+
subject.month(:pagesize => 1).first.creation_date.should be_within(1.month)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "week" do
|
37
|
+
subject.week(:pagesize => 1).first.creation_date.should be_within(1.week)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "votes" do
|
41
|
+
subject.votes(:pagesize => 1).first.up_vote_count.should eql(1190)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "unanswered by votes" do
|
45
|
+
question = subject.unanswered_by_votes(:pagesize => 1).first
|
46
|
+
question.should be_is_a(Stacked::Question)
|
47
|
+
question.answers.should be_blank
|
48
|
+
end
|
49
|
+
|
50
|
+
it "tagged" do
|
51
|
+
question = subject.tagged(:tags => ["ruby", "ruby-on-rails"], :pagesize => 1).first
|
52
|
+
question.tags.map(&:name).should include("ruby")
|
53
|
+
question.tags.map(&:name).should include("ruby-on-rails")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "instance methods" do
|
58
|
+
subject { Stacked::Question.find(1236996, :comments => true, :body => true) }
|
59
|
+
|
60
|
+
aliases(
|
61
|
+
:created_at => :creation_date,
|
62
|
+
:down_votes => :down_vote_count,
|
63
|
+
:favorites => :favorite_count,
|
64
|
+
:favourites => :favorite_count,
|
65
|
+
:id => :question_id,
|
66
|
+
:updated_at => :last_edit_date,
|
67
|
+
:up_votes => :up_vote_count,
|
68
|
+
:user => :owner,
|
69
|
+
:views => :view_count
|
70
|
+
)
|
71
|
+
|
72
|
+
|
73
|
+
it "is the right question" do
|
74
|
+
subject.title.should eql("Calculating the distance between two times")
|
75
|
+
end
|
76
|
+
|
77
|
+
it "has a body" do
|
78
|
+
subject.body.should_not be_blank
|
79
|
+
end
|
80
|
+
|
81
|
+
it "retreives comments" do
|
82
|
+
subject.comments.should_not be_empty
|
83
|
+
subject.comments.first.should be_is_a(Stacked::Comment)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "retreives answers" do
|
87
|
+
subject.answers.should_not be_empty
|
88
|
+
subject.answers.first.should be_is_a(Stacked::Answer)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "retreives tags" do
|
92
|
+
subject.tags.should_not be_empty
|
93
|
+
subject.tags.first.should be_is_a(Stacked::Tag)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "finds the user for a question" do
|
97
|
+
subject.user.should be_is_a(Stacked::User)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "finds the accepted answer" do
|
101
|
+
subject.accepted_answer.should be_is_a(Stacked::Answer)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "shows the timeline of the question" do
|
105
|
+
subject.timeline.first.should be_is_a(Stacked::Posttimeline)
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|