Class: BookmarkQuery
- Inherits:
-
Object
- Object
- BookmarkQuery
- Defined in:
- lib/bookmark_query.rb
Overview
Allows us to query Bookmark records for lists. Used mainly in the user/activity/bookmarks page.
Instance Attribute Summary collapse
-
#count ⇒ Object
readonly
Returns the value of attribute count.
-
#guardian ⇒ Object
readonly
Returns the value of attribute guardian.
Class Method Summary collapse
- .on_preload(&blk) ⇒ Object
- .preload(bookmarks, object) ⇒ Object
-
.preload_polymorphic_associations(bookmarks, guardian) ⇒ Object
These polymorphic associations are loaded to make the UserBookmarkListSerializer’s life easier, which conditionally chooses the bookmark serializer to use based on the type, and we want the associations all loaded ahead of time to make sure we are not doing N+1s.
Instance Method Summary collapse
-
#initialize(user:, guardian: nil, search_term: nil, page: nil, per_page: nil) ⇒ BookmarkQuery
constructor
A new instance of BookmarkQuery.
- #list_all(&blk) ⇒ Object
- #unread_notifications(limit: 20) ⇒ Object
Constructor Details
#initialize(user:, guardian: nil, search_term: nil, page: nil, per_page: nil) ⇒ BookmarkQuery
Returns a new instance of BookmarkQuery.
29 30 31 32 33 34 35 36 |
# File 'lib/bookmark_query.rb', line 29 def initialize(user:, guardian: nil, search_term: nil, page: nil, per_page: nil) @user = user @search_term = search_term @guardian = guardian || Guardian.new(@user) @page = page ? page.to_i : 0 @per_page = per_page ? per_page.to_i : 20 @count = 0 end |
Instance Attribute Details
#count ⇒ Object (readonly)
Returns the value of attribute count.
27 28 29 |
# File 'lib/bookmark_query.rb', line 27 def count @count end |
#guardian ⇒ Object (readonly)
Returns the value of attribute guardian.
27 28 29 |
# File 'lib/bookmark_query.rb', line 27 def guardian @guardian end |
Class Method Details
.on_preload(&blk) ⇒ Object
8 9 10 |
# File 'lib/bookmark_query.rb', line 8 def self.on_preload(&blk) (@preload ||= Set.new) << blk end |
.preload(bookmarks, object) ⇒ Object
12 13 14 15 |
# File 'lib/bookmark_query.rb', line 12 def self.preload(bookmarks, object) preload_polymorphic_associations(bookmarks, object.guardian) @preload.each { |preload| preload.call(bookmarks, object) } if @preload end |
.preload_polymorphic_associations(bookmarks, guardian) ⇒ Object
These polymorphic associations are loaded to make the UserBookmarkListSerializer’s life easier, which conditionally chooses the bookmark serializer to use based on the type, and we want the associations all loaded ahead of time to make sure we are not doing N+1s.
21 22 23 24 25 |
# File 'lib/bookmark_query.rb', line 21 def self.preload_polymorphic_associations(bookmarks, guardian) Bookmark.registered_bookmarkables.each do |registered_bookmarkable| registered_bookmarkable.perform_preload(bookmarks, guardian) end end |
Instance Method Details
#list_all(&blk) ⇒ Object
38 39 40 41 42 43 44 45 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 89 90 |
# File 'lib/bookmark_query.rb', line 38 def list_all(&blk) ts_query = @search_term.present? ? Search.ts_query(term: @search_term) : nil search_term_wildcard = @search_term.present? ? "%#{@search_term}%" : nil queries = Bookmark .registered_bookmarkables .map do |bookmarkable| interim_results = bookmarkable.perform_list_query(@user, @guardian) # this could occur if there is some security reason that the user cannot # access the bookmarkables that they have bookmarked, e.g. if they had 1 bookmark # on a topic and that topic was moved into a private category next if interim_results.blank? if @search_term.present? interim_results = bookmarkable.perform_search_query(interim_results, search_term_wildcard, ts_query) end # this is purely to make the query easy to read and debug, otherwise it's # all mashed up into a massive ball in MiniProfiler :) "---- #{bookmarkable.model} bookmarkable ---\n\n #{interim_results.to_sql}" end .compact # same for interim results being blank, the user might have been locked out # from all their various bookmarks, in which case they will see nothing and # no further pagination/ordering/etc is required return [] if queries.empty? union_sql = queries.join("\n\nUNION\n\n") results = Bookmark.select("bookmarks.*").from("(\n\n#{union_sql}\n\n) as bookmarks") results = results.order( "(CASE WHEN bookmarks.pinned THEN 0 ELSE 1 END), bookmarks.reminder_at ASC, bookmarks.updated_at DESC", ) @count = results.count results = results.offset(@page * @per_page) if @page.positive? if updated_results = blk&.call(results) results = updated_results end results = results.limit(@per_page).to_a BookmarkQuery.preload(results, self) results end |
#unread_notifications(limit: 20) ⇒ Object
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/bookmark_query.rb', line 92 def unread_notifications(limit: 20) reminder_notifications = Notification .(@user.id, limit: [limit, 100].min) .unread .where(notification_type: Notification.types[:bookmark_reminder]) reminder_bookmark_ids = reminder_notifications.map { |n| n.data_hash[:bookmark_id] }.compact # We preload associations like we do above for the list to avoid # N1s in the can_see? guardian calls for each bookmark. bookmarks = Bookmark.where(user: @user, id: reminder_bookmark_ids) BookmarkQuery.preload(bookmarks, self) # Any bookmarks that no longer exist, we need to find the associated # records using bookmarkable details. # # First we want to group these by type into a hash to reduce queries: # # { # "Post": { # 1234: <Post>, # 566: <Post>, # }, # "Topic": { # 123: <Topic>, # 99: <Topic>, # } # } # # We may not need to do this most of the time. It depends mostly on # a user's auto_delete_preference for bookmarks. deleted_bookmark_ids = reminder_bookmark_ids - bookmarks.map(&:id) deleted_bookmarkables = reminder_notifications .select do |notif| deleted_bookmark_ids.include?(notif.data_hash[:bookmark_id]) && notif.data_hash[:bookmarkable_type].present? end .inject({}) do |hash, notif| hash[notif.data_hash[:bookmarkable_type]] ||= {} hash[notif.data_hash[:bookmarkable_type]][notif.data_hash[:bookmarkable_id]] = nil hash end # Then, we can actually find the associated records for each type in the database. deleted_bookmarkables.each do |type, bookmarkable| records = Bookmark.registered_bookmarkable_from_type(type).model.where(id: bookmarkable.keys) records.each { |record| deleted_bookmarkables[type][record.id] = record } end reminder_notifications.select do |notif| bookmark = bookmarks.find { |bm| bm.id == notif.data_hash[:bookmark_id] } # This is the happy path, it's easiest to look up using a bookmark # that hasn't been deleted. if bookmark.present? bookmarkable = Bookmark.registered_bookmarkable_from_type(bookmark.bookmarkable_type) bookmarkable.can_see?(@guardian, bookmark) else # Otherwise, we have to use our cached records from the deleted # bookmarks' related bookmarkable (e.g. Post, Topic) to determine # secure access. bookmarkable = deleted_bookmarkables.dig( notif.data_hash[:bookmarkable_type], notif.data_hash[:bookmarkable_id], ) bookmarkable.present? && Bookmark.registered_bookmarkable_from_type( notif.data_hash[:bookmarkable_type], ).can_see_bookmarkable?(@guardian, bookmarkable) end end end |