Class: PlanMyStuff::Comment
- Inherits:
-
ApplicationRecord
- Object
- ApplicationRecord
- PlanMyStuff::Comment
- Defined in:
- lib/plan_my_stuff/comment.rb
Overview
Wraps a GitHub comment with parsed PMS metadata. Class methods provide the public API for CRUD operations.
Follows an ActiveRecord-style pattern:
-
‘Comment.new(**attrs)` creates an unpersisted instance
-
‘Comment.create!` / `Comment.list` return persisted instances
-
‘comment.save!` / `comment.update!` / `comment.reload` for persistence
Instance Attribute Summary collapse
-
#waiting_on_reply ⇒ Boolean?
Transient flag used by the new-comment form to request waiting-on-user state when a support user posts.
Attributes inherited from ApplicationRecord
Class Method Summary collapse
-
.create!(issue:, body:, user: nil, visibility: :public, custom_fields: {}, skip_responded: false, issue_body: false, waiting_on_reply: false) ⇒ PlanMyStuff::Comment
Creates a comment on a GitHub issue with PMS metadata and a visible header.
-
.find(id, issue:) ⇒ PlanMyStuff::Comment
Finds a single comment by ID, given its parent issue.
-
.list(issue:, pms_only: false) ⇒ Array<PlanMyStuff::Comment>
Lists comments on a GitHub issue, optionally filtering to PMS-only comments.
-
.update!(id:, repo:, body:) ⇒ Object
Updates an existing GitHub comment body.
Instance Method Summary collapse
-
#as_json(options = {}) ⇒ Hash
Serializes the comment to a JSON-safe hash, excluding the back-reference to the parent issue to prevent recursive serialization cycles.
-
#body ⇒ String?
Comment body without the metadata HTML comment.
-
#body_without_header ⇒ String
Returns the comment body with the header stripped.
-
#header ⇒ String?
Extracts the ‘### Name at timestamp:` header line from the comment body.
-
#html_url ⇒ String?
GitHub web URL for this comment, for escape-hatch “View on GitHub” links.
-
#id ⇒ Integer?
GitHub comment ID.
-
#issue ⇒ PlanMyStuff::Issue?
Parent issue.
-
#metadata ⇒ PlanMyStuff::CommentMetadata
Parsed metadata (empty when no PMS metadata present).
- #pms_comment? ⇒ Boolean
-
#raw_body ⇒ String?
Full body as stored on GitHub.
-
#reload ⇒ self
Re-fetches this comment from GitHub and updates all local attributes.
-
#save! ⇒ self
Persists the comment.
-
#update!(user: nil, **attrs) ⇒ self
Updates this comment on GitHub.
-
#updated_at ⇒ Time?
GitHub’s updated_at timestamp.
-
#visibility ⇒ Symbol?
Returns the comment visibility as a symbol.
-
#visible_to?(user) ⇒ Boolean
Checks if the comment is visible to the given user.
Methods inherited from ApplicationRecord
#destroyed?, #initialize, #new_record?, #persisted?, read_field
Constructor Details
This class inherits a constructor from PlanMyStuff::ApplicationRecord
Instance Attribute Details
#waiting_on_reply ⇒ Boolean?
Returns transient flag used by the new-comment form to request waiting-on-user state when a support user posts. Not persisted.
29 30 31 |
# File 'lib/plan_my_stuff/comment.rb', line 29 def waiting_on_reply @waiting_on_reply end |
Class Method Details
.create!(issue:, body:, user: nil, visibility: :public, custom_fields: {}, skip_responded: false, issue_body: false, waiting_on_reply: false) ⇒ PlanMyStuff::Comment
Creates a comment on a GitHub issue with PMS metadata and a visible header.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/plan_my_stuff/comment.rb', line 46 def create!( issue:, body:, user: nil, visibility: :public, custom_fields: {}, skip_responded: false, issue_body: false, waiting_on_reply: false ) raise(PlanMyStuff::LockedIssueError, "Issue ##{issue.number} is locked") if issue.locked? resolved_user = UserResolver.resolve(user) visibility = resolve_visibility(visibility, resolved_user) = CommentMetadata.build( user: resolved_user, visibility: visibility.to_s, custom_fields: custom_fields, issue_body: issue_body, ) .validate_custom_fields! header = build_header(resolved_user) full_body = "#{header}\n\n#{body}" serialized_body = MetadataParser.serialize(.to_h, full_body) client = PlanMyStuff.client result = client.rest(:add_comment, issue.repo, issue.number, serialized_body) store_etag_to_cache( client, issue.repo, read_field(result, :id), result, cache_writer: :write_comment, ) mark_issue_responded_if_first_support_comment(issue, resolved_user) unless skip_responded comment = build(result, issue: issue) PlanMyStuff::Notifications.instrument('comment.created', comment, user: resolved_user) apply_waiting_state_transitions(issue, resolved_user, waiting_on_reply, comment) comment end |
.find(id, issue:) ⇒ PlanMyStuff::Comment
Finds a single comment by ID, given its parent issue.
118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/plan_my_stuff/comment.rb', line 118 def find(id, issue:) client = PlanMyStuff.client github_comment = fetch_with_etag_cache( client, issue.repo, id, rest_method: :issue_comment, cache_reader: :read_comment, cache_writer: :write_comment, ) build(github_comment, issue: issue) end |
.list(issue:, pms_only: false) ⇒ Array<PlanMyStuff::Comment>
Lists comments on a GitHub issue, optionally filtering to PMS-only comments.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/plan_my_stuff/comment.rb', line 139 def list(issue:, pms_only: false) client = PlanMyStuff.client params = { issue_number: issue.number } cached = PlanMyStuff::Cache.read_list(:comment, issue.repo, params) = cached ? { headers: { 'If-None-Match' => cached[:etag] } } : {} github_comments = client.rest(:issue_comments, issue.repo, issue.number, **) comments = if cached && not_modified?(client) cached[:body].map { |gc| build(gc, issue: issue) } else store_list_etag_to_cache(client, :comment, issue.repo, params, github_comments) github_comments.map { |gc| build(gc, issue: issue) } end pms_only ? comments.select(&:pms_comment?) : comments end |
.update!(id:, repo:, body:) ⇒ Object
Updates an existing GitHub comment body.
98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/plan_my_stuff/comment.rb', line 98 def update!(id:, repo:, body:) client = PlanMyStuff.client result = client.rest(:update_comment, repo, id, body) store_etag_to_cache( client, repo, id, result, cache_writer: :write_comment, ) result end |
Instance Method Details
#as_json(options = {}) ⇒ Hash
Serializes the comment to a JSON-safe hash, excluding the back-reference to the parent issue to prevent recursive serialization cycles.
340 341 342 343 |
# File 'lib/plan_my_stuff/comment.rb', line 340 def as_json( = {}) merged_except = Array.wrap([:except]) + ['issue'] super(.merge(except: merged_except)).merge('issue_number' => issue&.number) end |
#body ⇒ String?
Returns comment body without the metadata HTML comment.
19 |
# File 'lib/plan_my_stuff/comment.rb', line 19 attribute :body, :string |
#body_without_header ⇒ String
Returns the comment body with the header stripped.
391 392 393 |
# File 'lib/plan_my_stuff/comment.rb', line 391 def body_without_header (body || '').sub(/\A###\s.+?:\s*\n\n/, '') end |
#header ⇒ String?
Extracts the ‘### Name at timestamp:` header line from the comment body.
382 383 384 385 |
# File 'lib/plan_my_stuff/comment.rb', line 382 def header match = (body || '').match(/\A(###\s.+?:\s*)\n/) match&.captures&.first&.strip end |
#html_url ⇒ String?
GitHub web URL for this comment, for escape-hatch “View on GitHub” links.
331 332 333 |
# File 'lib/plan_my_stuff/comment.rb', line 331 def html_url safe_read_field(github_response, :html_url) end |
#id ⇒ Integer?
Returns GitHub comment ID.
13 |
# File 'lib/plan_my_stuff/comment.rb', line 13 attribute :id, :big_integer |
#issue ⇒ PlanMyStuff::Issue?
Returns parent issue.
21 |
# File 'lib/plan_my_stuff/comment.rb', line 21 attribute :issue |
#metadata ⇒ PlanMyStuff::CommentMetadata
Returns parsed metadata (empty when no PMS metadata present).
17 |
# File 'lib/plan_my_stuff/comment.rb', line 17 attribute :metadata, default: -> { PlanMyStuff::CommentMetadata.new } |
#pms_comment? ⇒ Boolean
346 347 348 |
# File 'lib/plan_my_stuff/comment.rb', line 346 def pms_comment? .schema_version.present? end |
#raw_body ⇒ String?
Returns full body as stored on GitHub.
15 |
# File 'lib/plan_my_stuff/comment.rb', line 15 attribute :raw_body, :string |
#reload ⇒ self
Re-fetches this comment from GitHub and updates all local attributes.
321 322 323 324 325 |
# File 'lib/plan_my_stuff/comment.rb', line 321 def reload fresh = self.class.find(id, issue: issue) hydrate_from_comment(fresh) self end |
#save! ⇒ self
Persists the comment. Creates if new, updates if persisted.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/plan_my_stuff/comment.rb', line 271 def save! if new_record? created = self.class.create!( issue: issue, body: body, visibility: visibility || :public, ) hydrate_from_comment(created) else update!(body: body) end self end |
#update!(user: nil, **attrs) ⇒ self
Updates this comment on GitHub. Raises StaleObjectError if the remote has been modified since this instance was loaded.
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/plan_my_stuff/comment.rb', line 295 def update!(user: nil, **attrs) raise_if_stale! captured_changes = capture_update_changes(attrs) new_body = attrs[:body] || body new_body = preserve_header(new_body) if attrs.key?(:body) = .to_h if attrs.key?(:visibility) new_visibility = attrs[:visibility].to_s [:visibility] = new_visibility end serialized = MetadataParser.serialize(, new_body) self.class.update!(id: id, repo: issue.repo, body: serialized) reload PlanMyStuff::Notifications.instrument('comment.updated', self, user: user, changes: captured_changes) self end |
#updated_at ⇒ Time?
Returns GitHub’s updated_at timestamp.
23 |
# File 'lib/plan_my_stuff/comment.rb', line 23 attribute :updated_at |
#visibility ⇒ Symbol?
Returns the comment visibility as a symbol. Uses the locally set value if present, otherwise falls back to metadata.
25 |
# File 'lib/plan_my_stuff/comment.rb', line 25 attribute :visibility |
#visible_to?(user) ⇒ Boolean
Checks if the comment is visible to the given user. Public PMS comments: visible to everyone the parent issue is visible to. Internal PMS comments: visible only to support users. Non-PMS comments: visible only to support users.
368 369 370 371 372 373 374 375 376 |
# File 'lib/plan_my_stuff/comment.rb', line 368 def visible_to?(user) resolved = PMS::UserResolver.resolve(user) if pms_comment? issue.visible_to?(resolved) && (visibility != :internal || PMS::UserResolver.support?(resolved)) else PMS::UserResolver.support?(resolved) end end |