napcs-pivotalrecord 0.0.1 → 0.0.3

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.
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