0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Ruby on Rails】AWSのcloud9(無料利用枠)でRuby on Railsを使う方法

Last updated at Posted at 2020-08-25

【Ruby on Rails】AWSのcloud9(無料利用枠)でRuby on Railsを使う方法

個人用メモです。

Ruby on Railsの基本知識から、アプリケーションの作成まで。
サーバーはAWSのcould9を無料枠内で使用。

目次

  1. Ruby on Railsの設計思想
    2. MVCアーキテクチャ
    3. CoC
    4. DRY
    5. RESTful
  2. AWS事前準備
    6. AWS cloud9とは?
    7. cloud9の料金目安
    8. AWSの注意事項
    9. IAMの設定
    10. ルートユーザーとIAMユザーの違い
    11. IAMの設定手順
    12. 請求アラートの設定
  3. cloud9のセットアップ
  4. Ruby on Railsでプロジェクト作成
  5. No connection poolエラー発生した場合の対応
  6. 画面にhello worldを表示する
  7. 電話帳アプリの開発
    18. 電話帳アプリの機能
    19. アプリ用プロジェクトの作成
    20. modelの作成とmigrate
    21. migrateファイルとは?
    22. controllerの作成
    23. アクションとは?
    24. routeファイルの編集
    25. resources : usersの中身
    26. ページの作成
    27. users一覧表示画面の作成
    28. 変数の前のアットマークの役割は?
    29. <% %><%= %>の違い
    30. ユーザー新規登録ページの作成
    31. form_forとは?
    32. link_toとは?
    33. バリデーション機能の作成
    34. 編集機能の追加
    35. 詳細ページの作成
    36. 共通の処理をまとめる

## Ruby on Railsの設計思想
  1. MVCアーキテクチャ
  2. CoC
  3. DRY
  4. RESTful

MVCアーキテクチャ

ソフトウェア開発の設計思想の一つ。

・Model:データ操作(DBとの連携)
DBは通常SQLで操作するが、RailsではRubyで操作する。
DB-SQLの関係をDB-rubyに読み替えたものがmodel。DBの構造を表すため設計書として解説されている。

・View:画面表示
最終的に画面に表示するファイル。

・Controller:ModelとViewの仲介
ユーザーからのリクエストを受け取り、内容に応じてmodelやviewを操作する。

image.png


<MVCを用いた代表的なフレームワーク> Ruby on Rails(Ruby) Laravel(PHP) Cake PHP(PHP)

CoC

Convention over Configuration
「設定より規約」という設計思想。

通常必要なフレームワークの設定を、決められたルールに則って行うことで面倒な作業を省く。

DRY

Don't Repeat Yourself.
同じ記述を繰り返さないという、コーディング時の思想。

  • 重複は無駄
  • 関連記述は一箇所にまとめる

RESTful

ルーティングで用いられる考え方の一つ。

RESTな設計思想に基づいたWEBサービスのこと。

RESTとは?

  • REpresentational State Transferの略。
  • 同じURLやパラメータの組み合わせから、常に同じ結果が返る。
  • システムの状態やセッションに依存しない。

## AWS事前準備 AWSのcloud9を使用するための予備知識および、事前設定について。

AWS cloud9とは?

  • クラウドベースの統合開発環境 (IDE)
  • ブラウザのみでコードを記述、実行、デバッグできる
  • コードエディタ、デバッガー、ターミナルが含まれrる
  • JavaScript、Python、PHP などの一般的なプログラム言語に不可欠なツールがあらかじめパッケージ化済み
  • ファイルをインストールしたり、開発マシンを設定したりする必要がない。

cloud9の料金目安

無料枠内の利用では1年間、750時間分が対象。
無料期間終了後の目安はデフォルト設定で、月約200円。

  • 30分間の自動休止状態。
  • 1ヶ月に20日間、1日あたり4時間IDEを実行する場合。
  • 上記条件で90時間の使用を想定。

AWSの注意事項

▼アカウント管理

  • アカウント情報は厳重管理
  • ID、PW、secret key
  • 不正利用されると高額請求がくる場合がある

▼課金

  • 利用しなくなったサーバーは停止する
  • 従量課金サービス
  • ログインしていなくても、サーバーが停止していなければ料金が発生する
  • Cloud9は30分間利用しないと自動OFFになる設定がデフォルト
  • 請求アラートの設定をしておく
  • 無料期間は750時間

### IAMの設定 #### IAM(アイアム)とは?
  • AWS Identity and Access Managementの略
  • 安全にアクセスするためのサービス
  • ユーザーのアクセス権限管理

ルートユーザーとIAMユザーの違い

・ルートユーザー
すべてのリソースへのアクセス権限をもつため、日常的に利用しない。
請求書周りの確認はルートユーザーで行う。

・IAMユーザー
IAMで権限が制限されたユーザー。日常的に使う。

IAMの設定手順

  1. AWSにサインインし、検索窓でIAMを検索。
  2. ユーザー → ユーザーを追加

image.png

  1. ユーザー名、AWSマネジメントコンソールへのアクセス、コンソールのPWは自動生成、パスワードのリセットにチェック。

image.png

  1. グループの作成

image.png

  1. グループ名(任意)を設定し、AdministratorAccessにチェック。

image.png

  1. 次のステップ:タグをクリック

image.png

  1. タグの追加オプション
    未入力で次のステップ:確認をクリック
    image.png

  2. 内容を確認し、ユーザーの作成をクリック
    image.png

  3. csvのダウンロード
    認証情報をダウンロードする。Eメール送信も可能。
    DLなしで次の画面に遷移してしまうと、現在のユーザーで二度とログインできなくなってしまうので注意。

image.png

10)DLしたファイルを表計算ソフトで開く
ExcelやVScode(Excel Viewer)など。

  • user name
  • password
  • Console login link(URL)
  1. IAMでコンソールにログインする。
    URLをクリックし、user nameとPWを入力
    image.png

※アカウントIDは入力済み。

  1. IAMユーザーでコンソールにログイン完了
    アカウント名が設定したIAMユーザー名になっている。

image.png


## 請求アラートの設定 1) ルートアカウントでAWSコンソールにログイン。 2) billingにアクセスする ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563526/210fe47e-8249-c861-0030-495484412f63.png)
  1. billingの設定をクリック
    image.png

  2. 「無料利用枠の使用アラート受信」と「請求アラートを受け取る」にチェックを入れる

・PDF請求書の受け取り:任意
・Eメールアドレス:任意(アドレス追加したい場合に入力)

image.png

  1. 設定の保存で完了

## cloud9のセットアップ 1) IAMユーザーでログイン 2) could9を検索しクリック

※下記エラーが出る場合は、IAMグループ(またはユーザー)に正しく権限が付与されていない。

対処方法:ルートユーザーでログインしIAMを設定し直す。

image.png

  1. create environmentを選択
    image.png

※ルートユーザーでのcloud9作成は非推奨

  1. Could9の名前(任意)を設定。
    image.png

  2. デフォルト設定で次へをクリック
    image.png

  3. 内容を確認し、create environmentをクリック

  • t2.micro(無料利用枠のサーバー性能)
  • After 30 minutes(自動シャットダウン設定)

image.png

  1. cloud9セットアップ完了(約2分)
    image.png

画面の色やレイアウトはViewから変更可能。

  1. rubyのバージョン確認
    ターミナルでruby -vを実行。

image.png

  1. cloud9の終了
    タブを閉じれば、30分後に自動終了になる。

  2. cloud9の再開
    検索窓でcould9を検索&クリックし、Open IDEをクリック。
    image.png


## Ruby on Railsでプロジェクト作成 1)「hello」というプロジェクトを作成する。

rails new プロジェクト名

image.png

  1. 作成したプロジェクトに移動
    cd プロジェクト名

  2. フォルダの内容を確認する
    ls

image.png

  1. Railsサーバーの立ち上げ
    rails s -b 0.0.0.0

  2. Preview→ Preview Running Applicationをクリック

image.png


No connection poolエラー発生した場合の対応

下記エラーが発生した場合は、Gemのsqliteの設定を明示する必要がある。

image.png

5-1) Gemfileを開く
Gemfile: rubyのライブラリ(gem)を管理しているファイル。

image.png

5-2) gem sqliteに追記し、保存する。

# gem 'sqlite3' 元の状態
gem 'sqlite3', '~> 1.3.6'

5-3) ターミナルでbundle updateを実行する。
サーバーが立ち上がってる場合はctrl+cでストップ。

5-4) ターミナルでdbを立ち上げるrails db:create
image.png


  1. 表示されたURLをコピーし、Chromeの新しいタブに貼り付け
    image.png

cloud9上のブラウザでは接続が拒否されましたと出るが、別ウィンドウで開くと接続できる。

image.png

Ruby on Railsの主要ファイル

  1. controllers
  2. models
  3. views
  4. routing

公式ページのフォルダ構造の説明

1. controllers

image.png

1. models

image.png

2. views

image.png

3. routing

image.png


## 画面にhello worldを表示する applicationのアクションを使って、画面上に指定したテキストを表示する。

MVCモデルのMC動作確認用。(この段階でViewは未使用)

  1. controllersのapplication_controller.rbを開く
  2. helloアクションを定義する

render plain:"文字列"

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  
  #追記
  def hello
    #   render text: "hello world" 直近のバージョンで「render text:」廃止。
      render plain: "hello world"
  end
  #
  
end
  1. routingの設定をする。
    config > routes.rbを開く

root 'application#hello'
rootのURLでアクセスした場合に、applicationコントローラーのhelloアクションを呼び出す。

Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  
  #追記
  root 'application#hello'
  #
end
  1. ページ更新
    hello worldと表示されたらOK。

image.png


## 電話帳アプリの開発 ### 電話帳アプリの機能 DBに情報を登録する。使える機能は下記5つ。
  1. 一覧表示
  2. 新規登録
  3. 編集
  4. 詳細
  5. 削除

アプリ用プロジェクトの作成

  1. プロジェクトを作成する
    cloud9のターミナルで下記を実行。
rails new address_book

2) PJのディレクトリに移動
cd address_book

3) DBとしてsqlite3のバージョンを明示する。

3-1) Gemfileを開く
Gemfile: rubyのライブラリ(gem)を管理しているファイル。

image.png

3-2) gem sqliteに追記し、保存する。

# gem 'sqlite3' 元の状態
gem 'sqlite3', '~> 1.3.6'

3-3) ターミナルでbundle updateを実行する。

bundle update

サーバーが立ち上がってる場合はctrl+cでストップ。

3-4) ターミナルでdbを作成する

rails db:create

image.png


#### modelの作成とmigrate 4) modelの作成 modelとmigrateファイルを作成する。
  • モデル名:User
  • カラム:name, phone (いずれも文字列)

rails g model モデル名
 ┗ g: generateの略
 ┗ モデル名:頭文字大文字
 ┗ 末尾で「カラム名:カラムの型」を指定できる
  (型の指定がない場合はstringになる)

rails g model User name:string phone:string 

下記2ファイルが作成される
・modelsフォルダの中に、user.rb
・dbファイルの中に、日付_create_モデル名.rb

image.png

image.png

migrateファイルとは?

DBの設計図。

DBは通常SQL言語で操作を行うが、ruby on railsでは、rubyにより操作できるようにする。

・通常: SQL -> DB
・Rials: Ruby -> (SQL) -> DB

rubyでSQLを操作するために、DBの構造をrubyがわかる状態にする必要がある。それがmigrateファイル。

作成したmigrateファイルの内容は、dbに反映させる必要がある。
(migrateファイルに変更があった場合は、つど反映が必要)


5) migrateファイルをdbと紐づける。
rake db:migrate

migrateフォルダにschema.rbが作成されれば(既存の場合は内容更新)反映完了。

image.png

rakeコマンドとは?

Ruby-makeの略。Linuxのmakeコマンドを模したもの。
Rubyでよく使う処理をパッケージ化し、まとめたライブラリ。

rake タスク名で実行できる。


#### controllerの作成 6) controllerの作成

rails g controller コントローラー名 アクション名
 ┗ コントローラー名は頭文字大文字
 ┗ アクション:指定したアクションを作成できる(※中身は空)

rails g controller Users index new edit update show

下記3つの変更がなされる。
①controllersフォルダの中に、コントローラー名_controller.rbができる。
②configフォルダの中の、routes.rbにget 'コントローラー名/アクション名'が追記される。
③viewsフォルダにusersフォルダ作成され、アクション名_html.erbが作成される。

image.png

users_controller.rb
class UsersController < ApplicationController
  def index
  end

  def new
  end

  def edit
  end

  def update
  end

  def show
  end

end

image.png

routes.rb
Rails.application.routes.draw do
  get 'users/index'

  get 'users/new'

  get 'users/edit'

  get 'users/update'

  get 'users/show'

  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

image.png

index.html.erb
<h1>Users#index</h1>
<p>Find me in app/views/users/index.html.erb</p>

アクションとは?

controllerに記述する実行内容(action)。


#### routeファイルの編集 7) routeファイルの編集 既存の内容を削除し、下記を記述。 RESTfulなルーティングが設定される。
routes.rb
Rails.application.routes.draw do
  resources :users
end

resources : usersの中身

rake routesコマンドで確認できる。

user1:~/environment/address_book $ rake routes

   Prefix Verb   URI Pattern               Controller#Action
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

デフォルト設定されている基本の7アクションを呼び出せる。

アクション名 ルーティング名 HTTPメソッド 概要 URLパターン
index users get 一覧表示 /コントローラー名
create post 詳細表示 /コントローラー名
new new_user get 新規表示 /コントローラー名/new
edit edit_user get 登録 /コントローラー名/:id/edit
show user get 編集 /コントローラー名/:id
update patch/put(post) 更新 /コントローラー名/:id
destroy delete(post) 削除 /コントローラー名/:id

8) サーバーの起動 下記コマンドでサーバーを起動し、URLコピーし別タブで開く。
rails s -b 0.0.0.0

image.png

-b 0.0.0.0とは?

サーバー起動時のオプション。外部サイトからも見れるようにしている。

通常はローカルの3000番ポートで http://localhost:3000 からのアクセスのみのところ、0.0.0.0とバインド(-b)することで仮想マシンが持っているipアドレスからアクセスできるようにしている。

AWSを使っているため、外部サイトからアクセスすることができるようになる。


9) ルーティングの確認 URLの末尾に`users`を付与し、下記ページが表示されればOK。

image.png

ページの作成

users一覧表示画面の作成

  1. Userコントローラーのindexを編集する。

image.png

@users = User.all
すべてのユーザーを取得して、インスタンス変数@usersに代入する。

class UsersController < ApplicationController
  def index
    @users = User.all
  end

# ~省略~

end

変数の前のアットマークの役割は?

変数に@をつけることで、コントローラーからビューに値を渡すことができる。

  1. index.html.erbの編集
    viewsフォルダのindex.html.erbファイルを編集する。

image.png

<h1>ユーザー一覧</h1>
<% @users.each do |u| %>
<p>
    <%= u.id %>
    <%= u.name %>
    <%= u.phone %>
</p>
<% end %>

ブラウザでルートURL/usersを開き、下記画面が表示されたらOK。

image.png

<% %><%= %>の違い

rubyをテンプレートファイル(.erb)で使うための記述。

<% %>

  • 画面上に表示しない。
  • 記述された式を実行したり、データを呼び出す時に使う。

<%= %>
画面上にデータを表示する


### ユーザー新規登録ページの作成 12) viewsフォルダのnew.html.erbファイルを開く。

image.png

new.html.erb
<h1>新規登録</h1>
<%= form_for (@user) do |f| %>
    <p>
        <%= f.label :name %><br>
        <%= f.text_field :name %>
    </p>
    <p>
        <%= f.label :phone %><br>
        <%= f.text_field :phone %>
    </p>
    <p>
        <%= f.submit %>
    </p>
<% end %>

form_forとは?

railsでformタグを簡単に記述するためのメソッド。

form_for
<%= form_for (モデル, [オプション]) do |f| %>
   formの中身
<% end %>
  • モデル:@user@postなどのモデルオブジェクト
  • オプション:送信先urlやタグの設定など(基本不要)
form_forの中身
# カラム名のlabelタグを作成
   <%= f.label :カラム名 %>

# カラム名のテキストボックスを作成
   <%= f.text_field :カラム名 %>

# 送信ボタン
   <%= f.submit  "ボタンの表示内容" %>

13) newアクションを編集 コントローラーを編集する。

image.png

@user = User.new
Userというインスタンスを生成し、変数userに格納する。
@をつけることで、viewにデータを受けわたす。

class UsersController < ApplicationController
  def new
    @user = User.new
  end

# ~省略~

end

14) createアクションの作成 Userのコントローラーに下記を追記する。 DBは不正な更新を防ぐため、指定されたカラムのみ更新可能できる設定にする。
class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    @user.save
      redirect_to users_path
  end

  private
  def user_params
    params[:user].permit(:name, :phone)
  end

# ~省略~

end

**▼createの処理**
  • 送信されてきたパラメータ(user_params)を使って、インスタンスを生成し、変数@userに格納する。
  • 格納されたデータを保存(.save)する。
  • ユーザー一覧画面(users_path)にリダイレクトする。

**▼user_paramasの設定理由** 不正なデータでDBが更新されるのを防ぐ。 `.permit(:name, :phone)`:nameとphoneのみ更新を許可。

<不正の例>
DBにユーザー課金の状態を表すデータがあるがある場合に、不正に更新し支払い済みの状態にするなど。


15) 一覧画面に新規登録画面へのリンクを追加する

image.png

<h1>ユーザー一覧</h1>
<% @users.each do |u| %>
<p>
    <%= u.id %>
    <%= u.name %>
    <%= u.phone %>
</p>
<% end %>

<!--新規登録ページへのリンク-->
<p>
    <%= link_to "新規登録", new_user_path %>
</p>

link_toとは?

viewテンプレートでaタグを設置するヘルパー。

<%= link_to "アンカーテキスト", "URL" %>
<%= link_to "アンカーテキスト", ルーティング名_path %>
<%= link_to "アンカーテキスト", ルーティング名_path(引数) %>
 ┗ 引数で user.idなどパラメータを指定し、リンク先にデータを渡す。


16) 表示の確認 サーバーを起動し、ページを確認する。
image.png
image.png

▼登録できるか確認

image.png

バリデーション機能の作成

現状の新規登録では、名前や電話番号が空でも登録されてしまう。
必要事項が入力されていない場合はエラーを表示し、登録できないようにする。

①modelでnameカラムが空の場合にエラーメッセージを表示する処理を記述する。
②controllerのcreateアクションに、nameが空の場合にトップページに遷移せず、新規登録ページを表示する処理を追加する。
③viewsのnewファイルにvalidationエラーがあった場合にメッセージを表示する処理を記述。


**①modelでnameカラムが空の場合にエラーメッセージを表示する処理を記述**

image.png

uer.rb
class User < ApplicationRecord
    validates :name, 
               presence: {message: "名前を入力してください。"}
end

validates: カラム, 処理
presence: 空白の時falseになる
presence: true: 空白の時エラーになる
presence: {message: "テキスト"}
uniquness: true: 重複する場合エラー
length: { minimum: 整数}]:最低文字数

image.png


**②controllerのcreateアクションに、nameが空の場合にトップページに遷移せず、新規登録ページを表示する処理を追加**

image.png

users_controller.rb
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to users_path
    else
      render 'new'
    end
  end

**③viewsのnewファイルにvalidationエラーがあった場合にメッセージを表示する処理を記述**
new.html.erb
<h1>新規登録</h1>
<%= form_for (@user) do |f| %>
    <p>
        <%= f.label :name %><br>
        <%= f.text_field :name %>
        
        <!--validationエラーの場合の表示-->
        <% if @user.errors.any? %>
            <%= @user.errors.messages[:name][0] %>
        <% end %>
    </p>
    <p>
        <%= f.label :phone %><br>
        <%= f.text_field :phone %>
    </p>
    <p>
        <%= f.submit %>
    </p>
<% end %>

<% if @user.errors.any? %><% end %>
指定したオブジェクトでエラーが発生している場合の処理。


### 編集機能の追加 1) viewテンプレートの作成。 新規追加ページとほぼ同じ(タイトル以外)のため、コピペする。

image.png

edit.html.erb
<h1>編集</h1>
<%= form_for (@user) do |f| %>
    <p>
        <%= f.label :name %><br>
        <%= f.text_field :name %>
        
        <!--validationエラーの場合の表示-->
        <% if @user.errors.any? %>
            <%= @user.errors.messages[:name][0] %>
        <% end %>
    </p>
    <p>
        <%= f.label :phone %><br>
        <%= f.text_field :phone %>
    </p>
    <p>
        <%= f.submit %>
    </p>
<% end %>
  1. editアクション、updateアクションの編集
    コントローラーのアクションを編集する。

image.png

users_controller.rb
  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user.update(user_pramas)
      redirect_to users_path
    else
      render 'edit'
    end
  end
  1. 一覧画面に編集ページへのリンクを設置

image.png

index.html.erb
<h1>ユーザー一覧</h1>
<% @users.each do |u| %>
<p>
    <%= u.id %>
    <%= u.name %>
    <%= u.phone %>
    <%= link_to "[編集]", edit_user_path(u.id) %>
</p>
<% end %>

<!--新規登録ページへのリンク-->
<p>
    <%= link_to "新規登録", new_user_path %>
</p>

<%= link_to "[編集]", edit_user_path(u.id) %>
 ┗ ルート名「edit_user」に遷移
 ┗ リンク先のページにデータ(u.id)を渡す


### 詳細ページの作成 1) controllerのshowアクションを編集
users_controller.rb
 def show
    @user = User.find(params[:id])
 end

2) viewファイルを編集 views > users > show.html.erb
show.html.erb
<h1>詳細</h1>
<p>
    名前:<%= @user.name %>
</p>
<p>
    電話番号<%= @user.phone %>
</p>

3) ユーザー一覧画面に詳細ページへのリンク追加
## 削除機能 1) コントローラーにdestroyアクションを追加
  def destroy
    @user = User.find(params[:id])
    @user.destroy
      redirect_to users_path
  end

・.findでデータを取り出す。
・データは[:id]で指定。
・.destroyで取り出したデータを削除。


2) viewファイルに削除ボタンを追加 削除ボタン(リンク)クリックで、アラートを表示し、OKを押すとデータを削除する。
index.html.erb
<h1>ユーザー一覧</h1>
<% @users.each do |u| %>
<p>
    <%= u.id %>
    <%= u.name %>
    <%= u.phone %>
    <%= link_to "[詳細]", user_path(u.id) %>
    <%= link_to "[編集]", edit_user_path(u.id) %>
    <%= link_to "[削除]", user_path(u.id), method: :delete, data:{ confirm: "削除してもいいですか?"} %>
</p>
<% end %>

<!--新規登録ページへのリンク-->
<p>
    <%= link_to "新規登録", new_user_path %>
</p>

image.png


### 共通の処理をまとめる dry原則に則り、controllersで共通している処理を簡略化する。

before_action :①アクション名, only: [:②アクション名, :アクション名,,,]
 ┗ before_action: 各アクションを実行する前に適用する
 ┗ ①共通化する処理を記述したアクション名
 ┗ ②適用するアクション名(カンマでつなぐ)

※適用するアクション内の共通部は削除でOK(指定した①アクション名に置き換える必要はない)

users_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:edit, :update, :show, :destroy]
  
 #~省略~

  def edit
  end

  def update
    if @user.update(user_params)
      redirect_to users_path
    else
      render 'edit'
    end
  end

  def show
  end
  
  def destroy
    @user.destroy
      redirect_to users_path
  end
  
  private
  def user_params
  end

  def set_user
    @user = User.find(params[:id])
  end

end


以上で完了。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?