React custom hooks

1. カスタムフックとは

カスタムフックはReactにおいて、組み込みのReactフックを使用して独自のロジックを持った独自に作成したフックのことです。カスタムフックを使用することでロジックを再利用可能になります。また、ロジックとビューを分離して実装することができます。

1.1. 命名規則

カスタムフックはuseOnlineStatusのようにuseで始まるキャメルケースで命名する必要があります。リンタが設定されている場合この命名規則が強制されます。

1.2. カスタムフックの中身

カスタムフックはコンポーネントの中のロジック部分を抽出したものです。ですが、ロジックの中で既存のフックを使用していない場合は、カスタムフックとして扱う必要はありません。ロジックを担当する単なる関数とカスタムフックの差は内部でフックを呼び出しているのか、呼び出していないのかにあります。

2. カスタムフックの作り方

公式のコードを使ってカスタムフックのつるり方を説明します。

ネットワークの接続状況を監視するロジックを実装します。カスタムフックを使わない場合は以下のようなコードになります。

import { useState, useEffect } from 'react';

export default function StatusBar() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}

ビュー部分とロジック部分が同じコンポーネントに書かれていて責任が分担されていません。また、同じロジックを別の部分で利用したいときにロジック部分をコピペする必要があります。

カスタムフックを実装するためにはコンポーネントの中からロジックを抽出する必要があります。上記のコードのロジック部分は以下です。

const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

この部分を分離してカスタムフックを作成します。

import { useState, useEffect } from 'react';

export function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  return isOnline;
}

さらにこのカスタムフックを使ってビューを実装すると以下のようになります。

import { useOnlineStatus } from './useOnlineStatus.js';

function StatusBar() {
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}

export default function App() {
  return (
    <>
      <StatusBar />
    </>
  );
}

コンポーネントからロジック部分が分離されてコードの量がかなり削減されました。またロジックが抽象化されたことにより何をしているのかがわかりやすくなっています。

3. カスタムフックを使うメリット

3.1. 再利用性

カスタムフックを利用することで同じロジックを複数回実装するときに何度も同じコードを書く必要がなくなります。

3.2. ロジックの分離

カスタムフックを使ってロジック部分をビューから分離することで責任を分割することができます。

また、コンポーネントを見たときに何をやっているのかを把握しやすくなります。

認証について

認証とは

IT分野における認証とは、システムやデータへのアクセスを管理するためのプロセス。ユーザーは自分がだれかを示し、システムがそれを確認します。確認が成功すればシステムやデータへのアクセスを許可します。

認証方法の種類

1. Basic認証

ユーザー名とパスワードを組み合わせて身元を確認する認証方法。

リクエストヘッダーにBase64エンコードされたユーザー名とパスワードを記載してサーバーに送信する。サーバーはヘッダーからユーザー名とパスワードを取り出し検証する。検証が通れば、サーバーはリクエストを処理し、通らなければ拒否する。

Base64でのエンコードは簡単にデコードが可能なためBasic認証にはセキュリティ上の脆弱性がある。最近はほかのよりセキュアな認証メカニズムが推奨されている。

2. Digest認証

先述のBasic認証をセキュリティの面で強化したような認証方法です。Digest認証ではユーザー名やパスワードなどをハッシュ化して扱うことでセキュリティ性を高めています。

より具体的には、

  1. クライアントが認証の必要な領域へのリクエストをサーバーへ送信すると、401エラーを返して認証要求をします。またnonceと呼ばれるランダムな文字列を生成して、送信します。
  2. クライアントはユーザー名、パスワード、nonce、クライアントで生成したランダム文字列cnonceを含む情報をハッシュ関数を使用してハッシュ化します。
  3. クライアントがサーバーへハッシュ化した情報を送信する。
  4. サーバーは受け取った情報をハッシュ関数を適用して、検証する。

ランダムな値であるnonce、cnonceを追加することで、クライアントに成りすますことを困難にしている。

また、ハッシュ化にはMD5方式やSHA-256が使用される。

3. Session based Authentication

Webアプリケーションやウェブサイトにおいて、ユーザーがログイン状態を管理するための手法の一つ。サーバー上にユーザーのリクエストを追跡できるように、ID、ログイン時間、有効期限などを生成及び保存する。ユーザーはこれらの情報の一部をリクエストごとにCookieとして受信する。サーバーはリクエストごとにCookieを確認して認証する。

より具体的には、

  1. ユーザーからサーバーにログインを要求する
  2. サーバーは認証情報を検証し、成功したらセッションIDを生成、データベースへ保存する。
  3. サーバーはユーザーにセッションIDをCookieとして送信する。Cookieはブラウザに保存される。
  4. ユーザーが次のリクエストを送信すると、ブラウザはCookieとして保存されたセッションIDを含めてサーバーに送信する。
  5. サーバーはCookieから受け取ったセッションIDを用いてユーザーのセッションを特定して認証情報を検証する。

セッションベースの認証では、セッションに関する情報がサーバー側に保存されるためセキュリティチームが特定のユーザーを即座にログアウト状態にすることができます。ただしセッション情報をサーバー側で保存するためにスケーラビリティに問題が発生する可能性があります。

セキュリティ上の懸念としてはCookieを使用することによりクロスサイトリクエストフォージェリにさらされる可能性があります。

4. Token based Authentication

認証情報をトークンと呼ばれる特定の文字列で表現し、これを使用してユーザーの認証を行う。

より具体的には、

  1. ユーザーが認証譲歩をサーバーに送信する。
  2. サーバーは認証情報を検証し、成功した場合トークンを生成し、クライアントに送信する。
  3. クライアントはトークンをCookieやlocal storageに保存する
  4. ユーザーが次のリクエストを送信するときにリクエストヘッダーやCookieとしてトークンを記載する。
  5. サーバーはトークンが有効か検証する。

トークンベースの認証では、セッションの詳細をサーバーが保持しておく必要がないためパフォーマンスを向上することができます。

セキュリティ上の懸念点として、認証に関する情報がクライアント側に保存されるため、サーバー側から操作を行うことが困難になることが挙げられます。

5. OAuth

OAuth(Open Authorization)は、サードパーティーのアプリケーションがユーザーのリソースにアクセスするために必要な権限を取得するためのオープンスタンダードな認可プロトコルFacebookGoogleなどのアプリケーションから名前、生年月日、電子メールその他の必要なデータなどのユーザー関連情報にアクセスできる。

WebアプリケーションなどのログインページにあるGoogleアカウントでログイン/サインアップするボタンはOAuthを用いて行われている。

OAuthは以下の主な役割とフローがあります:

  1. リソースオーナー(Resource Owner):
    • ユーザーを指し、自身のリソースに対するアクセス権を管理します。
  2. クライアント(Client):
    • ユーザーのリソースへのアクセスを必要とするアプリケーションやサービスを指します。
  3. リソースサーバー(Resource Server):
    • リソースオーナーのデータ(例: ユーザーの写真、プロファイル情報)を保持し、クライアントがアクセス権を得るためのサーバーです。
  4. 認可サーバー(Authorization Server):
    • ユーザーの同意を得て、クライアントに対してアクセストークンなどの認証情報を発行するサーバーです。

OAuthの基本的なフローは次の通りです:

  • 認可フロー(Authorization Code Flow):
    1. クライアントがリソースサーバーに対してリソースへのアクセスを要求し、リソースサーバーが認可サーバーにリダイレクトします。
    2. ユーザーは認可サーバーでログインし、クライアントが求める権限を確認し同意します。
    3. 認可サーバーは認可コードを発行し、クライアントに送信します。
    4. クライアントは認可コードを使用して認可サーバーにアクセスし、アクセストークンを取得します。
    5. クライアントはアクセストークンを使用してリソースサーバーにアクセスし、リソースに対する操作を行います

    ## まとめ

    認証方法はいろいろな実装方法が存在する。最も基本的は認証方法はBasic認証だがセキュリティに問題がある。その他Digest認証やSession based認証 Token based認証, OAuthなどがある。

Railsの関連付け(association)について

1. 関連付けとは?

2つのActive Recordモデル同士のつながりのことです。
どのモデルとどのモデルの間に関連があるのか、その関連はどのような形態なのかを定義することができます。

一つの例として以下のように記述します。

class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
  belongs_to :user
end

上の関連付けでは、 User と Post の間に1対多の関連付けを定義しています。

2. 関連付けの種類

Railsの関連付けにはいくつかの種類があります。この記事で取り上げる関連付けを以下に示します。

  • belongs_to
  • has_one
  • has_many
  • has_many :through
  • has_one :through
  • has_and_belongs_to_many

2.1. belongs_to関連付け

belongs_toは1対1の関連付けを表します。belongs_toの宣言を行ったモデルのインスタンスは別のモデルに従属 (belongs to) します。

例えば、記事(Post)とユーザー(User)の関係を考えましょう。

1つの記事は1人のユーザーに所属(従属)しています。つまり、記事はユーザーにbelongs_toしていると言えます。

この場合のモデルは以下のようになります。

class Post < ApplicationRecord
  belongs_to :user
end

class User < ApplicationRecord
end

Postモデルではbelongs_to :userと定義することで、Userモデルとの1対1の所属関係を表現しています。

belongs_toでは、外部キーとして所属するモデルのidが自動でカラムとして追加されます。 この場合、postsテーブルにはuser_idというカラムができます。

また、belongs_toを定義したモデルでは、所属するモデルのインスタンスに簡単にアクセスできるようになります。

post = Post.first
post.user # postに関連付いたuserオブジェクトを取得できる

この関連付けに対応するマイグレーションは例えば、以下のようになります。

class CreatePosts < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :name
      t.timestamps
    end

    create_table :posts do |t|
      t.belongs_to :user
      t.datetime :published_at
      t.timestamps
    end
  end
end

2.2. has_one関連付け

has_oneもbelongs_toと同様に1対1の関連付けを表します。ただし、belongs_toとは逆に関連付けしたモデルを従属させます。

例えば、ユーザーとプロフィールの関係を考えましょう。

1人のユーザーは、1つのプロフィールを持っています。 逆に、1つのプロフィールは1人のユーザーに所属しています。

この場合のモデルは以下のようになります。

class User < ApplicationRecord
  has_one :profile
end

class Profile < ApplicationRecord
  belongs_to :user
end

Userモデルではhas_one :profileと定義することで、Profileモデルとの1対1の関係を表現しています。

has_oneの場合、外部キーは相手側のモデルに置かれます。 この例では、profilesテーブルにuser_idというカラムが追加されます。

また、has_oneを定義した側では、関連するモデルのインスタンスにアクセスできます。

user = User.first 
user.profile # userに関連づいたprofileオブジェクトを取得できる

この関連付けに対応するマイグレーションは例えば、以下のようになります。

class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :name
      t.timestamps
    end

    create_table :profiles do |t|
      t.belongs_to :user
      t.string :profile
      t.timestamps
    end
  end
end

このように、has_oneによって「1つ持っている」関係を表現できます。
belongs_toとhas_oneは同じ1対1の関係ですが、視点が逆なので注意が必要です。

2.3. has_many関連付け

has_many関連付けは1対多の関連付けを表します。has_oneと似ていますが、一つのインスタンスが相手のインスタンスを複数持っている点が異なります。

例えば、ユーザーと記事の関係を考えましょう。

1人のユーザーは、多数の記事を書くことができます。 逆に、1つの記事は1人のユーザーに所属しています。

この場合の定義は以下のようになります。

class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
  belongs_to :user
end

Userモデルではhas_many :postsと定義することで、1対多の関係を表現しています。

has_manyの場合も、belongs_to側のモデルに外部キーが置かれます。 postsテーブルにはuser_idが追加されます。

また、has_manyを定義した側では、関連するモデルのコレクションにアクセスできます。

user = User.first
user.posts # userが書いた複数のpostオブジェクトを取得できる

この関連付けに対応するマイグレーションは例えば、以下のようになります。

class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :name
      t.timestamps
    end

    create_table :posts do |t|
      t.belongs_to :user
      t.timestamps
    end
  end
end

このように、has_manyを使うことで1対多の関連付けができます。アプリケーションではよく利用する関連付けの1つです。

2.4. has_many :through関連付け

has_many :through関連付けは2つのモデルの多対多の関連付けを表現します。中間テーブルを介して、それを経由(through)して2つのモデルを関連付けます。

例えば、ユーザーとグループの関係を考えましょう。

  • 1人のユーザーは複数のグループに所属できます
  • 1つのグループは複数のユーザーを含むことができます

この場合の定義は以下のようになります。

class User < ApplicationRecord
  has_many :group_users
  has_many :groups, through: :group_users
end

class Group < ApplicationRecord
  has_many :group_users
  has_many :users, through: :group_users
end

class GroupUser < ApplicationRecord
  belongs_to :user
  belongs_to :group
end

GroupUserが中間テーブルとなり、このテーブルを介してUserとGroupは多対多で関連付けられます。

このように、has_many :throughを使うことで、複雑な多対多の関連付けが定義できます。

この関連付けに対応するマイグレーションは例えば、以下のようになります。

class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :Users do |t|
      t.string :name
      t.timestamps
    end

    create_table :Groups do |t|
      t.string :name
      t.timestamps
    end

    create_table :GroupUsers do |t|
      t.belongs_to :user
      t.belongs_to :group
      t.timestamps
    end
  end
end

またhas_many :throughは、中間テーブルにカラムを追加して、関連付けに属性を持たせることもできます。

2.5. has_one :through関連付け

has_one :through関連付けは、2つのモデル間の1対1の関連付けを中間テーブルを介して表現するものです。

例えば、ユーザーとプロフィールの関係を考えましょう。 ユーザー登録時に住所情報も保存したい場合を考えます。

  • 1人のユーザーは1つのプロフィールを持つ
  • 1つのプロフィールは1つの住所を持つ

この場合の定義は以下のようになります。

class User < ApplicationRecord
  has_one :profile
end

class Profile < ApplicationRecord
  belongs_to :user
  has_one :address
end

class Address < ApplicationRecord
  belongs_to :profile
end

この場合、UserとAddressは直接関連付けされていませんが、中間のProfileモデルを介することで、 1対1の関連付けが表現できます。

この関連付けに対応するマイグレーションは例えば、以下のようになります。

class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :name
      t.timestamps
    end

    create_table :profiles do |t|
      t.belongs_to :user
      t.timestamps
    end

    create_table :addresses do |t|
      t.belongs_to :profile
      t.timestamps
    end
  end
end

2.6. has_and_belongs_to_many関連付け

has_and_belongs_to_many関連付けは、2つのモデル間の多対多の関連付けを、中間モデルを使用することなく直接定義できるものです。ただし、データベースに中間テーブルは必要です。

例えば、ユーザーとスキルの関係を考えましょう。

  • 1人のユーザーは複数のスキルを持っている
  • 1つのスキルは複数のユーザーが持っている

この場合の定義は以下のようになります。

class User < ApplicationRecord
  has_and_belongs_to_many :skills
end

class Skill < ApplicationRecord
  has_and_belongs_to_many :users
end

この定義により、バックグラウンドでusers_skillsという中間テーブルが作成されます。 このテーブルを介して、ユーザーとスキルは多対多で関連付けられます。

また、関連するレコードには以下のようにアクセスできます。

user = User.first
user.skills # ユーザーの持つスキル

skill = Skill.first
skill.users # そのスキルを持つユーザー

このように、has_and_belongs_to_manyは中間テーブルを意識せずに、 多対多の関連付けを定義できる便利な関連付けです。

3. 関連付けを使うメリット

関連付けを行うと、モデル間の関連データを簡単に操作できるようになります。

例えば、UserとPostの関連について考えてみましょう。

関連付けを行っていない場合は、新しいpostを作成したいときに以下のようなコードを実行する必要があります。

@post = Post.create(content: "Hello world", user_id: @user.id)

同じようにユーザーを1人削除する場合は、ユーザーとそのユーザーのポストをすべて削除する必要があります。

@posts = Post.where(user_id: @user_id)
@posts.each do |post|
  post.destroy
end
@user.destroy

以下の関連付けを定義しておくと上記の操作をもっと簡単に記述することができます。

class User < ApplicationRecord
  has_many :posts, dependent: :destroy
end

class Post < ApplicationRecord
  belongs_to :user
end

上記の関連付けを記述した場合は以下のように1行で新しいポストを追加することができるようになります。

@post = @user.posts.create(content: "Hello World")

同じように1行でuserと関連するpostをすべて削除することができます。

@user.destroy

4. まとめ

関連付けを使うことでモデル同士の関連を簡単に扱えるようになります。そのことにより適切に使用すれば、アプリケーションの保守性や拡張性を向上させることができます。

チームで初めてアプリを作りました

こんにちは。私は現在、株式会社ユーブルが提供するエンジニア実習サービス「アプレンティスシップ」に参加しています。

アプレンティスシップのカリキュラムの中で、チーム開発に参加した経験についてまとめたいと思います。

1. はじめに

1.1. 背景

アプレンティスシップでは、エンジニアになるためのトレーニングの一環として、チームでアプリを開発する課題があります。実際のエンジニアの業務では、チームで開発を進めることが求められます。この課題は、その予行演習として機能します。

1.2. 課題の概要

今回のチーム開発の課題は、自分たちに役立つアプリを作ることでした。

技術的な制約として、これまでの課題で学んだ技術を主に使用して作成する必要がありました。具体的には、HTML/CSS/JavaScriptRubyまたはPHPMySQLを主に使用します。フレームワークやライブラリは、今回は使用できませんが、今後の課題で学ぶ予定です。

また、ローカルで動作するアプリを作成する必要があります。

1.3. チームについて

私たちのチームはバックエンドの言語としてRubyを選択した4人で構成されていました。チームメンバーは全員が開発経験がゼロでした。

2. 何を作ったのか

2.1. 制作物

れしぴよ!

という名前の、料理のレシピから買い物リストを生成するアプリを作成しました。

作りたいレシピを選択すると、必要な材料のリストが自動的に生成されます。

アプリホーム画面

レシピ選択画面

リスト編集画面

リスト画面

3. 開発の流れ

3.1. 大まかなスケジュール

チーム開発全体の期間は5週間でした。そのうち4週間は他の課題をこなしながら実行しました。残りの1週間はチーム開発に専念し、アプリを作成しました。

3.2. 最初の4週間

他の課題を実行しながらの4週間で下記の工程を実行しました。

  • イデア決め: 自分たちが開発するアプリを決める
  • スライド作: 発表用のスライドを作成する
  • 要件定義: どんな機能が必要か、要件を定義する
  • 設計: 全体像を整理し、機能を実現できるように設計する
  • タスク出し: 実装する際にどのような作業が必要か、またその作業がどのくらいの時間で遂行できそうか見積もる。

3.3. 最後の1週間

最後の1週間でアプリの実装とプレゼンの準備をしました。

3.4. 自分の担当

最後の1週間は、私はバックエンドの処理の実装を担当しました。webrickを使用してサーバーを立ち上げ、フロントエンドとの通信を作成しました。また、mysql2を使用してデータベースの操作を実装しました。

4. チーム開発を進めていく上で行き当たった課題

このセクションでは、今回のチーム開発を行っていくうえで遭遇した課題とそれを解決した方法を紹介します。

4.1. 課題

チームでの一番の課題はアプリの目的やコア機能が不明確だったことです。

今回のチーム開発では、アプリのアイデアを自分たちで考え、要件定義から実装までを行う必要がありました。しかし、私たちにはいずれの工程も初めての経験であり、実装に入る前の工程についていい加減に進めてしまいました。そのため最終工程である実装の段階に至っても、アイデアの輪郭やアプリの要件があいまいであり苦労しました。

4.1. 解決方法

以上の課題を解決した方法は、実装の優先順位について合意をとることでした。

実装の優先順位について話し合うことによりどの機能が必要なのか、を明確にすることができました。そして、コアとなる機能が何なのかを明確にすることができました。

5. 個人の振り返り

5.1. できた事

  1. チームでの話し合いの中で意見をしっかりと主張できたこと。チームの一員として話し合いに参加することは何よりも重要だと思います。なので私はしっかりと意見を出し参加することを意識してこのプロジェクトに参加していました。今回のチーム開発では、GitHubを使った開発の流れやプルリクに関するルールの取り決めなどしっかりと意見を出すことができました。
  2. チームでの開発をスムーズに進めるための準備をすること。具体的にはチームで使うGitHubの Organization の作成や会議で使用した、Google スライドやスプレッドシートの用意、最初のテーブル設計などです。

5.2. できなかった事

  1. うまく仕事を分担すること。バックエンドは私も含めて2人で担当したのですが、もう少しうまく仕事を分担し、協力して開発できたのではないかと考えています。
  2. いいアイデアを出すこと。正直一番最初のアイデアを出す段階において自分は、役立たずだったなと今思い返しても思ってしまいます。この点については特にチームメンバーに非常に助けられました。

6. チームとしての振り返り

5.1. できた事

  1. お互い良く助け合うことができた。今回のチーム開発ではチームメンバーがアラートを上げることがありました。そしたらすぐにチームメンバーが助けに入って、無事完成にまでこもっていくことができました。アラートが挙げやすい雰囲気を作ることやすぐにフォローに入れる体制を作っていたことは今回のチーム開発において非常に良かった点だと思います。

5.2 できなかった事

  1. 解決したかった課題が途中でぶれてしまったこと。私たちは最初、買い忘れを防止するアプリを作ろうとしていました。ですが、追加の機能を考えたり、要件定義やその他の工程をしている内に何を解決するアプリを作っているのかが曖昧になってしまいました。なので追加機能を考えるのではなく、課題をより良く解決するためにはどうすれば良いのかを考えるべきでした。

7. この開発で得た学び

7.1. 学び

今回の開発で得た学びは、以下の2つです。

  • プロジェクトの目的を明確にし、チームで合意をとる。
  • イメージを共有するために目に見える具体例を提示する。

プロジェクトを進めていくと次第に何を目的としていたのかが曖昧になってしまったりします。それを防ぐためにコンセプトをしっかりと定めること非常に重要だと思いました。

また、チームでの共通した理解を形成するために既存のアプリと自分たちが作ろうとしているアプリを比較したり、することが大切だと思いました。

7.2. これから

今回のチーム開発はアプリの構想から実装までを実施した初めての経験になりました。良いところも悪いところも色々ありました。ですが、非常に価値のある経験になったのでこの学びを生かしていきたいと思います。

JavaScriptのイベントリスナーに関数を渡す際に初心者が犯しがちな間違い

addEventListener メソッドは JavaScript を使って HTML に動きを持たせようとしたときに最も良く使用されるメソッドの一つです。
そんな addEventListener メソッドですが、初心者が躓きやすいポイントが幾つかあります。この記事ではそれらの躓きやすい点についてまとめようと思います。

1. 引数に()を付けてしまう

以下のコードは hello ボタンを押すとコンソールに hello と表示させようとして失敗しているコードです。

<body>
    <button id="btn">hello</button>
    <script>
        const btn = document.getElementById("btn");

        // 関数定義
        function hello() {
            console.log("hello");
        }

        // ボタンにイベントを追加する
        btn.addEventListener("click", hello());
    </script>
</body>

このコードを実行するとページのロードと共にイベントリスナーの関数 hello が実行されます。また、ボタンを押してもコンソールに hello が追加されることはありません。
この処理の問題点は関数 hello がイベントリスナーの追加と共に実行されてしまっていることです。
hello はイベントリスナーの追加時に一度だけ実行されイベント発火時には呼び出されません。

改善する方法は以下の通りです。

<body>
    <button id="btn">hello</button>
    <script>
        const btn = document.getElementById("btn");

        // 関数定義
        function hello() {
            console.log("hello");
        }

        // 1. ()を付けない
        btn.addEventListener("click", hello);

        // 2. アロー関数でラップする
        btn.addEventListener("click", () => hello());

        // 3. 無名関数でラップする
        btn.addEventListener("click", function() { hello() });
    </script>
</body>

上記いずれかの方法を用いることで想定通りの挙動を実装することができます。

無名関数やアロー関数を使った場合

先ほどアロー関数や無名関数を使って実行したい処理をラップする方法を紹介しましたが、それらの方法で設定したイベントリスナーは削除することが難しいです。 ですので、もしつかしたイベントリスナーを削除する予定がある場合はそれらの方法は避けた方が良いでしょう。

既に設定されているイベントを削除するためには removeEventListener メソッドを使います。このメソッドでは下記の通り削除したいイベント型とイベントリスナー関数を指定する必要があります。

btn.removeEventListener('click', hello);

無名関数やアロー関数はそのままでは使いまわしができない関数です。ですので removeEventListener の引数に指定することはできません。上記のようにアロー関数や無名関数の中で実行されている関数を指定しても、それらの関数はイベントリスナー関数ではないので削除することは出来ません。

削除する予定のあるイベントリスナーの設定は上記3つの方法の内 1番目の方法で行う必要があります。

<body>
    <!-- hello ボタン -->
    <button id="btn">hello</button>
    <!-- remove ボタン -->
    <button id="remove">remove</button>
    <script>
        const btn = document.getElementById("btn");
        const removeBtn = document.getElementById("remove");

        function hello() {
            console.log("hello");
        }

        // イベント設定
        btn.addEventListener("click", hello);

        // イベントを消去する関数
        function removeEvent() {
            btn.removeEventListener('click', hello);
        }

        // removeボタンでhelloイベントを削除
        removeBtn.addEventListener('click', removeEvent);
    </script>
</body>

このコードではhelloボタンのクリックでコンソールにhelloと表示されremoveボタンを押すとhelloボタンのイベントリスナーが削除されhelloが追加されなくなります。

データベースにおけるロックについて

データベースの勉強中ロックについての理解があいまいだと思ったのでこの記事を書きます。

ロックとは

同じデータを複数人が同時に変更できないようにすることを「ロック」といいます。
そのため、あるトランザクションがデータを変更しているときロックがかかっていると,他のトランザクションは同じデータを変更することができなくなります。

なぜ必要なのか?

データ変更を複数同時に行う際に問題が起きないようにするためです。

ロックが存在しない場合

ロックが存在しない場合を考えてみましょう。
Xというデータに対して、AとBという二つのトランザクションが同時に1を加える変更をしようとしています。
この場合、AとBの両トランザクションが終了した時点でXはX+2の状態である必要があります。
ですが、ロックが存在しない場合2つのトランザクションが終了した時点でのXはX+1である可能性があります。
奇妙なことに思えますが、下記の手順で処理が行われると十分に起こり得ます。

  • AとBがともにXを参照する
  • AとBがともにXに1を加える
  • AがXX+1に変更する
  • BがX+1X+1に変更する

ロックが存在する場合

ロックが存在する場合はそのような事は起こりません。
ロックが存在する場合、AとBが同時にXへの変更を実行しようとしたときに、どちらかのトランザクションが優先して実行されます。もう片方のトランザクションは、前のトランザクションが終了するまで実行を待機することになります。
この場合、下記の手順で処理が実行されます。

  • AがXを参照する
  • Aが1を加える
  • AがXX+1に変更する
  • BがX+1を参照する
  • Bが1を加える
  • BがX+1X+2に変更する

ロックの種類

ロックには共有ロックと占有ロックの二つの種類があります。

共有ロック

共有ロックはロック中も別のトランザクションから読み取りが許可されているロックです。
許可されているのは読み取りのみで、変更や削除を行うことはできません。その場合待機状態になります。

占有ロック

占有ロックでは読み込みも含めてすべてのアクセスが遮断されます。他のすべてのトランザクションは待機状態になります。

デッドロック

ロックは便利な仕組みですが、大規模なデータベースでは問題を引き起こす場合もあります。その一つがデッドロックです。
あるトランザクションが変更しようとしているデータがロック状態にある時そのトランザクションはロックが解除されるまで待機状態になります。この時2つのトランザクションがお互いに相手を待つ待機状態になり処理が進まなくなることをデッドロックといいます。デッドロックは以下のような状況で起こりえます。

  • トランザクションAがデータ1に対してロックを取得し、次にデータ2に対するロックを要求する。
  • 同時に、トランザクションBがデータ2に対してロックを取得し、次にデータ1に対するロックを要求する。
  • この結果、トランザクションAとBがお互いにロック解除を待つ状態になり、どちらも処理が進まなくなる。

Ruby で AtCoder に挑戦するときに使う標準入力

私は駆け出しのエンジニアなのですが、最近 Ruby の勉強を始めました。そんな中、勉強の一環として RubyAtCoder に挑戦しています。AtCoder とは簡単に説明すると、競技プログラミングに挑戦できるサイトです。
AtCoder では入力を受け取ってそれをもとに問題を解いていく必要があります。この入力を受け取る処理は、恐らく本題ではないのですが、普通に勉強してきただけの初心者は結構戸惑います(私だけかもしれませんが)。

そこでこの記事では、RubyAtCoder に2週間ほど挑戦する中で使用頻度の高かった入力の受け取り方をまとめようと思います。

※ 対象読者

  • 筆者自身
  • RubyAtCoder に挑戦しようとしている初心者の方

入力される要素が1つのパターン

最も基本的な標準入力の方法です。 Rubyでは入力を受け取る際に gets メソッドを使います。

入力例 :

123.4

String として取得したい場合

chomp メソッドを使用することで末尾の改行コードを取り除いて必要な値のみを取得することができます。

# 入力
# 123.4

s = gets.chomp
p s

# 出力
#  => "123.4"


# chompを使用しない場合
s = gets
p s

# 出力
# => "123.4\n"

Integer として取得したい場合

to_i メソッドを使用することで String を Integer に変換することができます。
to_i メソッドでは整数とみなせない文字よりも前の文字までを Integer に変換するので改行コードは自動的に無視されます。そのため chomp メソッドを使用する必要はありません。
to_i メソッドでは小数点以下は切り捨てられるので注意する必要があります。

# 入力
# 123.4

i = gets.to_i
p i

# 出力
#  => 123

Float として取得したい場合

to_f メソッドを使用することで String を Float に変換することができます。
to_f メソッドでも to_i メソッドの時と同様にchompを使用する必要はありません。

# 入力
# 123.4

f = gets.to_f
p f

# 出力
#  => 123.4

1文字ずつの配列として取得したい場合

split メソッドの引数に空の String ("") を入れることで String を1文字ずつに分解することができます。

# 入力
# 123.4

c = gets.chomp.split("")
p c

# 出力
#  => ["1", "2", "3", ".", "4"]

入力が1行に複数あるパターン

AtCoder では半角スペースで区切られた複数の要素が入力されることがあります。
それらの要素を個別に受け取るためには split を使います。

入力例 :

123 456 789

String の配列として取得したい場合

split メソッドの引数に " " 半角スペースを指定することで String を半角スペースで分割することができます。

# 入力
#123 456 789

s = gets.split(" ")
p s

# 出力
#  => ["123", "456", "789"]

# splitの引数を nil にしても、引数を取らなくても同じ結果になります。

s = gets.split(nil)
p s

# 出力
# => ["123", "456", "789"]

s = gets.split
p s

# 出力
# => ["123", "456", "789"]

Integer の配列として取得したい場合

split メソッドで String の配列を生成した後に map メソッドで配列の各要素を Integer に変換します。

# 入力
#123 456 789

i = gets.split(" ").map(&:to_i)
p i

# 出力
# => [123, 456, 789]

各要素を分解して1文字ずつの配列として取得したい場合

delete メソッドに " " 半角スペースを指定して半角スペースを削除した後、split メソッドで1文字ずつに分割します。 delete(" ") では改行コードを削除しないので chomp メソッドが必要です。

# 入力
#123 456 789

c = gets.chomp.delete(" ").split("")
p c

# 出力
# => ["1", "2", "3", "4", "5", "6", "7", "8", "9"]

# map を使うことで String 以外の型の配列にできます。

i = gets.chomp.delete(" ").split("").map(&:to_i)
p i

# 出力
# => [1, 2, 3, 4, 5, 6, 7, 8, 9]

f = gets.chomp.delete(" ").split("").map(&:to_f)
p f

# 出力
# => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]

複数行の入力が一つのリストになっているパターン

要素のリストが1行ではなく複数行に渡って入力される事もあります。 このような場合、最初にリストの長さが渡され,その後にリストの本体が渡されます。
最初に入力される整数を手掛かりに、times メソッドを使ってリストの長さ分 gets を繰り返します。

入力例 :

7 # 以下に続くリストの長さ
1 # リストの本体
2
3
4
5
6
7

String の1次元配列として取得したい場合

n = gets.to_i # リストの長さを取得
i = n.times.map { gets.chomp }
p i

# 出力
# => ["1", "2", "3", "4", "5", "6", "7"]

Integer の1次元配列として取得したい場合

n = gets.to_i # リストの長さを取得
i = n.times.map { gets.to_i }
p i

# 出力
# => [1, 2, 3, 4, 5, 6, 7]

入力が複数行で1行1行がリストになっているパターン

複数の配列が入力される事もあります。
このような場合配列の数が最初に入力され、その後に半角スペースで区切られた配列が複数行にわたって入力されます。
ここでも times メソッドを用いて行の長さ分配列の取得を繰り返します。

入力例 :

3       # 以下に続くリストの数
12 34   # リストの本体
56 78
90 100

String の2次元配列として取得したい場合

n = gets.to_i  # リストの数を取得
s = n.times.map { gets.split(" ") }

Integer の2次元配列として取得したい場合

n = gets.to_i  # リストの数を取得
i = n.times.map { gets.split(" ").map(&:to_i) }
p i

# 出力
# => [[12, 34], [56, 78], [90, 100]]

まとめ

Ruby を使って AtCoder に挑戦し始めて2週間がたった中で使用頻度の高かった標準入力の方法をまとめてみました。主に自分用のまとめですが、もし閲覧された方がいるのであれば、参考になると幸いです。 この記事は新たな入力ケースに遭遇するたびに更新していく予定です(予定は未定)。