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.
-
#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) ⇒ PlanMyStuff::Comment
Creates a comment on a GitHub issue with PMS metadata and a visible header.
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 89 90 |
# File 'lib/plan_my_stuff/comment.rb', line 48 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 = 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! 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.
120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/plan_my_stuff/comment.rb', line 120 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.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/plan_my_stuff/comment.rb', line 141 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.
100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/plan_my_stuff/comment.rb', line 100 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.
343 344 345 346 |
# File 'lib/plan_my_stuff/comment.rb', line 343 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.
393 394 395 |
# File 'lib/plan_my_stuff/comment.rb', line 393 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.
384 385 386 387 |
# File 'lib/plan_my_stuff/comment.rb', line 384 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.
334 335 336 |
# File 'lib/plan_my_stuff/comment.rb', line 334 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
349 350 351 |
# File 'lib/plan_my_stuff/comment.rb', line 349 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.
324 325 326 327 328 |
# File 'lib/plan_my_stuff/comment.rb', line 324 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.
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/plan_my_stuff/comment.rb', line 269 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, ) 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.
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/plan_my_stuff/comment.rb', line 297 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.
370 371 372 373 374 375 376 377 378 |
# File 'lib/plan_my_stuff/comment.rb', line 370 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 |