Module: CacheMarkdownField
- Extended by:
- ActiveSupport::Concern
- Included in:
- AbuseReport, Appearance, ApplicationSetting, ApplicationSetting::Term, Commit, DesignManagement::Design, IncidentManagement::TimelineEvent, Issuable, Label, Namespace, Note, Project, Release, ResourceLabelEvent, Snippet, System::BroadcastMessage, Timebox, UserStatus, WorkItems::Type
- Defined in:
- app/models/concerns/cache_markdown_field.rb
Overview
This module takes care of updating cache columns for Markdown-containing fields. Use like this in the body of your class:
include CacheMarkdownField
cache_markdown_field :foo
cache_markdown_field :bar
cache_markdown_field :baz, pipeline: :single_line
cache_markdown_field :baz, whitelisted: true
Corresponding foo_html, bar_html and baz_html fields should exist.
Constant Summary collapse
- INVALIDATED_BY =
changes to these attributes cause the cache to be invalidates
%w[author project].freeze
Instance Attribute Summary collapse
-
#skip_markdown_cache_validation ⇒ Object
(also: #skip_markdown_cache_validation?)
Returns the value of attribute skip_markdown_cache_validation.
Instance Method Summary collapse
- #attribute_invalidated?(attr) ⇒ Boolean
-
#banzai_render_context(field) ⇒ Object
Returns the default Banzai render context for the cached markdown field.
- #cached_html_for(markdown_field) ⇒ Object
- #cached_html_up_to_date?(markdown_field) ⇒ Boolean
- #can_cache_field?(field) ⇒ Boolean
- #invalidated_markdown_cache? ⇒ Boolean
- #latest_cached_markdown_version ⇒ Object
- #local_version ⇒ Object
- #mentionable_attributes_changed?(changes = saved_changes) ⇒ Boolean
-
#mentioned_filtered_user_ids_for(refs) ⇒ Object
Overriden on objects that needs to filter mentioned users that cannot read them, for example, guest users that are referenced on a confidential note.
- #parent_user ⇒ Object
-
#refresh_markdown_cache ⇒ Object
Update every applicable column in a row if any one is invalidated, as we only store one version per row.
- #refresh_markdown_cache! ⇒ Object
- #rendered_field_content(markdown_field) ⇒ Object
- #skip_project_check? ⇒ Boolean
- #store_mentions! ⇒ Object
-
#updated_cached_html_for(markdown_field) ⇒ Object
Updates the markdown cache if necessary, then returns the field Unlike ‘cached_html_for` it returns `nil` if the field does not exist.
Instance Attribute Details
#skip_markdown_cache_validation ⇒ Object Also known as: skip_markdown_cache_validation?
Returns the value of attribute skip_markdown_cache_validation.
27 28 29 |
# File 'app/models/concerns/cache_markdown_field.rb', line 27 def skip_markdown_cache_validation @skip_markdown_cache_validation end |
Instance Method Details
#attribute_invalidated?(attr) ⇒ Boolean
100 101 102 |
# File 'app/models/concerns/cache_markdown_field.rb', line 100 def attribute_invalidated?(attr) __send__("#{attr}_invalidated?") # rubocop:disable GitlabSecurity/PublicSend end |
#banzai_render_context(field) ⇒ Object
Returns the default Banzai render context for the cached markdown field.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'app/models/concerns/cache_markdown_field.rb', line 31 def banzai_render_context(field) raise ArgumentError, "Unknown field: #{field.inspect}" unless cached_markdown_fields.key?(field) # Always include a project key, or Banzai complains project = self.project if self.respond_to?(:project) group = self.group if self.respond_to?(:group) context = cached_markdown_fields[field].merge(project: project, group: group) # Banzai is less strict about authors, so don't always have an author key context[:author] = self. if self.respond_to?(:author) context[:markdown_engine] = Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE if Feature.enabled?(:personal_snippet_reference_filters, context[:author]) context[:user] = self.parent_user end context end |
#cached_html_for(markdown_field) ⇒ Object
104 105 106 107 108 109 |
# File 'app/models/concerns/cache_markdown_field.rb', line 104 def cached_html_for(markdown_field) raise ArgumentError, "Unknown field: #{markdown_field}" unless cached_markdown_fields.key?(markdown_field) __send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend end |
#cached_html_up_to_date?(markdown_field) ⇒ Boolean
84 85 86 87 88 89 90 91 92 93 94 |
# File 'app/models/concerns/cache_markdown_field.rb', line 84 def cached_html_up_to_date?(markdown_field) return false if cached_html_for(markdown_field).nil? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend html_field = cached_markdown_fields.html_field(markdown_field) markdown_changed = markdown_field_changed?(markdown_field) html_changed = markdown_field_changed?(html_field) latest_cached_markdown_version == cached_markdown_version && (html_changed || markdown_changed == html_changed) end |
#can_cache_field?(field) ⇒ Boolean
23 24 25 |
# File 'app/models/concerns/cache_markdown_field.rb', line 23 def can_cache_field?(field) true end |
#invalidated_markdown_cache? ⇒ Boolean
96 97 98 |
# File 'app/models/concerns/cache_markdown_field.rb', line 96 def invalidated_markdown_cache? cached_markdown_fields.html_fields.any? { |html_field| attribute_invalidated?(html_field) } end |
#latest_cached_markdown_version ⇒ Object
133 134 135 |
# File 'app/models/concerns/cache_markdown_field.rb', line 133 def latest_cached_markdown_version @latest_cached_markdown_version ||= (Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) | local_version end |
#local_version ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'app/models/concerns/cache_markdown_field.rb', line 137 def local_version # because local_markdown_version is stored in application_settings which # uses cached_markdown_version too, we check explicitly to avoid # endless loop return local_markdown_version if respond_to?(:has_attribute?) && has_attribute?(:local_markdown_version) settings = Gitlab::CurrentSettings.current_application_settings # Following migrations are not properly isolated and # use real models (by calling .ghost method), in these migrations # local_markdown_version attribute doesn't exist yet, so we # use a default value: # db/migrate/20170825104051_migrate_issues_to_ghost_user.rb # db/migrate/20171114150259_merge_requests_author_id_foreign_key.rb if settings.respond_to?(:local_markdown_version) settings.local_markdown_version else 0 end end |
#mentionable_attributes_changed?(changes = saved_changes) ⇒ Boolean
195 196 197 198 199 200 201 202 |
# File 'app/models/concerns/cache_markdown_field.rb', line 195 def mentionable_attributes_changed?(changes = saved_changes) return false unless is_a?(Mentionable) self.class.mentionable_attrs.any? do |attr| changes.key?(cached_markdown_fields.html_field(attr.first)) && changes.fetch(cached_markdown_fields.html_field(attr.first)).last.present? end end |
#mentioned_filtered_user_ids_for(refs) ⇒ Object
Overriden on objects that needs to filter mentioned users that cannot read them, for example, guest users that are referenced on a confidential note.
191 192 193 |
# File 'app/models/concerns/cache_markdown_field.rb', line 191 def mentioned_filtered_user_ids_for(refs) refs.mentioned_user_ids.presence end |
#parent_user ⇒ Object
158 159 160 |
# File 'app/models/concerns/cache_markdown_field.rb', line 158 def parent_user nil end |
#refresh_markdown_cache ⇒ Object
Update every applicable column in a row if any one is invalidated, as we only store one version per row
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'app/models/concerns/cache_markdown_field.rb', line 61 def refresh_markdown_cache updates = cached_markdown_fields.markdown_fields.to_h do |markdown_field| [ cached_markdown_fields.html_field(markdown_field), rendered_field_content(markdown_field) ] end updates['cached_markdown_version'] = latest_cached_markdown_version updates.each { |field, data| write_markdown_field(field, data) } end |
#refresh_markdown_cache! ⇒ Object
74 75 76 77 78 79 80 81 82 |
# File 'app/models/concerns/cache_markdown_field.rb', line 74 def refresh_markdown_cache! updates = refresh_markdown_cache if updates.present? && save_markdown(updates) # save_markdown updates DB columns directly, so compute and save mentions # by calling store_mentions! or we end-up with missing mentions although those # would appear in the notes, descriptions, etc in the UI store_mentions! if mentionable_attributes_changed?(updates) end end |
#rendered_field_content(markdown_field) ⇒ Object
52 53 54 55 56 57 |
# File 'app/models/concerns/cache_markdown_field.rb', line 52 def rendered_field_content(markdown_field) return unless can_cache_field?(markdown_field) = { skip_project_check: skip_project_check? } Banzai::Renderer.cacheless_render_field(self, markdown_field, ) end |
#skip_project_check? ⇒ Boolean
19 20 21 |
# File 'app/models/concerns/cache_markdown_field.rb', line 19 def skip_project_check? false end |
#store_mentions! ⇒ Object
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'app/models/concerns/cache_markdown_field.rb', line 162 def store_mentions! # We can only store mentions if the mentionable is a database object return unless self.is_a?(ApplicationRecord) identifier = user_mention_identifier # this may happen due to notes polymorphism, so noteable_id may point to a record # that no longer exists as we cannot have FK on noteable_id return if identifier.blank? refs = all_references(self.) references = {} references[:mentioned_users_ids] = mentioned_filtered_user_ids_for(refs) references[:mentioned_groups_ids] = refs.mentioned_group_ids.presence references[:mentioned_projects_ids] = refs.mentioned_project_ids.presence if references.compact.any? user_mention_class.upsert(references.merge(identifier), unique_by: identifier.compact.keys) else user_mention_class.delete_by(identifier) end true end |
#updated_cached_html_for(markdown_field) ⇒ Object
Updates the markdown cache if necessary, then returns the field Unlike ‘cached_html_for` it returns `nil` if the field does not exist
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'app/models/concerns/cache_markdown_field.rb', line 113 def updated_cached_html_for(markdown_field) return unless cached_markdown_fields.key?(markdown_field) if attribute_invalidated?(cached_markdown_fields.html_field(markdown_field)) # Invalidated due to Markdown content change # We should not persist the updated HTML here since this will depend on whether the # Markdown content change will be persisted. Both will be persisted together when the model is saved. if changed_attributes.key?(markdown_field) refresh_markdown_cache else # Invalidated due to stale HTML cache # This could happen when the Markdown cache version is bumped or when a model is imported and the HTML is empty. # We persist the updated HTML here so that subsequent calls to this method do not have to regenerate the HTML again. refresh_markdown_cache! end end cached_html_for(markdown_field) end |