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, attachments: []) ⇒ 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.
-
#created_at ⇒ Time?
GitHub’s created_at timestamp; settable on unpersisted comments for use with
Issue.import. -
#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!(user: nil) ⇒ 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.
30 31 32 |
# File 'lib/plan_my_stuff/comment.rb', line 30 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, attachments: []) ⇒ PlanMyStuff::Comment
Creates a comment on a GitHub issue with PMS metadata and a visible header.
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 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/plan_my_stuff/comment.rb', line 52 def create!( issue:, body:, user: nil, visibility: :public, custom_fields: {}, skip_responded: false, issue_body: false, waiting_on_reply: false, attachments: [] ) raise(PlanMyStuff::LockedIssueError, "Issue ##{issue.number} is locked") if issue.locked? resolved_user = PlanMyStuff::UserResolver.resolve(user) visibility = resolve_visibility(visibility, resolved_user) = PlanMyStuff::CommentMetadata.build( user: resolved_user, visibility: visibility.to_s, custom_fields: custom_fields, issue_body: issue_body, ) .validate_custom_fields! . = PlanMyStuff::AttachmentUploader.upload_all!( repo: issue.repo, issue_number: issue.number, files: , ) header = build_header(resolved_user) full_body = "#{header}\n\n#{body}" serialized_body = PlanMyStuff::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.
130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/plan_my_stuff/comment.rb', line 130 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.
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/plan_my_stuff/comment.rb', line 151 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.
110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/plan_my_stuff/comment.rb', line 110 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.
354 355 356 357 |
# File 'lib/plan_my_stuff/comment.rb', line 354 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.
18 |
# File 'lib/plan_my_stuff/comment.rb', line 18 attribute :body, :string |
#body_without_header ⇒ String
Returns the comment body with the header stripped.
404 405 406 |
# File 'lib/plan_my_stuff/comment.rb', line 404 def body_without_header (body || '').sub(/\A###\s.+?:\s*\n\n/, '') end |
#created_at ⇒ Time?
Returns GitHub’s created_at timestamp; settable on unpersisted comments for use with Issue.import.
24 |
# File 'lib/plan_my_stuff/comment.rb', line 24 attribute :created_at |
#header ⇒ String?
Extracts the ‘### Name at timestamp:` header line from the comment body.
395 396 397 398 |
# File 'lib/plan_my_stuff/comment.rb', line 395 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.
345 346 347 |
# File 'lib/plan_my_stuff/comment.rb', line 345 def html_url safe_read_field(github_response, :html_url) end |
#id ⇒ Integer?
Returns GitHub comment ID.
12 |
# File 'lib/plan_my_stuff/comment.rb', line 12 attribute :id, :big_integer |
#issue ⇒ PlanMyStuff::Issue?
Returns parent issue.
20 |
# File 'lib/plan_my_stuff/comment.rb', line 20 attribute :issue |
#metadata ⇒ PlanMyStuff::CommentMetadata
Returns parsed metadata (empty when no PMS metadata present).
16 |
# File 'lib/plan_my_stuff/comment.rb', line 16 attribute :metadata, default: -> { PlanMyStuff::CommentMetadata.new } |
#pms_comment? ⇒ Boolean
360 361 362 |
# File 'lib/plan_my_stuff/comment.rb', line 360 def pms_comment? .schema_version.present? end |
#raw_body ⇒ String?
Returns full body as stored on GitHub.
14 |
# File 'lib/plan_my_stuff/comment.rb', line 14 attribute :raw_body, :string |
#reload ⇒ self
Re-fetches this comment from GitHub and updates all local attributes.
335 336 337 338 339 |
# File 'lib/plan_my_stuff/comment.rb', line 335 def reload fresh = self.class.find(id, issue: issue) hydrate_from_comment(fresh) self end |
#save!(user: nil) ⇒ self
Persists the comment. Creates if new, updates if persisted.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/plan_my_stuff/comment.rb', line 279 def save!(user: nil) if new_record? created = self.class.create!( issue: issue, body: body, user: user || .created_by, visibility: visibility || :public, custom_fields: .custom_fields.to_h, issue_body: .issue_body, waiting_on_reply: waiting_on_reply, attachments: ., ) hydrate_from_comment(created) else update_attrs = { user: user, body: body } update_attrs[:visibility] = visibility if visibility_changed? update!(**update_attrs) 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.
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/plan_my_stuff/comment.rb', line 308 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) resolved_user = PlanMyStuff::UserResolver.resolve(user) new_visibility = self.class.__send__(:resolve_visibility, attrs[:visibility], resolved_user).to_s [:visibility] = new_visibility end serialized = PlanMyStuff::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.
22 |
# File 'lib/plan_my_stuff/comment.rb', line 22 attribute :updated_at |
#visibility ⇒ Symbol?
Returns the comment visibility as a symbol. Uses the locally set value if present, otherwise falls back to metadata.
26 |
# File 'lib/plan_my_stuff/comment.rb', line 26 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.
381 382 383 384 385 386 387 388 389 |
# File 'lib/plan_my_stuff/comment.rb', line 381 def visible_to?(user) resolved = PlanMyStuff::UserResolver.resolve(user) if pms_comment? issue.visible_to?(resolved) && (visibility != :internal || PlanMyStuff::UserResolver.support?(resolved)) else PlanMyStuff::UserResolver.support?(resolved) end end |