@@ -182,7 +184,7 @@ function buildArticleHTML(article) {
-
+
diff --git a/app/decorators/article_decorator.rb b/app/decorators/article_decorator.rb
index 2007e9a5f..00d456e20 100644
--- a/app/decorators/article_decorator.rb
+++ b/app/decorators/article_decorator.rb
@@ -1,6 +1,15 @@
class ArticleDecorator < ApplicationDecorator
LONG_MARKDOWN_THRESHOLD = 900
+ # @return [String] JSON formatted string.
+ #
+ # @example
+ # > Article.last.decorate.user_data_info_to_json
+ # => "{\"user_id\":1,\"className\":\"User\",\"style\":\"full\",\"name\":\"Duane \\\"The Rock\\\" Johnson\"}"
+ def user_data_info_to_json
+ DataInfo.to_json(object: cached_user, class_name: "User", id: user_id, style: "full")
+ end
+
def current_state_path
published ? "/#{username}/#{slug}" : "/#{username}/#{slug}?preview=#{password}"
end
diff --git a/app/decorators/notification_decorator.rb b/app/decorators/notification_decorator.rb
index 4c40cbd78..7f3ad73b4 100644
--- a/app/decorators/notification_decorator.rb
+++ b/app/decorators/notification_decorator.rb
@@ -3,6 +3,9 @@ class NotificationDecorator < ApplicationDecorator
def class
Struct.new(:name).new(name)
end
+
+ # @see ApplicationRecord#class_name
+ alias_method :class_name, :name
end.freeze
# returns a stub notifiable object with name and id
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 366f4c024..5247e0641 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -112,12 +112,7 @@ module ApplicationHelper
return if followable == Users::DeletedUser
user_follow = followable.instance_of?(User) ? "follow-user" : ""
- followable_type = if followable.respond_to?(:decorated?) && followable.decorated?
- followable.object.class.name
- else
- followable.class.name
- end
-
+ followable_type = followable.class_name
followable_name = followable.name
tag.button(
@@ -126,12 +121,7 @@ module ApplicationHelper
name: :button,
type: :button,
data: {
- info: {
- id: followable.id,
- className: followable_type,
- name: followable_name,
- style: style
- }
+ info: DataInfo.to_json(object: followable, className: followable_type, style: style)
},
class: "crayons-btn follow-action-button whitespace-nowrap #{classes} #{user_follow}",
aria: {
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index cd08562a9..8dd6494d9 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -35,6 +35,16 @@ class ApplicationRecord < ActiveRecord::Base
false
end
+ # In our view objects, we often ask "What's this object's class's name?"
+ #
+ # We can either first check "Are you decorated?" If so, ask for the decorated object's class
+ # name. Or we can add a helper method for that very thing.
+ #
+ # @return [String]
+ def class_name
+ self.class.name
+ end
+
# Decorate collection with appropriate decorator
def self.decorate
decorator_class.decorate_collection(all)
diff --git a/app/models/tag.rb b/app/models/tag.rb
index b31f9a6c2..da1147015 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -82,6 +82,13 @@ class Tag < ActsAsTaggableOn::Tag
scope :eager_load_serialized_data, -> {}
scope :supported, -> { where(supported: true) }
+ # @return [String]
+ #
+ # @see ApplicationRecord#class_name
+ def class_name
+ self.class.name
+ end
+
# possible social previews templates for articles with a particular tag
def self.social_preview_templates
Rails.root.join("app/views/social_previews/articles").children.map { |ch| File.basename(ch, ".html.erb") }
diff --git a/app/models/users/deleted_user.rb b/app/models/users/deleted_user.rb
index 8ec043034..72824bcd0 100644
--- a/app/models/users/deleted_user.rb
+++ b/app/models/users/deleted_user.rb
@@ -24,6 +24,13 @@ module Users
def self.path() = nil
def self.tag_line() = nil
+ # @return [String]
+ #
+ # @see ApplicationRecord#class_name
+ def self.class_name
+ User.name
+ end
+
def self.decorate
self
end
diff --git a/app/view_objects/data_info.rb b/app/view_objects/data_info.rb
new file mode 100644
index 000000000..db3329575
--- /dev/null
+++ b/app/view_objects/data_info.rb
@@ -0,0 +1,36 @@
+# Throughout Forem, we encode JSON information in our Ruby templates.
+# By convention, we often have a data-info attribute that is a Hash.
+#
+# This module provides some much needed character escaping to ensure
+# that our JS that parses the data-info attribute doesn't choke.
+#
+# @see ./app/javascript/packs/followButtons.js for parsing
+module DataInfo
+ # A convenience method to ensure properly escape JSON data attributes.
+ #
+ # @param object [#class, #id, #name] responds to `#class`, `#id`,
+ # and `#name`, though you can "cheat" and pass the specific
+ # values to allow for over-riding (or when you have not quite
+ # well formed objects; looking at ArticleDecorator's
+ # cached_user. It's not quite a valid object.)
+ # @param id [String, Integer]
+ # @param class_name [String]
+ # @param name [String]
+ # @param kwargs [Hash] any additional attributes to include in the
+ # JSON object. In some cases we include a "style" attribute.
+ #
+ # @return [String] JSON formatted string.
+ #
+ # @example
+ # > DataInfo.to_json(object: User.first, name: "Duane \"The Rock\" Johnson", id: 1, style: "full")
+ # => "{\"user_id\":1,\"className\":\"User\",\"style\":\"full\",\"name\":\"Duane \\\"The Rock\\\" Johnson\"}"
+ def self.to_json(object:, id: object.id, class_name: object.class_name, name: object.name, **kwargs)
+ kwargs.merge(
+ {
+ id: id,
+ className: class_name,
+ name: name
+ },
+ ).to_json
+ end
+end
diff --git a/app/views/articles/_single_story.html.erb b/app/views/articles/_single_story.html.erb
index 991852b1d..1f444ac11 100644
--- a/app/views/articles/_single_story.html.erb
+++ b/app/views/articles/_single_story.html.erb
@@ -65,7 +65,7 @@