tsenart-pivotal-tracker 0.4.0
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/Gemfile +16 -0
- data/Gemfile.lock +48 -0
- data/LICENSE +20 -0
- data/README.rdoc +77 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/pivotal-tracker/activity.rb +46 -0
- data/lib/pivotal-tracker/attachment.rb +16 -0
- data/lib/pivotal-tracker/client.rb +41 -0
- data/lib/pivotal-tracker/extensions.rb +11 -0
- data/lib/pivotal-tracker/iteration.rb +34 -0
- data/lib/pivotal-tracker/membership.rb +20 -0
- data/lib/pivotal-tracker/note.rb +58 -0
- data/lib/pivotal-tracker/project.rb +58 -0
- data/lib/pivotal-tracker/proxy.rb +66 -0
- data/lib/pivotal-tracker/story.rb +148 -0
- data/lib/pivotal-tracker/task.rb +53 -0
- data/lib/pivotal-tracker/validation.rb +68 -0
- data/lib/pivotal-tracker.rb +40 -0
- data/lib/pivotal_tracker.rb +2 -0
- data/pivotal-tracker.gemspec +121 -0
- data/spec/fixtures/activity.xml +177 -0
- data/spec/fixtures/created_note.xml +14 -0
- data/spec/fixtures/created_story.xml +14 -0
- data/spec/fixtures/iterations_all.xml +237 -0
- data/spec/fixtures/iterations_backlog.xml +163 -0
- data/spec/fixtures/iterations_current.xml +47 -0
- data/spec/fixtures/iterations_done.xml +33 -0
- data/spec/fixtures/memberships.xml +42 -0
- data/spec/fixtures/notes.xml +33 -0
- data/spec/fixtures/project.xml +51 -0
- data/spec/fixtures/project_activity.xml +177 -0
- data/spec/fixtures/projects.xml +103 -0
- data/spec/fixtures/stale_fish.yml +100 -0
- data/spec/fixtures/stories.xml +293 -0
- data/spec/fixtures/tasks.xml +24 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/stale_fish_fixtures.rb +67 -0
- data/spec/unit/pivotal-tracker/activity_spec.rb +32 -0
- data/spec/unit/pivotal-tracker/attachment_spec.rb +62 -0
- data/spec/unit/pivotal-tracker/client_spec.rb +87 -0
- data/spec/unit/pivotal-tracker/iteration_spec.rb +52 -0
- data/spec/unit/pivotal-tracker/membership_spec.rb +20 -0
- data/spec/unit/pivotal-tracker/note_spec.rb +61 -0
- data/spec/unit/pivotal-tracker/project_spec.rb +55 -0
- data/spec/unit/pivotal-tracker/story_spec.rb +185 -0
- data/spec/unit/pivotal-tracker/task_spec.rb +21 -0
- metadata +236 -0
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
group :runtime do
|
4
|
+
gem 'rest-client', '~> 1.6.0'
|
5
|
+
gem 'happymapper', '>= 0.3.2'
|
6
|
+
gem 'builder'
|
7
|
+
gem 'nokogiri', '~> 1.4'
|
8
|
+
end
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
gem 'rspec', '~> 1.3.0', :require => 'spec'
|
12
|
+
gem 'rake'
|
13
|
+
gem 'jeweler'
|
14
|
+
gem 'stale_fish', '~> 1.3.0'
|
15
|
+
gem "ruby-debug"
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (2.3.8)
|
5
|
+
builder (2.1.2)
|
6
|
+
columnize (0.3.2)
|
7
|
+
fakeweb (1.2.8)
|
8
|
+
gemcutter (0.6.1)
|
9
|
+
git (1.2.5)
|
10
|
+
happymapper (0.3.2)
|
11
|
+
libxml-ruby (~> 1.1.3)
|
12
|
+
jeweler (1.4.0)
|
13
|
+
gemcutter (>= 0.1.0)
|
14
|
+
git (>= 1.2.5)
|
15
|
+
rubyforge (>= 2.0.0)
|
16
|
+
json_pure (1.4.5)
|
17
|
+
libxml-ruby (1.1.4)
|
18
|
+
linecache (0.43)
|
19
|
+
mime-types (1.16)
|
20
|
+
nokogiri (1.4.4)
|
21
|
+
rake (0.8.7)
|
22
|
+
rest-client (1.6.0)
|
23
|
+
mime-types (>= 1.16)
|
24
|
+
rspec (1.3.2)
|
25
|
+
ruby-debug (0.10.4)
|
26
|
+
columnize (>= 0.1)
|
27
|
+
ruby-debug-base (~> 0.10.4.0)
|
28
|
+
ruby-debug-base (0.10.4)
|
29
|
+
linecache (>= 0.3)
|
30
|
+
rubyforge (2.0.4)
|
31
|
+
json_pure (>= 1.1.7)
|
32
|
+
stale_fish (1.3.0)
|
33
|
+
activesupport
|
34
|
+
fakeweb
|
35
|
+
|
36
|
+
PLATFORMS
|
37
|
+
ruby
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
builder
|
41
|
+
happymapper (>= 0.3.2)
|
42
|
+
jeweler
|
43
|
+
nokogiri (~> 1.4)
|
44
|
+
rake
|
45
|
+
rest-client (~> 1.6.0)
|
46
|
+
rspec (~> 1.3.0)
|
47
|
+
ruby-debug
|
48
|
+
stale_fish (~> 1.3.0)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Justin Smestad
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
= pivotal-tracker.rb
|
2
|
+
|
3
|
+
Ruby wrapper for Pivotal Tracker API, no frameworks required. Simply Ruby.
|
4
|
+
|
5
|
+
== Features
|
6
|
+
|
7
|
+
* Compatible with Pivotal Tracker API version 3
|
8
|
+
* ActiveRecord-style Wrapper API
|
9
|
+
* Support for SSL protected repositories
|
10
|
+
|
11
|
+
== Overview
|
12
|
+
|
13
|
+
PivotalTracker::Client.token('myusername@email.com', 'secretpassword') # Automatically fetch API Token
|
14
|
+
PivotalTracker::Client.token = 'jkfduisj97823974j2kl24899234' # Manually set API Token
|
15
|
+
|
16
|
+
@projects = PivotalTracker::Project.all # return all projects
|
17
|
+
@a_project = PivotalTracker::Project.find(84739) # find project with a given ID
|
18
|
+
|
19
|
+
@a_project.stories.all # return all stories for "a_project"
|
20
|
+
@a_project.stories.all(:label => 'overdue', :story_type => ['bug', 'chore']) # return all stories that match the passed filters
|
21
|
+
@a_project.stories.find(847762630) # find story with a given ID
|
22
|
+
|
23
|
+
@a_project.stories.create(:name => 'My Story', :story_type => 'feature') # create a story for this project
|
24
|
+
|
25
|
+
# all tracker defined filters are allowed, as well as :limit & :offset for pagination
|
26
|
+
|
27
|
+
# The below pattern below is planned to be added to the final release:
|
28
|
+
|
29
|
+
@a_project.stories << PivotalTracker::Story.new(84739, :name => 'Ur Story') # same as earlier story creation, useful for copying/cloning from proj
|
30
|
+
|
31
|
+
|
32
|
+
@story = @a_project.stories.find(847762630)
|
33
|
+
@story.notes.all # return all notes (comments) for a story
|
34
|
+
@story.notes.create(:text => 'A new comment', :noted_at => '06/29/2010 05:00 EST') # add a new note
|
35
|
+
|
36
|
+
|
37
|
+
@story.attachments # return an array of all attachment items (data only, not the files)
|
38
|
+
@story.upload_attachment(file_path) # add a file attachment to @story that can be found at file_path
|
39
|
+
|
40
|
+
|
41
|
+
# All 4 examples below return a PivotalTracker::Story from the new project, with the same story ID
|
42
|
+
|
43
|
+
@story.move_to_project(123456) # move @story to the project with ID 123456
|
44
|
+
@story.move_to_project('123456') # same as above
|
45
|
+
@story.move_to_project(@project) # move @story to @project
|
46
|
+
@story.move_to_project(@another_story) # move @story into the same project as @another_story
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
The API is based on the following this gist: http://gist.github.com/283120
|
51
|
+
|
52
|
+
== Getting Started
|
53
|
+
|
54
|
+
* Installing:
|
55
|
+
|
56
|
+
$ gem install pivotal-tracker
|
57
|
+
|
58
|
+
* Contributing (requires Bundler >= 0.9.7):
|
59
|
+
|
60
|
+
$ git clone git://github.com/jsmestad/pivotal-tracker
|
61
|
+
$ cd pivotal-tracker
|
62
|
+
$ bundle install
|
63
|
+
$ bundle exec rake
|
64
|
+
|
65
|
+
== Additional Information
|
66
|
+
|
67
|
+
* Wiki: http://wiki.github.com/jsmestad/pivotal-tracker
|
68
|
+
* Documentation: http://rdoc.info/projects/jsmestad/pivotal-tracker
|
69
|
+
* Pivotal API v3 Docs: http://www.pivotaltracker.com/help/api?version=v3
|
70
|
+
|
71
|
+
== Contributors along the way
|
72
|
+
|
73
|
+
* Justin Smestad (http://github.com/jsmestad)
|
74
|
+
* Josh Nichols (http://github.com/technicalpickles)
|
75
|
+
* Terence Lee (http://github.com/hone)
|
76
|
+
* Jon Mischo (http://github.com/supertaz)
|
77
|
+
* Gabor Ratky (http://github.com/rgabo)
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "tsenart-pivotal-tracker"
|
8
|
+
gem.summary = %Q{Ruby wrapper for the Pivotal Tracker API}
|
9
|
+
gem.email = "justin.smestad@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/tsenart/pivotal-tracker"
|
11
|
+
gem.authors = ["Justin Smestad", "Josh Nichols", "Terence Lee", "Tomás Senart"]
|
12
|
+
|
13
|
+
gem.add_dependency 'rest-client', '~> 1.6.0'
|
14
|
+
gem.add_dependency 'happymapper', '>= 0.3.2'
|
15
|
+
gem.add_dependency 'builder'
|
16
|
+
gem.add_dependency 'nokogiri', '~> 1.4.3.1'
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
gem.add_development_dependency 'bundler', '>= 0.9.26'
|
20
|
+
gem.add_development_dependency 'jeweler'
|
21
|
+
gem.add_development_dependency 'stale_fish', '~> 1.3.0'
|
22
|
+
end
|
23
|
+
Jeweler::GemcutterTasks.new
|
24
|
+
rescue LoadError
|
25
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'spec/rake/spectask'
|
29
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
35
|
+
spec.libs << 'lib' << 'spec'
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
begin
|
43
|
+
require 'yard'
|
44
|
+
YARD::Rake::YardocTask.new
|
45
|
+
rescue LoadError
|
46
|
+
task :yardoc do
|
47
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.0
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Activity
|
3
|
+
include HappyMapper
|
4
|
+
class << self
|
5
|
+
def all(project=nil, options={})
|
6
|
+
params = self.encode_options(options)
|
7
|
+
if project
|
8
|
+
parse(Client.connection["/projects/#{project.id}/activities#{params}"].get)
|
9
|
+
else
|
10
|
+
parse(Client.connection["/activities#{params}"].get)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def encode_options(options)
|
17
|
+
return nil if !options.is_a?(Hash) || options.empty?
|
18
|
+
|
19
|
+
options_string = []
|
20
|
+
options_string << "limit=#{options.delete(:limit)}" if options[:limit]
|
21
|
+
options_string << "newer_than_version=#{options.delete(:newer_than_version)}" if options[:newer_than_version]
|
22
|
+
|
23
|
+
if options[:occurred_since]
|
24
|
+
options_string << "occurred_since_date=\"#{options[:occurred_since].utc}\""
|
25
|
+
elsif options[:occurred_since_date]
|
26
|
+
#NOTE currently forces UTC as the timezone
|
27
|
+
options_string << "occurred_since_date=#{URI.escape options[:occurred_since_date].strftime("%Y/%m/%d %H:%M:%S UTC")}"
|
28
|
+
end
|
29
|
+
|
30
|
+
return "?#{options_string.join('&')}"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
element :id, Integer
|
36
|
+
element :version, Integer
|
37
|
+
element :event_type, String
|
38
|
+
element :occurred_at, DateTime
|
39
|
+
element :author, String
|
40
|
+
element :project_id, Integer
|
41
|
+
element :description, String
|
42
|
+
|
43
|
+
has_many :stories, Story
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Attachment
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
tag 'attachment'
|
6
|
+
|
7
|
+
element :id, Integer
|
8
|
+
element :filename, String
|
9
|
+
element :description, String
|
10
|
+
element :uploaded_by, String
|
11
|
+
element :uploaded_at, DateTime
|
12
|
+
element :url, String
|
13
|
+
element :status, String
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Client
|
3
|
+
|
4
|
+
class NoToken < StandardError; end
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_writer :use_ssl, :token
|
8
|
+
|
9
|
+
def use_ssl
|
10
|
+
@use_ssl || false
|
11
|
+
end
|
12
|
+
|
13
|
+
def token(username, password, method='post')
|
14
|
+
return @token if @token
|
15
|
+
response = if method == 'post'
|
16
|
+
RestClient.post 'https://www.pivotaltracker.com/services/v3/tokens/active', :username => username, :password => password
|
17
|
+
else
|
18
|
+
RestClient.get "https://#{username}:#{password}@www.pivotaltracker.com/services/v3/tokens/active"
|
19
|
+
end
|
20
|
+
@token= Nokogiri::XML(response.body).search('guid').inner_html
|
21
|
+
end
|
22
|
+
|
23
|
+
# this is your connection for the entire module
|
24
|
+
def connection(options={})
|
25
|
+
raise NoToken if @token.to_s.empty?
|
26
|
+
|
27
|
+
@connections ||= {}
|
28
|
+
|
29
|
+
@connections[@token] ||= RestClient::Resource.new("#{protocol}://www.pivotaltracker.com/services/v3", :headers => {'X-TrackerToken' => @token, 'Content-Type' => 'application/xml'})
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def protocol
|
35
|
+
use_ssl ? 'https' : 'http'
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Happymapper patch for RestClient API Change (response => response.body)
|
2
|
+
|
3
|
+
module HappyMapper
|
4
|
+
module ClassMethods
|
5
|
+
alias_method :orig_parse, :parse
|
6
|
+
def parse(xml, options={})
|
7
|
+
xml = xml.to_s if xml.is_a?(RestClient::Response)
|
8
|
+
orig_parse(xml, options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Iteration
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all(project, options={})
|
7
|
+
params = PivotalTracker.encode_options(options)
|
8
|
+
parse(Client.connection["/projects/#{project.id}/iterations#{params}"].get)
|
9
|
+
end
|
10
|
+
|
11
|
+
def current(project)
|
12
|
+
array = parse(Client.connection["projects/#{project.id}/iterations/current"].get)
|
13
|
+
array.first if array
|
14
|
+
end
|
15
|
+
|
16
|
+
def done(project, options={})
|
17
|
+
params = PivotalTracker.encode_options(options)
|
18
|
+
parse(Client.connection["/projects/#{project.id}/iterations/done#{params}"].get)
|
19
|
+
end
|
20
|
+
|
21
|
+
def backlog(project, options={})
|
22
|
+
params = PivotalTracker.encode_options(options)
|
23
|
+
parse(Client.connection["/projects/#{project.id}/iterations/backlog#{params}"].get)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
element :id, Integer
|
28
|
+
element :number, Integer
|
29
|
+
element :start, DateTime
|
30
|
+
element :finish, DateTime
|
31
|
+
has_many :stories, Story
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Membership
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all(project, options={})
|
7
|
+
parse(Client.connection["/projects/#{project.id}/memberships"].get)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
element :id, Integer
|
12
|
+
element :role, String
|
13
|
+
|
14
|
+
# Flattened Attributes from <person>...</person>
|
15
|
+
element :name, String, :deep => true
|
16
|
+
element :email, String, :deep => true
|
17
|
+
element :initials, String, :deep => true
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Note
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all(story, options={})
|
7
|
+
notes = parse(Client.connection["/projects/#{story.project_id}/stories/#{story.id}/notes"].get)
|
8
|
+
notes.each { |n| n.project_id, n.story_id = story.project_id, story.id }
|
9
|
+
return notes
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :project_id, :story_id
|
14
|
+
|
15
|
+
element :id, Integer
|
16
|
+
element :text, String
|
17
|
+
element :author, String
|
18
|
+
element :noted_at, DateTime
|
19
|
+
has_one :story, Story
|
20
|
+
|
21
|
+
def initialize(attributes={})
|
22
|
+
if attributes[:owner]
|
23
|
+
self.story = attributes.delete(:owner)
|
24
|
+
self.project_id = self.story.project_id
|
25
|
+
self.story_id = self.story.id
|
26
|
+
end
|
27
|
+
|
28
|
+
update_attributes(attributes)
|
29
|
+
end
|
30
|
+
|
31
|
+
def create
|
32
|
+
response = Client.connection["/projects/#{project_id}/stories/#{story_id}/notes"].post(self.to_xml, :content_type => 'application/xml')
|
33
|
+
return Note.parse(response)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Pivotal Tracker API doesn't seem to support updating or deleting notes at this time.
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def to_xml
|
41
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
42
|
+
xml.note {
|
43
|
+
#xml.author "#{author}"
|
44
|
+
xml.text_ "#{text}"
|
45
|
+
xml.noted_at "#{noted_at}"
|
46
|
+
}
|
47
|
+
end
|
48
|
+
return builder.to_xml
|
49
|
+
end
|
50
|
+
|
51
|
+
def update_attributes(attrs)
|
52
|
+
attrs.each do |key, value|
|
53
|
+
self.send("#{key}=", value.is_a?(Array) ? value.join(',') : value )
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module PivotalTracker
|
2
|
+
class Project
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all
|
7
|
+
@found = parse(Client.connection['/projects'].get)
|
8
|
+
end
|
9
|
+
|
10
|
+
def find(id)
|
11
|
+
if @found
|
12
|
+
@found.detect { |document| document.id == id }
|
13
|
+
else
|
14
|
+
parse(Client.connection["/projects/#{id}"].get)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
element :id, Integer
|
20
|
+
element :name, String
|
21
|
+
element :account, String
|
22
|
+
element :week_start_day, String
|
23
|
+
element :point_scale, String
|
24
|
+
element :week_start_day, String
|
25
|
+
element :velocity_scheme, String
|
26
|
+
element :iteration_length, Integer
|
27
|
+
element :initial_velocity, Integer
|
28
|
+
element :current_velocity, Integer
|
29
|
+
element :last_activity_at, DateTime
|
30
|
+
element :use_https, Boolean
|
31
|
+
|
32
|
+
def activities
|
33
|
+
@activities ||= Proxy.new(self, Activity)
|
34
|
+
end
|
35
|
+
|
36
|
+
def iterations
|
37
|
+
@iterations ||= Proxy.new(self, Iteration)
|
38
|
+
end
|
39
|
+
|
40
|
+
def stories
|
41
|
+
@stories ||= Proxy.new(self, Story)
|
42
|
+
end
|
43
|
+
|
44
|
+
def memberships
|
45
|
+
@memberships ||= Proxy.new(self, Membership)
|
46
|
+
end
|
47
|
+
|
48
|
+
def iteration(group)
|
49
|
+
case group.to_sym
|
50
|
+
when :done then Iteration.done(self)
|
51
|
+
when :current then Iteration.current(self)
|
52
|
+
when :backlog then Iteration.backlog(self)
|
53
|
+
else
|
54
|
+
raise ArgumentError, "Invalid group. Use :done, :current or :backlog instead."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class BasicObject #:nodoc:
|
2
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|instance_eval|proxy_|^object_id$)/ }
|
3
|
+
end unless defined?(BasicObject)
|
4
|
+
|
5
|
+
module PivotalTracker
|
6
|
+
class Proxy < BasicObject
|
7
|
+
|
8
|
+
def initialize(owner, target)
|
9
|
+
@owner = owner
|
10
|
+
@target = target
|
11
|
+
@opts = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def all(options={})
|
15
|
+
proxy_found(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(param, options={})
|
19
|
+
return all(options) if param == :all
|
20
|
+
return @target.find(param, @owner.id) if @target.respond_to?("find")
|
21
|
+
return proxy_found(options).detect { |document| document.id == param }
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(*objects)
|
25
|
+
objects.flatten.each do |object|
|
26
|
+
if obj = object.create
|
27
|
+
return obj
|
28
|
+
else
|
29
|
+
return object
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(args)
|
35
|
+
object = @target.new(args.merge({:owner => @owner}))
|
36
|
+
if obj = object.create
|
37
|
+
return obj
|
38
|
+
else
|
39
|
+
return object
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def proxy_found(options)
|
46
|
+
# Check to see if options have changed
|
47
|
+
if @opts == options
|
48
|
+
@found ||= load_found(options)
|
49
|
+
else
|
50
|
+
load_found(options)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def method_missing(method, *args, &block)
|
57
|
+
@target.send(method, *args, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def load_found(options)
|
61
|
+
@opts = options
|
62
|
+
@target.all(@owner, @opts)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|