L08084のブログ

技術記事の執筆は、祈りに近い

【JavaScript】return なしでもPromiseはメソッドチェーンできる

はじめに

then関数に新しいPromiseオブジェクトを返す機能があるので、then内でPromiseをreturnする処理を書かなくても、Promiseはメソッドチェーンが可能だということを最近知ったのでメモ。

Promiseチェーンの色々な例

はじめにで述べたことを理解するために、色々なタイプのPromiseをメソッドチェーンで繋ぎます

returnなし

then()内でreturnしない場合でも、thenが新しいPromiseを返してくれるのでメソッドチェーンが可能です

// new Promise(resolve => resolve()) と同じ
const promise = Promise.resolve();

// 実行結果
//    A
//    B
//    C
promise.then(console.log('A'))
       .then(console.log('B'))
       .then(console.log('C'));

returnあり

チェーンの次の処理に前の処理の結果を渡したい場合は、then()内に渡したい値をreturnする処理を書く必要があります。
returnした値はthenの機能でPromiseオブジェクトに変換されるので、数値や文字列だけでなく、オブジェクトでもPromiseでもどの値を返しても、Promiseはメソッドチェーンが可能です

数値を伝搬するPromiseチェーン

数値をreturnで次の処理に渡しています。returnした数値はthenの機能でPromiseオブジェクトに変換して渡されます

// new Promise(resolve => resolve()) と同じ
const promise = Promise.resolve();

// 実行結果
// 6
// arron functionを使っているのでreturnは省略されているが、
// 実際には数値がreturnされている
promise.then(() => 1)
       .then(value => value + 2)
       .then(value => value + 3)
       .then(value => console.log(value));
Promiseを伝搬するPromiseチェーン

PromiseをPromiseチェーンを使って次の処理に渡すこともできます。
次の例では、戻り値がPromiseのサードパーティ製ライブラリのメソッドをreturnしています。

// 戻り値がPromiseのメソッド
this.keychainTouchId.isAvailable()
    .then((res: any) => {
        // 戻り値がPromiseのメソッド
        return this.keychainTouchId.has(BioAuthService.KEY_A);
    }).then((res: any) => {
        // 戻り値がPromiseのメソッド
        return this.keychainTouchId
            .verify(BioAuthService.KEYCHAIN_KEY, `ロックを解除してください`);
    }).then((res: any) => {
        this.password = res;
        // 戻り値がPromiseのメソッド
        return this.storage.get(BioAuthService.KEY_B);
    }).then((res: any) => {
        this.userId = res;
        const params = {
            loginId: this.userId,
            password: this.password,
            deviceToken: null
        };
        this.action.login(params);
    }).catch((error: any) => {
        // catchは一つでよい
        console.error(error);
    });

結論

何も考えずにthenでつないどけば、Promiseはメソッドチェーンが可能

参考サイト

JavaScript Promiseの本

Promiseを使う - JavaScript | MDN

HTMLメールのCSSなどレイアウト設定について

はじめに

最近仕事でHTMLメールのレイアウトを設定する機会があったので、HTMLメールを作成するときの注意点や参考サイトなどをまとめました

ベースになるテンプレート

HTMLメールを作るのが初めての人は、そもそもHTMLメールのHTMLってどう書けばいいの?ってなると思うんですが、それについてはGmailの公式サイトの方で説明してくれています。

CSS Support  |  Gmail & Inbox Sender Resources  |  Google Developers

  • Gmail公式が紹介しているHTMLメールのテンプレート
<html>
  <head>
    <style>
      .colored {
        color: blue;
      }
      #body {
        font-size: 14px;
      }
    </style>
  </head>
  <body>
    <div id='body'>
      <p>Hi Pierce,</p>
      <p class='colored'>This text is blue.</p>
      <p>Jerry</p>
    </div>
  </body>
</html>

Gmailについては上記のテンプレートでいいですが、メールクライアントによっては、インラインCSSでないとダメなクライアントもあると思います(調べてませんが)。
そのようなメールクライアントへの対応が必要な場合は、下記のようなCSSをインラインCSSに変換するサービスを使うと便利です。

CSSをインラインCSSに変換してくれるWebサービス「Inline styler」

メールクライアントごとの違い

メールクライアント(Gmail, Outlook, Yahoo!メール...)ごとに、使用できるCSSやHTMLタグが大きく異なるため特に注意が必要です。

HTMLメールのレイアウトを検証できるサービス

メールアドレスとHTMLメールのテンプレートを入力するとメールを送信してくれるサービスもあります。メールを送信する環境を構築しなくてもレイアウトの確認ができるので便利

New Email Test — Litmus PutsMail

【HTML】押すと電話がかかるボタンを作る

はじめに

押すと、電話がかかるボタンを作りたい

TELリンクを使う

a要素href="tel:[電話番号]"を設定すると、電話発信用のリンクを作ることができる。

<a href="tel:090XXXXXXXX">電話をかける(090XXXXXXXX)</a>

f:id:l08084:20180901153652p:plain
実際の表示

TELリンクをボタン風に表示する

TELリンクを設定したa要素(アンカータグ)でボタンを囲むことによって、クリックすると、電話を発信するボタンが完成する

<a href="tel:090XXXXXXXX">
  <button ion-button>電話をかける(090XXXXXXXX)</button>
</a>

f:id:l08084:20180901154743p:plain
実際の表示

PCでこのボタンを押すと、FaceTimeだったりSkypeだったりを呼び出してくれる

f:id:l08084:20180901154901p:plain

参考サイト

HTML5/テキスト/a要素 電話発信用のリンクを設定する - TAG index

【Java8】年月日の計算が簡単にできる Date and Time APIについて

新卒ぶりくらいにJavaを触ったら、日時の計算が楽にできるようになってて驚いた

この記事について

Java8で導入された Date and Time API による、日時の計算についてのメモ

Date and Time API

Date and Time APIでは、Java8以前の日付クラス(java.util.Date, java.util.Calender)と比べて、下記の特徴・メリットがあります。

  • 日付・時間・日時をそれぞれ別クラスで扱っている(不要な情報を持たなくてよい)
  • 年月日の計算が楽

日付・時間・日時をそれぞれ別クラスで扱う

Date and Time APIでは、日付・時間・日時を扱うクラスが次のように3つに別れています。

  • 日付
    • java.time.LocalDate クラス
  • 時間
    • java.time.LocalTime クラス
  • 日時
    • java.time.LocalDateTime クラス

コード

実際にDate and Time APIを使って、日時処理をしていく

現在の日付、時間、日時の取得

// 日付
LocalDate date = LocalDate.now();
// 出力: 2018-08-26
System.out.println(date);

// 時間
LocalTime time = LocalTime.now();
// 出力: 14:06:44.359369
System.out.println(time);

// 日時
LocalDateTime dateTime = LocalDateTime.now();
// 出力: 2018-08-26T14:06:44.359400
System.out.println(dateTime);

実行結果

2018-08-26
14:06:44.359369
2018-08-26T14:06:44.359400

StringからLocalDateに変換

文字列をLocalDate型に変換する

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class TestDate {
    public static void main(String... args) {

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

        String text = "2017/03/08";

        // StringからLocalDateに変換
        LocalDate localDate = LocalDate.parse(text, formatter);

        // 出力: 2017-03-08
        System.out.println(localDate);
    }
}

実行結果

2017-03-08

LocalDateからStringに変換

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class TestDate {
    public static void main(String... args) {

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");

        // 現在の日付を取得
        LocalDate localDate = LocalDate.now();

        // StringからLocalDateに変換
        String formattedString = localDate.format(formatter);
        // 出力: 2018年08月26日
        System.out.println(formattedString);
    }
}

実行結果

2018年08月26日

生年月日から年齢を計算する

生年月日から、その人の年齢を計算する

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

public class TestDate {
    public static void main(String... args) {

        // 生年月日
        String birthdate = "1989/10/16";

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

        // 生年月日を表す文字列から、LocalDateを生成
        LocalDate localBirdhdate = LocalDate.parse(birthdate, formatter);

        // 現在の日付を取得
        LocalDate nowDate = LocalDate.now();

        // 現在と生年月日の差分を年単位で算出することによって、年齢を計算する
        long age = ChronoUnit.YEARS.between(localBirdhdate, nowDate);

        // 年齢: 28
        System.out.println("年齢: " + age);
    }
}

実行結果

年齢: 28

年月日の計算

日単位の加算を行ったり、差分を日・時間・分 単位で出したりなど

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class TestDate {
    public static void main(String... args) {

        // 現在の日時を取得
        LocalDateTime nowLocalDate = LocalDateTime.now();

        // 現在の日時の1日後を取得
        LocalDateTime nextLocalDate = nowLocalDate.plusDays(1);

        // 今日と明日の差分を日単位で取得
        long days = ChronoUnit.DAYS.between(nowLocalDate, nextLocalDate);

        // 今日と明日の差分を時間単位で取得
        long hours = ChronoUnit.HOURS.between(nowLocalDate, nextLocalDate);

        // 今日と明日の差分を分単位で取得
        long minutes = ChronoUnit.MINUTES.between(nowLocalDate, nextLocalDate);
        
        System.out.println("現在の日付: " + nowLocalDate);
        System.out.println("明日の日付: " + nextLocalDate);
        System.out.println("差分(日): " + days);
        System.out.println("差分(時間): " + hours);
        System.out.println("差分(分): " + minutes);

    }
}

実行結果

現在の日付: 2018-08-26T17:37:26.337370
明日の日付: 2018-08-27T17:37:26.337370
差分(日): 1
差分(時間): 24
差分(分): 1440

参考サイト

Javaで2つの期間より差を検出する。 - m_shige1979のささやかな抵抗と欲望の日々

【CSS】PCでもモバイルと同じレイアウトの画面で表示する(スマホのフレーム画像に画面をはめる)

今回やること

PCからみると、スマホの枠の中にコンテンツが表示されるようなデザインの画面を作る。参考サイト(フィッシャーマン・コール)

f:id:l08084:20180819164939p:plain
PCだとこーいう表示になる

f:id:l08084:20180819165154p:plain
モバイルからだとこういう表示になる

メリット

PC用の画面デザインを用意せずに、スマホ用のレイアウト一本でいける

大まかな流れ

  1. 表示している端末がPCかモバイルかを判定する
  2. (PCの場合)スマホのフレーム画像を表示する
  3. (PCの場合)画面をスマホの枠に当てはまるように、指定の高さ、幅で表示する
  4. (モバイルの場合)スマホのフレーム画像を表示しない
  5. (モバイルの場合)画面をスクリーンいっぱいに表示する

コード

HTMLとCSSを書いていく

メディアクエリを使用する

メディアクエリを使って、画面の幅でPCかモバイルかを判定する

  @media screen and (max-width: 760px) {
     /* 760pxの幅まで適用される(モバイルの時のレイアウト) */
    .frame {
      /* モバイルの時は、スマートフォンのフレーム画像を表示しない */  
      display: none;
    }
  }

  @media screen and (min-width: 760px) {
     /* 幅が760pxより大きい時に適用される(PC用のレイアウト) */
    .frame {
      /* スマホのフレーム画像を上下中央に表示 */
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      margin: auto;
      z-index: 5;
      width: 450px;
      height: 787px;
      .frame-img {
        position: relative;
        margin-top: 20px;
        margin-left: -1.3px;
      }
      /* コンテンツをスマホのフレーム内に収まるように上下中央に表示 */
      .main {
        width: 376px;
        height: 603px;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        margin: auto;
        z-index: 10;
      }
    }

HTMLはだいたいこんな感じで

<div class="frame">
  <!-- スマホのフレーム画像 -->
  <img alt="" src="./assets/imgs/phone-frame.png" class="frame-img">
</div>
<!-- 表示するコンテンツ -->
<ion-nav class="main" [root]="rootPage"></ion-nav>

【JavaScript】reduceを使ってオブジェクトの配列から一番大きいIDを取得する

モチベーション

強力そうだけどいまいち使いこなせていない関数筆頭のArray.prototype.reduce()について、ちゃんと習得したいという思い

バージョン情報

  • Angular: 6.0.3
  • Typescript: 2.7.2

今回やること

下記のオブジェクトの配列から、reduce()を使って値が最大のIDを取得してみる

  weapons = [
    {id: 1, name: 'ブロードソード', power: 117},
    {id: 2, name: '太陽の直剣', power: 112},
    {id: 3, name: '輪の騎士の直剣', power: 120}
  ];

コードとしては、下記のように書けば最大のIDを取得することができる

// リスト内で一番大きいIDを取得
const max = this.weapons.reduce((a, b) => a > b.id ? a : b.id, 0);

(a, b) => a > b.id ? a : b.idは、reduceのコールバック関数、0はreduceの最初の引数になるinitialValueとなる。
コールバック関数の引数aはコールバックの戻り値を累積するaccumulator(ひとつ前の処理結果または initialValue を指す)にあたり、bは現在処理されている配列要素であるcurrentValueとなる。
処理内容としては、「0と最初の配列要素のidと比較して、大きい方を次の配列要素のidと比較して、大きかった方をさらにその次の配列要素のidと比較して...」という流れになる。

上記のサンプルコードを使用して、配列にオブジェクトを追加するコードを書いてみる

app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
            名前<input type="text" (keyup)="name=$event.target.value">
          <button (click)="addWeapon()">武器を追加</button>
          <ul>
            <li *ngFor="let weapon of weapons">
              ID:{{ weapon.id }} {{ weapon.name }} 攻撃力:{{ weapon.power}}
            </li>
          </ul>
`
})
export class AppComponent {
  name: string;
  weapons = [
    {id: 1, name: 'ブロードソード', power: 117},
    {id: 2, name: '太陽の直剣', power: 112},
    {id: 3, name: '輪の騎士の直剣', power: 120}
  ];

  // 新しい武器を追加する
  public addWeapon(): void {
    // リスト内で一番大きいIDを取得
    const max = this.weapons.reduce((a, b) => a > b.id ? a : b.id, 0);
    // 新しい武器をリストに追加
    this.weapons.push({
      id: max + 1,
      name: this.name,
      power: 200
    });
  }
}

動作確認

サンプルコードを実際に動かしてみる

テキストフォームに、武器名を入力して... f:id:l08084:20180819160226p:plain

「武器を追加」ボタンを押すと、最大のIDが採番された状態でリストに武器が追加されていることがわかる f:id:l08084:20180819160244p:plain

参考サイト

Array.prototype.reduce() - JavaScript | MDN

Express(Node.js) + SQLite でパパッとブログを作る

パパッとは作れてないです。

はじめに

Express(JavaScriptベースのサーバサイドフレームワーク)に入門したくなったので、簡単なブログを作ってみます。Node.jsとnpmはインストールされている前提で進めていくので、インストールされてない方は公式サイトでインストールしてください。

なお、完成版のソースコードは、GitHubに置いています。

github.com

バージョン情報

  • npm: 5.6.0
  • Node.js: 8.11.3
  • Express: 4.16.0

エディタは Visual Studio Code を使用

1.アプリのひな形を作る

Expressアプリケーションのひな形を作成してくれるライブラリであるExpress Generator を使って、アプリの基本部分を作成していきます。

# Express Generatorをグローバルインストール
npm i -g express-generator

Express Generator のインストールが完了したら、下記のコマンドでアプリを作成します。

# ex-blogのところには、アプリ名を入れてください
express -e ex-blog

-eというオプションがついていますが、これは「テンプレートエンジンにEJSを使用する」という意味です。-eを省略すると、テンプレートエンジンにJadeが採用された状態でアプリケーションが作成されます。

下記コマンドを実行すると、アプリが起動するので、ブラウザを立ち上げて、http://localhost:3000/を入力してください。「Express」とブラウザに表示されれば、雛形の作成は完了です。

cd ex-blog
npm i
# アプリ起動
npm start

2. SQLiteのインストール

データベースについては、環境構築とか設定が楽そうなのでSQLiteを使います。公式サイトに行って環境(OSとかの)にあったやつをダウンロードしてください。

3. SQLiteのGUIツールもインストール

GUIツールもついでに落としましょう。OSにあったインストーラをダウンロードしてください。DB Browser for SQLite

4. データベースを設計する

f:id:l08084:20180707205211p:plain

DB Browser for SQLite を起動して「New Database」ボタンをクリックすると、データベースファイルの保存ダイアログが現れるので、mydb.sqlite3という名称で、ex-blogフォルダの配下に新規作成してください。

f:id:l08084:20180707205703p:plain

データベースファイルを作成したら、投稿したブログを保存する用のテーブルを作成しましょう。設定については、下記の画像を参考にしてください。テーブル名をpostに設定しています。

f:id:l08084:20180707210429p:plain

上記の画像では、idのTypeをINTEGERに設定して右側に見えるチェックボックスをすべてONにしていますが、こうすることでidがオートインクリメントの主キーになります。

f:id:l08084:20180707211058p:plain

注意点ですが、テーブルの作成に限らず、データベースの設定が終わったら「Write Changes」ボタンを押すのを忘れないようにしてください。押さないと変更が適用されません。

最後にターミナルでアプリケーションフォルダ(ex-blog)をカレントディレクトリに設定して、SQLite3モジュールをインストールすると、ExpressからSQLiteにアクセスする準備が完了します。

npm i sqlite3

5.コードを書く

Express Generator が作成した雛形のプログラムファイル・テンプレートファイルを更新して、ブログを作っていきます。

5-1. 記事の投稿画面を作る

まず、views/write.ejsファイルを新規作成して、下記のように記入してください。このテンプレートファイルは、記事投稿画面として使用されます。

views/write.ejs

<!DOCTYPE html>
<html>
  <head>
    <title>Express Blog</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
      <h1>新しい記事を投稿</h1>
        <form action="/write" method="POST">
            <table>
                <tr>
                    <!-- 記事のタイトルを入力する欄 -->
                    <th>Title</th>
                    <td>
                        <input type="text" class="title-input" name="title" />
                    </td>
                </tr>
                <tr>
                    <!-- 記事の内容を入力する欄 -->
                    <th>Content</th>
                    <td>
                        <textarea name="content"></textarea>
                    </td>
                </tr>
                <tr>
                    <!-- 記事の投稿ボタン -->
                    <th></th>
                    <td>
                        <button type="submit">投稿</button>
                    </td>
                </tr>
        </table>
        </form>
  </body>
</html>

続いて、記事投稿画面のルーティングスクリプト(投稿画面に遷移した時の処理)を作成していきます。routes/write.jsファイルを新規作成してください。

routes/write.js

var express = require('express');
var router = express.Router();
// sqlite3モジュールのインポート
var sqlite3 = require('sqlite3');

var db = new sqlite3.Database('mydb.sqlite3');
// /write にgetメソッドでリクエストすると、write.ejsをレンダリング
router.get('/', (req, res, next) => res.render('write'));

// /write にpostメソッドでリクエストした時の処理
router.post('/', (req, res, next) => {
    // 投稿画面のフォームからタイトルと本文を取得
    const title = req.body.title;
    const content = req.body.content;
    // 投稿日付を取得
    const createdtime = new Date();
    // 投稿記事をDBにinsertする
    db.run(
        'insert into post (title, content, createdtime) values (?, ?, ?)',
        title,
        content,
        createdtime
    );
    // ホーム画面(index.ejs)にリダイレクト
    res.redirect('/');
});

module.exports = router;

記事投稿画面へのルーティングを追加するために、app.jsにも修正を加えます。(add this!とコメントされている部分を追加すればOKです。)

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
// add this!
// write.jsのロード
var writeRouter = require('./routes/write');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
// add this!
// アドレスの割当
app.use('/write', writeRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

レイアウトもテキトーに整えましょう

public/stylesheets/style.css

body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

input[type="text"],
textarea {
    width: 400px;
    padding: 0.8em;
    outline: none;
    border: 1px solid #DDD;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    border-radius: 3px;
    font-size: 16px;
}
textarea {
    width: 600px;
    height: 200px;
}

input[type="text"]:focus,
textarea:focus {
    box-shadow: 0 0 7px #3498db;
    border: 1px solid #3498db;
}

button {
  border:solid 1px #ccc;
  padding:15px 30px;
  margin: 20px 0;
  font-family:Arial, sans-serif;
  font-size:1.2em;
  text-transform:uppercase;
  font-weight:bold;
  color:#333;
  cursor:pointer;
}

この段階で、npm run startコマンドでアプリを起動した後に、http://localhost:3000/writeにブラウザでアクセスすると、こんな感じの画面が表示されるはずです。

f:id:l08084:20180708161039p:plain
記事投稿画面

5-2. 記事の閲覧画面(ホーム画面)を作る

続いて、投稿した記事を閲覧する画面の方も作っていきましょう。テンプレートファイルのviews/index.ejsを下記のように書き換えてください。

views/index.ejs

<!DOCTYPE html>
<html>
  <head>
    <title>Express Blog</title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1>Express Blog</h1>
    <a href="/write">Write</a>
    <% for(var i=0; i<posts.length; i++) {%>
    <h2><%= posts[i].title%></h2>
    <p><%- posts[i].content%></p>
    <% } %>
  </body>
</html>

ポイントとしては、posts[i].contentの部分で、改行タグ<br>を出力するために<%= %>ではなく、エスケープなしで展開してくれる<%- %>を使っている部分に若干注意が必要です。

    <h2><%= posts[i].title%></h2>
    <p><%- posts[i].content%></p>

記事閲覧画面のルーティングスクリプトも書き換えます。DBからの記事一覧取得と改行コードを改行タグ<br>に変換する処理を行なっています。

routes/index.js

var express = require('express');
var router = express.Router();

var sqlite3 = require('sqlite3');

var db = new sqlite3.Database('mydb.sqlite3');

/* GET home page. */
router.get('/', (req, res, next) => {
  db.serialize(() => {
    // DBから投稿されたブログをすべて取得
    db.all('select * from post', (err, rows) => {
      if (!err && rows) {
        // 改行コードを<br>に変換
        const newRows = rows.map(row => {
          if (row.content) {
            row.content = row.content.replace(/\r?\n/g, '<br>');
          }
          return row;
        });
        console.log(newRows);
        // postsパラメータを渡した状態で、index.ejsをレンダリング
        res.render('index', { posts: newRows });
      }
    });
  });
});

module.exports = router;

6.動作確認

これでいったん完成です。npm run startコマンドの後にブラウザでhttp://localhost:3000/下記のような、ブログのホーム画面が表示されるはずです。

f:id:l08084:20180708172517p:plain
ホーム画面

記事の投稿画面から記事を投稿して、ホーム画面に記事が表示されるかなども確認してみてください。

f:id:l08084:20180708172841p:plain

つまづいたところ

  • アプリから、DBのpostテーブルを呼び出す処理でno such tableみたいなエラーが出まくる
    • テーブルを作った後に、DB Browser for SQLiteでWrite Changeボタンを押しておらず、変更が適用されていなかったのが原因だった