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, ) issue.mark_responded!(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.
331 332 333 334 |
# File 'lib/plan_my_stuff/comment.rb', line 331 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.
381 382 383 |
# File 'lib/plan_my_stuff/comment.rb', line 381 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.
372 373 374 375 |
# File 'lib/plan_my_stuff/comment.rb', line 372 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.
322 323 324 |
# File 'lib/plan_my_stuff/comment.rb', line 322 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
337 338 339 |
# File 'lib/plan_my_stuff/comment.rb', line 337 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.
312 313 314 315 316 |
# File 'lib/plan_my_stuff/comment.rb', line 312 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.
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/plan_my_stuff/comment.rb', line 256 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.
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/plan_my_stuff/comment.rb', line 285 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.
358 359 360 361 362 363 364 365 366 |
# File 'lib/plan_my_stuff/comment.rb', line 358 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 |