Class: BaseBookmarkable

Inherits:
Object
  • Object
show all
Defined in:
app/services/base_bookmarkable.rb

Overview

Anything that we want to be able to bookmark must be registered as a bookmarkable type using Plugin::Instance#register_bookmarkable(bookmarkable_klass), where the bookmarkable_klass is a class that implements this BaseBookmarkable interface. Some examples are TopicBookmarkable and PostBookmarkable.

These methods are then called by the RegisteredBookmarkable class through a public interface, used in places where we need to list, send reminders for, or otherwise interact with bookmarks in a way that is unique to the bookmarkable type.

See RegisteredBookmarkable for additional documentation.

Direct Known Subclasses

PostBookmarkable, TopicBookmarkable

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#modelObject (readonly)

Returns the value of attribute model.



16
17
18
# File 'app/services/base_bookmarkable.rb', line 16

def model
  @model
end

#preload_associationsObject (readonly)

Returns the value of attribute preload_associations.



16
17
18
# File 'app/services/base_bookmarkable.rb', line 16

def preload_associations
  @preload_associations
end

#serializerObject (readonly)

Returns the value of attribute serializer.



16
17
18
# File 'app/services/base_bookmarkable.rb', line 16

def serializer
  @serializer
end

Class Method Details

.after_create(guardian, bookmark, opts) ⇒ Object

Optional additional actions may need to occur after a bookmark is created via the BookmarkManager.

Parameters:

  • guardian (Guardian)

    The guardian for the user which is creating the bookmark.

  • bookmark (Model)

    The bookmark which was created.

  • opts (Hash)

    Additional options that may be passed down via BookmarkManager.



181
182
183
# File 'app/services/base_bookmarkable.rb', line 181

def self.after_create(guardian, bookmark, opts)
  # noop
end

.after_destroy(guardian, bookmark, opts) ⇒ Object

Optional additional actions may need to occur after a bookmark is destroyed via the BookmarkManager.

Parameters:

  • guardian (Guardian)

    The guardian for the user which is destroying the bookmark.

  • bookmark (Model)

    The bookmark which was destroyed.

  • opts (Hash)

    Additional options that may be passed down via BookmarkManager.



192
193
194
# File 'app/services/base_bookmarkable.rb', line 192

def self.after_destroy(guardian, bookmark, opts)
  # noop
end

.bookmark_metadata(bookmark, user) ⇒ Hash

Some additional information about the bookmark or the surrounding relations may be required when the bookmark is created or destroyed. For example, when destroying a bookmark within a topic we need to know whether there are other bookmarks still remaining in the topic.

Parameters:

  • bookmark (Bookmark)

    The bookmark that we are retrieving additional metadata for.

  • user (User)

    The current user which is accessing the bookmark metadata.

Returns:

  • (Hash)

    (optional)



159
160
161
# File 'app/services/base_bookmarkable.rb', line 159

def self.(bookmark, user)
  {}
end

.can_see?(guardian, bookmark) ⇒ Boolean

Access control is dependent on what has been bookmarked, the appropriate guardian can_see_X? method should be called from the bookmarkable class to determine whether the bookmarkable record (e.g. Post, Topic) is accessible by the guardian user.

Parameters:

  • guardian (Guardian)

    The guardian class for the user that we are performing the access check for.

  • bookmark (Bookmark)

    The bookmark which we are checking access for using the bookmarkable association.

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


146
147
148
# File 'app/services/base_bookmarkable.rb', line 146

def self.can_see?(guardian, bookmark)
  raise NotImplementedError
end

.cleanup_deletedObject

Some bookmarkable records are Trashable, and as such we don’t delete the bookmark with dependent_destroy. This should be used to delete those records after a grace period, defined by the bookmarkable. For example, post bookmarks may be deleted 3 days after the post or topic is deleted.

In the case of bookmarkable records that are not trashable, and where dependent_destroy is not used, this shouldjust delete the bookmarks pointing to the record which no longer exists in the database.



205
206
207
# File 'app/services/base_bookmarkable.rb', line 205

def self.cleanup_deleted
  # noop
end

.has_preloads?Boolean

Returns:

  • (Boolean)


38
39
40
# File 'app/services/base_bookmarkable.rb', line 38

def self.has_preloads?
  preload_associations.present?
end

.list_query(user, guardian) ⇒ Bookmark::ActiveRecord_AssociationRelation

This is where the main query to filter the bookmarks by the provided bookmarkable type should occur. This should join on additional tables that are required later on to preload additional data for serializers, and also is the place where the bookmarks should be filtered based on security checks, which is why the Guardian instance is provided.

Parameters:

  • user (User)

    The user to perform the query for, this scopes the bookmarks returned.

  • guardian (Guardian)

    An instance of Guardian for the user to be used for security filters.

Returns:

  • (Bookmark::ActiveRecord_AssociationRelation)

    Should be an approprialely scoped list of bookmarks for the user.

Raises:

  • (NotImplementedError)


61
62
63
# File 'app/services/base_bookmarkable.rb', line 61

def self.list_query(user, guardian)
  raise NotImplementedError
end

.modelActiveRecord::Base

Returns The ActiveRecord model class which will be used to denote the type of the bookmarkable upon registration along with querying.

Returns:

  • (ActiveRecord::Base)

    The ActiveRecord model class which will be used to denote the type of the bookmarkable upon registration along with querying.

Raises:

  • (NotImplementedError)


21
22
23
# File 'app/services/base_bookmarkable.rb', line 21

def self.model
  raise NotImplementedError
end

.perform_custom_preload!(bookmarks_of_type, guardian) ⇒ Object

Implementations can define their own preloading logic here

Parameters:

  • bookmarks_of_type (Array)

    The list of bookmarks to preload data for. Already filtered to be of the correct class.

  • guardian (Guardian)

    An instance of Guardian for the current_user



47
48
49
# File 'app/services/base_bookmarkable.rb', line 47

def self.perform_custom_preload!(bookmarks_of_type, guardian)
  nil
end

.preload_associationsArray

Returns Used for preloading associations on the bookmarks for listing purposes. Should be in the same format used for .includes() e.g.

{ topic: [:topic_users, :tags

}, :user].

Returns:

  • (Array)

    Used for preloading associations on the bookmarks for listing purposes. Should be in the same format used for .includes() e.g.

    { topic: [:topic_users, :tags

    }, :user]



34
35
36
# File 'app/services/base_bookmarkable.rb', line 34

def self.preload_associations
  nil
end

.reminder_conditions(bookmark) ⇒ Boolean

When sending bookmark reminders, we want to make sure that whatever we are sending the reminder for has not been deleted or is otherwise inaccessible. Most of the time we can just check if the bookmarkable record is present because it will be trashable, though in some cases there will be additional conditions in the form of a lambda that we should use instead.

The logic around whether it is the right time to send a reminder does not belong here, that is done in the BookmarkReminderNotifications job.

Parameters:

  • bookmark (Bookmark)

    The bookmark that we are considering sending a reminder for.

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)


95
96
97
# File 'app/services/base_bookmarkable.rb', line 95

def self.reminder_conditions(bookmark)
  raise NotImplementedError
end

.reminder_handler(bookmark) ⇒ void

This method returns an undefined value.

Different bookmarkables may have different ways of notifying a user or presenting the reminder and what it is for, so it is up to the bookmarkable to register its preferred method of sending the reminder.

Parameters:

  • bookmark (Bookmark)

    The bookmark that we are sending the reminder notification for.

Raises:

  • (NotImplementedError)


106
107
108
# File 'app/services/base_bookmarkable.rb', line 106

def self.reminder_handler(bookmark)
  raise NotImplementedError
end

.search_query(bookmarks, query, ts_query, &bookmarkable_search) ⇒ Bookmark::ActiveRecord_AssociationRelation

Called from BookmarkQuery when the initial results have been returned by perform_list_query. The search_query should join additional tables required to filter the bookmarks further, as well as defining a string used for where_sql, which can include comparisons with the :q parameter.

Parameters:

  • bookmarks (Bookmark::ActiveRecord_Relation)

    The bookmark records returned by perform_list_query

  • query (String)

    The search query from the user surrounded by the %% wildcards

  • ts_query (String)

    The postgres TSQUERY string used for comparisons with full text search columns

  • bookmarkable_search (Block)

    This block must be called with the additional WHERE clause SQL relevant for the bookmarkable to be searched, as well as the bookmarks relation with any additional joins applied.

Returns:

  • (Bookmark::ActiveRecord_AssociationRelation)

    The list of bookmarks from perform_list_query filtered further by the query parameter.

Raises:

  • (NotImplementedError)


79
80
81
# File 'app/services/base_bookmarkable.rb', line 79

def self.search_query(bookmarks, query, ts_query, &bookmarkable_search)
  raise NotImplementedError
end

.send_reminder_notification(bookmark, notification_data) ⇒ void

This method returns an undefined value.

Can be used by the inheriting class via reminder_handler, most of the time we just want to make a Notification for a bookmark reminder, this gives consumers a way to do it without having provide all of the required data themselves.

Parameters:

  • bookmark (Bookmark)

    The bookmark that we are sending the reminder notification for.

  • notification_data (Hash)

    Any data, either top-level (e.g. topic_id, post_number) or inside the data sub-key, which should be stored when the notification is created.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'app/services/base_bookmarkable.rb', line 121

def self.send_reminder_notification(bookmark, notification_data)
  if notification_data[:data].blank? || notification_data[:data][:bookmarkable_url].blank? ||
       notification_data[:data][:title].blank?
    raise Discourse::InvalidParameters.new(
            "A `data` key must be present with at least `bookmarkable_url` and `title` entries.",
          )
  end

  notification_data[:data] = notification_data[:data].merge(
    display_username: bookmark.user.username,
    bookmark_name: bookmark.name,
    bookmark_id: bookmark.id,
  ).to_json
  notification_data[:notification_type] = Notification.types[:bookmark_reminder]
  bookmark.user.notifications.create!(notification_data)
end

.serializerApplicationSerializer

Returns The serializer class inheriting from UserBookmarkBaseSerializer.

Returns:

Raises:

  • (NotImplementedError)


26
27
28
# File 'app/services/base_bookmarkable.rb', line 26

def self.serializer
  raise NotImplementedError
end

.validate_before_create(guardian, bookmarkable) ⇒ Object

Optional bookmarkable specific validations may need to be run before a bookmark is created via the BookmarkManager. From here an error should be raised if there is an issue with the bookmarkable.

Parameters:

  • guardian (Guardian)

    The guardian for the user which is creating the bookmark.

  • bookmarkable (Model)

    The ActiveRecord model which is acting as the bookmarkable for the new bookmark.



170
171
172
# File 'app/services/base_bookmarkable.rb', line 170

def self.validate_before_create(guardian, bookmarkable)
  # noop
end