napcs-pivotalrecord 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -2,9 +2,49 @@
2
2
 
3
3
  Provides a simple interface to PivotalTracker using syntax similar to ActiveRecord.
4
4
 
5
- @projects = Pivotal::Project.find(246)
5
+ @projects = Pivotal::Project.find :all
6
+ @project = Pivotal::Project.find 24
7
+
8
+ There's some basic association support built in.
9
+
6
10
  @stories = @projects.first.stories
11
+ @iterations = @projects.first.iterations
12
+
13
+
14
+ ==Working with Stories
15
+
16
+ Getting all stories for a project
17
+
18
+ @project = Pivotal::Project.find 24
19
+ @stories = @project.stories
20
+
21
+ @stories = Pivotal::Story.find_all_by_project_id 24
22
+
23
+ Getting a specific story for a project
24
+
25
+ @story = Pivotal::Story.find_by_project_id_and_id 24, 14
26
+
27
+ or
28
+
29
+ @story = Pivotal::Story.find 14, :project_id => 24
7
30
 
31
+ Getting a story from a project by name
32
+ @project = Pivotal::Project.find(246)
33
+ p.stories.find {|s| s.name = "Deal with web thumbnails"}
34
+
35
+
36
+ ==Filtering
37
+
38
+ Getting all features for a project
39
+
40
+ Pivotal::Story.find :all, :project_id => 4860, :filter => {:type => "feature"}
41
+
42
+ Filters can be <tt>bug</tt>, <tt>release</tt>, <tt>feature</tt>.
43
+
44
+ ===Limits
45
+
46
+ Pivotal::Story.find :all, :project_id => 4860, :filter => {:type => "feature"}, :limit => 2
47
+
8
48
  == Configuring
9
49
 
10
50
  Require the library in your project and then configure it with your API key.
@@ -13,14 +53,24 @@ Require the library in your project and then configure it with your API key.
13
53
  Pivotal::Configuration.options[:api_key] = "asdfagjasd"
14
54
 
15
55
 
56
+
16
57
  ==Known Issues
17
58
 
18
59
  This is extremely rudimentary and not meant for any sort of production use.
19
- There is no write support yet, nor are there any search filters.
60
+ There is no write support yet.
61
+
20
62
 
21
63
 
22
64
  == Changelog
23
65
 
66
+ ===0.0.3
67
+ * Refactored finders
68
+ * Added CollectionProxy to allow story filtering
69
+ * Added finders for locating individual projects and stories
70
+ * refactored models to reduce duplicated code
71
+ * preserved original hash
72
+ ===0.0.2
73
+ * Fixed requires
24
74
  ===0.0.1
25
75
  * Basic record retrieval from PivotalTracker.
26
76
 
data/Rakefile CHANGED
@@ -22,7 +22,7 @@ spec = Gem::Specification.new do |s|
22
22
 
23
23
  # Change these as appropriate
24
24
  s.name = "pivotalrecord"
25
- s.version = "0.0.1"
25
+ s.version = "0.0.3"
26
26
  s.summary = "Simple ActiveRecord-style interface for PivotalTracker"
27
27
  s.author = "Brian Hogan"
28
28
  s.email = "brianhogan@naopcs.com"
data/lib/pivotal/base.rb CHANGED
@@ -6,17 +6,32 @@ module Pivotal
6
6
  self.send("#{key}=", value) if self.respond_to?("#{key}=")
7
7
  end
8
8
  end
9
+
9
10
 
10
11
  class << self
11
12
 
12
13
  # parses the collection from Pivotal. Invokes the class's parse_result method
13
14
  # which the class should implement.
14
- def parse_results(results)
15
+ def parse_results(results, options)
15
16
  return [] if results[self.resource].nil?
16
17
  results[self.resource].collect do |item|
17
18
  klass = self.name.constantize
18
- klass.parse_result(item)
19
+ klass.parse_result(item, options)
20
+ end
21
+ end
22
+
23
+ def parse_result(data, options)
24
+ klass = self.name.constantize
25
+ attributes = {}
26
+ self.fields.each do |field|
27
+ attributes[field.to_sym] = get_content_for(data[field.to_s])
19
28
  end
29
+ attributes[:original_hash] = data
30
+ result = klass.new attributes
31
+ result.project_id = options[:project_id] if result.respond_to?("project_id") && !options[:project_id].blank?
32
+ result.story_id = options[:story_id] if result.respond_to?("story_id") && !options[:story_id].blank?
33
+
34
+ result
20
35
  end
21
36
 
22
37
 
@@ -56,6 +71,7 @@ module Pivotal
56
71
  def resource
57
72
  name.split('::').last.downcase
58
73
  end
74
+
59
75
 
60
76
  # Declares the relationship between the models.
61
77
  #
@@ -63,12 +79,15 @@ module Pivotal
63
79
  #
64
80
  # Creates a <tt>stories</tt> method to retrive the related records.
65
81
  def has_many(things)
66
- parent_resource = resource
82
+ parent_id = "#{resource}_id"
67
83
  object = things.to_s.classify
84
+
85
+
68
86
  class_eval <<-EOF
69
87
 
70
- def #{things}
71
- Pivotal::#{object}.find_all_by_parent_id("#{parent_resource.pluralize}", self.id)
88
+ def #{things}(options ={})
89
+ options.merge!(:#{parent_id} => id)
90
+ @#{things} = Pivotal::#{object}.find_all(options)
72
91
  end
73
92
 
74
93
  EOF
@@ -77,21 +96,49 @@ module Pivotal
77
96
  # Creates a reverse association, making it easy to grab
78
97
  # the parent object.
79
98
  def belongs_to(parent)
99
+
80
100
  object = parent.to_s.classify
81
101
  parent_id = "#{parent.to_s.downcase}_id"
102
+
103
+ if parent.to_s == "project"
104
+ attr_accessor parent_id.to_sym
105
+ else
106
+ attr_accessor parent_id.to_sym, :project_id
107
+ end
108
+
82
109
  class_eval <<-EOF
83
-
110
+
84
111
  def #{parent.to_s}
85
- Pivotal::#{object}.find_by_id(#{parent_id})
112
+ @#{parent.to_s} = Pivotal::#{object}.find_by_id(#{parent_id})
113
+ end
114
+
115
+
116
+ def self.find_all_by_#{parent.to_s}_id(id, options={})
117
+ options.merge!(:#{parent_id} => id)
118
+ find_all(options)
86
119
  end
87
120
 
88
- def self.find_all_by_#{parent.to_s}_id(id)
89
- find_all_by_parent_id("#{parent.to_s.pluralize}", id)
121
+ def self.find_by_#{parent.to_s}_id_and_id(parent_id, id, options = {})
122
+ options.merge!(:#{parent_id} => parent_id)
123
+ find_by_id(id, options)
90
124
  end
125
+
126
+
91
127
 
92
128
  EOF
93
129
  end
94
130
 
131
+ def set_fields(*fields)
132
+ attr_accessor :id, :original_hash
133
+ fields.each {|field| attr_accessor field }
134
+ @fields = fields
135
+ @fields << :id
136
+ end
137
+
138
+ def fields
139
+ @fields
140
+ end
141
+
95
142
  # Retrives the token from the configuration
96
143
  def token
97
144
  @@token = Pivotal::Configuration.options[:api_key]
@@ -108,46 +155,120 @@ module Pivotal
108
155
  @@url = Pivotal::Configuration.options[:url] || "http://www.pivotaltracker.com/services/v2"
109
156
  end
110
157
 
111
- # Base find method
112
- def find(type)
158
+ # Convenience method for finding all objects. Options are supported
159
+ def all(options = {})
160
+ self.find :all, options
161
+ end
162
+
163
+ # Convenience method for finding the first object. Options are supported
164
+ def first(options = {})
165
+ self.find :first, options
166
+ end
167
+
168
+ # Find records. See find_by_id for options.
169
+ #
170
+ # Pivotal::Project.find :all
171
+ # Pivotal::Project.find :first
172
+ # Pivotal::Project.find 2552
173
+ def find(type, options = {})
113
174
  if type == :all
114
- find_all
175
+ find_all options
176
+ elsif type == :first
177
+ find_one options.merge(:limit => 1)
115
178
  else
116
- find_by_id(type)
179
+ find_by_id(type, options)
117
180
  end
118
181
  end
119
182
 
120
- # find a specific record by id
121
- def find_by_id(id)
122
- query = "#{self.url}/#{self.resource.pluralize}/#{id}?token=#{self.token}"
123
- puts "query : #{query}" if self.debugging?
124
- results = RestClient.get query
125
- result = XmlSimple.xml_in(results.to_s)
126
- parse_result(result)
127
- end
128
-
129
- # generic finder, used by associations
130
- def find_all_by_parent_id(parent, id)
131
- return [] if id.nil?
132
- query = "#{self.url}/#{parent}/#{id}/#{self.resource.pluralize}?token=#{self.token}"
133
- puts "query : #{query}" if self.debugging?
134
- results = RestClient.get query
135
- results = XmlSimple.xml_in(results.to_s)
136
- parse_results(results)
183
+ # find a specific record by id.
184
+ # Options:
185
+ # * <tt>:filter</tt> - a hash for finding records by label and type.
186
+ #
187
+ # :filter => {:label => "Needs approval", :type => "feature"}
188
+ # * <tt>:limit</tt> - limit the number of records that come back.
189
+ # * <tt>:offest</tt> - for pagination
190
+ #
191
+ # Additonally, you may need to provide a hierarchy if you're referencing a Story or an Iteration.
192
+ #
193
+ # Pivotal::Story.find_by_id 25, :project_id => 2534
194
+
195
+ def find_by_id(id, options = {})
196
+ get_result(id, options)
137
197
  end
138
-
198
+
139
199
  # find all records for the given object.
140
- def find_all
141
- query ="#{self.url}/#{self.resource.pluralize}?token=#{self.token}"
142
- puts "query : #{query}" if self.debugging?
200
+ def find_all(options ={})
201
+ get_results(options)
202
+ end
203
+
204
+ def find_one(options ={})
205
+ # get_results is going to return a proxy. I am only expecting one record.
206
+ get_results(options).first
207
+ end
208
+
209
+ def base_path(options)
210
+ path = "projects"
211
+ path << "/#{options[:project_id]}/stories" if options[:project_id]
212
+ path << "/stories/#{options[:story_id]}/tasks" if options[:story_id]
213
+ path
214
+ end
215
+
216
+ def get_results(options={})
217
+ path = base_path(options)
218
+ opts = parse_options(options)
219
+ query ="#{self.url}/#{path}#{opts}"
220
+ puts "query : #{query}" #if self.debugging?
143
221
  results = RestClient.get query
144
- puts results.inspect if self.debugging?
145
222
  results = XmlSimple.xml_in(results.to_s)
146
- parse_results(results)
223
+ results = parse_results(results, options)
224
+ CollectionProxy.new(self.name.constantize, results)
225
+
147
226
  end
148
-
227
+
228
+ def get_result(id, options ={})
229
+ path = base_path(options)
230
+ opts = parse_options(options)
231
+ query = "#{self.url}/#{path}/#{id}#{opts}"
232
+ puts "query : #{query}" #if self.debugging?
233
+ results = RestClient.get query
234
+ result = XmlSimple.xml_in(results.to_s)
235
+ parse_result(result, options)
236
+ end
237
+
238
+ def parse_options(options)
239
+ if options[:iteration].blank?
240
+ opts = ["?token=#{self.token}"]
241
+ else
242
+ opts = ["#{options[:iteration].to_s}?token=#{self.token}"]
243
+ end
244
+
245
+ if options[:limit]
246
+ opts << "limit=#{options[:limit]}"
247
+ end
248
+
249
+ if options[:offset]
250
+ opts << "offset=#{options[:offset]}"
251
+ end
252
+
253
+ if options[:filter]
254
+ opts << parse_filter(options[:filter])
255
+ end
256
+
257
+
258
+ return opts.join("&")
259
+
260
+ end
261
+
149
262
 
263
+ def parse_filter(opts)
264
+ opts = opts.map{|key, value| "#{key}:\"#{value}\""}.join " "
265
+ opts = URI.escape(opts, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
266
+ opts = "filter=#{opts}"
267
+
268
+ end
269
+
150
270
 
271
+
151
272
 
152
273
  end
153
274
 
@@ -0,0 +1,66 @@
1
+ module Pivotal
2
+
3
+ module ProjectFinders
4
+
5
+ end
6
+
7
+ module IterationFinders
8
+
9
+
10
+ end
11
+
12
+ module StoryFinders
13
+
14
+ def accepted
15
+ find_all_by_current_state "accepted"
16
+ end
17
+
18
+ def bugs
19
+ find_all_by_story_type "bug"
20
+ end
21
+
22
+ def features
23
+ find_all_by_story_type "feature"
24
+ end
25
+
26
+ def chores
27
+ find_all_by_story_type "chore"
28
+ end
29
+
30
+ def releases
31
+ find_all_by_story_type "releoase"
32
+ end
33
+
34
+ def find_all_by_story_type(type)
35
+ Pivotal::CollectionProxy.new Story, @records.select{|s| s.story_type == type.to_s}
36
+ end
37
+
38
+ def find_all_by_current_state(current_state)
39
+ Pivotal::CollectionProxy.new Story, @records.select{|s| s.current_state == current_state.to_s}
40
+ end
41
+
42
+ end
43
+
44
+ class CollectionProxy
45
+ def initialize(source, records)
46
+ @source = source
47
+ @records = records
48
+ self.send :extend, "#{source}Finders".constantize
49
+ end
50
+
51
+ def inspect
52
+ @records.inspect
53
+ end
54
+
55
+ def all
56
+ @records
57
+ end
58
+
59
+ def method_missing(name, *args, &block)
60
+ @records.send(name, *args, &block)
61
+ end
62
+
63
+
64
+ end
65
+
66
+ end
@@ -4,18 +4,17 @@ module Pivotal
4
4
  # An iteration maps to a Pivotal Project and represents the sprint.
5
5
  class Iteration < Pivotal::Base
6
6
 
7
- belongs_to :project
7
+ belongs_to :project
8
8
 
9
- attr_accessor :start, :finish, :id, :stories, :number
9
+ set_fields :start, :finish, :number
10
10
 
11
- def self.parse_result(iteration)
12
- Pivotal::Iteration.new( :id => get_content_for(iteration["id"]),
13
- :number => get_content_for(iteration["number"]),
14
- :start => get_content_for(iteration["start"]),
15
- :finish => get_content_for(iteration["finish"]),
16
- :stories => Pivotal::Story.parse_results(iteration["stories"].first)
17
- )
18
- end
19
-
11
+ def stories
12
+ @stories ||= self.original_hash["stories"].nil? ? [] : Pivotal::Story.parse_results(original_hash["stories"].first)
13
+ end
14
+
15
+
16
+ def self.backlog
17
+
18
+ end
20
19
  end
21
20
  end
@@ -4,16 +4,15 @@ module Pivotal
4
4
  # and to get its stories.
5
5
  class Project < Pivotal::Base
6
6
 
7
- attr_accessor :id, :name
7
+ set_fields :name, :original_hash
8
8
 
9
9
  has_many :stories
10
-
10
+ has_many :iterations
11
11
 
12
- def self.parse_result(project)
13
- Pivotal::Project.new( :id => get_content_for(project["id"]),
14
- :name => get_content_for(project["name"]) )
15
- end
16
12
 
13
+ def backlog()
14
+ Pivotal::Iteration.backlog_for(self.id)
15
+ end
17
16
 
18
17
  end
19
18
  end
data/lib/pivotal/story.rb CHANGED
@@ -1,30 +1,12 @@
1
1
  module Pivotal
2
2
  class Story < Pivotal::Base
3
3
 
4
- attr_accessor :id, :project_id, :name, :description, :url, :estimate, :story_type, :accepted_at
5
-
6
-
4
+ set_fields :name, :description, :url, :estimate, :story_type, :accepted_at
7
5
 
8
6
  belongs_to :project
9
7
 
10
- private
8
+
11
9
 
12
10
 
13
- def self.parse_result(story)
14
- Pivotal::Story.new(
15
- :id => get_content_for(story["id"]),
16
- :name => get_content_for(story["name"]),
17
- :description => get_content_for(story["description"]),
18
- :url => get_content_for(story["url"]),
19
- :estimate => get_content_for(story["estimate"]),
20
- :story_type => get_content_for(story["story_type"]),
21
- :accepted_at => get_content_for(story["accepted_at"]),
22
- :current_state => get_content_for(story["current_state"])
23
- )
24
- end
25
-
26
-
27
-
28
-
29
11
  end
30
12
  end
data/lib/pivotal.rb CHANGED
@@ -1,10 +1,14 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
1
3
  require 'rubygems'
2
4
  require 'activesupport'
3
5
  require 'rest_client'
4
6
  require 'xmlsimple'
7
+ require 'uri'
5
8
 
6
- require 'lib/pivotal/base'
7
- require 'lib/pivotal/configuration'
8
- require 'lib/pivotal/project'
9
- require 'lib/pivotal/story'
10
- require 'lib/pivotal/iteration'
9
+ require 'pivotal/base'
10
+ require 'pivotal/configuration'
11
+ require 'pivotal/project'
12
+ require 'pivotal/story'
13
+ require 'pivotal/iteration'
14
+ require 'pivotal/collection_proxy'
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), "/../spec_helper")
2
+
3
+ describe Pivotal::Base do
4
+ before(:each) do
5
+ @service = "http://www.pivotaltracker.com/services/v2"
6
+ @token = "abcd"
7
+ Pivotal::Configuration.options[:api_key] = @token
8
+ end
9
+ describe "with options" do
10
+ it "should return just the token" do
11
+ Pivotal::Base.parse_options(Hash.new).should == "?token=#{@token}"
12
+ end
13
+ it "should return backlog with token" do
14
+ Pivotal::Base.parse_options(:iteration => :backlog).should == "backlog?token=#{@token}"
15
+ end
16
+
17
+ it "should return backlog with token and limit" do
18
+ Pivotal::Base.parse_options(:limit => 10, :iteration => :backlog).should == "backlog?token=#{@token}&limit=10"
19
+ end
20
+
21
+ it "should return backlog with token and limit and offset" do
22
+ Pivotal::Base.parse_options(:limit => 10, :offset => 2, :iteration => :backlog).should == "backlog?token=#{@token}&limit=10&offset=2"
23
+ end
24
+
25
+ it "should return with token and limit and offset but no backlog" do
26
+ Pivotal::Base.parse_options(:limit => 10, :offset => 2).should == "?token=#{@token}&limit=10&offset=2"
27
+ end
28
+
29
+ it "should create a filter for features that need feedback" do
30
+ Pivotal::Base.parse_filter(:type => "feature", :label => "needs feedback").should == "filter=type%3A%22feature%22%20label%3A%22needs%20feedback%22"
31
+ end
32
+
33
+ it "should return all stories that are features, limited by 5" do
34
+ Pivotal::Base.parse_options(:limit => 10, :filter => {:type => "feature", :label => "needs feedback"}).should == "?token=#{@token}&limit=10&filter=type%3A%22feature%22%20label%3A%22needs%20feedback%22"
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -7,6 +7,7 @@ describe Pivotal::Iteration do
7
7
  Pivotal::Configuration.options[:api_key] = @token
8
8
  FakeWeb.clean_registry
9
9
  FakeWeb.allow_net_connect = false
10
+ @project = Pivotal::Project.new("id" => "4860", :name => "FeelMySkills")
10
11
  end
11
12
 
12
13
 
@@ -343,15 +344,27 @@ describe Pivotal::Iteration do
343
344
  </iterations>}
344
345
  url = "#{@service}/projects/4860/iterations?token=#{@token}"
345
346
  FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
346
- @project = Pivotal::Iteration.new("id" => "4860", :name => "FeelMySkills")
347
347
  end
348
348
  it "should get iterations" do
349
349
  iterations = Pivotal::Iteration.find_all_by_project_id(@project.id)
350
350
  iterations.each{|i| i.should be_a(Pivotal::Iteration)}
351
351
  iterations.detect{|i| i.number == 28}.should be_true
352
-
353
352
  end
353
+
354
+
354
355
  end
355
356
 
357
+ describe "with an iteration" do
358
+ before(:each) do
359
+ url = "#{@service}/projects/4860/iterations?token=#{@token}"
360
+ FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
361
+ @project = Pivotal::Iteration.new("id" => "4860", :name => "FeelMySkills")
362
+ end
363
+ it "should get an iteration by project and id" do
364
+ iterations = Pivotal::Iteration.find_all_by_project_id(@project.id)
365
+ iterations.each{|i| i.should be_a(Pivotal::Iteration)}
366
+ iterations.detect{|i| i.number == 28}.should be_true
367
+ end
368
+ end
356
369
 
357
370
  end
@@ -39,6 +39,11 @@ describe Pivotal::Project do
39
39
  p = Pivotal::Project.find_by_id(4860)
40
40
  p.name.should == "FeelMySkills"
41
41
  end
42
+ it "should still have the raw attributes" do
43
+ p = Pivotal::Project.find_by_id(4860)
44
+ p.original_hash.should == {"name"=>["FeelMySkills"], "iteration_length"=>[{"type"=>"integer", "content"=>"1"}], "week_start_day"=>["Monday"], "point_scale"=>["0,1,2,3"], "id"=>["4860"]}
45
+
46
+ end
42
47
 
43
48
  describe "stories" do
44
49
  before(:each) do
@@ -15,6 +15,31 @@ describe Pivotal::Story do
15
15
  FakeWeb.allow_net_connect = true
16
16
  end
17
17
 
18
+ describe "With a story" do
19
+ before(:each) do
20
+ xml=%Q{<?xml version="1.0" encoding="UTF-8"?>
21
+ <story>
22
+ <id type="integer">300970</id>
23
+ <story_type>release</story_type>
24
+ <url>http://www.pivotaltracker.com/story/show/300970</url>
25
+ <current_state>accepted</current_state>
26
+ <description></description>
27
+ <name>rel_1-1-5</name>
28
+ <requested_by>Brian Hogan</requested_by>
29
+ <created_at type="datetime">2008/12/04 03:00:57 UTC</created_at>
30
+ <accepted_at type="datetime">2009/01/14 06:32:15 UTC</accepted_at>
31
+ <deadline type="datetime">2008/12/12 20:00:00 UTC</deadline>
32
+ </story>
33
+ }
34
+ url = "#{@service}/projects/4860/stories/300970?token=#{@token}"
35
+ FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
36
+ @project = Pivotal::Project.new("id" => "4860", :name => "FeelMySkills")
37
+ end
38
+ it "should load the story" do
39
+ @story = Pivotal::Story.find 300970, :project_id => "4860"
40
+ @story.id.should == 300970
41
+ end
42
+ end
18
43
 
19
44
  describe "with many stories" do
20
45
  before(:each) do
@@ -213,7 +238,7 @@ describe Pivotal::Story do
213
238
  </story>
214
239
  <story>
215
240
  <id type="integer">300472</id>
216
- <story_type>feature</story_type>
241
+ <story_type>bug</story_type>
217
242
  <url>http://www.pivotaltracker.com/story/show/300472</url>
218
243
  <estimate type="integer">1</estimate>
219
244
  <current_state>unscheduled</current_state>
@@ -227,16 +252,69 @@ describe Pivotal::Story do
227
252
  FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
228
253
  @project = Pivotal::Project.new("id" => "4860", :name => "FeelMySkills")
229
254
  end
230
- it "should get stories" do
255
+ it "should get stories using find_all_by_project_id" do
231
256
  stories = Pivotal::Story.find_all_by_project_id(@project.id)
232
257
  stories.each{|s| s.should be_a(Pivotal::Story)}
233
- stories.detect{|s| s.name == "Deal with web thumbnails"}
234
- stories.detect{|s| s.name == "Add Recommendations"}
235
- stories.detect{|s| s.name == "Implement Exception Notification plugin"}
236
- stories.detect{|s| s.name == "Implement Pro account subscription"}
237
- stories.detect{|s| s.name == "iPhone interface for viewing profiles"}
258
+ stories.detect{|s| s.name == "Deal with web thumbnails"}.should_not be_nil
259
+ stories.detect{|s| s.name == "Add Recommendations"}.should_not be_nil
260
+ stories.detect{|s| s.name == "Implement Exception Notification plugin"}.should_not be_nil
261
+ stories.detect{|s| s.name == "Implement Pro account subscription"}.should_not be_nil
262
+ stories.detect{|s| s.name == "iPhone interface for viewing profiles"}.should_not be_nil
263
+
264
+ end
265
+ it "should get stories using the project_id option" do
266
+ stories = Pivotal::Story.find :all, :project_id => @project.id
267
+
268
+ stories.each{|s| s.should be_a(Pivotal::Story)}
269
+ stories.detect{|s| s.name == "Deal with web thumbnails"}.should_not be_nil
270
+ stories.detect{|s| s.name == "Add Recommendations"}.should_not be_nil
271
+ stories.detect{|s| s.name == "Implement Exception Notification plugin"}.should_not be_nil
272
+ stories.detect{|s| s.name == "Implement Pro account subscription"}.should_not be_nil
273
+ stories.detect{|s| s.name == "iPhone interface for viewing profiles"}.should_not be_nil
274
+
275
+ end
276
+
277
+ describe "with filter and limit" do
278
+ before(:each) do
279
+ xml = %Q{<stories type="array" count="1" limit="1" total="10" filter="type:'feature'">
280
+ <story>
281
+ <id type="integer">300939</id>
282
+ <story_type>feature</story_type>
283
+ <url>http://www.pivotaltracker.com/story/show/300939</url>
284
+ <estimate type="integer">1</estimate>
285
+ <current_state>started</current_state>
286
+ <description>Images need to be linked and modalbox needs testing in other browsers besides FF and Safari.</description>
287
+ <name>Finish modalbox implementation</name>
288
+ <requested_by>Brian Hogan</requested_by>
289
+ <owned_by>Brian Hogan</owned_by>
290
+ <created_at type="datetime">2008/12/04 02:33:33 UTC</created_at>
291
+ </story>
292
+ </stories>}
293
+
294
+ url = "#{@service}/projects/4860/stories?token=#{@token}&limit=1&filter=type%3A%22feature%22"
295
+ FakeWeb.register_uri(:get, url, :string => xml, :content_type => "application/xml", :status => ["200", "OK"])
296
+ @project = Pivotal::Project.new("id" => "4860", :name => "FeelMySkills")
297
+ end
298
+
299
+ it "should get the story via limit and filter" do
300
+ @stories = Pivotal::Story.find :all, :project_id => 4860, :filter => {:type => "feature"}, :limit => 1
301
+ @stories.length.should ==1
302
+ end
303
+ end
304
+
305
+
306
+ it "should get bugs through the proxy" do
307
+ stories = Pivotal::Story.find :all, :project_id => @project.id
308
+ stories = stories.bugs
309
+ stories.each{|s| s.should be_a(Pivotal::Story)}
310
+ stories.detect{|s| s.name == "Deal with web thumbnails"}.should_not be_nil
311
+ stories.detect{|s| s.name == "Add Recommendations"}.should be_nil
312
+ stories.detect{|s| s.name == "Implement Exception Notification plugin"}.should be_nil
313
+ stories.detect{|s| s.name == "Implement Pro account subscription"}.should be_nil
314
+ stories.detect{|s| s.name == "iPhone interface for viewing profiles"}.should be_nil
238
315
 
239
316
  end
317
+
240
318
  end
241
319
 
242
320
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: napcs-pivotalrecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Hogan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-08 00:00:00 -07:00
12
+ date: 2009-08-13 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -63,6 +63,7 @@ extra_rdoc_files:
63
63
  files:
64
64
  - Rakefile
65
65
  - spec/pivotal
66
+ - spec/pivotal/base_spec.rb
66
67
  - spec/pivotal/iteration_spec.rb
67
68
  - spec/pivotal/project_spec.rb
68
69
  - spec/pivotal/story_spec.rb
@@ -70,6 +71,7 @@ files:
70
71
  - spec/spec_helper.rb
71
72
  - lib/pivotal
72
73
  - lib/pivotal/base.rb
74
+ - lib/pivotal/collection_proxy.rb
73
75
  - lib/pivotal/configuration.rb
74
76
  - lib/pivotal/iteration.rb
75
77
  - lib/pivotal/project.rb