PC自作.comの構成

# 自己紹介
わたしはエンジニアではなく、誰かに習ったこともなければ、学校でプログラミングを習ったこともないので、趣味の領域でググって出てくる情報だけで作っています。
（もちろん仕事としてエンジニアをしているわけでもないです。そのへんの学生です...）
たくさんアンチパターンを踏んでいると思いますので、ご指摘いただければと思います。

# 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テーブル
ユーザー情報

```ruby:user.rb
has_many :part_lists
has_many :comments
has_many :likes
has_many :builds
```

#### Partlistテーブル

```ruby:partlist.rb
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を使っています。下のコードみたいな感じです。

```ruby:like.rb
class Like < ApplicationRecord
  belongs_to :likable, polymorphic: true # 親が削除されたら一緒に削除される
  belongs_to :user, optional: true # likeはユーザーが削除されても残してあげる。
end
```

#### ProductShopテーブル
商品(product)の販売店舗情報を保存しています。

### Railsに当てているパッチ（webp使うため)
#### active_storageの_blob.html.erb
```erb:_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で
上４行以外のところでは
https://github.com/rails/rails/blob/157920aead96865e3135f496c09ace607d5620dc/activestorage/app/models/active_storage/variant.rb
このvariant.rbにパッチを当てています。

```ruby:actiontext.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させていません。

```ruby
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メソッドのところで

```ruby
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;
}
```

を追記する。