tracker_api 0.2.12 → 1.0.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +1 -1
  4. data/lib/tracker_api.rb +5 -3
  5. data/lib/tracker_api/client.rb +12 -2
  6. data/lib/tracker_api/endpoints/epic.rb +8 -0
  7. data/lib/tracker_api/endpoints/story.rb +2 -0
  8. data/lib/tracker_api/resources/account.rb +1 -1
  9. data/lib/tracker_api/resources/activity.rb +2 -2
  10. data/lib/tracker_api/resources/change.rb +1 -1
  11. data/lib/tracker_api/resources/comment.rb +4 -4
  12. data/lib/tracker_api/resources/epic.rb +5 -5
  13. data/lib/tracker_api/resources/iteration.rb +2 -2
  14. data/lib/tracker_api/resources/label.rb +1 -1
  15. data/lib/tracker_api/resources/me.rb +4 -4
  16. data/lib/tracker_api/resources/membership_summary.rb +1 -1
  17. data/lib/tracker_api/resources/notification.rb +1 -1
  18. data/lib/tracker_api/resources/person.rb +1 -1
  19. data/lib/tracker_api/resources/primary_resource.rb +1 -1
  20. data/lib/tracker_api/resources/project.rb +11 -10
  21. data/lib/tracker_api/resources/project_membership.rb +1 -1
  22. data/lib/tracker_api/resources/shared/base.rb +30 -0
  23. data/lib/tracker_api/resources/shared/collection.rb +17 -0
  24. data/lib/tracker_api/resources/story.rb +31 -17
  25. data/lib/tracker_api/resources/task.rb +1 -1
  26. data/lib/tracker_api/version.rb +1 -1
  27. data/lib/virtus/attribute/nullify_blank.rb +27 -0
  28. data/lib/virtus/dirty_attribute.rb +104 -0
  29. data/lib/virtus/dirty_attribute/session.rb +54 -0
  30. data/test/client_test.rb +16 -1
  31. data/test/minitest_helper.rb +1 -0
  32. data/test/story_test.rb +10 -3
  33. data/test/vcr/cassettes/client_done_iterations_with_pagination.json +1 -1
  34. data/test/vcr/cassettes/client_get_all_stories_with_pagination.json +1 -1
  35. data/test/vcr/cassettes/client_get_limited_stories_with_no_pagination.json +1 -1
  36. data/test/vcr/cassettes/client_get_single_epic_by_epic_id.json +1 -0
  37. data/test/vcr/cassettes/client_get_single_story_by_story_id.json +1 -1
  38. data/test/vcr/cassettes/create_story.json +1 -1
  39. data/test/vcr/cassettes/create_story_with_lengthy_params.json +1 -1
  40. data/test/vcr/cassettes/create_task.json +1 -1
  41. data/test/vcr/cassettes/get_all_projects.json +1 -1
  42. data/test/vcr/cassettes/get_another_story.json +1 -1
  43. data/test/vcr/cassettes/get_current_iteration.json +1 -1
  44. data/test/vcr/cassettes/get_done_iterations.json +1 -1
  45. data/test/vcr/cassettes/get_epics.json +1 -1
  46. data/test/vcr/cassettes/get_iteration_by_number.json +1 -1
  47. data/test/vcr/cassettes/get_labels.json +1 -1
  48. data/test/vcr/cassettes/get_me.json +1 -1
  49. data/test/vcr/cassettes/get_my_activities.json +1 -1
  50. data/test/vcr/cassettes/get_owners_for_story.json +1 -1
  51. data/test/vcr/cassettes/get_project.json +1 -1
  52. data/test/vcr/cassettes/get_project_activity.json +1 -1
  53. data/test/vcr/cassettes/get_project_with_epics.json +1 -1
  54. data/test/vcr/cassettes/get_project_with_labels.json +1 -1
  55. data/test/vcr/cassettes/get_story.json +1 -1
  56. data/test/vcr/cassettes/get_story_activity.json +1 -1
  57. data/test/vcr/cassettes/get_story_comments.json +1 -1
  58. data/test/vcr/cassettes/get_story_owners.json +1 -1
  59. data/test/vcr/cassettes/get_story_with_tasks.json +1 -1
  60. data/test/vcr/cassettes/get_tasks.json +1 -1
  61. data/test/vcr/cassettes/get_tasks_for_story.json +1 -1
  62. data/test/vcr/cassettes/get_tasks_when_stories_filtered.json +1 -1
  63. data/test/vcr/cassettes/get_unscheduled_stories.json +1 -1
  64. data/test/vcr/cassettes/get_unscheduled_story.json +1 -1
  65. data/test/vcr/cassettes/save_story.json +1 -1
  66. data/test/vcr/cassettes/save_story_with_multiple_changes.json +1 -1
  67. data/test/vcr/cassettes/save_story_with_new_label.json +1 -1
  68. data/test/vcr/cassettes/update_story_to_create_activity.json +1 -1
  69. data/tracker_api.gemspec +2 -0
  70. metadata +23 -5
  71. data/lib/tracker_api/resources/shared/has_id.rb +0 -18
  72. data/test/vcr/cassettes/get_story_with_owners.json +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a4ea13ce0c74d3e644ebc7425d5100931b0e1e3a
4
- data.tar.gz: 4e5e624540678a7a39757ce74b102e3300dbce8d
3
+ metadata.gz: 3fbc8caee5a56cdf59e58fc545d0390f76685dc2
4
+ data.tar.gz: cae843ef9bdc193e763d440db310a79e9fb7fe4a
5
5
  SHA512:
6
- metadata.gz: 9356f13e4620b8a7c420b52a2beba5bbd29ef6a921882df0edc0e612d8b77886869a964a1a723fc6b4bc8d73d0eb9f74a0dafd0737f54b40dec8cc3d9a816abe
7
- data.tar.gz: 199de08b674ddd887ece1a3f8d30e8be802723185554f70fdacc9dc74c3031c1fd3963b4beaa2c44ad7f61fc685e8f2f6d33874c6b24b90b65baa8bfc211e1e8
6
+ metadata.gz: 81a23229a778910df741e89fea770bec3096cadeb85c40dc9f79b587fc8fa1e6617c4e853f7f0722d6eb6037162276d49a6f6132e0501a8dfd0fdd1aa6db2e58
7
+ data.tar.gz: 5be684a973dd96a30c032fd50179790505bab01ba8be8a61d0fb7b9a9bd30ec8a25bb697b8836764f116e7c0070f130d21d69f548e7e80c67ea171c19e4da36c
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ spec/reports
17
17
  test/tmp
18
18
  test/version_tmp
19
19
  tmp
20
+ .byebug_history
data/README.md CHANGED
@@ -53,7 +53,7 @@ story.activity # Get
53
53
 
54
54
  story.name = 'Save the Ewoks' # Update a single story attribute
55
55
  story.attributes = { name: 'Save the Ewoks', description: '...' } # Update multiple story attributes
56
- story.labels << TrackerApi::Resources::Label.new(name: 'Endor') # Add a new label to an existing story
56
+ story.add_label('Endor') # Add a new label to an existing story
57
57
  story.save # Save changes made to a story
58
58
 
59
59
  story = TrackerApi::Resources::Story.new( client: client,
@@ -1,17 +1,18 @@
1
1
  require 'tracker_api/version'
2
2
 
3
3
  # dependencies
4
- require 'virtus'
5
4
  require 'faraday'
6
5
  require 'faraday_middleware'
6
+
7
7
  if defined?(ActiveSupport)
8
8
  require 'active_support/core_ext/object/blank'
9
9
  else
10
10
  require 'core_ext/object/blank'
11
11
  end
12
12
  require 'equalizer'
13
- require 'representable/json'
13
+
14
14
  require 'multi_json'
15
+ require 'representable/json'
15
16
 
16
17
  # stdlib
17
18
  require 'addressable/uri'
@@ -51,7 +52,8 @@ module TrackerApi
51
52
 
52
53
  module Resources
53
54
  module Shared
54
- autoload :HasId, 'tracker_api/resources/shared/has_id'
55
+ autoload :Base, 'tracker_api/resources/shared/base'
56
+ autoload :Collection, 'tracker_api/resources/shared/collection'
55
57
  end
56
58
  autoload :Activity, 'tracker_api/resources/activity'
57
59
  autoload :Account, 'tracker_api/resources/account'
@@ -138,9 +138,19 @@ module TrackerApi
138
138
  # Get information about a client story without knowing what project the story belongs to
139
139
  #
140
140
  # @param [String] story_id
141
+ # @param [Hash] params
141
142
  # @return [TrackerApi::Resources::Story]
142
- def story(story_id)
143
- Endpoints::Story.new(self).get_story(story_id)
143
+ def story(story_id, params={})
144
+ Endpoints::Story.new(self).get_story(story_id, params)
145
+ end
146
+
147
+ # Get information about an epic without knowing what project the epic belongs to
148
+ #
149
+ # @param [String] epic_id
150
+ # @param [Hash] params
151
+ # @return [TrackerApi::Resources::Epic]
152
+ def epic(epic_id, params={})
153
+ Endpoints::Epic.new(self).get_epic(epic_id, params)
144
154
  end
145
155
 
146
156
  # Get notifications for the authenticated person
@@ -13,6 +13,12 @@ module TrackerApi
13
13
  Resources::Epic.new({ client: client, project_id: project_id }.merge(data))
14
14
  end
15
15
 
16
+ def get_epic(epic_id, params={})
17
+ data = client.get("/epics/#{epic_id}", params: params).body
18
+
19
+ Resources::Epic.new({ client: client }.merge(data))
20
+ end
21
+
16
22
  def create(project_id, params={})
17
23
  data = client.post("/projects/#{project_id}/epics", params: params).body
18
24
 
@@ -25,6 +31,8 @@ module TrackerApi
25
31
  data = client.put("/projects/#{epic.project_id}/epics/#{epic.id}", params: params).body
26
32
 
27
33
  epic.attributes = data
34
+ epic.clean!
35
+ epic
28
36
  end
29
37
  end
30
38
  end
@@ -31,6 +31,8 @@ module TrackerApi
31
31
  data = client.put("/projects/#{story.project_id}/stories/#{story.id}", params: params).body
32
32
 
33
33
  story.attributes = data
34
+ story.clean!
35
+ story
34
36
  end
35
37
  end
36
38
  end
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Account
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :created_at, DateTime
7
7
  attribute :status, String
@@ -11,8 +11,8 @@ module TrackerApi
11
11
  attribute :project_version, Integer
12
12
  attribute :message, String
13
13
  attribute :highlight, String
14
- attribute :changes, Array[Change]
15
- attribute :primary_resources, Array[PrimaryResource]
14
+ attribute :changes, Shared::Collection[Change]
15
+ attribute :primary_resources, Shared::Collection[PrimaryResource]
16
16
  attribute :project, Project
17
17
  attribute :performed_by, Person
18
18
  attribute :occurred_at, DateTime
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Change
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :change_type, String
7
7
  attribute :kind, String
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Comment
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :story_id, Integer
7
7
  attribute :epic_id, Integer
@@ -9,11 +9,11 @@ module TrackerApi
9
9
  attribute :person_id, Integer
10
10
  attribute :created_at, DateTime
11
11
  attribute :updated_at, DateTime
12
- attribute :file_attachment_ids, Array[Integer]
13
- attribute :google_attachment_ids, Array[Integer]
12
+ attribute :file_attachment_ids, Shared::Collection[Integer]
13
+ attribute :google_attachment_ids, Shared::Collection[Integer]
14
14
  attribute :commit_identifier, String
15
15
  attribute :commit_type, String
16
16
  attribute :kind, String
17
17
  end
18
18
  end
19
- end
19
+ end
@@ -1,16 +1,16 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Epic
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :client
7
7
 
8
- attribute :comment_ids, Array[Integer]
9
- attribute :comments, Array[Comment]
8
+ attribute :comment_ids, Shared::Collection[Integer]
9
+ attribute :comments, Shared::Collection[Comment]
10
10
  attribute :created_at, DateTime
11
11
  attribute :description, String
12
- attribute :follower_ids, Array[Integer]
13
- attribute :followers, Array[Person]
12
+ attribute :follower_ids, Shared::Collection[Integer]
13
+ attribute :followers, Shared::Collection[Person]
14
14
  attribute :kind, String
15
15
  attribute :label, Label
16
16
  attribute :label_id, Integer
@@ -13,8 +13,8 @@ module TrackerApi
13
13
  attribute :planned, Boolean
14
14
  attribute :project_id, Integer
15
15
  attribute :start, DateTime
16
- attribute :stories, [Story]
17
- attribute :story_ids, [Integer]
16
+ attribute :stories, Shared::Collection[Story]
17
+ attribute :story_ids, Shared::Collection[Integer]
18
18
  attribute :team_strength, Float
19
19
 
20
20
  def stories=(data)
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Label
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :created_at, DateTime
7
7
  attribute :kind, String
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Me
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :name, String
7
7
  attribute :initials, String
@@ -9,9 +9,9 @@ module TrackerApi
9
9
  attribute :time_zone, TimeZone
10
10
  attribute :api_token, String
11
11
  attribute :has_google_identity, Boolean
12
- attribute :project_ids, Array[Integer]
13
- attribute :projects, [MembershipSummary]
14
- attribute :workspace_ids, Array[Integer]
12
+ attribute :project_ids, Shared::Collection[Integer]
13
+ attribute :projects, Shared::Collection[MembershipSummary]
14
+ attribute :workspace_ids, Shared::Collection[Integer]
15
15
  attribute :email, String
16
16
  attribute :receives_in_app_notifications, Boolean
17
17
  attribute :kind, String
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class MembershipSummary
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :kind, String
7
7
  attribute :last_viewed_at, DateTime
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Notification
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :client
7
7
 
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Person
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :kind, String
7
7
  attribute :name, String
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class PrimaryResource
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :kind, String
7
7
  attribute :name, String
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Project
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :client
7
7
 
@@ -17,14 +17,14 @@ module TrackerApi
17
17
  attribute :enable_incoming_emails, Boolean
18
18
  attribute :enable_planned_mode, Boolean
19
19
  attribute :enable_tasks, Boolean
20
- attribute :epic_ids, Array[Integer]
21
- attribute :epics, Array[Epic]
20
+ attribute :epic_ids, Shared::Collection[Integer]
21
+ attribute :epics, Shared::Collection[Epic]
22
22
  attribute :has_google_domain, Boolean
23
23
  attribute :initial_velocity, Integer
24
24
  attribute :iteration_length, Integer
25
25
  attribute :kind, String
26
- attribute :label_ids, Array[Integer]
27
- attribute :labels, Array[Label]
26
+ attribute :label_ids, Shared::Collection[Integer]
27
+ attribute :labels, Shared::Collection[Label]
28
28
  attribute :name, String
29
29
  attribute :number_of_done_iterations_to_show, Integer
30
30
  attribute :point_scale, String
@@ -54,7 +54,7 @@ module TrackerApi
54
54
  # @param [Hash] params
55
55
  # @return [Array[Label]] labels of this project
56
56
  def labels(params = {})
57
- if @labels && @labels.any?
57
+ if @labels && @labels.present?
58
58
  @labels
59
59
  else
60
60
  @labels = Endpoints::Labels.new(client).get(id, params)
@@ -66,10 +66,11 @@ module TrackerApi
66
66
  # @param [Hash] params
67
67
  # @return [Array[Epic]] epics associated with this project
68
68
  def epics(params={})
69
- raise ArgumentError, 'Expected @epics to be an Array' unless @epics.is_a? Array
70
- return @epics unless @epics.empty?
71
-
72
- @epics = Endpoints::Epics.new(client).get(id, params)
69
+ if @epics && @epics.present?
70
+ @epics
71
+ else
72
+ @epics = Endpoints::Epics.new(client).get(id, params)
73
+ end
73
74
  end
74
75
 
75
76
  # Provides a list of all the iterations in the project.
@@ -1,7 +1,7 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class ProjectMembership
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :person_id, Integer
7
7
  attribute :project_id, Integer
@@ -0,0 +1,30 @@
1
+ require 'virtus'
2
+ if Virtus::Attribute::NullifyBlank.method_defined?(:coerce)
3
+ require 'virtus/attribute/nullify_blank'
4
+ else
5
+ raise """
6
+ WARNING: The above monkey patch can't be applied as expected.
7
+ See discussion here: https://github.com/dashofcode/tracker_api/commit/27599e7e2169776c32bbff8c972a31b930452879
8
+ """
9
+ end
10
+ require 'virtus/dirty_attribute'
11
+
12
+ module TrackerApi
13
+ module Resources
14
+ module Shared
15
+ module Base
16
+ def self.included(base)
17
+ base.class_eval do
18
+ include Virtus.model(nullify_blank: true)
19
+ include Virtus::DirtyAttribute
20
+ include Virtus::DirtyAttribute::InitiallyClean
21
+
22
+ include Equalizer.new(:id)
23
+
24
+ attribute :id, Integer
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module TrackerApi
2
+ module Resources
3
+ module Shared
4
+ class Collection < Array
5
+ def <<(item)
6
+ warn """
7
+ WARNING: Direct mutation of an attribute value skips coercion
8
+ and dirty tracking. Please use direct assignment or the
9
+ specialized add_* methods to get expected behavior.
10
+ https://github.com/solnic/virtus#important-note-about-member-coercions
11
+ """
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,37 +1,37 @@
1
1
  module TrackerApi
2
2
  module Resources
3
3
  class Story
4
- include Shared::HasId
4
+ include Shared::Base
5
5
 
6
6
  attribute :client
7
7
 
8
8
  attribute :accepted_at, DateTime
9
- attribute :comment_ids, Array[Integer]
10
- attribute :comments, Array[Comment], :default => []
9
+ attribute :comment_ids, Shared::Collection[Integer]
10
+ attribute :comments, Shared::Collection[Comment]
11
11
  attribute :created_at, DateTime
12
12
  attribute :current_state, String # (accepted, delivered, finished, started, rejected, planned, unstarted, unscheduled)
13
13
  attribute :deadline, DateTime
14
14
  attribute :description, String
15
15
  attribute :estimate, Float
16
16
  attribute :external_id, String
17
- attribute :follower_ids, Array[Integer]
18
- attribute :followers, Array[Person]
17
+ attribute :follower_ids, Shared::Collection[Integer]
18
+ attribute :followers, Shared::Collection[Person]
19
19
  attribute :integration_id, Integer
20
20
  attribute :kind, String
21
- attribute :label_ids, Array[Integer]
22
- attribute :labels, Array[Label], :default => []
21
+ attribute :label_ids, Shared::Collection[Integer]
22
+ attribute :labels, Shared::Collection[Label]
23
23
  attribute :name, String
24
24
  attribute :owned_by_id, Integer # deprecated!
25
25
  attribute :owned_by, Person
26
- attribute :owner_ids, Array[Integer]
27
- attribute :owners, Array[Person], :default => []
26
+ attribute :owner_ids, Shared::Collection[Integer]
27
+ attribute :owners, Shared::Collection[Person]
28
28
  attribute :planned_iteration_number, Integer
29
29
  attribute :project_id, Integer
30
30
  attribute :requested_by, Person
31
31
  attribute :requested_by_id, Integer
32
32
  attribute :story_type, String # (feature, bug, chore, release)
33
- attribute :task_ids, Array[Integer]
34
- attribute :tasks, Array[Task], :default => []
33
+ attribute :task_ids, Shared::Collection[Integer]
34
+ attribute :tasks, Shared::Collection[Task]
35
35
  attribute :updated_at, DateTime
36
36
  attribute :url, String
37
37
 
@@ -39,7 +39,7 @@ module TrackerApi
39
39
  class UpdateRepresenter < Representable::Decorator
40
40
  include Representable::JSON
41
41
 
42
- property :follower_ids
42
+ property :follower_ids, if: ->(options) { !options[:input].blank? }
43
43
  property :name
44
44
  property :description
45
45
  property :story_type
@@ -48,7 +48,7 @@ module TrackerApi
48
48
  property :accepted_at
49
49
  property :deadline
50
50
  property :requested_by_id
51
- property :owner_ids
51
+ property :owner_ids, if: ->(options) { !options[:input].blank? }
52
52
  collection :labels, class: Label, decorator: Label::UpdateRepresenter, render_empty: true
53
53
  property :integration_id
54
54
  property :external_id
@@ -59,6 +59,20 @@ module TrackerApi
59
59
  @label_list ||= labels.collect(&:name).join(',')
60
60
  end
61
61
 
62
+ # Adds a new label to the story.
63
+ #
64
+ # @param [Label|Hash|String] label
65
+ def add_label(label)
66
+ new_label = if label.kind_of?(String)
67
+ Label.new(name: label)
68
+ else
69
+ label
70
+ end
71
+
72
+ # Use attribute writer to get coercion and dirty tracking.
73
+ self.labels = @labels.dup.push(new_label)
74
+ end
75
+
62
76
  # Provides a list of all the activity performed on the story.
63
77
  #
64
78
  # @param [Hash] params
@@ -72,7 +86,7 @@ module TrackerApi
72
86
  # @param [Hash] params
73
87
  # @return [Array[Comment]]
74
88
  def comments(params = {})
75
- if params.blank? && @comments.any?
89
+ if params.blank? && @comments.present?
76
90
  @comments
77
91
  else
78
92
  @comments = Endpoints::Comments.new(client).get(project_id, id, params)
@@ -84,7 +98,7 @@ module TrackerApi
84
98
  # @param [Hash] params
85
99
  # @return [Array[Task]]
86
100
  def tasks(params = {})
87
- if params.blank? && @tasks.any?
101
+ if params.blank? && @tasks.present?
88
102
  @tasks
89
103
  else
90
104
  @tasks = Endpoints::Tasks.new(client).get(project_id, id, params)
@@ -96,7 +110,7 @@ module TrackerApi
96
110
  # @param [Hash] params
97
111
  # @return [Array[Person]]
98
112
  def owners(params = {})
99
- if params.blank? && @owners.any?
113
+ if params.blank? && @owners.present?
100
114
  @owners
101
115
  else
102
116
  @owners = Endpoints::StoryOwners.new(client).get(project_id, id, params)
@@ -113,7 +127,7 @@ module TrackerApi
113
127
  def save
114
128
  raise ArgumentError, 'Can not update a story with an unknown project_id.' if project_id.nil?
115
129
 
116
- Endpoints::Story.new(client).update(self, UpdateRepresenter.new(self))
130
+ Endpoints::Story.new(client).update(self, UpdateRepresenter.new(Story.new(self.dirty_attributes)))
117
131
  end
118
132
  end
119
133
  end