U-Yuri’s 健忘録

U-Yuri’s 備忘録

プログラミングを勉強しています。アウトプットに活用しているブログです。

【Rails】Active Storageを使用し、画像投稿のフォームを作成する

セットアップ

% rails active_storage:install
Copied migration 20240327082510_create_active_storage_tables.active_storage.rb from active_storage
% rails db:migrate
== 20240327082510 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs, {:id=>:primary_key})
   -> 0.0065s
-- create_table(:active_storage_attachments, {:id=>:primary_key})
   -> 0.0037s
-- create_table(:active_storage_variant_records, {:id=>:primary_key})
   -> 0.0020s
== 20240327082510 CreateActiveStorageTables: migrated (0.0126s) ===============

Active Storageが利用する3つのテーブルが作成される。

Active Storageのサービスはconfig/storage.ymlで宣言する。

ローカルDiskサービスをdevelopment環境(開発環境)で使うには、config/environments/development.rbに以下を記述。 (デフォルトはこの設定なので変更する必要なし)

config.active_storage.service = :local
#ファイルをローカルに保存する。

上記の:localconfig/storage.yml内のlocal:の箇所。

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

amazon:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  bucket: ""
  region: "" # 例: 'ap-northeast-1'

amazon s3を使用するのであれば、:amazonとする。

ファイルをレコードに添付する

has_one_attached

レコードとファイルの間に1対1のマッピングを設定します。レコード1件ごとに1個のファイルを添付できる。
今回wantモデルに一件の画像を追加したいので、app/models/want.rbhas_one_attached :imageを追記。

class Want < ApplicationRecord
  belongs_to :category
  belongs_to :user
  has_one_attached :image
end

*もしまだモデルを作成していない場合は、$ rails generate model Want image:attachmentを実行。
(今回は既にモデルが作成されているので直接app/models/want.rbhas_one_attached :imageを追記)

<%= form.file_field :image %>で書くことで画像付きのwantを作成できます。

<%= form_with url: wants_path, model: @wants do |form| %>
   <select name="category" >
      <% @categories.each do |category| %> 
          <option value= <%= category.id %> ><%= category.name %></option>
      <% end %>
    </select>

    <%= form.file_field :image %>
    
    <div><%= form.label :商品名 %> <%= form.text_field :name %></div>
    <div><%= form.label :金額 %> <%= form.text_field :money %></div>
    <div><%= form.submit "新規登録" %></div>
  <% end %>

app/controllers/wants.controller.rbprivatewant_params:imageを追加。

private

  def want_params
    params.require(:want).permit(:name, :money, :category_id, :image)
  end

app/controllers/wants_controller.rbWant.create!を変数に入れ、want.image.attach(params[:image])を追記。

def create
    want = Want.create!(
      name: params[:name],
      money: params[:money],
      user_id: current_user.id,
      category_id: params[:category]
    )

    want.image.attach(params[:image])

    redirect_to "/wants"
  end

scheema.rbを削除しrails db:resetrails db:migrate
その後画像をアップロードし% tree storageで画像が保存されているか確認。

% tree storage 
storage
├── m7
│   └── hy
│       └── m7hyoyzztbvzqu5ndqitbbzqqh66
├── py
│   └── 15
│       └── py15yatuw28y0o7yrrkliedqyi62
└── zf
    └── 4l
        └── zf4ll26t6asw1t6bz5lq91argxdn

7 directories, 3 files

rails dbselect * from active_storage_attachments;select * from active_storage_blobs;を確認。
$ tree storageでフォルダに格納されているか確認。

ファイルを表示する

ファイルを表示する方法は2種類ある。
1. リダイレクトモード(※今回はこちらで実行する)
2. プロキシモード

リダイレクトモード

表示したいerbファイルに以下のいずれかを記述することで表示可能。

# どちらでも良い
<img src="<%= url_for(want.image) %>" >

<%= image_tag url_for(want.image) %>

画像のリサイズ

image_processingのgemをインストールする

Gemfileのgem "image_processing", "~> 1.2"コメントアウトを外し、$ bundle installでインストール。
表示したいviewファイルを編集(今回はmy-page.html.erb)

<%= image_tag want.image.variant(resize_to_limit: [100, 100])%>

& brew vipsvipsをインストール

【Git】オリジナルコマンド作成

git のaliasの後にオリジナルコマンドを入力、その後元のコマンドを入力し実行。

% git config --global alias.co checkout

以下の方法で~/.gitconfig[alias]部分に直接書き込むことも可能。

macuser@MacUser-no-MacBook-Air Pokeca % cat ~/.gitconfig
[user]
    name = yuriririn
    email = 80314978+yuriririn@users.noreply.github.com
[filter "lfs"]
    required = true
    clean = git-lfs clean -- %f
    smudge = git-lfs smudge -- %f
    process = git-lfs filter-process
[color]
    ui = auto
[core]
    editor = code --wait
[init]
    defaultBranch = main
[alias]
    co = checkout

【rails】複数の外部キーを設定しrubyでテストする方法

①テーブルの作成

$ rails g model Pokemon sinka:string name:string special:string hp:integer zokusei:string

rails db:migrateでmigrateファイルを作成 ③db/migrate/~_create_pokemons.rbを編集

class CreatePokemons < ActiveRecord::Migration[7.0]
  def change
    create_table :pokemons do |t|
      t.string :sinka
      t.string :name
      t.string :special
      t.integer :hp
      t.string :zokusei
      t.references :tokusei, foreign_key: true
      t.integer :skill1_id, index: true
      t.integer :skill2_id, index: true

      t.timestamps
    end
    add_foreign_key :pokemons, :skills, column: :skill1_id
    add_foreign_key :pokemons, :skills, column: :skill2_id
  end
end

※t.referencesはカラム作成+外部キーを作成するが、カラムの名前を選ぶことができない(つまり複数作成できない)ので、t.integerを使い、add_foreign_keyを設定する必要がある。

add/models/~.rbを編集

class Pokemon < ApplicationRecord
  belongs_to :skill1, class_name: 'Skill', foreign_key: :skill1_id
  belongs_to :skill2, class_name: 'Skill', foreign_key: :skill2_id, optional: true
  belongs_to :tokusei
end

belongs_to :skill1はクラス名ではないので、 class_name: 'Skill'でクラス名を指定してあげる。 ※skill2はnullの可能性もあるので、optional: true でnullでもOKの設定を追記(これを書かないとdb:reset時に以下のようなエラーになる)。
エラー画面

% rails db:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
rails aborted!
ActiveRecord::RecordInvalid: Validation failed: Skill2 must exist
/Users/macuser/PokeCa/Pokeca/db/seeds.rb:96:in `<main>'
Tasks: TOP => db:reset => db:setup => db:seed
(See full trace by running task with --trace)

add/models/skill.rbを編集

class Skill < ApplicationRecord
  has_many :pokemons, foreign_key: :skill1_id
  has_many :pokemons, foreign_key: :skill2_id
end

⑥seeds.rbを編集

Pokemon.create!(
  name: 'ビッパ',
  sinka: 'たね',
  zokusei: '無色',
  hp: '60',
  special: nil,
  tokusei_id: '2',
  skill1_id: '3',
  skill2_id: nil
)

※空白は『''』より『nil』の方が良い。

⑦コードが動くかrails consoleで確認

irb(main):002> p = Pokemon.find_by(name:'ビッパ')
  Pokemon Load (0.2ms)  SELECT "pokemons".* FROM "pokemons" WHERE "pokemons"."name" = ? LIMIT ?  [["name", "ビッパ"], ["LIMIT", 1]]
=> 
#<Pokemon:0x000000010ae44fc0
...
irb(main):003> p.skill1
  Skill Load (0.2ms)  SELECT "skills".* FROM "skills" WHERE "skills"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
=> 
#<Skill:0x000000010b20b8e8
 id: 3,
 name: "ひっさつまえば",
 koka: "コインを1回投げウラなら、このワザは失敗。",
 attack_point: 30,
 symbol: "",
 created_at: Fri, 02 Feb 2024 06:27:38.514466000 UTC +00:00,
 updated_at: Fri, 02 Feb 2024 06:27:38.514466000 UTC +00:00>
irb(main):004> 

確認完了。

■has_manyの設定(記載し忘れていたので追記) app/models/skill.rbhas_manyを設定

class Skill < ApplicationRecord
  has_many :pokemons1, class_name: 'Pokemon', foreign_key: :skill1_id
  has_many :pokemons2, class_name: 'Pokemon', foreign_key: :skill2_id
end

※複数でない場合は'foreign_key:'のデフォルトで良いので設定は必要なく'has_many :pokemons'だけでいい。 今回は複数なのでhas_many :pokemons1と設定しclass_name: 'Pokemon'で指定する必要がある。

■テストする方法 ①$ rails db:reset実行 ②$ rails consoleirbを開く ④以下を実行

irb(main):009> p = Deck.find_by(name:'ゲンガーデッキ')
  Deck Load (0.2ms)  SELECT "decks".* FROM "decks" WHERE "decks"."name" = ? LIMIT ?  [["name", "ゲンガーデッキ"], ["LIMIT", 1]]
=> 
#<Deck:0x00000001124e28d0
...
irb(main):010> p.card_in_decks
  CardInDeck Load (2.7ms)  SELECT "card_in_decks".* FROM "card_in_decks" WHERE "card_in_decks"."deck_id" = ?  [["deck_id", 1]]
=> 
[#<CardInDeck:0x0000000111b95d90
  id: 1,
  deck_id: 1,
  pokemon_id: 1,
  item_id: nil,
  support_id: nil,
  pokemon_no_item_id: nil,
  stajiamu_id: nil,
  energy_id: nil,
  created_at: Sat, 03 Feb 2024 05:13:25.184044000 UTC +00:00,
  updated_at: Sat, 03 Feb 2024 05:13:25.184044000 UTC +00:00>,
 #<CardInDeck:0x0000000111c37a28
  id: 2,
  deck_id: 1,
  pokemon_id: nil,
  item_id: 2,
  support_id: nil,
  pokemon_no_item_id: nil,
  stajiamu_id: nil,
  energy_id: nil,
  created_at: Sat, 03 Feb 2024 05:13:25.193452000 UTC +00:00,
  updated_at: Sat, 03 Feb 2024 05:13:25.193452000 UTC +00:00>,
 #<CardInDeck:0x0000000111c37910
  id: 3,
  deck_id: 1,
  pokemon_id: nil,
  item_id: nil,
  support_id: 1,
  pokemon_no_item_id: nil,
  stajiamu_id: nil,
  energy_id: nil,
 

【sqlite3】元々あるテーブルに追加して外部キーを設定していく方法

■環境
rails
・sqlite3
・あらかじめ外部キーを設定したいテーブルは作成済み

①テーブルを作成(カラムに何も入れずテーブルだけ作成)

$ rails generate model Card_in_deck 

$rails db:migrateでテーブルを作成

ーーーーーーーーーーーFKの設定ーーーーーーーーーーー
③作成したテーブルにカラムを追加する

$ rails generate migrate AddPokemonNoItemsToCardInDeck

db/migrate/~~~~.rbファイルに異kのように外部キーの設定をする

class AddPokemonNoItemsToCardInDeck < ActiveRecord::Migration[7.0]
  def change
    add_reference :card_in_decks, :pokemon_no_items, foreign_key: true
  end
end

※もし上記を書く際間違えた場合は、書き直してdb/schema.rbを削除、rails db:reset実行、rails db:migrateを実行
add_referenceの部分はadd_columnになっていると思うので変更し、2つ目のカラム名もテーブル名に変更する。
type: integerはデフォルトでいいので書かなくて良い。
※また、IDがNULLになって欲しくない場合は、null: falseforeign_key: true,の後に追加。
→テーブルのschemaにnut nullを追加する設定。
$ rails db:migrate
sqlite> .schema card_in_decksでカラムが作成されたか確認。

sqlite> .schema card_in_decks
CREATE TABLE IF NOT EXISTS "card_in_decks" (
"id" integer NOT NULL PRIMARY KEY, 
"created_at" datetime(6) NOT NULL, 
"updated_at" datetime(6) NOT NULL, 
"pokemon_no_items_id" integer DEFAULT NULL, 
CONSTRAINT "fk_rails_e40888835a"FOREIGN KEY ("pokemon_no_items_id")  REFERENCES "pokemon_no_items" ("id")
Us
CREATE INDEX "index_card_in_decks_on_pokemon_no_items_id" ON "card_in_decks" ("pokemon_no_items_id");

※↑見やすいように改行した!
外部キーが作成されているのでOK

【code】ターミナルでVScodeを開く

いつもあるタイミングでcodeコマンドが使えなりその度検索するのでここに記録する。

■環境 ・mac ・ターミナル

■手順 ①VScode上でmac: shift + command + PWindowsではShift Ctrl + P) ②検索バーから「code」を検索しインストール

解決!

参考:ターミナルからVScodeを起動させる方法 | logsuke

⑤Railsアプリをデプロイするまで【SSL】【Let’s Encrypt】

はじめに

RailsアプリをさくらVPSにデプロイし、ドメインを当てるところまでできたので、今度はLet’s Encryptを使用しSSL化していきます。

環境

Let's Encrypt から 発行料 無料の SSL/TLS サーバー証明書を取得する

  • $ sudo apt-get -y install certbot

  • $ sudo nginx -t

$ sudo nginx -t

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
  • ブラウザで確認(なにもエラーが起きないかどうか)
  • certbotでドキュメントルート(/var/www/html)内に認証用ディレクトリ(.well-known/)の中にファイルが生成される。
$ ls /var/www/html

index.nginx-debian.html
  • 接続できるかテストしたいので一度.well-known/を作成しその中に適当なファイルを作成する。
$ vim /var/www/html/test.html
  • /etc/nginx/nginx.confの中にもう一つlocation /.well-knownのrootを作成する(ドメイン/.well-known/test.htmlで接続する設定)。
$ vim /etc/nginx/nginx.conf

 server {
                listen 0.0.0.0:80;
                server_name 〇〇.studio;
                proxy_set_header X-Forwarded-Host $host;
                location / {
                        proxy_pass http://127.0.0.1:3000;
                }
#以下追加
                location /.well-known {
                        root /var/www/html;
                }
        }
  • 設定できたかどうかブラウザで確認(ドメイン/.well-known/testでtestの内容が表示されるはず)

    成功!

参考:nginx連載5回目: nginxの設定、その3 - locationディレクティブ - インフラエンジニアway - Powered by HEARTBEATS

$ sudo certbot --nginx

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel):〇〇@gmail.com(自分のメールアドレスを入力)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y(上記を読んでくださいという内容なのでyes)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: n(メール送られたくないのでnoにする)
Account registered.

Which names would you like to activate HTTPS for?
We recommend selecting either all domains, or all domains in a VirtualHost/server block.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: 〇〇.studio(自分のメールアドレスが設定されている)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1(選択肢が一つしかないので1を選択)
Requesting a certificate for 〇〇.studio

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/〇〇.studio/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/〇〇.studio/privkey.pem
This certificate expires on 2023-12-12.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for 〇〇.studio to /etc/nginx/nginx.conf
Congratulations! You have successfully enabled HTTPS on https://〇〇.studio

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  • ブラウザでドメインを確認してみる

    SSL化できている

  • /etc/nginx/nginx.confの中を確認

server {
                server_name〇〇.studio;
                proxy_set_header X-Forwarded-Host $host;
                location / {
                        proxy_pass http://127.0.0.1:3000;
                }
                location /.well-known/ {
                        root /var/www/html;
                }

# ここより下が追加されている
                listen 443 ssl; # managed by Certbot
                ssl_certificate /etc/letsencrypt/live/〇〇.studio/fullchain.pem; # managed by Certbot
                ssl_certificate_key /etc/letsencrypt/live/〇〇.studio/privkey.pem; # managed by Certbot
                include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
                ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
        }

        server {
                if ($host = 〇〇.studio) {
                        return 301 https://$host$request_uri;
                } # managed by Certbot

                listen 0.0.0.0:80;
                server_name 〇〇.studio;
                return 404; # managed by Certbot
        }
  • 動作確認するとログインや新規登録ができない!!!!
  • $ tail -f log/production.logコマンドでlog確認
---------省略---------------
W, [2023-09-15T14:00:33.173199 #46179]  WARN -- : [867f933e-1d32-4d2e-844b-a3d79f797096] HTTP Origin header (https://〇〇.studio) didn't match request.base_url (http://〇〇.studio)
I, [2023-09-15T14:00:33.173449 #46179]  INFO -- : [867f933e-1d32-4d2e-844b-a3d79f797096] Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms | Allocations: 371)
F, [2023-09-15T14:00:33.174100 #46179] FATAL -- : [867f933e-1d32-4d2e-844b-a3d79f797096]
[867f933e-1d32-4d2e-844b-a3d79f797096] ActionController::InvalidAuthenticityToken (HTTP Origin header (https://〇〇.studio) didn't match request.base_url (http://〇〇.studio)):
---------省略---------------

この中の、ここ!

ActionController::InvalidAuthenticityToken (HTTP Origin header (https://〇〇.studio) didn't match request.base_url (http://〇〇.studio)):

※HOSTとoriginはHTTPSとHTTPで違う!!!!というような内容。

  • /etc/nginx/nginx.confHTTPSからHTTPにリダイレクトする記述を追加(proxy_set_header X-Forwarded-Proto $schemeを追加する)
 server {
                server_name memo.uezono.studio;
                proxy_set_header X-Forwarded-Host $host;
                proxy_set_header X-Forwarded-Proto $scheme;
                location / {
                        proxy_pass http://127.0.0.1:3000;
                }
                location /.well-known/ {
                        root /var/www/html;
                }

参考:Nginx のリバースプロキシ設定のメモ - Qiita

  • $ sudo systemctl reload nginxコマンドでnginxをリロードし、ブラウザで確認

以上です! お疲れさまでした。

④Railsアプリをデプロイするまで【ドメインを当てる】

はじめに

前回の続きです。 前回はGoogleドメインサブドメインを作成しました。 今回はデプロイしたRailsアプリにサブドメインを当てていく作業です。

環境

  • Rails
  • さくらVPSDebian
  • nginx
  • SQLite3

  • $ sudo vim /etc/nginx/nginx.conf 内のserver_nameを欲しいドメインに書き換える

 server {
                listen 0.0.0.0:80;
                server_name 〇〇.com;
                proxy_set_header X-Forwarded-Host $host;
                location / {
                        proxy_pass http://127.0.0.1:3000;
                }
        }
  • ブラウザを開いてみる。

    エラー

  • $ tail -f log/production.logコマンドでlogを見ながらもう一度ブラウザを開いてみる

$ tail -f log/production.log

----省略-----

I, [2023-09-12T17:34:24.745938 #37719]  INFO -- : [99903f48-ccea-466d-8a71-94dbc2d5caed] Started GET "/" for 127.0.0.1 at 2023-09-12 17:34:24 +0900
F, [2023-09-12T17:34:24.746355 #37719] FATAL -- : [99903f48-ccea-466d-8a71-94dbc2d5caed]
[99903f48-ccea-466d-8a71-94dbc2d5caed] ActionController::RoutingError (No route matches [GET] "/"):
[99903f48-ccea-466d-8a71-94dbc2d5caed]

RoutingErrorなのでおそらくRails側のRootの設定なのでは? (ちなみに〇〇.com/my_memosであれば接続可能だが、パスは含めないで接続できるようにしたい。)

  • config/routes.rbを見てみる
ails.application.routes.draw do
  devise_for :users

  devise_scope :user do
    get '/users/sign_out' => 'devise/sessions#destroy'
  end

  

  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Defines the root path route ("/")
  # root "articles#index"
  resources :my_memos, only: [:index, :new, :create, :edit, :update, :destroy, :show]
end
 

rootはドメイン/my_memoなので設定が必要。

  • config/routes.rbコメントアウトされている# root "articles#index"で設定するようなのでほしいrootに変更する。
root "my_memos#index"

〇〇.comへのリクエストが来たらmy_memos#indexにリダイレクトする。

これでドメインを当てる作業は終了。

まとめ

$ sudo vim /etc/nginx/nginx.confconfig/routes.rbだけを変更するのみでできました!