Class: Note
- Inherits:
-
ApplicationRecord
show all
- Extended by:
- ActiveModel::Naming, Gitlab::Utils::Override
- Includes:
- AfterCommitQueue, Awardable, CacheMarkdownField, EachBatch, Editable, FasterCacheKeys, FromUnion, Gitlab::SQL::Pattern, Gitlab::Utils::StrongMemoize, Import::HasImportSource, Importable, Mentionable, Notes::ActiveRecord, Notes::Discussion, Participable, Redactable, ResolvableNote, Sortable, Spammable, ThrottledTouch
- Defined in:
- app/models/note.rb
Overview
A note on the root of an issue, merge request, commit, or snippet.
A note of this type is never resolvable.
Constant Summary
collapse
- TYPES_RESTRICTED_BY_PROJECT_ABILITY =
{
branch: :download_code
}.freeze
- TYPES_RESTRICTED_BY_GROUP_ABILITY =
{
contact: :read_crm_contact
}.freeze
- NON_DIFF_NOTE_TYPES =
['Note', 'DiscussionNote', nil].freeze
ThrottledTouch::TOUCH_INTERVAL
Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_TERM
ResolvableNote::RESOLVABLE_TYPES
CacheMarkdownField::INVALIDATED_BY
Constants included
from Redactable
Redactable::UNSUBSCRIBE_PATTERN
Import::HasImportSource::IMPORT_SOURCES
ApplicationRecord::MAX_PLUCK
HasCheckConstraints::NOT_NULL_CHECK_PATTERN
ResetOnColumnErrors::MAX_RESET_PERIOD
Instance Attribute Summary collapse
#skip_markdown_cache_validation
Attributes included from Importable
#importing, #user_contributions
Class Method Summary
collapse
Instance Method Summary
collapse
extended, extensions, included, method_added, override, prepended, queue_verification, verify!
Methods included from Spammable
#allow_possible_spam?, #check_for_spam, #clear_spam_flags!, #invalidate_if_spam, #needs_recaptcha!, #recaptcha_error!, #render_recaptcha?, #spam, #spam!, #spam_description, #spam_title, #spammable_attribute_changed?, #spammable_entity_type, #spammable_text, #submittable_as_spam?, #submittable_as_spam_by?, #supports_recaptcha?, #unrecoverable_spam_error!
split_query_to_search_terms
Methods included from Editable
#last_edited_by
#potentially_resolvable?, #resolvable?, #resolve!, #resolve_without_save, #resolved?, #to_be_resolved?, #unresolve!, #unresolve_without_save
#run_after_commit, #run_after_commit_or_now
#attribute_invalidated?, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #mentionable_attributes_changed?, #refresh_markdown_cache!, #rendered_field_content, #store_mentions!, #store_mentions?, #store_mentions_after_commit?, #updated_cached_html_for
#cache_key
#imported?
Methods included from Awardable
#awarded_emoji?, #downvotes, #grouped_awards, #upvotes, #user_authored?, #user_can_award?
#all_references, #create_cross_references!, #create_new_cross_references!, #directly_addressed_users, #extractors, #gfm_reference, #local_reference, #matches_cross_reference_regex?, #referenced_group_users, #referenced_groups, #referenced_mentionables, #referenced_project_users, #referenced_projects, #referenced_users
#participant?, #participants, #visible_participants
#derive_discussion_id, #discussion, #discussion=, #discussion_class, #discussion_id, #ensure_discussion_id, #in_reply_to?, #part_of_discussion?, #start_of_discussion?, #to_discussion
#last_edited_by
===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, delete_all_returning, #deleted_from_database?, id_in, id_not_in, iid_in, nullable_column?, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, sharding_keys, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order
#reset_on_union_error, #reset_on_unknown_attribute_error
#serializable_hash
Instance Attribute Details
#commands_changes ⇒ Object
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
|
# File 'app/models/note.rb', line 673
def commands_changes
@commands_changes&.slice(
:due_date,
:label_ids,
:remove_label_ids,
:add_label_ids,
:canonical_issue_id,
:clone_with_notes,
:confidential,
:create_merge_request,
:add_contacts,
:remove_contacts,
:assignee_ids,
:milestone_id,
:time_estimate,
:spend_time,
:discussion_locked,
:merge,
:rebase,
:wip_event,
:target_branch,
:reviewer_ids,
:health_status,
:promote_to_epic,
:weight,
:emoji_award,
:todo_event,
:subscription_event,
:state_event,
:title,
:tag_message,
:tag_name
)
end
|
#quick_actions_status ⇒ Object
Attribute used to store the status of quick actions.
60
61
62
|
# File 'app/models/note.rb', line 60
def quick_actions_status
@quick_actions_status
end
|
#redacted_note_html ⇒ Object
Attribute containing rendered and redacted Markdown as generated by Banzai::ObjectRenderer.
48
49
50
|
# File 'app/models/note.rb', line 48
def redacted_note_html
@redacted_note_html
end
|
#skip_keep_around_commits ⇒ Object
Attribute used to determine whether keep_around_commits will be skipped for diff notes.
63
64
65
|
# File 'app/models/note.rb', line 63
def skip_keep_around_commits
@skip_keep_around_commits
end
|
#skip_touch_noteable ⇒ Object
Attribute used to skip updates of ‘updated_at` for the noteable when it could impact database health.
66
67
68
|
# File 'app/models/note.rb', line 66
def skip_touch_noteable
@skip_touch_noteable
end
|
#total_reference_count ⇒ Object
Total of all references as generated by Banzai::ObjectRenderer
51
52
53
|
# File 'app/models/note.rb', line 51
def total_reference_count
@total_reference_count
end
|
#user_visible_reference_count ⇒ Object
Number of user visible references as generated by Banzai::ObjectRenderer
54
55
56
|
# File 'app/models/note.rb', line 54
def user_visible_reference_count
@user_visible_reference_count
end
|
Class Method Details
.cherry_picked_merge_requests(shas) ⇒ Object
313
314
315
|
# File 'app/models/note.rb', line 313
def cherry_picked_merge_requests(shas)
where(noteable_type: 'MergeRequest', commit_id: shas).select(:noteable_id)
end
|
.count_for_collection(ids, type, count_column = 'COUNT(*) as count') ⇒ Object
296
297
298
299
300
|
# File 'app/models/note.rb', line 296
def count_for_collection(ids, type, count_column = 'COUNT(*) as count')
user.select(:noteable_id, count_column)
.group(:noteable_id)
.where(noteable_type: type, noteable_id: ids)
end
|
.grouped_diff_discussions(diff_refs = nil) ⇒ Object
Group diff discussions by line code or file path. It is not needed to group by line code when comment is on an image.
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
|
# File 'app/models/note.rb', line 270
def grouped_diff_discussions(diff_refs = nil)
groups = {}
diff_notes.order_created_at_id_asc.discussions.each do |discussion|
group_key =
if discussion.on_image?
discussion.file_new_path
else
discussion.line_code_in_diffs(diff_refs)
end
if group_key
discussions = groups[group_key] ||= []
discussions << discussion
end
end
groups
end
|
.model_name ⇒ Object
259
260
261
|
# File 'app/models/note.rb', line 259
def model_name
ActiveModel::Name.new(self, nil, 'note')
end
|
.parent_object_field ⇒ Object
263
264
265
|
# File 'app/models/note.rb', line 263
def parent_object_field
:noteable
end
|
.positions ⇒ Object
290
291
292
293
294
|
# File 'app/models/note.rb', line 290
def positions
where.not(position: nil)
.select(:id, :type, :position) .map(&:position)
end
|
.search(query) ⇒ Object
302
303
304
|
# File 'app/models/note.rb', line 302
def search(query)
fuzzy_search(query, [:note])
end
|
.simple_sorts ⇒ Object
309
310
311
|
# File 'app/models/note.rb', line 309
def simple_sorts
super.except('name_asc', 'name_desc')
end
|
.with_web_entity_associations ⇒ Object
317
318
319
|
# File 'app/models/note.rb', line 317
def with_web_entity_associations
preload(:project, :author, :noteable)
end
|
Instance Method Details
#active? ⇒ Boolean
338
339
340
|
# File 'app/models/note.rb', line 338
def active?
true
end
|
#award_emoji? ⇒ Boolean
491
492
493
|
# File 'app/models/note.rb', line 491
def award_emoji?
can_be_award_emoji? && contains_emoji_only?
end
|
#banzai_render_context(field) ⇒ Object
605
606
607
608
609
610
|
# File 'app/models/note.rb', line 605
def banzai_render_context(field)
additional_attributes = { noteable: noteable, system_note: system?, label_url_method: noteable_label_url_method }
additional_attributes[:group] = namespace if namespace.is_a?(Group)
super.merge(additional_attributes)
end
|
#broadcast_noteable_notes_changed ⇒ Object
557
558
559
|
# File 'app/models/note.rb', line 557
def broadcast_noteable_notes_changed
noteable&.broadcast_notes_changed
end
|
#bump_updated_at ⇒ Object
542
543
544
545
546
547
548
549
550
551
552
553
554
555
|
# File 'app/models/note.rb', line 542
def bump_updated_at
attributes_to_update = { updated_at: Time.current }
if updated_by_id.present? && read_attribute(:last_edited_at).blank?
attributes_to_update[:last_edited_at] = updated_at
end
update_columns(attributes_to_update)
end
|
#can_be_award_emoji? ⇒ Boolean
499
500
501
|
# File 'app/models/note.rb', line 499
def can_be_award_emoji?
noteable.is_a?(Awardable) && !part_of_discussion?
end
|
#can_be_discussion_note? ⇒ Boolean
523
524
525
|
# File 'app/models/note.rb', line 523
def can_be_discussion_note?
self.noteable.supports_discussions? && !part_of_discussion? && !system?
end
|
#can_create_todo? ⇒ Boolean
527
528
529
530
|
# File 'app/models/note.rb', line 527
def can_create_todo?
!system? && !for_snippet?
end
|
#check_for_spam? ⇒ Boolean
Override method defined in Spammable Wildcard argument because user: argument is not used
739
740
741
742
743
744
745
|
# File 'app/models/note.rb', line 739
def check_for_spam?(*)
return false if system? || !spammable_attribute_changed? || confidential?
return false if noteable.try(:confidential?) == true || noteable.try(:public?) == false
return false if noteable.try(:group)&.public? == false || project&.public? == false
true
end
|
#commit ⇒ Object
418
419
420
|
# File 'app/models/note.rb', line 418
def commit
@commit ||= project.commit(commit_id) if commit_id.present?
end
|
#confidential?(include_noteable: false) ⇒ Boolean
467
468
469
470
471
|
# File 'app/models/note.rb', line 467
def confidential?(include_noteable: false)
return true if confidential
include_noteable && noteable.try(:confidential?)
end
|
#contains_emoji_only? ⇒ Boolean
#contributor? ⇒ Boolean
450
451
452
|
# File 'app/models/note.rb', line 450
def contributor?
project&.team&.contributor?(self.author_id)
end
|
#diff_note? ⇒ Boolean
rubocop: enable CodeReuse/ServiceClass
334
335
336
|
# File 'app/models/note.rb', line 334
def diff_note?
false
end
|
#editable? ⇒ Boolean
473
474
475
|
# File 'app/models/note.rb', line 473
def editable?
!system?
end
|
#edited? ⇒ Boolean
Since we used ‘updated_at` as `last_edited_at`, it could be touched by transforming / resolving a note. This makes sure it is only marked as edited when the note body is updated.
485
486
487
488
489
|
# File 'app/models/note.rb', line 485
def edited?
return false if read_attribute(:last_edited_at).blank? && updated_by.blank?
super
end
|
#emoji_awardable? ⇒ Boolean
495
496
497
|
# File 'app/models/note.rb', line 495
def emoji_awardable?
!system?
end
|
#exportable_record?(user) ⇒ Boolean
731
732
733
734
735
|
# File 'app/models/note.rb', line 731
def exportable_record?(user)
return true unless system?
readable_by?(user)
end
|
#for_abuse_report? ⇒ Boolean
410
411
412
|
# File 'app/models/note.rb', line 410
def for_abuse_report?
noteable_type == AbuseReport.name
end
|
#for_alert_mangement_alert? ⇒ Boolean
370
371
372
|
# File 'app/models/note.rb', line 370
def for_alert_mangement_alert?
noteable_type == 'AlertManagement::Alert'
end
|
#for_commit? ⇒ Boolean
350
351
352
|
# File 'app/models/note.rb', line 350
def for_commit?
noteable_type == "Commit"
end
|
#for_compliance_violation? ⇒ Boolean
378
379
380
|
# File 'app/models/note.rb', line 378
def for_compliance_violation?
noteable_type == 'ComplianceManagement::Projects::ComplianceViolation'
end
|
#for_design? ⇒ Boolean
402
403
404
|
# File 'app/models/note.rb', line 402
def for_design?
noteable_type == DesignManagement::Design.name
end
|
#for_issuable? ⇒ Boolean
406
407
408
|
# File 'app/models/note.rb', line 406
def for_issuable?
for_issue? || for_merge_request?
end
|
#for_issue? ⇒ Boolean
354
355
356
|
# File 'app/models/note.rb', line 354
def for_issue?
noteable_type == "Issue"
end
|
#for_merge_request? ⇒ Boolean
362
363
364
|
# File 'app/models/note.rb', line 362
def for_merge_request?
noteable_type == "MergeRequest"
end
|
#for_personal_snippet? ⇒ Boolean
386
387
388
|
# File 'app/models/note.rb', line 386
def for_personal_snippet?
noteable.is_a?(PersonalSnippet)
end
|
#for_project_noteable? ⇒ Boolean
394
395
396
|
# File 'app/models/note.rb', line 394
def for_project_noteable?
!(for_personal_snippet? || for_abuse_report? || group_level_issue?)
end
|
#for_project_snippet? ⇒ Boolean
382
383
384
|
# File 'app/models/note.rb', line 382
def for_project_snippet?
noteable.is_a?(ProjectSnippet)
end
|
#for_snippet? ⇒ Boolean
366
367
368
|
# File 'app/models/note.rb', line 366
def for_snippet?
noteable_type == "Snippet"
end
|
#for_vulnerability? ⇒ Boolean
374
375
376
|
# File 'app/models/note.rb', line 374
def for_vulnerability?
noteable_type == "Vulnerability"
end
|
#for_wiki_page? ⇒ Boolean
390
391
392
|
# File 'app/models/note.rb', line 390
def for_wiki_page?
noteable_type == "WikiPage::Meta"
end
|
#for_work_item? ⇒ Boolean
358
359
360
|
# File 'app/models/note.rb', line 358
def for_work_item?
noteable.is_a?(WorkItem)
end
|
#group_level_issue? ⇒ Boolean
398
399
400
|
# File 'app/models/note.rb', line 398
def group_level_issue?
(for_issue? || for_work_item?) && noteable&.project_id.blank?
end
|
#human_max_access ⇒ Object
455
456
457
|
# File 'app/models/note.rb', line 455
def human_max_access
project&.team&.human_max_access(self.author_id)
end
|
#issuable_ability_name ⇒ Object
727
728
729
|
# File 'app/models/note.rb', line 727
def issuable_ability_name
confidential? ? :read_internal_note : :read_note
end
|
#last_edited_at ⇒ Object
We used ‘last_edited_at` as an alias of `updated_at` before. This makes it compatible with the previous way without data migration.
479
480
481
|
# File 'app/models/note.rb', line 479
def last_edited_at
super || updated_at
end
|
#mentioned_filtered_user_ids_for(references) ⇒ Object
716
717
718
719
720
721
722
723
724
725
|
# File 'app/models/note.rb', line 716
def mentioned_filtered_user_ids_for(references)
return super unless confidential?
user_ids = references.mentioned_user_ids.presence
return [] if user_ids.blank?
users = User.where(id: user_ids)
Ability.users_that_can_read_internal_notes(users, resource_parent).pluck(:id)
end
|
#mentioned_users(current_user = nil) ⇒ Object
708
709
710
711
712
713
714
|
# File 'app/models/note.rb', line 708
def mentioned_users(current_user = nil)
users = super
return users unless confidential?
Ability.users_that_can_read_internal_notes(users, resource_parent)
end
|
#merge_requests ⇒ Object
Notes on merge requests and commits can be traced back to one or several MRs. This method returns a relation if the note is for one of these types, or nil if it is a note on some other object.
425
426
427
428
429
430
431
|
# File 'app/models/note.rb', line 425
def merge_requests
if for_commit?
project.merge_requests.by_commit_sha(commit_id)
elsif for_merge_request?
MergeRequest.id_in(noteable_id)
end
end
|
#noteable ⇒ Object
override to return commits, which are not active record
434
435
436
437
438
439
440
441
442
|
# File 'app/models/note.rb', line 434
def noteable
return commit if for_commit?
super
rescue StandardError
nil
end
|
#noteable_ability_name ⇒ Object
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
|
# File 'app/models/note.rb', line 507
def noteable_ability_name
if for_snippet?
'snippet'
elsif for_alert_mangement_alert?
'alert_management_alert'
elsif for_vulnerability?
'security_resource'
elsif for_wiki_page?
'wiki_page'
elsif for_compliance_violation?
'compliance_violations_report'
else
noteable_type.demodulize.underscore
end
end
|
#noteable_author?(noteable) ⇒ Boolean
459
460
461
|
# File 'app/models/note.rb', line 459
def noteable_author?(noteable)
noteable.author == self.author
end
|
#noteable_type=(noteable_type) ⇒ Object
FIXME: Hack for polymorphic associations with STI
For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
446
447
448
|
# File 'app/models/note.rb', line 446
def noteable_type=(noteable_type)
super(noteable_type.to_s.classify.constantize.base_class.to_s)
end
|
#notify_after_create ⇒ Object
597
598
599
|
# File 'app/models/note.rb', line 597
def notify_after_create
noteable&.after_note_created(self)
end
|
#notify_after_destroy ⇒ Object
601
602
603
|
# File 'app/models/note.rb', line 601
def notify_after_destroy
noteable&.after_note_destroyed(self)
end
|
#parent_user ⇒ Object
632
633
634
|
# File 'app/models/note.rb', line 632
def parent_user
noteable.author if for_personal_snippet?
end
|
#post_processed_cache_key ⇒ Object
640
641
642
643
644
645
646
|
# File 'app/models/note.rb', line 640
def post_processed_cache_key
cache_key_items = [cache_key, author&.cache_key]
cache_key_items << project.team.human_max_access(author&.id) if author.present?
cache_key_items << Digest::SHA1.hexdigest(redacted_note_html) if redacted_note_html.present?
cache_key_items.join(':')
end
|
#project_name ⇒ Object
463
464
465
|
# File 'app/models/note.rb', line 463
def project_name
project&.name
end
|
#references ⇒ Object
532
533
534
535
536
537
538
539
540
|
# File 'app/models/note.rb', line 532
def references
refs = [noteable]
if part_of_discussion?
refs += discussion.notes.take_while { |n| n.id < id }
end
refs
end
|
#refresh_markdown_cache ⇒ Object
750
751
752
753
754
755
756
757
758
759
760
761
|
# File 'app/models/note.rb', line 750
def refresh_markdown_cache
return super if sharding_key_columns.any? { |s_column| self[s_column].present? }
ensure_organization_id
ensure_namespace_id
sharding_key_changes = changes.select { |k, _| sharding_key_columns.include?(k.to_sym) }
sharding_key_updates = sharding_key_changes.transform_values(&:last)
super.merge(sharding_key_updates)
end
|
#resource_parent ⇒ Object
616
617
618
|
# File 'app/models/note.rb', line 616
def resource_parent
noteable.try(:resource_parent) || project
end
|
#retrieve_upload(_identifier, paths) ⇒ Object
612
613
614
|
# File 'app/models/note.rb', line 612
def retrieve_upload(_identifier, paths)
Upload.find_by(model: self, path: paths)
end
|
#show_outdated_changes? ⇒ Boolean
664
665
666
667
668
669
670
671
|
# File 'app/models/note.rb', line 664
def show_outdated_changes?
return false unless for_merge_request?
return false unless system?
return false if change_position&.on_file?
return false unless change_position&.line_range
change_position.line_range["end"] || change_position.line_range["start"]
end
|
#skip_notification? ⇒ Boolean
636
637
638
|
# File 'app/models/note.rb', line 636
def skip_notification?
review.present? || !author.can_trigger_notifications?
end
|
#skip_project_check? ⇒ Boolean
414
415
416
|
# File 'app/models/note.rb', line 414
def skip_project_check?
!for_project_noteable?
end
|
#supports_suggestion? ⇒ Boolean
346
347
348
|
# File 'app/models/note.rb', line 346
def supports_suggestion?
false
end
|
#system_note_visible_for?(user) ⇒ Boolean
626
627
628
629
630
|
# File 'app/models/note.rb', line 626
def system_note_visible_for?(user)
return true unless system?
system_note_viewable_by?(user) && all_referenced_mentionables_allowed?(user)
end
|
#system_note_with_references? ⇒ Boolean
rubocop: disable CodeReuse/ServiceClass
323
324
325
326
327
328
329
330
331
|
# File 'app/models/note.rb', line 323
def system_note_with_references?
return unless system?
if force_cross_reference_regex_check?
matches_cross_reference_regex?
else
::SystemNotes::IssuablesService.cross_reference?(note)
end
end
|
#touch(*args, **kwargs) ⇒ Object
561
562
563
564
565
566
567
568
569
570
|
# File 'app/models/note.rb', line 561
def touch(*args, **kwargs)
touch_noteable
super
end
|
#touch_noteable ⇒ Object
By default Rails will issue an “SELECT *” for the relation, which is overkill for just updating the timestamps. To work around this we manually touch the data so we can SELECT only the columns we need.
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
|
# File 'app/models/note.rb', line 575
def touch_noteable
return if for_vulnerability? || for_commit?
assoc = association(:noteable)
noteable_object =
if assoc.loaded?
noteable
else
assoc.scope.select(:id, :updated_at).take
end
noteable_object&.touch
noteable_object
end
|
#trigger_note_subscription_create ⇒ Object
220
221
222
223
224
|
# File 'app/models/note.rb', line 220
def trigger_note_subscription_create
return unless trigger_note_subscription?
GraphqlTriggers.work_item_note_created(noteable.to_work_item_global_id, self)
end
|
#trigger_note_subscription_destroy ⇒ Object
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
# File 'app/models/note.rb', line 232
def trigger_note_subscription_destroy
return unless trigger_note_subscription?
deleted_note_data = {
id: self.id,
model_name: self.class.name,
discussion_id: self.discussion_id,
last_discussion_note: discussion.notes == [self]
}
GraphqlTriggers.work_item_note_deleted(noteable.to_work_item_global_id, deleted_note_data)
end
|
#trigger_note_subscription_update ⇒ Object
226
227
228
229
230
|
# File 'app/models/note.rb', line 226
def trigger_note_subscription_update
return unless trigger_note_subscription?
GraphqlTriggers.work_item_note_updated(noteable.to_work_item_global_id, self)
end
|
#trigger_work_item_updated_subscription ⇒ Object
249
250
251
252
253
254
|
# File 'app/models/note.rb', line 249
def trigger_work_item_updated_subscription
return unless trigger_note_subscription?
return unless system_note_work_item_reference?
GraphqlTriggers.work_item_updated(noteable)
end
|
#user_mention_class ⇒ Object
649
650
651
652
653
|
# File 'app/models/note.rb', line 649
def user_mention_class
return if noteable.blank?
noteable.user_mention_class
end
|
#user_mention_identifier ⇒ Object
656
657
658
659
660
661
662
|
# File 'app/models/note.rb', line 656
def user_mention_identifier
return if noteable.blank?
noteable.user_mention_identifier.merge({
note_id: id
})
end
|
#user_mentions ⇒ Object
620
621
622
623
624
|
# File 'app/models/note.rb', line 620
def user_mentions
return Note.none unless noteable.present?
noteable.user_mentions.where(note: self)
end
|