自己紹介
わたしはエンジニアではなく、誰かに習ったこともなければ、学校でプログラミングを習ったこともないので、趣味の領域でググって出てくる情報だけで作っています。
(もちろん仕事としてエンジニアをしているわけでもないです。そのへんの学生です...)
たくさんアンチパターンを踏んでいると思いますので、ご指摘いただければと思います。
PC自作.comについて
https://pcjisaku.com/
では互換性のチェックをしながら自作PCのパーツリストを作れます。
もちろん最安値のチェックもありますし、cinebenchやpassmarkでのcpuやgpuのベンチマークスコア情報もあります。
ユーザー登録なしで複数のパーツリスト情報を保存できます。
パーツリストをもとにブログのように自分の自作PCについて記事を書くことができます。
全体の構成
バックエンドは
nginx → unicorn → rails
という形です。
フロントエンドはvueです。
docker
docker-composeでunicornを走らせるrailsコンテナとnginxコンテナとredisコンテナを立ち上げます。
railsの方のDockerfileでは
画像をwebpに変換したいので、そのあたりのライブラリを追加しています。
RUN apt-get install -y imagemagick libjpeg-dev libpng-dev libtiff-dev libwebp-dev webp
gemもキャッシュしています。
rails
次にrailsの大まかな設計に移ります。
テーブル設計
Productテーブル
productsテーブルに全商品を入れています。
product_categoriesテーブルには20程度のカテゴリを保存しています。CPUやGPUなどです。
商品のスペック情報は1. 選択肢から選ぶスペック情報 と 2. テキストや数値などの生情報のスペック情報を別テーブルで保存しています。
Userテーブル
ユーザー情報
has_many :part_lists
has_many :comments
has_many :likes
has_many :builds
Partlistテーブル
has_many :part_list_products, dependent: :destroy
has_many :products, through: :part_list_products
has_many :builds
has_many :comments
has_many :likes
CommentテーブルやLikeテーブル
commentテーブルやlikeテーブルでコメントやライクを管理していますが、
あらゆるテーブルに対してcommentやlikeできるようにpolymorphicを使っています。下のコードみたいな感じです。
class Like < ApplicationRecord
belongs_to :likable, polymorphic: true # 親が削除されたら一緒に削除される
belongs_to :user, optional: true # likeはユーザーが削除されても残してあげる。
end
ProductShopテーブル
商品(product)の販売店舗情報を保存しています。
Railsに当てているパッチ(webp使うため)
active_storageの_blob.html.erb
<figure class="text-left my-2 attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
<% if blob.representable? %>
<picture>
<source type="image/webp" srcset="<%= blob.representation(define: 'webp:lossless=false', convert: 'webp', resize_to_limit: [ 800, 600 ]).processed.url %>">
<img src="<%= blob.representation(quality: 55, resize_to_limit: [ 800, 600 ]).processed.url %>">
</picture>
<% end %>
<figcaption class="attachment__caption">
<% if caption = blob.try(:caption) %>
<%= caption %>
<% end %>
</figcaption>
</figure>
actiontextのためにinitializerで
上4行以外のところでは
https://github.com/rails/rails/blob/157920aead96865e3135f496c09ace607d5620dc/activestorage/app/models/active_storage/variant.rb
このvariant.rbにパッチを当てています。
ActionText::ContentHelper.allowed_attributes.add 'type'
ActionText::ContentHelper.allowed_attributes.add 'srcset'
ActionText::ContentHelper.allowed_tags.add 'picture'
ActionText::ContentHelper.allowed_tags.add 'source'
class ActiveStorage::Variant
private
def specification
blob_content_type = variation.transformations[:convert] == "webp" ? "image/webp" : blob.content_type
@specification ||=
if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
Specification.new \
filename: blob.filename,
content_type: blob_content_type,
format: variation.transformations[:convert] == "webp" ? "webp" : nil
else
Specification.new \
filename: ActiveStorage::Filename.new("#{blob.filename.base}.png"),
content_type: "image/png",
format: "png"
end
end
end
actiontext content
==の定義がto_sになっているとactiontextのcontentのupdateの際に毎回renderされてしまいます。
それだと非常に時間がかかるのでto_htmlでrender _layoutさせていません。
module ActionText
class Content
def ==(other)
if other.is_a?(self.class)
to_html == other.to_html
end
end
end
end
actiontextの問題
actiontextでは、画像のdirect uploadの際にそのフォーマットの生画像だけ保存して終わりです。その際にvariant変換(resizeやwebpへの変換,cropなど)は行ってくれません。
これだと10枚の画像を一度に投稿された際 or 保存後はじめてそのコンテンツが表示された際に、10枚の画像のvariantの変換作業が走るので表示が遅れます。
そのため、direct uploadの直後に必ず走るActiveStorage::BlobsController showメソッドのところで
if @blob.created_at > DateTime.now - 1.minute && WEB_IMAGE_CONTENT_TYPES.include?(@blob.content_type)
@blob.variant(define: 'webp:lossless=false', convert: 'webp', resize_to_limit: [ 800, 600 ]).processed
@blob.variant(quality: 55, resize_to_limit: [ 800, 600 ]).processed
end
変換処理を呼び出しています。
1分以内にアップロードされたものに対してだけかけてあげています。
CMSについて
pc自作.comではcmsがあります。
パーツの選び方の記事や、おすすめのパーツリストなどを表示するブログ作成のためです。
cmsはspina cmsをベースにしていますが、実際のproductsテーブルとrelationを作るためにgemとして使うのではなく、すべて本体に移植しています。
trix.js editorについて
railsだとactiontextとtrix editorのセットっぽかったので、それを使いましたが、正直後悔しかありません。生のhtmlで書けないからです。
もちろんそのほうが安全ではあるわけですが、独自のタグであったり、iframe埋め込みなどをカスタマイズしようと思うと非常に面倒でした。
trix.jsのコードをほぼすべて読んで、パッチを当てまくっています。
ckeditorのほうがよっぽど使いやすいです。
Vueについて
とくに言うことはありませんが、vueを使っているページと使わないページがあります。
それはユーザー動線を考えて、シームレスにヌルサクで動いて欲しいところ(パーツリストを作成している画面)はvueを使い、googleの検索(「動画編集 自作pc」など)からやってくるであろうユーザーに対して見せる記事のページではvueを使わずに静的なファイルなげています。
これはもちろんseoのためです。
vuexでパーツリストを管理しています。
デザインパターンはatomic designのしょぼい版みたいな感じです。
fontawesome の軽量化
https://icomoon.io/app/#/select
に import icons で fontawesome の svg をアップロードする。
必要なものを pickup してダウンロードする
style.css を fontawesome.css に名前を変更。
fontawesome のベース(回転や大きさなどに対応)を使うために svg-with-js.min.css を fontawesome の本体から持ってくる。
fontawesome.css にライセンスとdisplay: inline-block;
を加える
[class^="fa-"],
[class*=" fa-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: "icomoon" !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
display: inline-block;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
を追記する。