How to get Paper_trail to work with Action Text?
Asked Answered
L

6

6

I had Paper Trail Gem nicely set up with my basic model Article which had a text column called body. However, after I have implemented Action Text to my application and removed the column body from the Article model, I can't get Paper Trail to track changes in associated body column. How can I get this to work?

Disclaimer: I am a newbie to Rails.

Article.rb

...
  has_rich_text :body
  has_paper_trail
...

Articles Schema (after deleting :body column)

  create_table "articles", force: :cascade do |t|
    t.string "title"
    t.string "slug"
    t.datetime "archived_at"
    t.datetime "published_at"
    ...
  end

Action Text Schema

create_table "action_text_rich_texts", force: :cascade do |t|
    t.string "name", null: false
    t.text "body"
    t.string "record_type", null: false
    t.bigint "record_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
  end

I'd love to return the same functionality to the app as before where I was able to see the changes made in the body of the Article. E.g. someone added a sentence, deleted a word, etc..

Lee answered 6/4, 2019 at 0:56 Comment(2)
is there a ActionTextRichText model available? if so, maybe you can add has_paper_trail to it – Summertree
@Summertree The model is not available. – Lee
L
3

After trying all the options provided here, I got an idea for this workaround that at the end worked really well.

What I did is that I simply replaced Action Text with pure Trix Editor version and therefore I was able to keep :body within my article.rb model, save the versions of the whole object and show diffs. Yay! πŸŽ‰

Lee answered 12/5, 2019 at 19:58 Comment(1)
How did you work around the ActiveStorage attachments part? – Hemato
B
5

Combining the answers worked for me:

# frozen_string_literal: true

ActiveSupport.on_load(:action_text_rich_text) do
  ActionText::RichText.class_eval do
    has_paper_trail
  end
end

in lib/rich_text_paper_trail.rb and making sure this file is loaded! For example by explicitly requiring it: require 'rich_text_paper_trail'

Behemoth answered 29/4, 2020 at 10:32 Comment(2)
Where exactly would one require this? – Gilli
I required it in the model that has action_text mounted has_rich_text, but it can also be required in the application_controller.rb or something more global, I think. Maybe you could also put this code in an initializer file in config/initializers/ – Behemoth
W
4

Try adding a config/initializers/rich_text_paper_trail.rb file with:

ActiveSupport.on_load(:action_text_rich_text) do
  has_paper_trail
end
Winniewinnifred answered 8/5, 2019 at 12:47 Comment(1)
Did not work. New versions (updates or creates) are not being saved into versions table. – Lee
L
3

After trying all the options provided here, I got an idea for this workaround that at the end worked really well.

What I did is that I simply replaced Action Text with pure Trix Editor version and therefore I was able to keep :body within my article.rb model, save the versions of the whole object and show diffs. Yay! πŸŽ‰

Lee answered 12/5, 2019 at 19:58 Comment(1)
How did you work around the ActiveStorage attachments part? – Hemato
L
1

Ruby allows you (for better or worse) to make modifications to things on the fly.

Put this in an initializer:

ActionText::RichText.class_eval do 
  has_paper_trail
end 
Leger answered 6/4, 2019 at 2:14 Comment(4)
I tried but now I can't even run the rails server because I would get: ...gems/activerecord-5.2.2/lib/active_record/dynamic_matchers.rb:22:in `method_missing': undefined method `has_many_attached' for #<Class:0x00007fa19c03afd0> (NoMethodError) – Lee
try creating and dropping it in app/models/action_text/rich_text.rb – Leger
I would get: Circular dependency detected while autoloading constant ActionText::RichText – Lee
You could try to put this in an initialiser and wrap it in a ActiveSupport.on_load(:active_storage_blob) do ... end – Dainedainty
S
1

I was inspired by this solution and just change it to use PaperTrail::Version for storing changes.

# config/initializers/action_text_version.rb

ActiveSupport.on_load(:action_text_rich_text) do
  ActionText::RichText.class_eval do
    before_save :record_change

    private

    def record_change
      return unless body_changed?
      return unless PaperTrail.request.enabled_for_model?(record.class)

      PaperTrail::Version.create(item: record, event: "update", whodunnit: PaperTrail::Request.whodunnit, object_changes: {name => [body.to_s, body_was.to_s]})
     end
  end  
end
Sulfamerazine answered 26/8, 2023 at 11:43 Comment(0)
G
0

We have done something similar on Rails 5.2 using an archive version of active text.

Gemfile

gem 'actiontext', git: 'https://github.com/kobaltz/actiontext.git', branch: 'archive', require: 'action_text'

models/article.rb

class Article < ApplicationRecord
  has_paper_tail
  serialize :body, ActionText::Content
  ...
end

helpers/trix_editor_helper.rb

require 'action_view/helpers/tags/placeholderable'

module TrixEditorHelper
  mattr_accessor(:id, instance_accessor: false) { 0 }

# Returns a +trix-editor+ tag that instantiates the Trix JavaScript editor as well as a hidden field
# that Trix will write to on changes, so the content will be sent on form submissions.
#
# ==== Options
# * <tt>:class</tt> - Defaults to "trix-content" which ensures default styling is applied.
#
# ==== Example
#
#   rich_text_area_tag "content", message.content
#   # <input type="hidden" name="content" id="trix_input_post_1">
#   # <trix-editor id="content" input="trix_input_post_1" class="trix-content" ...></trix-editor>

  def trix_editor_tag(name, value = nil, options = {})
    options = options.symbolize_keys

    options[:input] ||= "trix_input_#{TrixEditorHelper.id += 1}"
    options[:class] ||= "trix-content"

    options[:data] ||= {}
    options[:data][:direct_upload_url] = main_app.rails_direct_uploads_url
    options[:data][:blob_url_template] = main_app.rails_service_blob_url(":signed_id", ":filename")

    editor_tag = content_tag("trix-editor", "", options)
    input_tag = hidden_field_tag(name, value, id: options[:input])

    input_tag + editor_tag
  end
end

module ActionView::Helpers
  class Tags::TrixEditor < Tags::Base
    include Tags::Placeholderable
    delegate :dom_id, to: ActionView::RecordIdentifier

    def render
      options = @options.stringify_keys
      add_default_name_and_id(options)
      options['input'] ||= dom_id(object, [options['id'], :trix_input].compact.join('_')) if object
      @template_object.trix_editor_tag(options.delete("name"), editable_value, options)
    end

    def editable_value
      value&.try(:to_trix_html)
    end
  end

  module FormHelper
    def trix_editor(object_name, method, options = {})
      Tags::TrixEditor.new(object_name, method, self, options).render
    end
  end

  class FormBuilder
    def trix_editor(method, options = {})
      @template.trix_editor(@object_name, method, objectify_options(options))
    end
  end
end

We then installed trix version 1.1 from cdn and used the standard trix-attachments.js and Direct Uploads.

Looking to upgrade to Rails 6 and keep this concept if possible.

Guardianship answered 6/7, 2020 at 19:44 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.