trajectory 0.1.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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +19 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +57 -0
  9. data/Rakefile +18 -0
  10. data/config/env.yml +3 -0
  11. data/lib/trajectory/client.rb +24 -0
  12. data/lib/trajectory/core_ext/hash.rb +8 -0
  13. data/lib/trajectory/core_ext.rb +1 -0
  14. data/lib/trajectory/data_access/api.rb +54 -0
  15. data/lib/trajectory/data_access/data_store.rb +70 -0
  16. data/lib/trajectory/data_access.rb +2 -0
  17. data/lib/trajectory/domain/idea.rb +70 -0
  18. data/lib/trajectory/domain/ideas.rb +23 -0
  19. data/lib/trajectory/domain/iteration.rb +85 -0
  20. data/lib/trajectory/domain/iterations.rb +46 -0
  21. data/lib/trajectory/domain/project.rb +168 -0
  22. data/lib/trajectory/domain/projects.rb +55 -0
  23. data/lib/trajectory/domain/stories.rb +56 -0
  24. data/lib/trajectory/domain/story.rb +119 -0
  25. data/lib/trajectory/domain/update.rb +17 -0
  26. data/lib/trajectory/domain/user.rb +28 -0
  27. data/lib/trajectory/domain/users.rb +30 -0
  28. data/lib/trajectory/domain.rb +11 -0
  29. data/lib/trajectory/exceptions/bad_environment_error.rb +7 -0
  30. data/lib/trajectory/exceptions/missing_attribute_error.rb +12 -0
  31. data/lib/trajectory/exceptions/velocity_equal_to_zero_error.rb +11 -0
  32. data/lib/trajectory/exceptions.rb +3 -0
  33. data/lib/trajectory/version.rb +3 -0
  34. data/lib/trajectory.rb +15 -0
  35. data/spec/fabricators/iteration_fabricator.rb +18 -0
  36. data/spec/fabricators/project_fabricator.rb +11 -0
  37. data/spec/fabricators/story_fabricator.rb +18 -0
  38. data/spec/fixtures/vcr_cassettes/client.yml +853 -0
  39. data/spec/fixtures/vcr_cassettes/projects_and_ideas.yml +98 -0
  40. data/spec/fixtures/vcr_cassettes/projects_and_ideas_association.yml +52 -0
  41. data/spec/fixtures/vcr_cassettes/projects_and_iterations.yml +49 -0
  42. data/spec/fixtures/vcr_cassettes/projects_and_iterations_association.yml +49 -0
  43. data/spec/fixtures/vcr_cassettes/projects_and_stories.yml +101 -0
  44. data/spec/fixtures/vcr_cassettes/projects_and_stories_association.yml +147 -0
  45. data/spec/integration/client_spec.rb +96 -0
  46. data/spec/spec_helper.rb +13 -0
  47. data/spec/support/env.rb +11 -0
  48. data/spec/support/fabrication.rb +1 -0
  49. data/spec/support/simple_cov.rb +11 -0
  50. data/spec/support/timecop.rb +1 -0
  51. data/spec/support/vcr.rb +8 -0
  52. data/spec/unit/client_spec.rb +27 -0
  53. data/spec/unit/core_ext/hash_spec.rb +11 -0
  54. data/spec/unit/data_access/api_spec.rb +100 -0
  55. data/spec/unit/data_access/data_store_spec.rb +86 -0
  56. data/spec/unit/domain/idea_spec.rb +46 -0
  57. data/spec/unit/domain/ideas_spec.rb +22 -0
  58. data/spec/unit/domain/iteration_spec.rb +51 -0
  59. data/spec/unit/domain/iterations_spec.rb +47 -0
  60. data/spec/unit/domain/project_spec.rb +163 -0
  61. data/spec/unit/domain/projects_spec.rb +84 -0
  62. data/spec/unit/domain/stories_spec.rb +84 -0
  63. data/spec/unit/domain/story_spec.rb +71 -0
  64. data/spec/unit/domain/user_spec.rb +29 -0
  65. data/spec/unit/domain/users_spec.rb +36 -0
  66. data/spec/unit/exceptions/bad_environment_error_spec.rb +9 -0
  67. data/spec/unit/exceptions/missing_attribute_error_spec.rb +10 -0
  68. data/spec/unit/exceptions/velocity_equal_to_zero_error_spec.rb +10 -0
  69. data/trajectory.gemspec +21 -0
  70. metadata +174 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e4e38ce957170860b16f74c2ca873c005c978c5
4
+ data.tar.gz: 6e532e69b0e54ca48e3b469612644aea41e6ad67
5
+ SHA512:
6
+ metadata.gz: d7892c02afbb0156a5f7880c6f23b3343d226c696230371409ab31a053f2076ca4663c6932ff6c77fe43c29413cb88258422f0f90aaecbc83d37d96a34687e86
7
+ data.tar.gz: 7a0300e0f14c9fe74fe52ad18c65249f1a977e4f71b7156c503ab4d3fc719c2e523a3b1d77cf8e9e8b18ac304a7b5cc756d79b9140d64b60d5a0234c309a6c98
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ bin
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format doc
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p0
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-19mode
6
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'awesome_print'
5
+ gem 'rspec'
6
+ gem 'rake'
7
+ gem 'yard'
8
+ end
9
+
10
+ group :test do
11
+ gem 'vcr'
12
+ gem 'webmock'
13
+ gem 'fabrication'
14
+ gem 'simplecov', :require => false
15
+ gem 'timecop'
16
+ end
17
+
18
+ # Specify your gem's dependencies in trajectory.gemspec
19
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Loïc Minaudier
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Trajectory Ruby Wrapper
2
+
3
+ [![Build Status](https://travis-ci.org/lminaudier/trajectory.png?branch=master)](https://travis-ci.org/lminaudier/trajectory)
4
+
5
+ This gem is a wrapper to the Thoughbot's Trajectory app ([apptrajectory.com](http://apptrajectory.com)).
6
+
7
+ This is **work in progress** but you'll have read access to
8
+ - projects
9
+ - stories
10
+ - iterations
11
+ - ideas
12
+ - updates
13
+
14
+ Comments, Uploads wrappers are missing.
15
+
16
+ No write wrapper is provided at the moment.
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ gem 'trajectory'
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install trajectory
31
+
32
+ ## Usage
33
+
34
+ ### Read Access
35
+
36
+ #### Authentication
37
+
38
+ By default, the wrapper will look for the Trajectory API key in
39
+ `TRAJECTORY_API_KEY` environment variable. You can also pass it directly to the
40
+ constructor.
41
+
42
+ require 'trajectory'
43
+
44
+ client = Trajectory::Client.new
45
+ client.projects
46
+
47
+ #### API
48
+
49
+ See [RDoc](http://rdoc.info/github/lminaudier/trajectory/master/frames) for api details
50
+
51
+ ## Contributing
52
+
53
+ 1. Fork it
54
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
55
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
56
+ 4. Push to the branch (`git push origin my-new-feature`)
57
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.rspec_opts = '--format progress'
6
+ end
7
+
8
+ task :default => :spec
9
+
10
+ desc "List all code todos"
11
+ task :todo do
12
+ require 'yard'
13
+ YARD::Registry.load!.all.each do |o|
14
+ if o.tag(:todo)
15
+ puts "#{o.file}:#{o.line} #{o.tag(:todo).text}"
16
+ end
17
+ end
18
+ end
data/config/env.yml ADDED
@@ -0,0 +1,3 @@
1
+ test:
2
+ trajectory_api_key: 76900a72434cfe249da911eea2a6c94a
3
+ trajectory_account_keyword: fake-user-for-api-test
@@ -0,0 +1,24 @@
1
+ module Trajectory
2
+ class Client
3
+ # Creates a new trajectory client
4
+ #
5
+ # @return [Client]
6
+ def initialize
7
+ check_environment!
8
+ end
9
+
10
+ # Checks if environment variables are set.
11
+ #
12
+ # @raise [BadEvnrionmentError] if TRAJECTORY_API_KEY and TRAJECTORY_ACCOUNT_KEYWORD are not set
13
+ def check_environment!
14
+ raise BadEvnrionmentError if ENV['TRAJECTORY_API_KEY'].nil? || ENV['TRAJECTORY_ACCOUNT_KEYWORD'].nil?
15
+ end
16
+
17
+ # Fetches all trajectory projects of the account
18
+ #
19
+ # @return [Projects] the projects collection
20
+ def projects
21
+ DataStore.projects
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ def symbolize_keys!
3
+ keys.each do |key|
4
+ self[(key.to_sym rescue key) || key] = delete(key)
5
+ end
6
+ self
7
+ end
8
+ end
@@ -0,0 +1 @@
1
+ require 'trajectory/core_ext/hash'
@@ -0,0 +1,54 @@
1
+ require 'httparty'
2
+
3
+ module Trajectory
4
+ class Api
5
+ include HTTParty
6
+ base_uri "https://www.apptrajectory.com/api/#{ENV['TRAJECTORY_API_KEY']}/accounts/#{ENV['TRAJECTORY_ACCOUNT_KEYWORD']}"
7
+
8
+ class << self
9
+ # @return [JSON] a json array of all projects attributes from trajectory API
10
+ def projects
11
+ get_json("/projects.json")
12
+ end
13
+
14
+ # @return [JSON] a json array of all users (of the given project) attributes from trajectory API
15
+ def users_for_project(project)
16
+ get_json("/projects/#{project.keyword}/users.json")
17
+ end
18
+
19
+ # @return [JSON] a json array of all stories (of the given project) attributes from trajectory API
20
+ def stories_for_project(project)
21
+ get_json("/projects/#{project.keyword}/stories/completed.json")['stories'] +
22
+ get_json("/projects/#{project.keyword}/stories.json")['stories']
23
+ end
24
+
25
+ # @return [JSON] a json array of all iterations (of the given project) attributes from trajectory API
26
+ def iterations_for_project(project)
27
+ get_json("/projects/#{project.keyword}/iterations.json")
28
+ end
29
+
30
+ # @return [JSON] a json array of all ideas (of the given project) attributes from trajectory API
31
+ def ideas_for_project(project)
32
+ get_json("/projects/#{project.keyword}/ideas.json")
33
+ end
34
+
35
+ # @return [JSON] a json array of all updates (of the given project) attributes from trajectory API
36
+ def updates_for_project(project, since)
37
+ get_json("/projects/#{project.keyword}/updates.json?since=#{since.to_s}")
38
+ end
39
+
40
+ private
41
+ def get_json(url)
42
+ JSON.parse(get_body(url, options))
43
+ end
44
+
45
+ def options
46
+ {:headers => {'Content-Type' => 'application/json'}}
47
+ end
48
+
49
+ def get_body(*args)
50
+ get(*args).body
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,70 @@
1
+ module Trajectory
2
+ module DataStore
3
+ extend self
4
+
5
+ # @return [Projects] the collection of projects of the trajectory account
6
+ def projects
7
+ @projects ||= Projects.from_json(Api.projects)
8
+ end
9
+
10
+ # @return [Users] the collection of users of the trajectory account
11
+ def users_for_project(project)
12
+ Users.from_json Api.users_for_project(project)
13
+ end
14
+
15
+ # Fetches all stories of a given project
16
+ #
17
+ # @param project [Project] the project
18
+ # @return [Stories] the collection of stories
19
+ def stories_for_project(project)
20
+ Stories.from_json project, Api.stories_for_project(project)
21
+ end
22
+
23
+ # Fetches all ideas of a given project
24
+ #
25
+ # @param project [Project] the project
26
+ # @return [Ideas] the collection of ideas
27
+ def ideas_for_project(project)
28
+ Ideas.from_json project, Api.ideas_for_project(project)
29
+ end
30
+
31
+ # Fetches all updates of a given project since a given date
32
+ #
33
+ # @param project [Project] the project
34
+ # @param since [DateTime] a datetime
35
+ # @return [Ideas] the collection of updates
36
+ def updates_for_project(project, since = DateTime.now)
37
+ updates = Api.updates_for_project(project, since).symbolize_keys!
38
+
39
+ stories = Stories.from_json(project, updates[:stories])
40
+ iterations = Iterations.from_json(project, updates[:iterations])
41
+
42
+ Update.new(stories, iterations)
43
+ end
44
+
45
+ # Fetches a project by id in the collection of accessible projects
46
+ #
47
+ # @param id [Integer] the project id
48
+ # @return [Project, false] the found project or false
49
+ def find_project_by_id(id)
50
+ projects.find_by_id(id)
51
+ end
52
+
53
+ # Fetches a user of a given project by id
54
+ #
55
+ # @param project [Project] the project
56
+ # @param user_id [Integer] the user id
57
+ # @return [User, false] the found user or false
58
+ def find_user_of_project_with_id(project, user_id)
59
+ project.find_user_by_id(user_id)
60
+ end
61
+
62
+ # Fetches all iterations of a given project
63
+ #
64
+ # @param project [Project] the project
65
+ # @return [Iterations] the collection of iterations
66
+ def iterations_for_project(project)
67
+ Iterations.from_json project, Api.iterations_for_project(project)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,2 @@
1
+ require 'trajectory/data_access/api'
2
+ require 'trajectory/data_access/data_store'
@@ -0,0 +1,70 @@
1
+ module Trajectory
2
+ class Idea
3
+ include Virtus
4
+
5
+ # @return [Integer] the unique identifier of the idea.
6
+ # @raise [MissingAttributeError] if id is nil
7
+ attribute :id, Integer, default: lambda { |project, attribute| raise MissingAttributeError.new(project, :id) }
8
+ # @return [String] the subject (i.e title) of the idea
9
+ attribute :subject, String
10
+ # @return [Integer] the number of stories associated with the idea
11
+ attribute :stories_count, Integer
12
+ # @return [Integer] the number of comments on the idea
13
+ attribute :comments_count, Integer
14
+ # @return [String] Name of the last user that have commented on the idea
15
+ attribute :last_comment_user_name, String
16
+ # @return [DateTime] date of the last comment on the idea
17
+ attribute :last_comment_created_at, DateTime
18
+ # @return [DateTime] date of the last activity (i.e edit, comment, ...) on the idea
19
+ attribute :last_activity_at, DateTime
20
+ # @!method editable_by_current_user?
21
+ # @return [true, false] true if the user can edit the idea, false otherwise
22
+ attribute :editable_by_current_user, Boolean
23
+ # @return [Integer] idea of the user that created the idea
24
+ # @see #user
25
+ attribute :user_id, Integer
26
+ # @return [Array<Integer>] the ids of the users that have subscribed to the
27
+ # @see #subscribed_users
28
+ attribute :subscribed_user_ids, Array[Integer]
29
+ # @return [Integer] the id of the project the idea belongs to
30
+ # @see #project
31
+ attribute :project_id, Integer
32
+ # @return [String] the content of the idea
33
+ attribute :body, String
34
+
35
+ # Returns true if two ideas are the sames i.e they share the same id
36
+ # attribute
37
+ #
38
+ # @param other [Idea] the other object to compare
39
+ # @return [true, false]
40
+ def ==(other)
41
+ id == other.id
42
+ end
43
+
44
+ # Fetch the project the idea belongs to
45
+ #
46
+ # @return [Project]
47
+ def project
48
+ DataStore.find_project_by_id(project_id)
49
+ end
50
+
51
+ # Fetch the user that created the idea
52
+ #
53
+ # @return [User]
54
+ def user
55
+ DataStore.find_user_of_project_with_id(project, user_id)
56
+ end
57
+
58
+ # @todo Not Yet Implemented
59
+ def subscribed_users
60
+ end
61
+
62
+ # @todo Not Yet Implemented
63
+ def percent_complete
64
+ end
65
+
66
+ # @todo Not Yet Implemented
67
+ def stories
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,23 @@
1
+ module Trajectory
2
+ class Ideas < SimpleDelegator
3
+ alias :ideas :__getobj__
4
+
5
+ # Creates a new collection of {Idea}
6
+ #
7
+ # @param ideas [Array<Idea>] a arbitrary lenght list of {Idea} objects
8
+ def initialize(*ideas)
9
+ super(ideas)
10
+ end
11
+
12
+ # Create a new collection of {Idea} from a JSON array of attributes from trajectory API
13
+ #
14
+ # @param project [Project] the project the iterations belongs to
15
+ # @param json_attributes [Hash] the hash of attributes of each idea of the collection
16
+ def self.from_json(project, json_attributes)
17
+ new(*json_attributes.map do |attributes|
18
+ attributes = attributes.symbolize_keys!.merge({project_id: project.id})
19
+ Idea.new(attributes)
20
+ end)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,85 @@
1
+ module Trajectory
2
+ class Iteration
3
+ include Virtus
4
+
5
+ attr_writer :project
6
+
7
+ # @return [Integer] the unique identifier of the iteration.
8
+ # @raise [MissingAttributeError] if id is nil
9
+ attribute :id, Integer, default: lambda { |project, attribute| raise MissingAttributeError.new(project, :id) }
10
+ # @return [Float] the completion percentage of the iteration
11
+ attribute :percent_complete, Float
12
+ # @return [Integer] number of started stories in the iteration
13
+ attribute :started_stories_count, Integer
14
+ # @return [DateTime] creation date of the iteration
15
+ attribute :created_at, DateTime
16
+ # @return [Integer] estimated velocity of the project
17
+ attribute :estimated_velocity, Integer
18
+ # @return [Integer] number of delivered stories in the iteration
19
+ attribute :delivered_stories_count, Integer
20
+ # @return [DateTime] last modification date of the iteration
21
+ attribute :updated_at, DateTime
22
+ # @return [Integer] number of unstarted stories
23
+ attribute :unstarted_stories_count, Integer
24
+ # @return [DateTime] start date of the iteration
25
+ attribute :starts_on, DateTime
26
+ # @!method current?
27
+ # @return [true, false] true if iteration is the current iteration, false otherwise
28
+ attribute :current, Boolean
29
+ # @return [Integer] number of stories in the iteration
30
+ attribute :stories_count, Integer
31
+ # @!method complete?
32
+ # @return [true, false] return true if the iteration has been completed, false otherwise
33
+ attribute :complete, Boolean
34
+ # Sum of points accepted stories in the iteration
35
+ #
36
+ # @return [Integer] number of accepted points
37
+ attribute :accepted_points, Integer
38
+ # Sum of all points of all stories of the iteration
39
+ #
40
+ # @return [Integer] number of estimated points
41
+ attribute :estimated_points, Integer
42
+ # @return [Integer] number of comments of the story
43
+ attribute :comments_count, Integer
44
+ # @return [Integer] bumber of accepted stories
45
+ attribute :accepted_stories_count, Integer
46
+ # @return [Integer] d of the project the iteration belongs to
47
+ # @see #project
48
+ attribute :project_id, Integer
49
+
50
+ # @!method past?
51
+ #
52
+ # Returns true if iteration is a past one i.e has been completed
53
+ #
54
+ # @return [true, false]
55
+ alias :past? :complete?
56
+
57
+ # Returns true if the iteration is a future one i.e not started and not completed
58
+ def future?
59
+ !current? && !complete?
60
+ end
61
+
62
+ # Returns true if two iterations are the sames i.e they share the same id
63
+ # attribute
64
+ #
65
+ # @param other [Iteration] the other object to compare
66
+ # @return [true, false]
67
+ def ==(other)
68
+ id == other.id
69
+ end
70
+
71
+ # Fetch the project the iteration belongs to
72
+ #
73
+ # @return [Project]
74
+ def project
75
+ @project ||= DataStore.find_project_by_id(project_id)
76
+ end
77
+
78
+ # Fetch the stories that belongs to the iteration
79
+ #
80
+ # @return [Stories] the stories collection
81
+ def stories
82
+ project.stories_in_iteration(self)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,46 @@
1
+ module Trajectory
2
+ class Iterations < SimpleDelegator
3
+ alias :iterations :__getobj__
4
+
5
+ # Creates a new collection of {Iteration}
6
+ #
7
+ # @param iterations [Array<Iteration>] a arbitrary lenght list of {Iteration} objects
8
+ def initialize(*iterations)
9
+ super(iterations)
10
+ end
11
+
12
+ # Create a new collection of {Iteration} from a JSON array of attributes from trajectory API
13
+ #
14
+ # @param project [Project] the project the iterations belongs to
15
+ # @param json_attributes [Hash] the hash of attributes of each iteration of the collection
16
+ def self.from_json(project, json_attributes)
17
+ new(*json_attributes.map do |attributes|
18
+ attributes = attributes.symbolize_keys!.merge({project_id: project.id})
19
+ attributes[:current] = attributes[:current?]
20
+ attributes.delete(:current?)
21
+ Iteration.new(attributes)
22
+ end)
23
+ end
24
+
25
+ # Returns the current iteration of the project or false it no current iteration can be found
26
+ #
27
+ # @return [Iteration, false] the current iteration or false
28
+ def current
29
+ iterations.find { |iteration| iteration.current? } || false
30
+ end
31
+
32
+ # Returns the future iterations of the project
33
+ #
34
+ # @return [Iterations] the future iterations
35
+ def future
36
+ iterations.select { |iteration| iteration.future? }
37
+ end
38
+
39
+ # Returns the past iterations of the project
40
+ #
41
+ # @return [Iterations] the past iterations
42
+ def past
43
+ iterations.select { |iteration| iteration.past? }
44
+ end
45
+ end
46
+ end