ActionText in Ruby on Rails. Overview of the first meeting
Content
Target
Generated files
How our model instance is associated with the ActionText instance in the action_text_rich_texts table
How files/drawings are saved in posts
Total
Target
The ability to create, store and edit text with pictures, attached files is a common task. The essence of the task is to make a convenient tool for the content manager to add text data to the site.
IN Ruby on Rails there is a module for this purpose actiontext With relevant documentation, which adds the ability to edit text in Rails. It includes an editor Trix. The editor is written in JavaScript, runs on the client (browser) side and draws a fairly friendly interface.
Creating the Model Used Below in the Examples Note
its controller, views (show/edit/new.html.erb), routing, and other basic actions related to this model are beyond the scope of this article.
Generated files
To get started with the module Action Text
install the required libraries with the command:
bin/rails action_text:install
After executing this command, you will see a lot of modified and generated files:
It would be nice to understand what’s going on. Read the files and find out:
1. Gemfile, Gemfile.lock
In the previous step, we installed the gem image_processing. This package is needed to process (compress, resize) and upload images to the added content. This gem uses either ImageMagick/GraphicsMagickor libvips.
IN Gemfile.lock
you can see the installed dependency libraries for image_processing
. Their need is described Here.
For this gem to work, I added to config/application.rb
line (from the description of the ActiveStorage::Variant class:
config.active_storage.variant_processor = :mini_magick
2.app/javascript/application.js
Imported JavaScript libraries here trix And @rails/actiontext.
3.config/importmap.rb
In this file, libraries and generated js-files are matched and given to the client in accordance with these names. Added JavaScript code for Trix
. IN DevTools
tab Debugger
you can see these matches. An example in the figure below, where
1 – imports,
2 – js – files,
3 – Trix editor in the browser.
Let me remind you that a command is used to generate compiled css / js files (see. The Asset Pipeline:
bin/rails assets:precompile
4. test/fixtures/action_text/rich_texts.yml – file with test data
5. app/assets/stylesheets/actiontext.css – default styles
File contents
.trix-content .attachment-gallery > action-text-attachment,
.trix-content .attachment-gallery > .attachment {
flex: 1 0 33%;
padding: 0 0.5em;
max-width: 33%;
}
.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,
.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,
.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
flex-basis: 50%;
max-width: 50%;
}
.trix-content action-text-attachment .attachment {
padding: 0 !important;
max-width: 100% !important;
}
It seems to be a regular css file, but we see that the element is used when generating html action-text-attachment
which is not in the list html elementsbut the html element is generated <action-text-attachment>
With CustomElementRegistry.
6.db/migrate/20230210142600_create_active_storage_tables.active_storage.rb
This file describes a migration class that creates tables prefixed active_storage_
:
* active_storage_blobs
* active_storage_attachments
* active_storage_variant_records
These tables are described in more detail in the documentation. Active Storage Overview.
File contents 20230210142600_create_active_storage_tables.active_storage.rb
# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
def change
# Use Active Record's configured type for primary and foreign keys
primary_key_type, foreign_key_type = primary_and_foreign_key_types
create_table :active_storage_blobs, id: primary_key_type do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.string :service_name, null: false
t.bigint :byte_size, null: false
t.string :checksum
if connection.supports_datetime_with_precision?
t.datetime :created_at, precision: 6, null: false
else
t.datetime :created_at, null: false
end
t.index [ :key ], unique: true
end
create_table :active_storage_attachments, id: primary_key_type do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
t.references :blob, null: false, type: foreign_key_type
if connection.supports_datetime_with_precision?
t.datetime :created_at, precision: 6, null: false
else
t.datetime :created_at, null: false
end
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
create_table :active_storage_variant_records, id: primary_key_type do |t|
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
t.string :variation_digest, null: false
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
private
def primary_and_foreign_key_types
config = Rails.configuration.generators
setting = config.options[config.orm][:primary_key_type]
primary_key_type = setting || :primary_key
foreign_key_type = setting || :bigint
[primary_key_type, foreign_key_type]
end
end
Here are written what fields will be created in the above tables, field restrictions on the uniqueness of primary and foreign keys. For example, in the table active_storage_attachments
fields are created:
name
with the restriction that it must not be empty,record_type,
record_id
with typebigint
which withrecord_type
are created with the optionpolymorphic: true
(cm. Active Record Migrations References),blob_id
with typebigint
The referring to the entryid
from the tableactive_storage_blobs
which has a foreign key constraint onblob_id,
a restriction is created on the uniqueness of the combination of fields
record_type
,record_id
,name
,blob_id
.
7.db/migrate/20230210142601_create_action_text_tables.action_text.rb
This file describes a migration class that creates tables prefixed action_text_
(there is only one table):
In this table in the field body
and the content will be stored as html. All links to images and files will be generated during the creation of this content and immediately embedded in the text.
File contents 20230210142601_create_action_text_tables.action_text.rb
# This migration comes from action_text (originally 20180528164100)
class CreateActionTextTables < ActiveRecord::Migration[6.0]
def change
# Use Active Record's configured type for primary and foreign keys
primary_key_type, foreign_key_type = primary_and_foreign_key_types
create_table :action_text_rich_texts, id: primary_key_type do |t|
t.string :name, null: false
t.text :body, size: :long
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
t.timestamps
t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
end
end
private
def primary_and_foreign_key_types
config = Rails.configuration.generators
setting = config.options[config.orm][:primary_key_type]
primary_key_type = setting || :primary_key
foreign_key_type = setting || :bigint
[primary_key_type, foreign_key_type]
end
end
After running the migration with the command bin/rails db:migrate
as a result, tables should appear in your database.
8.app/views/layouts/action_text/contents/_content.html.erb
The content of the _content.html.erb file
<div class="trix-content">
<%= yield %>
</div>
Inserted here body
which is saved as html, from a table action_text_rich_texts
.
9.app/views/active_storage/blobs/_blob.html.erb
The contents of the _blob.html.erb file
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
<% if blob.representable? %>
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
<% end %>
<figcaption class="attachment__caption">
<% if caption = blob.try(:caption) %>
<%= caption %>
<% else %>
<span class="attachment__name"><%= blob.filename %></span>
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
<% end %>
</figcaption>
</figure>
<!--Если хочется иметь возможность скачать файл, можно привести ссылку на файл.-->
<% if !blob.image? %>
<div>
<span class="attachment__download"><%= link_to blob.filename, rails_blob_path(blob) %></span>
<span class="attachment__size">(<%= number_to_human_size blob.byte_size %>)</span>
</div>
<% end %>
In DevTools, saved content with a picture looks like this:
In this html piece of the page, a preview of the file and an image are drawn, the data about which is stored in the table active_storage_blobs
.
The description for the picture loaded through the Trix editor is stored as part of the text in the table action_text_rich_texts
.
IN _blob.html.erb
I added the ability to download a file, as opposed to the default file.
How does our model instance communicate with the ActionText instance in the action_text_rich_texts table?
In the model, I write the association has_rich_text.
class Note < ApplicationRecord
has_rich_text :body
end
Then, when creating an instance in the controller, I add to the allowed parameters body
:
class NotesController < ApplicationController
def create
@note = Note.create!(note_params)
redirect_to note_path @note
end
private
def note_params = params.require(:note).permit(:title, :body)
end
Through has_rich_text
the corresponding method from module ActionText::Attribute
.
Code for this method from the repository rails
I read like this:
Methods are created for the model instance: predicate
body?
setter and getter for this field.Created association
rich_text_body
with class nameActionText::RichText
with field namename
.as: :record
– sign polymorphic connectionwhich was also seen in the migration when creating the tableaction_text_rich_texts
. This association is inverse, i.e. from a model instance ActionText::RichText you can get the value of this field through a method callrecord
.
I check the above in the console:
> note = Note.find(1)
=begin
<Note:0x00007efee9a06ce0
id: 1,
title: "РыбаТекст помогает животным",
hidden: false,
created_at: Sun, 19 Feb 2023 11:24:47.510481000 UTC +00:00,
updated_at: Sun, 19 Feb 2023 11:24:47.613920000 UTC +00:00>
=end
>
> note.body
=begin
<ActionText::RichText:0x00007efee9848688
id: 1,
name: "body",
body: #<ActionText::Content "<div class=\"trix-conte...">,
record_type: "Note",
record_id: 1,
created_at: Sun, 19 Feb 2023 11:24:47.564128000 UTC +00:00,
updated_at: Sun, 19 Feb 2023 11:24:47.583755000 UTC +00:00>
=end
>
> action_text_item = ActionText::RichText.find(1)
=begin
<ActionText::RichText:0x00007efee9848688
id: 1,
name: "body",
body: #<ActionText::Content "<div class=\"trix-conte...">,
record_type: "Note",
record_id: 1,
created_at: Sun, 19 Feb 2023 11:24:47.564128000 UTC +00:00,
updated_at: Sun, 19 Feb 2023 11:24:47.583755000 UTC +00:00>
=end
>
> action_text_item.record
=begin
<Note:0x00007efee9a06ce0
id: 1,
title: "РыбаТекст помогает животным",
hidden: false,
created_at: Sun, 19 Feb 2023 11:24:47.510481000 UTC +00:00,
updated_at: Sun, 19 Feb 2023 11:24:47.613920000 UTC +00:00>
=end
Altogether, it is clear that note.body
and find an instance ActionText::RichText
according to the well-known id
refer to the same entry, which indicates that the option inverse_of
works as expected.
How are files/drawings saved in posts?
When creating a new entry in the Trix editor, by clicking on the paperclip icon you can add a file. When you select one, for example, a picture, in the console where the server is running, you can see that a request is being made POST "/rails/active_storage/direct_uploads"
which is being processed ActiveStorage::DirectUploadsController#create with parameters:
{
"blob": {
"filename": "cat.jpg",
"content_type": "image/jpeg",
"byte_size": 107329,
"checksum": "JuFhMdR3g4JjS73owAJLQA=="
},
"direct_upload": {
"blob": {
"filename": "cat.jpg",
"content_type": "image/jpeg",
"byte_size": 107329,
"checksum": "JuFhMdR3g4JjS73owAJLQA=="
}
}
}
IN ActiveStorage::DirectUploadsController#create
going on preservation desired parameters of this file.
In the console it looks like this:
TRANSACTION (0.4ms) BEGIN
ActiveStorage::Blob Create (0.7ms) INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "service_name", "byte_size", "checksum", "created_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING "id" [["key", "ikydgdsgyrgupbqqh0wdqtgs75y4"], ["filename", "cat.jpg"], ["content_type", "image/jpeg"], ["metadata", nil], ["service_name", "minio"], ["byte_size", 107329], ["checksum", "JuFhMdR3g4JjS73owAJLQA=="], ["created_at", "2023-02-23 16:35:59.956614"]]
TRANSACTION (0.8ms) COMMIT
S3 Storage (45.7ms) Generated URL for file at key: ikydgdsgyrgupbqqh0wdqtgs75y4 (http://localhost:9000/main/ikydgdsgyrgupbqqh0wdqtgs75y4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20230223%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230223T163600Z&X-Amz-Expires=300&X-Amz-SignedHeaders=content-length%3Bcontent-md5%3Bcontent-type%3Bhost&X-Amz-Signature=696eaa54d2f921beaa552de1263fdb8b548708d12ebbdd3df6e97d7077dedeff)
By default, files are stored on the local drive, described as local
in file config/storage.yml
and configured by default. In this case service
will Disk
. Of value key
saved file, the file name and its folder are generated and all folders with these files are stored in the folder storage
.
When the content manager adds text, clicks on the Publish button and saves the entry. The link to the file is stored directly in the body rich_text
. An entry is made in the table active_storage_attachments
which maintains relationships between tables action_text_rich_texts
And active_storage_blobs
.
Table active_storage_attachments
:
in field
record_id
preservedid
records from the tableaction_text_rich_texts
,in field
record_type
the name of the module/class of the record from the table is savedaction_text_rich_texts
,in field
blob_id
preservedid
file/picture from tableactive_storage_blobs
,in field
name
the file/picture type is saved.
Total
Covered:
Description of the files generated by the command
bin/rails action_text:install
Tables created in the database, what data is stored in these tables.
Embedding the Trix editor in the desired page through an association
has_rich_text
.File saving method
AсtionText
default.
I’ll finish with this. Obviously, that’s not all. Next you need to do:
Selecting and configuring a service for saving files not locally.
Using a package
libvips
instead ofImageMagick
. WriteWhatlibvips
newer and faster.Saving multiple image options for different device screen sizes using a table
active_storage_variant_records
. In attribute has_many_attached :embeds classActionText::RichText
I have not foundvariant
.
Enjoy programming, friends!