Yanonoblog!

こつこつと

GitHub Actionsのワークフローの基本的な解説

はじめに

GitHub Actionsでは、特定のGitHubイベントがトリガーとなって、ワークフローが実行されます。

本記事では、プルリクエストがオープンされたときに自動的にアサインを行うワークフローをベースに各オプションの解説を行います。

参考

https://tech.yappli.io/entry/github-assign-author

https://tech.youtrust.co.jp/entry/github_actions (寺井さんを見習いたい)

完成版

下記はプルリクエストがオープンされたときにユーザーのアサインを自動的に行うための設定になります。

.github/workflows/auto_assign.yml
name: Auto Assign

on:
  pull_request:
    types: [opened]

jobs:
  add-assignees:
    runs-on: ubuntu-latest

    steps:
      - name: Add Assignee
        uses: actions/github-script@v6
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const { number, user } = context.payload.pull_request;
            await github.rest.issues.addAssignees({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: number,
              assignees: [user.login]
            });

上記のようにymlファイルを適切に配置するだけでGitHub側が設定を読み込んで処理を実行してくれます。 毎回Assigneesを自分でポチポチ設定せずに済むようになります!

ワークフローのトリガー

on
on:
  pull_request:
    types: [opened]

on キーではワークフローがどのイベントでトリガーされるかを定義します。

ここではプルリクエストが新しく作成されたときにワークフローがトリガーされます。

今回はプルリクエスト作成時でトリガーするように設定されていますが、いろんなイベントのトリガーがあります。

イベント 説明 設定できるタイプ
push ブランチにコミットがプッシュされた時にトリガーします。
pull_request プルリクエストに関連するアクティビティが発生した時にトリガーします。 opened, closed, synchronize など
issue_comment イシューまたはプルリクエストにコメントが作成された時にトリガーします。 created, edited, deleted
schedule 指定したスケジュールに基づいてトリガーします。
workflow_run 他のワークフローが完了した時にトリガーします。

詳しくは公式のEvents that trigger workflowsを参照してください。

https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows

ジョブの定義

job
jobs:
  add-assignees:
    runs-on: ubuntu-latest

jobs キーでは、ワークフローに含まれるジョブを定義します。

ジョブはワークフロー内で実行される個々のタスクで、ここではアサインを追加するadd-assigneesジョブが定義されています。

runs-on キーは、GitHub Actions ワークフローが実行されるランナー(実行環境)を指定するためのキーです。

ubuntu-latestは、利用可能な最新バージョンのUbuntu仮想環境を使用することを意味します。

基本的にはUbuntuを用いると思います。

ステップの定義

steps:
  - name: Add Assignee
    uses: actions/github-script@v6
    with:
      github-token: ${{ secrets.GITHUB_TOKEN }}
    script: |
      ...略

GitHub Actions ワークフローは、一連のステップ(steps)で構成されます。

ここでは、steps キーでジョブを構成するステップを定義しています。

name

ステップに名前をつけるもので、特にワークフローのログを理解しやすくするために役立ちます。

名前が指定されていないステップでは、実行されるコマンドまたはアクションが名前として利用されます。

uses

特定のアクションを指定するために使用されます。

GitHubが提供しているアクションや、コミュニティが提供しているアクションを指定することができます。

ここではactions/github-script@v6を使用しています。

https://github.com/actions/github-script

with

アクションにパラメータを渡すために使用します。

アクションが要求する入力引数をここで設定できます。

with:
  github-token: ${{ secrets.GITHUB_TOKEN }}

ワークフローではGitHub APIを安全に利用するためにアクセストークンが必要になります。

特に開発者側で発行するものではなく、ワークフロー実行ごとに自動的に新しいトークンが生成されます。

GitHub Actionsで生成されるデフォルトのsecrets.GITHUB_TOKENは、リポジトリへの書き込みアクセスを持っています。

ただし、リポジトリ側にも書き込み許可の設定が必要のため、もし新しいリポジトリなどでワークフローを設定する場合は下記のpermissionsを確認するようにしましょう。

スクリプトの定義

GitHub Script Actionは、GitHub APIにアクセスしたり、ワークフローのデータを操作したりするためのスクリプトをNode.jsの文脈で実行するActionです。

script: |
  const { number, user } = context.payload.pull_request;
  await github.rest.issues.addAssignees({
    owner: context.repo.owner,
    repo: context.repo.repo,
    issue_number: number,
    assignees: [user.login]
  });
script

scriptキーでは実行するJavaScriptコードを定義します。

ここではプルリクエストの作成者をアサインしています。

アクションにどのような引数を渡すべきかは使用するそれぞれのアクションの公式ドキュメントを参照しましょう。

actions/github-script@v6: https://github.com/actions/github-script

github.rest.issues.addAssignees メソッド

githubオブジェクト github-scriptアクションが提供している、GitHub APIと通信を容易にするためのOctokitクライアントインスタンスです。

GitHub Actionsのgithub-scriptアクションを使用してGitHub APIを呼び出す際の構文の一部です。

下記はaddAssigneesに指定するオプションの解説です。

owner / repo

ターゲットとなるリポジトリのオーナー名とリポジトリ名を指定します。

issue_number

アサインを追加したいIssue(またはプルリクエスト)の番号を指定します。

assignees

アサインを追加したいユーザーのログイン名を配列で指定します。

コンテキストオブジェクト

ワークフロー実行時のコンテキスト情報を提供します。

このオブジェクトには、ワークフローが実行されている環境やイベントに関する多くの情報が含まれています。

context.payload

トリガーしたGitHubイベントのペイロードを含んでいます。ここからプルリクエストの情報を抽出します。

  const { number, user } = context.payload.pull_request;

分割代入でaddAssigneesに必要なオブジェクトをcontextから取り出しています。

下記のようにymlのscriptでconsole.log();を出力するとGitHub側のjobsでログを確認することができます。

script: |
  console.log("githubオブジェクト:", github);

contextのオブジェクトは下記のような構成になっていました。

contextオブジェクト: Context {
  payload: {
    action: 'edited',
    changes: { body: [Object] },
    number: 4,
    pull_request: {
      _links: [Object],
      active_lock_reason: null,
      additions: 3,
      assignee: null,
      assignees: [],
      author_association: 'OWNER',
      auto_merge: null,
      base: [Object],
      body: '## 概要\r\n \r\n## 確認方法\r\n',
      changed_files: 1,
      closed_at: null,
      comments: 0,
      comments_url: 'https://api.github.com/repos/ky/github-test/issues/4/comments',
      commits: 3,
      commits_url: 'https://api.github.com/repos/ky/github-test/pulls/4/commits',
      created_at: '2023-10-12T12:28:53Z',
      deletions: 8,
      diff_url: 'https://github.com/ky/github-test/pull/4.diff',
      draft: false,
      head: [Object],
      html_url: 'https://github.com/ky/github-test/pull/4',
      id: 1554049493,
      issue_url: 'https://api.github.com/repos/ky/github-test/issues/4',
      labels: [],
      locked: false,
      maintainer_can_modify: false,
      merge_commit_sha: 'c8bafe85602d5d97c9b6f091064048c845c47c9a',
      mergeable: true,
      mergeable_state: 'clean',
      merged: false,
      merged_at: null,
      merged_by: null,
      milestone: null,
      node_id: 'PR_kwDOKfVOO85coOnV',
      number: 4,
      patch_url: 'https://github.com/ky/github-test/pull/4.patch',
      rebaseable: true,
      requested_reviewers: [],
      requested_teams: [],
      review_comment_url: 'https://api.github.com/repos/ky/github-test/pulls/comments{/number}',
      review_comments: 0,
      review_comments_url: 'https://api.github.com/repos/ky/github-test/pulls/4/comments',
      state: 'open',
      statuses_url: 'https://api.github.com/repos/ky/github-test/statuses/aa8c0abcb9dd9f7fb9fb370872f9f23a97298f29',
      title: 'テスト',
      updated_at: '2023-10-12T12:41:26Z',
      url: 'https://api.github.com/repos/ky/github-test/pulls/4',
      user: [Object]
    },
    repository: {
      allow_forking: true,
      archive_url: 'https://api.github.com/repos/ky/github-test/{archive_format}{/ref}',
      archived: false,
      assignees_url: 'https://api.github.com/repos/ky/github-test/assignees{/user}',
      blobs_url: 'https://api.github.com/repos/ky/github-test/git/blobs{/sha}',
      branches_url: 'https://api.github.com/repos/ky/github-test/branches{/branch}',
      clone_url: 'https://github.com/ky/github-test.git',
      collaborators_url: 'https://api.github.com/repos/ky/github-test/collaborators{/collaborator}',
      comments_url: 'https://api.github.com/repos/ky/github-test/comments{/number}',
      commits_url: 'https://api.github.com/repos/ky/github-test/commits{/sha}',
...
      web_commit_signoff_required: false
    },
    sender: {
      avatar_url: 'https://avatars.githubusercontent.com/u/78721963?v=4',
      events_url: 'https://api.github.com/users/ky/events{/privacy}',
      followers_url: 'https://api.github.com/users/ky/followers',
      following_url: 'https://api.github.com/users/ky/following{/other_user}',
...
      url: 'https://api.github.com/users/ky'
    }
  },
  eventName: 'pull_request',
  sha: 'c8bafe85602d5d97c9b6f091064048c845c47c9a',
  ref: 'refs/pull/4/merge',
  workflow: 'Auto Assign',
  action: '__actions_github-script',
  actor: 'ky',
  job: 'add-assignees',
  runNumber: 5,
  runId: 6495775002,
  apiUrl: 'https://api.github.com',
  serverUrl: 'https://github.com',
  graphqlUrl: 'https://api.github.com/graphql'
}
context.repo

レポジトリの情報(オーナー名とレポジトリ名)を含んでいます。

ワークフローの保存と動作確認

このワークフローを.github/workflowsディレクトリに保存してGitHubリポジトリにプッシュすると、GitHub Actionsがこのワークフローを検出し、定義されたイベント(ここではプルリクエストのオープン)で自動的にジョブが実行されるようになります。

on:
  pull_request:
    types: [opened]

まとめ

GitHub ActionsとGitHub Script Actionを使用することで、GitHub上での開発フローを自動化し、効率化を図ることが可能です。

本記事では、プルリクエストがオープンされた際に自動的にオープナーをアサインするワークフローの設定や詳細について解説しました。

  • GitHub Actionsはイベントに応じて自動的な動作を設定可能
  • ワークフローはイベントトリガー、ジョブ、ステップで構成されている
  • 一例として**`github-script`**アクションでAPI利用してユーザーアサインを自動化出来ることができる
  • おしまい

    コメント

    本記事の内容は以上になります! 開発プロセスの自動化はどのプロジェクトにおいても重要だと思います。 チームでの開発が円滑に進められるようにさらに知見を深めていきたいと思います。


    プログラミングスクールのご紹介 (卒業生より)

    お世話になったプログラミングスクールであるRUNTEQです♪

    https://runteq.jp/r/ohtFwbjW

    こちらのリンクを経由すると1万円引きになります。

    RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

    もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

    https://twitter.com/outputky

    Reactにおける多重クリック対策について

    はじめに

    本記事では、多重クリックによって意図しない処理の重複実行を回避する方法についてまとめています。

    Reactでは何も考えずに実装するとボタン操作などで発生したイベントが複数回実行され不要なリクエストが発生する問題が発生します。

    多重クリック問題について

    ユーザーインターフェースにおいて、ボタンの多重クリックは予期せぬバグや、重複したリクエスト送信を引き起こす可能性があります。

    最初のクリック後、追加のクリックを無効にする仕組みを実装する必要があります。

    useStateを利用した多重クリック防止

    useStateを利用してボタンの状態を管理し、クリックを無効にする例です

    import React, { useState } from 'react';
    
    const ButtonComponent = () => {
      const [isClicked, setIsClicked] = useState(false);
    
      const handleClick = async () => {
        setIsClicked(true);
        // 何らかの処理(API呼び出し等)
        setIsClicked(false);
      };
    
      return (
        <button disabled={isClicked} onClick={handleClick}>
          Submit
        </button>
      );
    };
    

    useStateのデメリット

    useStateを使うの方法はシンプルでわかりやすいですが、いくつかデメリットもあります。

    レンダリングのコスト

    setIsClickedがコールされる度にコンポーネントが再レンダリングされます。

    非同期処理の複雑性

    async/awaitが使われる非同期処理では、setIsClickedが完了するまで待ってから次の処理を行う必要があります。

    これが複雑な処理フローを生む可能性があります。

    これらのデメリットを解決する方法として、Reactのrefを利用するアプローチがあります。

    useRefを利用した多重クリック防止

    refは通常、DOM要素への参照を保持するために用いますが、連打対策として操作フラグを保持する用途にも使えます。

    import React, { useRef } from 'react';
    
    const ButtonComponent = () => {
      const isClickedRef = useRef(false);
    
      const handleClick = () => {
        if (isClickedRef.current) {
          return;
        }
        isClickedRef.current = true;
        // 何らかの処理
        isClickedRef.current = false;
      };
    
      return (
        <button onClick={handleClick}>
          Submit
        </button>
      );
    };
    

    useRefを利用したアプローチのメリット

    レンダリングコストの削減

    useRefはコンポーネントの再レンダリングをトリガーしないため、無駄な描画を抑制します。

    シンプルなコードフロー

    非同期処理を含むロジックでも状態管理をシンプルに保てます。

    注意点

    useRefの値が更新されてもコンポーネントは再レンダリングされないため、UIの更新(ボタンの無効化等)が必要な場合は適切な状態管理手法を併用しましょう。

    例えば処理完了後、モーダルを閉じる際にフラグを更新するなど

    useEffect(() => {
      if (!open) {
        disabledFlagRef.current = false;
      }
    }, [open])
    

    まとめ

    多重クリック防止は、重複したイベント処理やリクエストの発行を防ぎ、意図しないバグを防ぐ重要な実装です。

  • **useState**を利用した方法はシンプルですが、再レンダリングや非同期処理の管理にコストがかかります。
  • useRefを利用することで、再レンダリングを減らし、非同期処理のコードをシンプルに保つことが可能です。
  • おしまい

    コメント

    本記事の内容は以上になります!


    プログラミングスクールのご紹介 (卒業生より)

    お世話になったプログラミングスクールであるRUNTEQです♪

    https://runteq.jp/r/ohtFwbjW

    こちらのリンクを経由すると1万円引きになります。

    RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

    もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

    https://twitter.com/outputky

    フロントエンド(React)における検索機能の実装例

    「OpenAPI仕様に基づいて構築されたオープンソースツールセットでありREST APIの設計、作成、文書化に役立つツールです。」

    https://outputky.hatenablog.com/entry/2023/03/06/015044

    はじめに

    本記事ではReactのフックとルーターを使って、URLのクエリパラメータに基づく検索機能の実装例をまとめています。

    データの量が多い場合、検索機能があると便利ですよね。

    実装内容

    クライアントサイドでのフィルタリング

    • すべてのデータを最初にフロントエンドに取得し、その後、ユーザーの入力に基づいてデータをフィルタリングします。

    URLベースの検索

    • 検索状態はURLのクエリパラメータとして保存されるため、ページの再読み込み時に検索状態を維持できます。

    実装手順

    必要なモジュールをインポート

    初めに、必要なライブラリや関数をインポートします。

    import { useState, useEffect, useCallback } from 'react';
    import { getData } from '../../../../apis/sample/path';
    import { DataType } from '../../../../interfaces/sampleType';
    import { useLocation, useNavigate } from 'react-router-dom';
    import { parseQueryParams, getURLParams } from '../../../../utils/strings';
    
    parseQueryParams

    サンプル

    /**
     * クエリストリングを解析し、オブジェクトに変換
     * @param {Object} defaults デフォルトのパラメーター
     * @param {string} queryString URLのクエリストリング
     * @returns {Object} キーと値のペアを持つオブジェクト
     */
    function parseQueryParams(defaults, queryString) {
        const params = new URLSearchParams(queryString);
        const parsed = {};
        for (let [key, value] of params.entries()) {
            parsed[key] = value;
        }
        return { ...defaults, ...parsed };
    }
    

    解説

    この関数は、URLSearchParams インターフェイスを使用してクエリストリングを解析することでURLからのキーと値のペアを簡単に取得できます。

    デフォルトのパラメーターも与えられるので、指定されたクエリパラメータが存在しない場合はデフォルトの値が使用されます。

    getURLParams

    サンプル

    /**
     * オブジェクトをクエリストリングに変換
     * @param {Object} obj キーと値のペアを持つオブジェクト
     * @returns {string} クエリストリング
     */
    function getURLParams(obj) {
        const params = new URLSearchParams();
        for (let [key, value] of Object.entries(obj)) {
            params.append(key, value);
        }
        return '?' + params.toString();
    }
    

    解説

    この関数もURLSearchParams インターフェイスを使用しますが、この場合はオブジェクトをクエリストリングに変換するために使用されます。

    オブジェクトの各エントリを繰り返し、それをURLSearchParams インスタンスに追加し、最後に整形されたクエリストリングを返します。

    これらの関数を組み合わせることで、Reactアプリケーション内でURLのクエリストリングを操作することができます。

    ステートの設定

    ReactのuseStateフックを使用して、アプリの状態を管理します。allDataAPIから取得したすべてのデータを保持し、filteredDataはフィルタリングされたデータを保持します。

    const [allData, setAllData] = useState<DataType[]>([]);
    const [filteredData, setFilteredData] = useState<DataType[]>([]);
    

    クエリパラメータの初期化

    useLocationフックで現在のURLの情報にアクセスします。この情報を元に、検索クエリの初期状態を設定します。

    こうすることで、URLから直接検索状態を取得できます。

    const location = useLocation();
    const initialQuery = parseQueryParams({ query: "" }, location.search);
    const [searchTerm, setSearchTerm] = useState<string>(initialQuery.query || "");
    

    データ取得と初期フィルタリング

    useEffectフックを使用して、ページロード時にデータを取得します。取得したデータはallDataに格納され、その後検索クエリに基づいてフィルタリングされます。

    useEffect(() => {
        getData().then(response => {
          setAllData(response.data.items);
    
          if (searchTerm) {
            const filtered = response.data.items.filter(item =>
              item.name.includes(searchTerm) || item.tag.includes(searchTerm)
            );
            setFilteredData(filtered);
          } else {
            setFilteredData(response.data.items);
          }
        });
    }, [searchTerm]);
    

    検索処理

    ユーザーが検索ボタンをクリックしたとき、または検索テキストを変更したときにonSearch関数が呼び出されます。

    この関数は、入力された検索クエリを元にデータをフィルタリングし、結果をfilteredDataに保存します。

    const onSearch = useCallback(() => {
        const filtered = allData.filter(item =>
          item.name.includes(searchTerm) || item.tag.includes(searchTerm)
        );
        setFilteredData(filtered);
    
        const queryParams = getURLParams({ query: searchTerm });
        navigate(`${location.pathname}${queryParams}`);
    }, [allData, searchTerm]);
    

    メリットとデメリット

    メリット

    ユーザーエクスペリエンスの向上

    • サーバーサイドとの通信が少ないため、レスポンスが高速

    状態の共有が容易

    • URLに検索状態が保存されるため、そのURLを共有するだけで状態を他のユーザーと共有できる
    デメリット

    初回ロードが遅くなる可能性

    • 大量のデータを前もって取得する必要があるため、初回のロード時間が長くなりそう

    データの変更に弱い

    • データが頻繁に変更される場合、都度フロントエンドのデータも更新する必要がある

    おしまい

    コメント

    本記事の内容は以上になります!


    プログラミングスクールのご紹介 (卒業生より)

    お世話になったプログラミングスクールであるRUNTEQです♪

    https://runteq.jp/r/ohtFwbjW

    こちらのリンクを経由すると1万円引きになります。

    RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

    もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

    https://twitter.com/outputky

    Mechanizeを用いた外部サイトへのアクセス処理の解説

    はじめに

    本記事はRailsのMechanizeを使用してスクレイピングする際に、外部サイトのログイン処理を行う方法について解説しています。

    社内共有用にしようかと思ったのですが外部向けにダミーコードにして公開します。

    Mechanizeとは

    今回の実装では、MechanizeというRubyのライブラリを使用しています。

    Mechanizeは、Webページの自動操作やスクレイピングを行うためのライブラリで、フォームの送信やリンクのクリックなどの操作をプログラムから行うことができます。

    取得というのはHTML要素をMechanizeのメソッドを用いて情報を持たせるということです。

    # 1. Mechanizeのインスタンスを初期化
    # agentという名前のMechanizeオブジェクトを作成。外部サイトにアクセスするための機能を持っている。
    agent = Mechanize.new
    
    # 2. Webページへのアクセス
    # agent.get(URL)メソッドを使用して、指定したURL('https://example.com')のページにアクセスしている。
    # アクセスした結果(ページのHTML構造)は`page`という変数に格納されている。
    page = agent.get('https://example.com')
    
    スクレイピングとは

    スクレイピングは、Webページから必要な情報をプログラムを使って自動的に取得する技術のことを指します。

    例えば、商品の価格や天気の情報など、Webページに表示されているデータを自動で収集する場合に使用されます。

    実装

    特定のサイトのログインを行うモジュールの実装例です。

    サイトに関する新しい機能やメソッドを追加する場合、実装するモジュールを取り込むことで、すべてのクラスやモジュールで新しい機能を利用することができます。

    module FakeSiteAccess
      extend ActiveSupport::Concern
    
      LOGOUT_URL = 'https://fakesite.com/user/logout'
    
      private
    
      def agent
        @agent ||= Mechanize.new
      end
    
      def login(url, fake_sites_login_id, fake_sites_login_password)
        page = agent.get(url)
        form = page.forms[0]
        form.field_with(name: 'loginID').value = fake_login_id
        form.field_with(name: 'password').value = fake_login_password
        page = agent.submit(form)
        if page.css('.errorText').present?
          message = 'FakeSiteへのログインに失敗しました。ログインIDとパスワードをご確認ください。'
          Rails.logger.error message
          raise message
        end
        sleep 1
        agent.get(url)
      rescue => e
        message = "FakeSiteへのアクセスに失敗しました。(エラー内容:#{e})"
        Rails.logger.error message
        raise message
      end
    
      def logout
        sleep 1
        agent.get(LOGOUT_URL)
      rescue => e
        message = 'FakeSiteからのログアウトに失敗しました。'
        Rails.logger.error message
        raise message
      end
    end
    

    1. モジュールの概要

    FakeSiteAccessモジュールは、外部のWebサービスへのログイン・ログアウトを助けるモジュールです。

    (ダミーでFakeSiteAccessと命名していますが、通常だとサイト名などで命名するのが好ましいです)

    ログイン・ログアウトが必要なサイトにアクセスする際は必須の処理ですね。

    このモジュールを実装することで、このサイトのスクレイピング処理を新規実装する際に再利用することができます。

    module FakeSiteAccess
      extend ActiveSupport::Concern
    
    • ActiveSupport::Concernモジュールを拡張して、このモジュールに関連するメソッドやクラスメソッドを簡単に定義できるようにしています。

    公式URL: https://api.rubyonrails.org/classes/ActiveSupport/Concern.html


    2. 定数の定義

    LOGOUT_URL = 'https://fakesite.com/user/logout'
    
    • WebサービスのログアウトURLを定数として定義しています。
    • サイト側のURLなどは不変な値のため、可読性やメンテナンス性も考慮して定数で管理します。

    3. プライベートメソッド

    agentメソッド

    def agent
      @agent ||= Mechanize.new
    end
    

    loginメソッド

    • loginメソッドは、指定されたURL、ログインID、およびパスワードを使用してWebサービスにログインします。
    • ログインに失敗した場合や、何らかのエラーが発生した場合には、エラーメッセージを出力し、例外を発生させます。
    • sleepは、スクレイピングにおいて外部サイトのアクセスする際に間隔を置かないとエラーになるケースがあります。 エラーになる場合はsleepを挟むようにすると解消されることがあります。
    # FakeSiteへのログイン処理を行うメソッド
      def login(url, fake_site_login_id, fake_site_login_password)
        # 指定されたURLにアクセスし、そのページの情報をpage変数に格納
        page = agent.get(url)
        # pageから最初のフォームを取得
        form = page.forms[0]
        # フォームのloginIDフィールドにweb_login_idを設定
        form.field_with(name: 'loginID').value = fake_site_login_id
        # フォームのpasswordフィールドにweb_login_passwordを設定
        form.field_with(name: 'password').value = fake_site_login_password
        # 設定された情報でフォームを送信
        page = agent.submit(form)
        # エラーメッセージが存在する場合の処理
        if page.css('.errorText').present?
          # エラーメッセージを定義
          message = 'FakeSiteへのログインに失敗しました。ログインIDとパスワードをご確認ください。'
          # エラーメッセージをログに出力
          Rails.logger.error message
          # エラーメッセージを例外としてスロー
          raise message
        end
        # 1秒待機
        sleep 1
        # 再度指定されたURLにアクセス
        agent.get(url)
      # 例外が発生した場合の処理
      rescue => e
        # エラーメッセージを定義
        message = "FakeSiteへのアクセスに失敗しました。(エラー内容:#{e})"
        # エラーメッセージをログに出力
        Rails.logger.error message
        # エラーメッセージを例外としてスロー
        raise message
      end
    

    logoutメソッド

    # FakeSiteからのログアウト処理を行うメソッド
      def logout
        # 1秒待機
        sleep 1
        # 定義されたログアウトURLにアクセス
        agent.get(LOGOUT_URL)
      # 例外が発生した場合の処理
      rescue => e
        # エラーメッセージを定義
        message = 'Webサービスからのログアウトに失敗しました。'
        # エラーメッセージをログに出力
        Rails.logger.error message
        # エラーメッセージを例外としてスロー
        raise message
      end
    
    • このメソッドは、Webサービスからログアウトします。
    • ログアウトに失敗した場合や、何らかのエラーが発生した場合には、エラーメッセージを出力し、例外を発生させます。

    おしまい

    コメント

    本記事の内容は以上になります!


    プログラミングスクールのご紹介 (卒業生より)

    お世話になったプログラミングスクールであるRUNTEQです♪

    https://runteq.jp/r/ohtFwbjW

    こちらのリンクを経由すると1万円引きになります。

    RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

    もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

    https://twitter.com/outputky

    HTML要素のiframeタグについて

    はじめに

    本記事はHTMLのiframeに関する情報を調べてまとめた内容になります。

    ドキュメントも参考にしています。

    https://developer.mozilla.org/ja/docs/Web/HTML/Element/iframe#属性

    iframe(Inline Frame)

    iframeは、ウェブページ内に別のウェブページを埋め込むためのHTML要素です。

    基本

    基本構文

    iframeタグでsrc、width と heightを指定します。

    <iframe src="https://example.com" width="500" height="300"></iframe>
    
    src
    • 埋め込むウェブページのURL。
    width & height
    • iframeのサイズ。

    その他の主要な属性

    allow
    • <iframe>に適用される権限ポリシーを指定します。
    • このポリシーは、利用可能な機能(マイク、カメラなど)をリクエストのオリジンに基づいて定義します。
    allowfullscreen
    • <iframe>が全画面モードにすることができる場合にtrueに設定します。
    sandbox
    • 埋め込まれたコンテンツに適用される制限を制御します。
    • この属性の値は、特定の制約を外すための空白区切りのトークンにすることができます。
    srcdoc
    • 埋め込むインラインHTMLを指定し、src属性を上書きします。

    サンプル

    ウェブページ内にYouTubeの動画を埋め込む例

    <iframe width="560" height="315" src="https://www.youtube.com/embed/VIDEO_ID" frameborder="0" allowfullscreen></iframe>
    

    活用方法

    サードパーティーコンテンツの埋め込み

    サイト内コンテンツの分離

    • ダッシュボード内の各セクション
    • ページ内に独立したスクロールエリアを持つコンテンツ

    注意点

    セキュリティ

    • iframeを使用する際は、信頼できるソースからのコンテンツのみを埋め込むこと。
    • クロスサイトスクリプティングXSS)のリスクがあるため、sandbox属性を使用して制限をかけることが推奨される。

    レスポンシブデザイン

    • iframeのサイズは固定であるため、レスポンシブデザインを考慮する際は、CSSJavaScriptを使用してサイズ調整が必要。

    おしまい

    コメント

    本記事の内容は以上になります!


    プログラミングスクールのご紹介 (卒業生より)

    お世話になったプログラミングスクールであるRUNTEQです♪

    https://runteq.jp/r/ohtFwbjW

    こちらのリンクを経由すると1万円引きになります。

    RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

    もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

    https://twitter.com/outputky

    Material-UIのMenuコンポーネント

    はじめに

    Material-UIのMenuコンポーネントを使ってドロップメニューを実装するサンプルを動かしていたのでまとめていきます。

    完成形

    https://codesandbox.io/s/upbeat-turing-3fjnkq?file=/src/App.tsx

    image

    サンプルコード

    Menuコンポーネントの基本的な形をつくるサンプルコードです。

    import React, { useState } from "react";
    import Menu from "@mui/material/Menu";
    import MenuItem from "@mui/material/MenuItem";
    import Button from "@mui/material/Button";
    import "./styles.css";
    
    export default function App() {
      // メニューのアンカー要素の状態を管理するためのuseState
      const [menuAnchor, setMenuAnchor] = useState(null);
    
      // メニューを開くための関数
      // クリックされた要素(ボタン)をアンカー要素として設定
      const handleOpen = (event) => {
        setMenuAnchor(event.currentTarget);
      };
    
      // メニューを閉じるための関数
      // アンカー要素の状態をnullにリセット
      const handleClose = () => {
        setMenuAnchor(null);
      };
    
      return (
        <div className="App">
          <h1>Material-UI Menu Demo</h1>
          {/* メニューを開くためのボタン */}
          <Button variant="contained" color="primary" onClick={handleOpen}>
            Open Menu
          </Button>
          {/* Menuコンポーネントの定義 */}
          {/* anchorElにはアンカー要素の状態を、openにはメニューが開いているかどうかのブール値を渡す */}
          <Menu
            anchorEl={menuAnchor}
            open={Boolean(menuAnchor)}
            onClose={handleClose}
          >
            {/* メニューの各項目 */}
            {/* クリックされたらメニューを閉じる */}
            <MenuItem onClick={handleClose}>Choice A</MenuItem>
            <MenuItem onClick={handleClose}>Choice B</MenuItem>
            <MenuItem onClick={handleClose}>Choice C</MenuItem>
          </Menu>
        </div>
      );
    }
    

    詳細

    コードブロックにも解説はありますが詳細を記載します。

    Menuは、menuAnchorプロパティに設定されたHTML要素に対して相対的に配置されます。

    メニューはユーザーがクリックした特定のボタンや他のUI要素のすぐ下に配置できます。

    開閉の管理

    openプロパティによってメニューの表示/非表示が制御されます。

    真偽値で、trueであればメニューが開き、falseであれば閉じます。

    メニューアイテム

    MenuItemコンポーネントは、メニューの各項目を表現します。それぞれのメニューアイテムはクリック可能で、通常は特定のアクションをトリガーします。

    おしまい

    コメント

    本記事の内容は以上になります!


    プログラミングスクールのご紹介 (卒業生より)

    お世話になったプログラミングスクールであるRUNTEQです♪

    https://runteq.jp/r/ohtFwbjW

    こちらのリンクを経由すると1万円引きになります。

    RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

    もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

    https://twitter.com/outputky

    Macアップデート後、ポート5000が立ち上がらなくなる現象

    はじめに

    サーバー起動時にポート5000が使用できない場合の解決方法についてまとめています。

    原因はMacのアップデートによるものでしたのでその部分の具体的な解決についても触れています。

    サーバー起動時にポート5000が立ち上がらない

    エラー内容:

    Error response from daemon: Ports are not available: listen tcp 0.0.0.0:5000: bind: address already in use

    このエラーは、指定したポート(この場合は5000)がすでに他のプロセスによって使用されていることを示しています。


    エラー自体の主な原因

    • 他のアプリケーションやサービスが同じポートを使用している。
    • 以前のサーバーのセッションが正しく終了していない。
    • OSの特定の機能やサービスがポートを占有している。

    通常のエラー解決方法

    1. 使用中のポートのプロセスを確認
    lsof -i:5000
    2. プロセスの強制終了

    関連するプロセスを強制的に終了します。

    kill -9 $(lsof -t -i:5000)

    プロセスについて解説

    • プロセスは、コンピュータ上で実行中のプログラムのインスタンスです。
    • 各プロセスは一意のプロセスID(PID)を持っています。
    • lsof コマンドは、特定のポートを使用しているプロセスを特定するのに役立ちます。

    具体的なエラーの原因特定

    macOS Montereyのアップデートにより、新しいAirPlay機能が追加されました。

    この機能は、ControlCeというプロセスを通じてポート5000や7000を占有することがあります。

    $ lsof -i :5000                                                                                                             ✘ 1 
    
    COMMAND    PID       USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
    ControlCe 4305 yano   21u  IPv4 0x23efc42ee04f50ed      0t0  TCP *:commplex-main (LISTEN)
    ControlCe 4305 yano   22u  IPv6 0x23efc433ba3c6135      0t0  TCP *:commplex-main (LISTEN)

    原因の解消方法 (Macの設定変更)

    1. システム環境設定を開きます。
    2. 「共有」をクリックします。
    3. 「AirPlay Receiver」のチェックを外して、機能を無効にします。

    image

    これでポート5000や7000が再び使用されることはなくなります。

    おしまい

    コメント

    本記事の内容は以上になります!


    プログラミングスクールのご紹介 (卒業生より)

    お世話になったプログラミングスクールであるRUNTEQです♪

    https://runteq.jp/r/ohtFwbjW

    こちらのリンクを経由すると1万円引きになります。

    RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

    もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

    https://twitter.com/outputky