OmniAICreator/Qiita-1.07M huggingface dataset:
遅まきながら音声読み上げをReactで実装しみました。

Google Cloud Text-to-Speechを利用して、React音声読み上げ機能を実装してみました。
※事前にGoogle Cloud Text-to-Speechを利用できるようにしておきます。API Keyも発行しておきます。
※割と雑な記事ですみません。

## 1. Reactアプリ作成
```bash
mkdir speech
cd speech
npx create-react-app .
```

## 2. コード実装(src/App.js)
```react
import React, { useState } from 'react';

function base64ToBlob(base64Data, contentType) {
  const byteCharacters = atob(base64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += 512) {
    const slice = byteCharacters.slice(offset, offset + 512);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, { type: contentType });
  return blob;
}

function App() {
  const [text, setText] = useState('');
  const [audioUrl, setAudioUrl] = useState('');

  const handleTextChange = (e) => {
    setText(e.target.value);
  };

  const handleTextToSpeech = async () => {
    const response = await fetch('https://texttospeech.googleapis.com/v1/text:synthesize?key=XXXXXXXXXXXXXXXXXX', {  # ご自身のAPI-Keyを入れてください。
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        input: {
          text: text,
        },
        voice: {
          languageCode: 'ja-JP',
          ssmlGender: 'NEUTRAL',
        },
        audioConfig: {
          audioEncoding: 'MP3',
          speakingRate: '1.25',
        },
      }),
    });

    const data = await response.json();

    if (data.audioContent) {
      const audioBlob = base64ToBlob(data.audioContent, 'audio/mp3');
      const audioUrl = URL.createObjectURL(audioBlob);
      setAudioUrl(audioUrl);
    }
  };

  return (
    <div>
      <textarea value={text} onChange={handleTextChange} />
      <button onClick={handleTextToSpeech}>Text to Speech</button>
      {audioUrl && <audio src={audioUrl} controls />}
    </div>
  );
}

export default App;
```

## 3. 実行
```bash
npm start
```

## 4. ブラウザアクセス・動作確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/14397d5e-7444-a4f0-3c80-162ee080594f.png)
※無事音声が聞こえました！
Raspberry Pi Pico Wで、ADC3を使った電源電圧を取得する

# はじめに

Raspberry Pi Pico Wを電池で使うにあたって、電源電圧を監視できるといいなと思った。

Pico/Pico Wは5つのADCを持っていて、そのうち1つ（ADC3）がVSYSにつながっている。[^1]
つまり、なにも追加しなくても、そのまま電源電圧が測定できる！はず！
[^1]:ユーザがいろいろ接続できるのは3つで、残りの1つは内臓の温度計につながっている。

しかし、全然取得できなかった。LEDのせい。

（環境はMicroPythonです。）

# 取得できなかった状況

起動したら、電源電圧を測定して、WiFiでレポートする装置を作りたかった。

- Pico WのADC3の線は、（Picoと違い、）2つの役割を持っている。
    - 1つは目当ての電圧測定（ADC3）
    - もう1つは、通信モジュールのクロック（GPIO29）
- これの切り替えは、GPIO25で行う。（highにしたら電圧測定モードになる）
    - デフォルトではhigh
        - 通信モジュールを有効化すると、lowになる。
    - つまり、通信モジュールの使用を開始したら、電圧測定できない。
    - Picoでは、GPIO25は基盤のLEDに接続されていた。
        - Pico Wでは、基盤のLEDは、通信モジュールのGPIOに接続されている。

なるほど？つまり、電圧を取得した後、通信モジュールを有効にすればいいわけね？
**→電圧取得できない！！！**

# 原因

LEDのせい。

**Pico Wでは、基盤のLEDは、通信モジュールのGPIOに接続されている。**

Pico Wで基盤のLEDを使うには、こうやる。
```python
led = Pin("LED", Pin.OUT, value=0)
```

使うGPIO（LED含む）を、全部コードの最初の方で初期化した。
**Pico Wでは、基盤のLEDは、通信モジュールのGPIOに接続されている。**
**LEDの初期化をした時点で、通信モジュールが有効化され、電圧計測できなくなる。**

# 解決

まず最初に、電源電圧を取得するためのgpioのみ初期化して、電源電圧を取得する。
```python
Pin(29, Pin.IN)
battery_adc = ADC(3)
battery_volt = battery_adc.read_u16() * 3 * 3.3 / 65535
```

そのあと、各種初期化する。
電圧取得とLEDの操作は共存できない。

:::note info
GPIO25がhighになっている必要があるが、デフォルトでhigh
:::

:::note warn
電源電圧を取得するには、GPIO29をINにする必要がある。
GPIO29とADC3はおなじところなので、↓これでもいい。（この方がかんたん）

```python
battery_adc = ADC(Pin(29, Pin.IN))
```
:::

:::note warn
ADCは、掛かる電圧`0～3.3`が`0～65535`として得られる。
つまり、電圧に変換するには、65535で割って3.3を掛ける。

ADC3には、VSYSの1/3が掛かるようになっているため、ADC3の値にさらに3を掛ければ、VSYSの電圧になる。
（VSYSには、3.3v以上掛けても良いため、こんな感じに分割されている？）

あと、若干電圧降下してる気がする。それとも測定誤差かもしれない。
:::
StreamReaderに対してAggregateする拡張メソッドを作る

# 追記：2023/7/5
そもそも`ReadLines()`の戻り値が`IEnumerable<string>`だったのに気づいていませんでした・・・。List<string>だと思い込んでました。

`ReadLines().Aggreagate(...)` すればいいですよね（汗）。

便利なはずなのに標準に存在しない機能には、ちゃんと理由がありますね。

この記事は無用の長物となりましたが、拡張メソッドのサンプル程度には役立つかもしれないので、残しておこうと思います。

# StreamReaderに対してAggregateしたい！

ふと、「StreamReaderから取得した各行に対して何か処理をしていく関数型のメソッドってないのかな？（全行取得してメモリにため込んで処理するのではないやつ）」と思い、ChatGPT-4に頼んで作ってもらいました。

# プロンプト

StreamReaderの拡張メソッド Aggregate( this StreamReader sr, Func<T, string, T> func, T initialValue)を定義してください。
次のように使用します。

```C#
int countOfLines = sr.Aggregate(0, (acc, line) => ++acc);
```

# StreamReaderExtensions.Aggregate<T>メソッド

できあがったやつです。

```C#
using System.IO;

public static class StreamReaderExtensions
{
    public static T Aggregate<T>(this StreamReader sr, T initialValue, Func<T, string, T> func)
    {
        T result = initialValue;
        while (!sr.EndOfStream)
        {
            string line = sr.ReadLine();
            result = func(result, line);
        }
        return result;
    }
}
```

```C#:使用方法
using (StreamReader sr = new StreamReader(filePath))
{
    int countOfLines = sr.Aggregate(0, (acc, line) => ++acc);
    Console.WriteLine(countOfLines);
}
```

# 普通に便利

```C#:先頭が#で始まる行は除いた文字列を作る
using (StreamReader sr = new StreamReader(filePath))
{
    var sb = sr.Aggregate( 
        new StringBuilder(),
        (acc, line) => line.StartsWith("#") 
            ? acc 
            : acc.AppendLine(line) 
    );
    
    Console.WriteLine(sb.ToString());
}	
```

なんかこれ普通に便利なんですが、巷に既に同じようなものが存在している感じでしょうか？
ChatGPTも一発で出してきましたし。

とはいえ、StreamReaderの現在位置を変えちゃうので副作用がありますし、あまり良くないのかも？

ネタとして残しておきます。
git rebase -iでコミットをまとめて、プルリクエストをきれいにする

# 過去のコミットに漏れがあった...
着々と機能を実装していたら、過去のコミットに含めるべきだった箇所が見つかった。
とりあえずその箇所をコミットしたが、コミットが分かれてしまいログがきれいじゃない。
↓本当はにんじんの欠片を入れる処理を、cf911d5のコミットに含めたい。
```terminal
8fccfcb (HEAD -> feature/cooking) にんじんの欠片が残っていたので入れた
051bd0b チョコレートを入れる
bb3eafc じゃがいもを入れる
cf911d5 にんじんを入れる
```
このように同じ箇所の実装のコミットが分かれていると、他の人がコミットごとにコードを見た時に「実装漏れか？」と思ってしまう。
できれば同じ箇所のコミットは一つにまとめておきたい。
# git rebase -iで複数コミットを一つにまとめる
複数のコミットを一つにまとめたいときは、git rebase -i HEAD~{n}を使う。

まずはまとめたいコミットがどこにあるかを確認する。
```terminal
$git log --oneline #コミットログを確認する
8fccfcb (HEAD -> feature/cooking) にんじんの欠片が残っていたので入れた
051bd0b チョコレートを入れる
bb3eafc じゃがいもを入れる
cf911d5 にんじんを入れる  
```
すると、対象のコミットまで4つのコミットが確認できる。
コミットをまとめる作業に入る。
git rebase -iコマンドを叩くと↓のように出力される。
```terminal
$git rebase -i HEAD~4
pick cf911d5 にんじんを入れる
pick bb3eafc じゃがいもを入れる
pick 051bd0b チョコレートを入れる
pick 8fccfcb にんじんの欠片が残っていたので入れた

# Rebase 8498aaf..8fccfcb onto 8498aaf (4 commands)
```
ここで、まとめたいコミット（8fccfcb にんじんの欠片が残っていたので入れた）を、まとめる元のコミット（cf911d5 にんじんを入れる）の下に移動させる
```terminal
pick cf911d5 にんじんを入れる
pick 8fccfcb にんじんの欠片が残っていたので入れた //移動させた
pick bb3eafc じゃがいもを入れる
pick 051bd0b チョコレートを入れる

# Rebase 8498aaf..8fccfcb onto 8498aaf (4 commands)
```
まとめるコミットをpickからsquashにする
```
pick cf911d5 にんじんを入れる
squash 8fccfcb にんじんの欠片が残っていたので入れた
pick bb3eafc じゃがいもを入れる
pick 051bd0b チョコレートを入れる

# Rebase 8498aaf..8fccfcb onto 8498aaf (4 commands)
```
wq!で保存すると↓のようになる。　ここでコミットメッセージも変更できる。変更しない場合、全てのコミットメッセージが残ったままになる。`squash`ではなく`fixup`とすると、上のコミットメッセージが残る。

```terminal
# This is a combination of 2 commits.
# This is the 1st commit message:

にんじんを入れる

# This is the commit message #2:

にんじんの欠片が残っていたので入れた

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
```
ここもwq!で保存すると、コミットをまとめるのが成功したと表示される。
```
Successfully rebased and updated refs/heads/feature/cooking.
```
試しにコミットログを見てみると、無事にんじんの欠片のコミットがまとめられていることが確認できる。
```terminal
$git log --oneline
0cab8d3 (HEAD -> feature/cooking) チョコレートを入れる
b0e1c98 じゃがいもを入れる
aca094d にんじんを入れる
```
# 注意点
以上のように、コミットをまとめることでコミットログをきれいにすることができます。
ただし、git rebaseは過去を書き換えてしまいます。
上記の例でも、`git rebase -i`の前後で、コミットのハッシュ値が変わっていることがわかると思います。
コミットログの変更はpush前だと問題ないのですが、すでにpushしてしまっている場合、
`git push -f`で強制pushする必要が出てきます。
↓push後のコミットをまとめてpushしようとしたら怒られます。
```terminal
 ! [rejected]        feature/cooking -> feature/cooking (non-fast-forward)
```
他の人と共有しているリモートリポジトリの過去を変えるのは、それなりに代償が伴う可能性があるのであまりおすすめはしません。
`git rebase -i`をするのはpush前のコミットにとどめとくのがいいのではないでしょうか。
備忘録：MATLAB_初学(1)


# はじめに
行列計算や解析 等で有名なMathWorks社の`MATLAB`は有償ということもあり、触れる機会が少なかったが、人気が高く非常に高性能なイメージがあり気になっていた。

# 目的
下記2点を調査する。
1. `MATLAB`は何ができるのか、どんなことに向いているのか
1. 同じく解析や機械学習に向け人気のある`Python` と比較し、優位性を整理する
# 方法
Webで`MATLAB`について調査し、調査内容より動画や教材にチャレンジして所感を含めた形でまとめる。

# MATLABとは
- `MATLAB`= Matrix Laboratoryの略。
- アルゴリズム開発、データ解析、可視化、数値計算のための統合開発環境。
- 特定用途向けに100以上のアドオン製品を提供。
※ 併せてよく聞く`Simulink`は、システムの設計やシミュレーション、実装、テストのためのグラフィカル実行環境。ブロック図を組合わせて、物理現象をシミュレーションできる。

# MATLABの基礎
https://youtube.com/watch?v=RBbF8fPEbd0

上記[動画]を参考にMATLABの基礎をまとめる。

[動画]: https://youtube.com/watch?v=RBbF8fPEbd0

### MATLABの特長
- 対話的開発環境
- 豊富な拡張ライブラリ
- 使用目的
    - 数値解析
    - データ解析、可視化
    - アルゴリズム開発、プログラミング
    - 外部アプリケーションとの連携

### 活用分野
- 航空宇宙
- 自動車
- 鉄道　　など

上記分野 等で下記などの`データ解析`に用いられている。
- コンディションモニタリング
- 予知保全
- 車両解析
- プロセス管理
- ヘルスモニタリング

### データ解析
- 外部から.`csvファイル`などのデータを入力し、Excel同様にGUI上からデータプロット可能。
- プログラミングを用いてプロットする事で、プロット結果にフィルタリングや外れ値判定などの結果を出力することも可能。　

# 参考文献
東京都市大学：[MATLAB入門]

[MATLAB入門]: https://www.itc.tcu.ac.jp/wp-content/uploads/2022/06/52ad1ee590aaddfba9477307f9af59da.pdf
[C#] キャッシング戦略とパフォーマンスプロファイリング

オリジナルの記事はこちらになります。
https://medium.com/@syantien/caching-strategies-and-performance-profiling-c7e78cd2b014

ASP.NET Coreアプリケーションのパフォーマンスを最適化するための主要なテクニックには、キャッシングとパフォーマンスプロファイリングがあります。このモジュールでは、利用可能なさまざまなキャッシング戦略とパフォーマンスプロファイリングツールを、具体的な例を交えて紹介します。

# 1. キャッシング戦略:

キャッシングは、高コストまたは複雑な操作の出力を後で使用するために保存するプロセスです。Web開発の文脈では、これはよくデータベースクエリの結果や計算の出力を保存することを意味します。ASP.NET Coreはキャッシングのためのいくつかのオプションを提供します。

## 1.1. インメモリキャッシング:

インメモリキャッシングは、最もシンプルな形式のキャッシングで、データはローカルのWebサーバーのメモリに保存されます。

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddMemoryCache();
}

public class MyController : Controller
{
    private readonly IMemoryCache _cache;

    public MyController(IMemoryCache cache)
    {
        _cache = cache;
    }

    public IActionResult Index()
    {
        string cacheEntry;

        // キャッシュキーを探す。
        if (!_cache.TryGetValue("MyKey", out cacheEntry))
        {
            // キーがキャッシュにない場合、データを取得する。
            cacheEntry = DateTime.Now.TimeOfDay.ToString();

            // キャッシュオプションを設定する。
            var cacheEntryOptions = new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromMinutes(5));

            // データをキャッシュに保存する。
            _cache.Set("MyKey", cacheEntry, cacheEntryOptions);
        }

        return View("Index", cacheEntry);
    }
}
```

## 1.2. 分散キャッシング:

分散キャッシングは、複数のサーバー間でキャッシュを共有することで、Webファームのシナリオに理想的です。ASP.NET Coreは、RedisまたはSQL Serverを使用した分散キャッシングの実装を提供します。


```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = "localhost";
        options.InstanceName = "SampleInstance";
    });
}
```

## 1.3. レスポンスキャッシング:

レスポンスキャッシングは、HTTPリクエスト全体の出力を保存して、全体のリクエストパイプラインの再実行を回避することを含みます。

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCaching();
}

[ResponseCache(Duration = 60)]
public IActionResult Index()
{
    return View();
}
```

# 2. パフォーマンスプロファイリング:

パフォーマンスプロファイリングは、アプリケーションのボトルネックを特定するために不可欠です。プロファイリングには、CPUやメモリの使用、I/O操作など、アプリケーションが実行中に使用するリソースの測定が含まれます。

## 2.1. プロファイリングのためのミドルウェア:

ASP.NET Coreは、HTTPパイプラインによるリクエストの処理にかかる時間を測定するためのミドルウェアを提供しています。

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMiddleware<PerformanceMiddleware>();
    // ...
}
```

## 2.2. プロファイリングツール:

.NETアプリケーションのプロファイリングには多くのツールが利用できます。人気のあるものには、Visual Studio Performance Profiler、JetBrains dotTrace、ANTS Performance Profilerなどがあります。

キャッシングとパフォーマンスプロファイリングはパフォーマンス最適化ツールボックスにとって重要なツールです。しかし、アプリケーションをパフォーマンス向上させることと同じくらい重要なのは、それらを安全に保つことです。

次の記事では「セキュリティ対策とベストプラクティスの実装」では、ASP.NET Coreアプリケーションを一般的なセキュリティ脆弱性と攻撃から保護する方法について議論します。認証、認可、データ保護、セキュアなコーディングの実践などのトピックについて深く掘り下げます。

# 絶賛採用中

C#,Azureの構成にて電子レシートサービスを運営しています。フルリモート環境での仕事になります、是非興味ある方はご相談ください。　こちらを参照ください。　https://receiptroller.com/ja/jobs
Leetcode 859. Buddy Strings

https://leetcode.com/problems/buddy-strings/description/

```java
class Solution {
/*  TLE
    public boolean buddyStrings(String s, String goal) {

        for (int i = 0; i < s.length(); i++) {

            char[] ss = s.toCharArray();

            for (int j = 0; j < s.length(); j++) {

                if (i == j) {
                    continue;
                }

                char si = s.charAt(i);
                char sj = s.charAt(j);

                ss[i] = sj;
                ss[j] = si;
                String newS = new String(ss);
                if(goal.equals(newS)){
                    return true;
                }
                ss = s.toCharArray();
            }
        }

        return false;
    }
*/


    public boolean buddyStrings(String s, String goal) {

        // 長さが違うと、どんなSWAPを行ってもGoalにならないので、Falseを返す
        if (s.length() != goal.length()) {
            return false;
        }

        ArrayList<Integer> saveTwoIndex = new ArrayList<>();
        HashMap<Character, Integer> sCharSaveCount = new HashMap<>();

        for (int i = 0; i < s.length(); i++) {
            char sChar = s.charAt(i);
            char goalChar = goal.charAt(i);

            if (sChar != goalChar) {
                // 違う場所をArrayListに保存する
                saveTwoIndex.add(i);
            }
            sCharSaveCount.put(sChar, sCharSaveCount.getOrDefault(sChar, 0) + 1);
        }

        if (s.equals(goal)) {

            // s    = [ab]
            // goal = [ab]
            // -> NG

            // s    = [aa]
            // goal = [aa]
            // -> OK

            // sとgoalが同じである場合、Sを全体検索し、
            // 1個の文字が一つだったらFalse,
            // 1個の文字が二つ以上あれば、True
            for (char sTemp : s.toCharArray()) {
                if (1 < sCharSaveCount.get(sTemp)) {
                    return true;
                }
            }
            return false;
        }


        // 二つのcharを交換する必要があるので、ArrayListの長さが、2でないと、Falseで終了すること
        if (saveTwoIndex.size() != 2) {
            return false;
        }

        // 各Indexを取得する
        int index0 = saveTwoIndex.get(0);
        int index1 = saveTwoIndex.get(1);

        // indexを交換してみて、同じであれば、True
        // 違う場合、False
        return s.charAt(index0) == goal.charAt(index1) && s.charAt(index1) == goal.charAt(index0);
    }
}
```


https://www.youtube.com/watch?v=AyesI86GztM

上記の解説を参考しました
Flutter地方都市イベントの需要を調査しました

## はじめに
最近オフラインでの勉強会が復活してきていますね。私の所属しているゆめみでもYOUTRUSTさんと合同のオフラインFlutter LT会を実施したりしました。

https://note.com/k9i/n/n25ffcc82e23e

東京でのFlutterイベントはFlutterGakkai(7/14)やFlutterKaigi(11/10)などのカンファレンスも予定されており盛り上がっていきそうです。

https://fluttergakkai.connpass.com/event/284732/

https://flutterkaigi.jp/2023/

今回は地方でのFlutterイベントって需要あるのかな？と気になりアンケートを取ったのでその結果を共有します。

## 既存の地方Flutterイベント

アンケート結果の紹介の前に、私の知っている既存のイベントを紹介します。

### Flutter大学

Flutter大学が〇〇.dart(〇〇は都市名)というイベントを各地で開催しています。
直近は8/5に札幌でやるみたいです👀

https://flutteruniv.connpass.com/event/286357/

### Flutter Japan User Group

Flutter Japan User Groupが大阪でもくもく会などを実施していたみたいです。
直近のイベントはなさそうでした。

https://flutter-jp.connpass.com/event/268806/

### Flutter.Okinawa

7/8にFlutter.Okinawa Meetup #3が開催予定みたいです。

https://flutter-okinawa.connpass.com/event/279896/

## アンケート結果

Twitterでアンケートをお願いしたところ現在23件の回答が集まったので結果を共有します。

https://twitter.com/K9i_apps/status/1673952000420220928?s=20

### 1. Flutter LTなどのイベントを開催したら参加したい地域はありますか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2542067/5dba1ba4-c1a4-7044-c394-e4a3ee17b129.png)

大阪圧勝と予想してたのですが、現時点では名古屋と福岡もなかなか需要ありそうです！

### 2. よろしければ住んでいる地域を教えてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2542067/24f743bf-840d-eb70-7df7-a7c794ab3644.png)

大阪6、名古屋5、福岡4でした。地方都市勢が積極的に回答してくれたみたいです🙇‍♂️

### 3. 【会場情報】Flutterイベントを実施するのにおすすめの企業や会場を知っていたら教えてください

ゆめみは勉強会ができる会場をもってないので、
- 地方都市でイベントやるなら会場情報が必要
- できたらその都市に拠点がある企業さんとコラボしたい

といった意図で行った質問でした👀

結果ですが大阪が回答無かったのが以外でした。他の地域に関しては上げてもらった会場や企業さんと連携できたらな〜と思っています🚀

#### 北海道
SAPPORO Incubation Hub DRIVE、サツドラ（エゾハブ）、13LABO

#### 名古屋
マネーフォワード、Freee、スタメン、TOYOTA

#### 大阪
（なし）

#### 福岡
エンジニアカフェ、FGN

#### 東京
10X

### 4. イベント時間帯はいつが良いですか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2542067/f56d55c2-5537-8146-431a-e9255984ab9e.png)

平日夜特に金曜夜が人気ですね

## まとめ
東京以外の都市でのFlutterイベント需要調査結果でした！
ゆめみはフルリモートで各地にFlutterエンジニアが散らばっているので、いろんな地域で勉強会したいなと思っています。

アンケートの回答はまだ受け付けているので、自分の地域で勉強会開催されると嬉しいみたいな人は是非回答ください〜

https://forms.gle/iHb9jqDip1KezFqA7
全国を飛び回った私だけど、店舗がある都道府県がわからん。教えてLINE BOT#with make

　
**日本のどこに店舗があるの？**
----------

私は日本全国に店舗がある某小売業の社員です。全国あちこち転勤でまわり、現在は北海道の店舗に所属しております。お客さまが困ったら相談する部署におり様々な難題を言われます。
「テレビで見た〇〇が使ってたアレどこにあるの」
「数年前◎◎がここでコンサートしてたでしょ。その時の写真ないかい？」
「◇◇市でこの商品券が使える店舗はある？」
知らぬ存ぜぬとはなかなか言いにくい環境であり、操作性の悪い社内専用のスマートフォンで検索してお答えしています。またチーム内の高齢化も進んでおり、慣れない社内専用スマートフォンで恐る恐る操作している姿が歯がゆくもあり、可愛くも見えちゃいます。あるパートさんが繁忙期で客数が多く業務に追われている時に大声で「主任！！◎◎県に店舗ある？検索するより聞いた方が早いわ！主任、聞いてる？？」と・・・。「私も接客中だよぉ！◎◎県に店舗２つあるよ！！」とバタバタしながら返事を返す。こんなことがしょっちゅうありました。

**いつも使ってるLINEなら使ってもらえるんじゃない？！**
-------
私のチームは新しい事や変わる事に対してはきちんと順序だてて説明してからじゃないとなかなか受け入れてくれません。新しいシステムを導入するという場合でもなかなか乗り気にはならず、説明＆ロープレを繰り返して、やっとお客さま対応ができる。それもしばらくすると忘れてしまうからまたロープレを実施する。人間って本当に忘れる生き物なんですね。正確な情報を誰でも簡単に教えてくれるロボットがいたらなぁ。
日本で83.7％が使用しているというLINE。これなら説明＆ロープレをしなくてもみんなに使ってもらえそうだ！とデジタルとは無縁の私の挑戦が始まりました。


**使用したもの**
----

1.　LINE Developers
2.　Make
3.　Google spread sheet

**作り方**
---

1.　LINE DevelopersにはLINEアクセストークンを登録

![アクセストークン.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472688/9617ecb6-a90e-89db-1564-27069a7b2bb8.gif)

:::note info 
 **Webhookの利用**　が　**ON**　になっているか確認！！
:::



2.　Make
シナリオは以下の通りです。はい、初心者丸出しですね！！
![make.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472688/1dc196ae-d2ba-95a6-2343-0946a3065e6e.gif)
あれあれ、エラーが出ているけど・・・どこが間違っているんだ？？何を変えたらいいんだ？状態。とりあえず一つずつ確認していく事にしました。
![スクリーンショット 2023-07-04 164413.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472688/934dc139-bc50-eaaa-6fd9-6e4dfda69d7d.jpeg)
私の泣きの言葉「だめだぁ・・・」は来ているからHTTPは問題なさそう。

![スクリーンショット 2023-07-04 164659.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472688/84af323b-c42d-1295-afea-4c317ffec140.png)


~~あ～あやしげな雰囲気・・・。ここでモジュールの中身をチェックしてはRUN　ONCEを繰り返す。できた！と思ったらなんか変・・・。あれ？ここか！え・・まただめになった。できないかもしれない、どうしよう(´;ω;｀)~~

数時間、格闘した結果！！
HTTPモジュール内のURLに「/」がなかった事が判明。
動くようになりました。
泣きそうになったときに助けていただいたのはこちらを参考にさせていただきました。↓
https://qiita.com/peko-peko-raim/items/082b0990f28af9c7109b


3.　Google spread sheet
![シート (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472688/03fcd333-aa62-0cac-4de1-36e7b8c8e837.gif)




**さあどうなった！？**



https://twitter.com/q6ehmfu3ymxfroc/status/1676224409391239174?s=12&t=iRqcte2Q3pN31kd_TDB6ZA

**挑戦は続く**
------


なんとかお返事してくれました。札幌の店舗が複数あるのに一番上の店舗しか教えてくれない意地悪な子になってしまったので次回はその辺を修正したいです。
まだまだ険しい道のりは続きそうです。
【Golang】time.ParseDuration を H:m:s.ms 形式で出力（経過秒のフォーマット出力）

> Go 言語（以下 golang）で、経過秒の文字列 "`1472.46s`" を、**"`00:24:32.460`" の書式に変換したい**。

[time.Duration](https://pkg.go.dev/time#Duration) 型は [Stringer インターフェースを実装している](https://pkg.go.dev/time#Duration.String)ため、`fmt.Print` 系で、ある程度のフォーマットをしてくれます。

```go
timeDur, _ := time.ParseDuration("1472.46s")
fmt.Println(timeDur)
// Output: 24m32.46s
```

しかし、`Duration.String()` では、**出力の書式が欲しいものと違う**のです。"`24m32.46s`" ではなく "`00:24:32.460`" の書式で欲しいのです。

[「golang "time.Duration" 経過秒からフォーマット出力」でググって](https://www.google.com/search?q=golang+%22time.Duration%22+%E7%B5%8C%E9%81%8E%E7%A7%92%E3%81%8B%E3%82%89%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88%E5%87%BA%E5%8A%9B)も、任意のフォーマットで出力する方法がタイトルから判別できる記事がなかったのと、あっても `time.Hour` などでゴリゴリ計算する方法ばかりだったので、自分のググラビリティとして。

## TL; DR <sub><sup>（今北産業）</sup></sub>

1. 経過秒の文字列から `time.Duration` オブジェクトを作成する
1. UNIX 時間がゼロ秒の `time.Time` オブジェクトを作成し、上記の経過ぶんを加算する
1. `time.Format` で[任意の書式（time フォーマット)](https://pkg.go.dev/time#pkg-constants) を指定する

## TS; DR

```go
package main

import (
	"fmt"
	"log"
	"time"
)

func Example() {
	const format = "15:04:05.000"

	input := "1472.46s"
	output := SecToTime(input, format)

	fmt.Println(output)
	// Output:
	// 00:24:32.460
}

func SecToTime(sec string, format string) string {
	tDur, err := time.ParseDuration(sec)
	if err != nil {
		log.Fatal(err)
	}

	tZero := time.Unix(0, 0).UTC()

	return tZero.Add(time.Duration(tDur)).Format(format)
}
```

- [オンラインで動作をみる](https://go.dev/play/p/_sI3LaMpdwx) @ GoPlayground

### Time フォーマットの種類

```go
func (t Time) Format(layout string) string
```

```go:leyout の定義済み定数と、表記例
const (
	Layout      = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order.
	ANSIC       = "Mon Jan _2 15:04:05 2006"
	UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
	RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
	RFC822      = "02 Jan 06 15:04 MST"
	RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
	RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
	RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
	RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
	RFC3339     = "2006-01-02T15:04:05Z07:00"
	RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
	Kitchen     = "3:04PM"
	// Handy time stamps.
	Stamp      = "Jan _2 15:04:05"
	StampMilli = "Jan _2 15:04:05.000"
	StampMicro = "Jan _2 15:04:05.000000"
	StampNano  = "Jan _2 15:04:05.000000000"
	DateTime   = "2006-01-02 15:04:05"
	DateOnly   = "2006-01-02"
	TimeOnly   = "15:04:05"
)
```



## 参考文献

- [time.Duration.String()](https://cs.opensource.google/go/go/+/refs/tags/go1.20.5:src/time/time.go;l=644) | go @ opensource.google
- [constants](https://pkg.go.dev/time#pkg-constants) | time package @ pkg.go.dev
- [How to format a duration](https://stackoverflow.com/a/69101998/18152508) @ StackOverflow
【FileMaker】cURLでファイルをアップロードする

前回まで、以下のような記事を残してきました。

https://qiita.com/tyuma/items/7292815e3cdeec31564d

https://qiita.com/tyuma/items/84c5a2982bcb9fb0a4be

今回は、ファイルをファイルサーバーなどにアップロードするケースです。
図にすると、こんな感じですね。
![スクリーンショット 2023-07-04 16.18.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60538/331f03e0-50a4-5741-e9ef-587b01f97838.png)

なぜかっこ付きで「かしこまり〜」かというと、ちゃんとファイルが置けたらうんともすんとも言わず（成功した場合コードが返ってこない）、なんともツンデレです。

# 実行環境
前回同様、cURLの扱えるようになったFileMaker Pro ver.16以上でお試しください。
この記事用に作成したサンプルは、FileMaker Pro 19.6.3です。

# curlがちょっと不安な方
こちらで、注意点をまとめています。
合わせてご覧ください。

https://qiita.com/tyuma/items/7f0c161509f3c5c78257

# 本家curlオプション一覧
Claris FileMakerのヘルプにある、「[サポートされている cURL オプション](https://help.claris.com/ja/pro-help/content/curl-options.html)」は必ずチェックしましょう。
今回、この記事で使用しているオプションは、ほんの一例です。

# 前提
ファイルサーバーなどで、ポートを開けておく設定などはすでに済んでいるものとします。

# 基本的なアップロードのcurl文

```
curl -T {fileobject} -u {user}:{pass} ftp://{host}/{file}
```
"-u"はFTP用に割り振られたユーザ名とパスワードを指定します。
"-T"は、アップロードするファイル自身を指定します。
接続先のアドレスとアップロードするファイルをcurl文に指定することで、目的のファイルをアップロードできます。

ということで、今回必要な情報は、以下になります。

・FTPなどのユーザー名
・FTPなどのパスワード
・接続するサーバーのアドレス
・アップロードするファイル名
・アップロードするファイル

アップロードするファイルは、あらかじめFileMakerのオブジェクトフィールドに格納してあります。

# FileMakerのステップに割り当てる

この、curl文をFileMakerの「[URL から挿入](https://help.claris.com/ja/pro-help/content/insert-from-url.html)」に割り当てます。

図解してみました。
![スクリーンショット 2023-07-04 16.31.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60538/1cc598f9-e188-bb98-08b8-56496d61e455.png)

構文では省きましたが、TLS/SSL通信で行う場合は、cURLオプションの設定で、"--ssl"を追加します。
その他、sftpの場合は、URLを指定する際の通信プロトコル"ftp"を"sftp"に変更してやってみてください。

:::note warn
FTPはユーザー名やパスワードなどの認証情報や転送するデータを暗号化しないで送受信します。
このため、ファイルの盗聴などリスキーと言われています。
TLS/SSLやSFTPなどでより安全にデータの送受信を実装しましょう〜
:::

# cURLで外部連携を簡単に
curl文をFileMakerで扱うことで、FileMakerと外部との連携のハードルが下がってきますね。
皆さん日常でもよく使う"https"もファイル転送プロトコルの一つです。
ブラウザで見てた世界をFileMakerに展開したり、お馴染みのpdfやcsv,Excelなどのデータをホストと連携するなど、用途はたくさんありますので、ぜひ活用してみてください。
【レビューで指摘されがちシリーズ】アサートで比較するものはべた書きのほうが分かりやすい

レビューで指摘された内容をそのまま放流するシリーズ。（今回は実はレビューじゃないけど）[まとめページ](https://qiita.com/lx-sasabo/items/c2d2e5ccae10ff3ecabd) もあります。

NUnit のテストで、インスタンスの型が期待通りの型になっているか確認するコードを下記のように書いていました。

```cs
        private Type _hogeType

        [SetUp]
        public void Init()
        {
            _hogeType = typeof(Hoge);
        }

        public void HogeTest1()
        {
            var instance = HogeFugaFactory.Create("HOGE");

            Assert.IsInstanceOf(_hogeType, instance);
        }

        // 以降似たようなテストが続く
```

このコードは一度マージされました。
そして数か月後に発掘され、コメントをもらいました。

> このような書き方はテストの見通しを悪くします。想定されるexpectedの値は直接定義するべきです。

コメントを見て、自分もそうだと思いましたｗ。
正直なぜ Init() の中で一度メンバ変数に落とし込んだのか、ちゃんと覚えていないのですが、推測するに `typeof(Hoge)` の処理を何度も書きたくなかったのだと思われます。

`typeof(Hoge)` を毎回書かないようにする手段としてメンバ変数に落とし込んだと思われるわけですが、実はこれ、IsInstanceOf の書き方で解決することができることも教えてもらいました。

https://docs.nunit.org/articles/nunit/writing-tests/assertions/classic-assertions/Assert.IsInstanceOf.html

![Assert.IsInstanceOf _ NUnit Docs - Google Chrome 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/31f802c4-ae0b-295e-d953-e390977b2af1.png)

つまり、こう書けばすっきり書けたわけです。

```cs
        public void HogeTest1()
        {
            var instance = HogeFugaFactory.Create("HOGE");

            Assert.IsInstanceOf<Hoge>(instance);
        }
```

アサートで比較するものはべた書きのほうが分かりやすいです。変数化すると定義元を確認しないといけないのでテスト内容が分かりにくくなります。
同じような指摘を RSpec でも受けた経験があるので、この話はテスト固有の話でも無いような気がします。


ということで、今後は同じような指摘をもうもらわないようにしたいと思います！（フリじゃないよ！）
Bigquery 備忘録２

# 日付のついたテーブルを特定の条件で絞り込む
ex)過去4月1日時点のデータが欲しい場合

```
  SELECT
    table_id,
    SUBSTR(table_id, -8) as ymd
  FROM
    `table.__TABLES_SUMMARY__`
  WHERE
    table_id LIKE 'table_20%0401'
```

| table_id       | ymd      |
|:---------------|:---------|
| table_20200401 | 20200401 |
| table_20210401 | 20210401 |
| table_20220401 | 20220401 |


上記をサブクエリ等にして再帰的にテーブルを検索する

```
with subquery as (
  SELECT
    table_id,
    SUBSTR(table_id, -8) as ymd
  FROM
    `table.__TABLES_SUMMARY__`
  WHERE
    table_id LIKE 'table_20%0401'
)  
select
   subquery.ymd,*
  FROM
    `table_*`,
    subquery
  WHERE
    _TABLE_SUFFIX = subquery.ymd
```
Leetcode 137. Single Number II

https://leetcode.com/problems/single-number-ii/description/

# アプローチ
- HashMap

```java
class Solution {
    public int singleNumber(int[] nums) {
      HashMap<Integer, Integer> hashMap = new HashMap<>();

      for (int num : nums) {
          hashMap.put(num, hashMap.getOrDefault(num, 0) + 1);
      }
      return hashMap.entrySet().stream()
                .filter(obj -> obj.getValue() == 1)
                .mapToInt(obj -> obj.getKey())
                .findFirst()
                .getAsInt();
    }
}
```
three.jsでanimation system その7

# 概要
three.jsでanimation systemを、理解したかった。
練習問題、やってみた。

# 練習問題
vrmのAnimationClipを自作せよ。

# 写真

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18104/fcee0dd2-17aa-a433-7ac7-55d98347404d.png)

# サンプルコード

```
let lastTime;
let mixer;
let currentVrm = null;
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(45, 500 / 400, 0.1, 1000)
camera.position.set(0, 1.2, -1.9)
camera.rotation.set(0, Math.PI, 0)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(500, 400)
document.body.appendChild(renderer.domElement)
const directionalLight = new THREE.DirectionalLight('#ffffff', 1)
directionalLight.position.set(1, 1, 1)
scene.add(directionalLight)
const loader = new THREE.GLTFLoader();
loader.load('https://drumath2237.github.io/three-vrm-test/models/undefined-chan-toon.vrm',
	(gltf) => {
		THREE.VRM.from(gltf).then((vrm) => {
			currentVrm = vrm;
			scene.add(vrm.scene);
			vrm.blendShapeProxy.setValue(THREE.VRMSchema.BlendShapePresetName.Joy, 1.0);
			vrm.blendShapeProxy.update();
			var clip = make();
			console.log(clip);
    	mixer = new THREE.AnimationMixer(vrm.scene);
	   	mixer.clipAction(clip).setEffectiveWeight(1.0).play();
		});
	},
	(progress) => console.log('Loading model...', 100.0 * (progress.loaded / progress.total), '%'),
	(error) => console.error(error)
)
function tick() {
	requestAnimationFrame(tick)
	let time = new Date().getTime();
	if (mixer)
		mixer.update(time - lastTime);
	lastTime = time;
	renderer.render(scene, camera)
}
function make() {
	const bones = [THREE.VRMSchema.HumanoidBoneName.Hips].map((boneName) => {
		return currentVrm.humanoid.getBoneNode(boneName)
	})
	//alert(bones);
	const clip = THREE.AnimationClip.parseAnimation({
		hierarchy: [{
			keys: [{
				rot: new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)).toArray(),
				time: 0.0,
			}, {
				rot: new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 1.2, 0)).toArray(),
				time: 100.0,
			}, {
				rot: new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)).toArray(),
				time: 200.0,
			}, {
				rot: new THREE.Quaternion().setFromEuler(new THREE.Euler(0, -1.2, 0)).toArray(),
				time: 300.0,
			}, {
				rot: new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)).toArray(),
				time: 400.0,
			}, {
				rot: new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)).toArray(),
				time: 500.0,
			}]
		}]
	}, bones)
	clip.tracks.some((track) => {
		track.name = track.name.replace(/^\.bones\[([^\]]+)\].(position|quaternion|scale)$/, '$1.$2')
	})
	return clip;
}

tick()


```


# 成果物

https://embed.plnkr.co/plunk/Ib25N7Pb2Fi2rofB

以上。
キャンペーン企画を検索するLINEBotを作ってみた！

## 「検索って地味に辛い」と思うこと、ありませんか？


私はクレジットカード会社で働いています。
仕事で他部署から
「ｘｘのクレジットカードは何のキャンペーン企画を実施していますか？」
「このクレジットカードはｘｘというキャンペーン企画の対象ですか？」

というような問い合わせが来たりします。

ひとつひとつのカードの種類に対してキャンペーンの企画が色々行われていて
その為の情報がフォルダやデータやシステムに散らばっていて
探すのが大変です。


そこで、
知りたいことを聞いたら
知りたいことを返して欲しい！

そんな「キャンペーン企画を検索するLINEBot」を作ろうと思います！




## ＜使用ツール＞
- [LINE Developers](https://developers.line.biz/ja/)
- [Make](https://us1.make.com/organization/747137)
- [SSSAPI](https://sssapi.app/)
- Google スプレッドシート




完全に初心者のため、ゆっくり一歩づつ行こうと思います。。。

・[LINE Developers](https://developers.line.biz/ja/)のアカウントを作成しログイン
　プロバイダを作成する



・Messaging APIでチャネルの名前など登録
　→ これがLINEBotの名前になります





## 準備

・Google スプレッドシートでキャンペーン企画をまとめた一覧表を作成して
　[SSSAPI](https://sssapi.app/)というものでAPI連携できるものにします

※調べたいクレジットカード名は、ここでは　1〜26　の数字を名称とさせていただきます。

![スクリーンショット 2023-07-04 23.19.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476276/bebc65fb-0c4e-fefe-758e-451808ba09c7.png)



・Google スプレッドシートは、データ内容を変更しても更新が反映してくれます
　（すごい！感動！）
　SSSAPIのサイトで簡単に作成ができました！


**API URL を、2：青いHTTPモジュールと、3：JSONモジュールのところで使います**



![スクリーンショット 2023-07-04 15.46.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476276/890956c3-8486-6546-1181-51900dd0c78a.png)










## Makeシナリオ
### [Make](https://us1.make.com/organization/747137)にログインし新しくシナリオを作成します




![スクリーンショット 2023-07-08 14.20.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476276/44f26ff2-7414-dcde-761e-6da5eeb31845.png)






### 1：まず左のLINEを作る

・出現したアイコンをクリック
　サービスの中から
　LINEを選択 → Watch Event
　Webhookを作成
　（LINEとMakeを繋ぐもの）

・コネクションを作成
　チャネルアクセストークンの部分にLINE Developersで発行したアクセス情報を入力

・Messaging API → LINE Developers → Messaging API設定
　画面下のチャネルアクセストークンを発行し貼り付ける


・LINE Webhook　アドレスを今度はLINE DevelopersのWebhook設定に貼り付ける

・Webhookの利用をON







### 2：青いHTTPモジュール


**準備で作成したSSSAPIのURLをここで使います**
・URLのところに先ほどSSSAPIで作成した企画一覧のAPI URLを貼り付け
　/で繋いでLINEからのメッセージを入れます



![スクリーンショット 2023-07-04 13.43.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476276/2bda5b74-02b9-5381-fdc0-71c00c20da95.png)







### 3：紫のJSONモジュール


![スクリーンショット 2023-07-04 15.57.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476276/cf335372-abd1-6c54-6934-145afd168800.png)




・準備で作成した企画一覧APIデータのE列のカード名を打ち込まれたら
　該当行の内容を返信します


**準備で作成したSSSAPIのURLをここで使います**
・SSSAPIのURLに繋いで
　filter__カード名__exact=（ここにLINEに送られてきたメッセージ）


![スクリーンショット 2023-07-08 21.06.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476276/4729da39-1c59-f104-f37b-ffcdc8f09134.png)



### 4：最後！右のLINEモジュール

・Messages > Item1 > Textの欄に、返して欲しい項目を選択します
これでできました！


![スクリーンショット 2023-07-04 13.44.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3476276/f1e44851-eafa-08aa-9d96-d456d9168785.png)










## やっとできた！


カード名称「3」を打つと、そのカードが対象の企画情報を返してくれます！



https://twitter.com/KumiKimu4488/status/1677546244313583616


### 感想

すこしうまくできかけた状態のコードを崩すのは
とても怖くて触れない

そして何も進まない（焦る・凹む・涙）

でもなぜ触れないか
今以上に未完成となって
今の出来以下になることが怖いから


それでも没頭していくと
楽しいという感覚が灯ってきた


できたこともあるが、実際の仕事で活かすには足りないことがあり
結果的にやりたいことまで道半ばという結果にはなったが
何かを作るという心構えを鍛えられたように思う。



スタート地点にすら立てていなかったことにも気づくことができて、
次回に向けて気を引き締めていきたい。
【Rails】退会機能実装(論理削除)

## はじめに
deviseを使用して退会機能(論理削除)の実装について
アウトプットします！

## 論理削除とは？
データベースのレコードを非表示にして削除されてないけど
削除されているのと同じ扱いにすること。

**メリット**
* レコードが残っているのでデータの復旧が可能
* 削除されたレコードの履歴を残せるのでデータの検索が出来る

**デメリット**
* データ量増大によりストレージが圧迫される

## 物理削除とは？
データベースのテーブルから対象のレコードを物理的に削除すること

**メリット**
* レコードが削除されるのでストレージが圧迫されにくい
* 管理がしやすい

**デメリット**
* データの復旧が容易ではない
* 削除さえたデータの参照ができない

## カラムの追加

```java:ターミナル
$ rails g migration AddIsValidToUsers is_deleted:boolean
```
このカラムにデフォルト値(false)を設定する。

```java:schema.rb
t.boolean "is_deleted", default: false
```
booleanを使用することにより、ture、falseで退会しているかどうかを判断します。
初期値をfalseに設定しますので、退会済みの場合はtrueとなります。

## ルーティング
```java:routes.rb
get "/out" => "parents#out"
patch "/withdraw" => "parents#withdraw"
```

## controllerの編集
```java:XXX_controller.rb
  def withdraw
    @user = current_user
    @user.update(is_deleted: true)
    reset_session
    redirect_to root_path
  end
```

## viewの編集
```java:app/views/parents/out.html.erb
<div>
  <h2>本当に退会しますか？</h2>
  <div>
    <p>退会する場合は、『退会する』をクリックしてください。</p>
  </div>
  <div>
    <%= link_to '退会しない', mypage_path(@user) %>
    <%= link_to "退会する", withdraw_user_path(@user), method: :patch %>
  </div>
</div>
```

## まとめ
今回は論理削除にて退会機能を実装しましたが、サービスによって
実装方法の検討をする必要性を感じました!
Slickスライドでブラウザ拡縮時にコンテンツ幅も保ちたい時の応急処置

Slickスライドでブラウザ幅を縮小した後に幅を拡大すると
スライドのwidth（仕様でついてる方）が縮小時の幅が効いたままでブラウザ幅拡大時にコンテンツ幅が戻らない!
という事象に出会いました。

その際の調整方法を備忘録として残します。

## 原因
基本的にスライドのコンテンツ幅がおかしくなる時の原因は以下のクラスについているwidthだったりします。

```
.slick-track
.slick-slide
```

## 応急処置
私の場合、上記のクラスにそれぞれ納めたいコンテンツ幅を
widthやmax-widthで指定する（いずれも`!importatnt`付き）
でなんとか表示が戻りました。

slick側でDOMを触っている雰囲気があるため、
外部cssでレイアウトを整えている場合は!importatntで指定しないとCss優先順位の影響でwidth効かない場合があるので注意が必要そう

## 学び
slickでwidth周りがおかしくなったら
- .slick-track
- .slick-slide

を真っ先に確認するのが良いかもしれないです

何年もslickを使っていますが、いまだに慣れないです....
【Django】カスタムユーザーの作成

# カスタムユーザーとは
Djangoにおけるカスタムユーザーとはwebアプリ開発者が独自に作成するユーザのモデルのことです。Djangoには標準で**User**モデルが用意されていますが、こちらのモデルを使うことはDjango公式でも推奨されていません。
カスタムユーザを定義することで標準のUserモデルでは定義されていないfieldを作成することができます。

# Userモデルについて
それでは標準で用意されているUserモデルではどのようなフィールドが与えられているか下にまとめてみました。(一部)


| field | 補足 |
| --- | --- |
|username| 必須。 150文字以下。<br>ユーザー名には英数字が含まれている場合があります|
|first_name|オプション(blank=True)。150 文字以下。|
|last_name|オプション(blank=True)。150 文字以下。|
|email|オプション(blank=True)。メール |
|password|必須。パスワードのハッシュとメタデータ。|


一部ですが標準のUserモデルは上記のようなfieldに対して値を設定できます。
webアプリケーションを作成する際にユーザーの情報としてもっとたくさんのfieldを定義したい！！といったときにカスタムユーザを定義する必要があります。
というか、アプリ作成をするにあたってカスタムユーザーの定義は必須です。前述の通り、Djangoの公式でもUserモデルは非推奨とされており、その理由は**Userモデルで一度定義していまうと(migrationすると)もうカスタムユーザーにはできないから**なんです。
要はあとからアプリの拡張をしたいと思ってユーザのfieldに追加したい項目ができても変更ができないということです。
参考：https://docs.djangoproject.com/en/4.2/ref/contrib/auth/

# カスタムユーザ作成方法
実際にカスタムユーザーを定義していきますが大きな手順としては以下の4つです。

1. カスタムユーザの定義
2. カスタムユーザーのカスタマイズ
2. ユーザ管理モデルとして適用させる
2. 管理画面でカスタムユーザを管理する


## 1.カスタムユーザの定義 
そもそも標準で準備されているUserモデルはどのように定義されているかというと、**AbstractUser**というクラスを継承して定義されています。つまり、**AbstractUser**を継承し、**CustomUser**を再定義すれば簡単にカスタムユーザを創ることができます。このAbstractUserは権限管理やバリデーションなどの機能を持っていますので、カスタムユーザーでの認証も簡単にすることができます。


```python:カスタムユーザーの定義
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    pass
```

実際にカスタムユーザーを作成しました。AbstractUserを継承してくるだけで処理を終えているのでこの状態のカスタムユーザーは実質Userモデルを使用しているのと変わりありません。(しかし、こうしておくことで最初はUserモデルと同等での使用でもあとからカスタマイズが可能になる。)

## 2.カスタムユーザーのカスタマイズ
それでは自身が欲しいfieldをカスタムユーザに追加していきましょう。
AbstractUserを継承していることから、上の表で示したUserモデルが持っているfieldはすでに持っているため、追加したいfieldのみ記述すればよいです。
今回は身長と体重をユーザ情報をとして持っておきたいので、weightとheightを定義することにします。
```Python:カスタマイズ
from django.db import models
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    height = models.FloatField()
    weight = models.FloatField()

    REQUIRED_FIELDS = ["height", "weight"]
````

また、ここでREQUIRED_FIELDSには追加したfieldをリスト形式で代入しています。これはコマンドでのユーザ作成**createsuperuser**コマンドでのときに入力必須フィールドだと知らせるための記述です。

## 3.ユーザ管理モデルとして適用させる
次に認証に使用するユーザーモデルをカスタムユーザに変更していきます。
現状ではUserモデルが認証に使われる設定になっています。そのためカスタムユーザを作成し、ユーザを作ろうとしても、認証がUserモデルのため全く意味をなさないことになります。
そのため、認証に使うモデルの設定を**settings.py**ファイル内で行いましょう。

```
AUTH_USER_MODEL = 'アプリ名.カスタムユーザーのModel名'

#今回だと
AUTH_USER_MODEL = 'アプリ名.CustomUser'
```

ここまで来ましたら、**makemigration→migrate**してもらえばモデルの定義ができています。

## 4.管理画面でカスタムユーザを管理する
カスタムユーザーの定義が済み、webアプリ内でのユーザー作成にカスタムユーザを適用させ認証時使用モデルも設定したため、これで終わりかと思いきや、このままではDjangoで用意されている管理画面にカスタムユーザーが適用されていません。(**localhost:ポート/admin画面**)
管理画面でカスタムユーザを適用させるためには、**admin.py**ファイルに以下の記述を加えます。

```python:admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model

CustomUser = get_user_model()

admin.site.register(CustomUser)
```

以上でカスタムユーザーの定義から、認証、管理に及ぶ設定の変更が完了です。

# おわりに
Djangoでwebアプリを作成するとこのカスタムユーザを作ることが必須であるため、忘れないように記事に残しておきました。カスタムユーザー定義内でメソッドの定義やオーバーライドもできたりするのでそちらも活用できると非常に読みやすいコードになりそう！
今後もDjangoでのwebアプリ開発がんばっていきましょ～～～
【FileMaker】cURLでファイルをダウンロードする

[前回](https://qiita.com/tyuma/items/7292815e3cdeec31564d)の記事では、FileMakerのcurlを使ってファイル内容を取得しました。

https://qiita.com/tyuma/items/7292815e3cdeec31564d

今回は、ファイルサーバーなどからファイル自身をダウンロードする方法です。
前回の記事を読まなくても大丈夫！

ホスト側に他システムからcsvやExcelでテンポラリ的にデータを出力して、FileMakerにそのデータを取り込む、というような流れです。
図にすると、こんな感じですね。
![スクリーンショット 2023-07-04 14.51.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60538/c78eb990-3be8-22c3-c9ac-6eb1e05d40cb.png)


# 実行環境
前回同様、cURLの扱えるようになったFileMaker Pro ver.16以上でお試しください。
この記事用に作成したサンプルは、FileMaker Pro 19.6.3です。

# curlがちょっと不安な方
こちらで、注意点をまとめています。
合わせてご覧ください。

https://qiita.com/tyuma/items/7f0c161509f3c5c78257

# 本家curlオプション一覧
Claris FileMakerのヘルプにある、「[サポートされている cURL オプション](https://help.claris.com/ja/pro-help/content/curl-options.html)」は必ずチェックしましょう。
今回、この記事で使用しているオプションは、ほんの一例です。

# 前提
ファイルサーバーなどで、ポートを開けておく設定などはすでに済んでいるものとします。

# 基本的なダウンロードのcurl文

```
curl -u {user}:{pass} -O ftp://{host}/{file}
```

"-u"はFTP用に割り振られたユーザ名とパスワードを指定します。
"-O"はアウトプットの"O"です。
接続先のアドレスとダウンロードするファイルをcurl文に指定することで、目的のファイルをダウンロードできます。

ということで、今回必要な情報は、以下になります。

・FTPなどのユーザー名
・FTPなどのパスワード
・接続するサーバーのアドレス
・ダウンロードするファイル名


# FileMakerのステップに割り当てる

この、curl文をFileMakerの「[URL から挿入](https://help.claris.com/ja/pro-help/content/insert-from-url.html)」に割り当てます。

図解してみました。
![スクリーンショット 2023-07-04 15.09.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60538/7824f877-70e9-2d2e-2659-c74581ca8ac6.png)

基本的にcurlで使うオプションは同じように使えます。
FileMakerでサポートされているcurlオプションには限りがありますので、ヘルプの[サポートされている cURL オプション](https://help.claris.com/ja/pro-help/content/curl-options.html)は必ずチェックしましょう。
大事なことなので、記事中2回言いました。

実装はこれだけです。
この１行だけです。

実際に運用で使えるようにするには、エラー処理やファイル名の組み立てなどあるかと思いますが、実際にファイルをダウンロードする行はこの１行のみです。

# TLS/SSLの場合のオプション
TLS/SSL通信を使う場合は、curlでいう"--ftp-ssl"は、FileMakerでは"--ssl"を指定します。
「URL から挿入」のcURLオプションの指定部分に、"--ssl"を追加するだけです。
![スクリーンショット 2023-07-04 15.23.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60538/4a172164-64f0-3c31-c980-a292c8b45cdf.png)

:::note warn
"--ssl"を"-o"の後ろにつづけて書くと、ファイル名と認識されてしまうので、前につけましょう。
:::

# SCP/SFTPの場合のオプション
基本的にオプションは変わらないです。
変わるのは、「URL を指定」の部分で指定する、"ftp"が"scp"や"sftp"に変わるだけです。
ファイル転送のプロトコルが変わるので、プロトコル名を変更する、という感じでしょうか。。。
追加するオプションがあるとすれば、"--pubkey"など、セキュリティ関連でパブリックキーファイルを指定して使用します。

:::note warn
FTPはユーザー名やパスワードなどの認証情報や転送するデータを暗号化しないで送ります。
このため、ファイルの盗聴などリスキーと言われています。
TLS/SSLやSFTPなどでより安全にデータの送受信を実装しましょう〜
:::

# ファイルをダウンロードできたら
あとは煮るなり焼くなりしましょう。
例えば、csvファイルでしたら、「[ファイルに関連するスクリプトステップ](https://help.claris.com/ja/pro-help/content/files-script-steps.html)」で中身をFileMakerのテーブルなどに展開するもよし、加工して再度ファイルをアップロードしてもよし。

# そしたらアップロードもしたいよね
ということで、アップロードも備忘録として次回残しておきます。
【Flutter】golden testでの At least listener of ～ threw an exception when the notifier tried to update　が起きた場合の対処

# At least listener of ～ threw an exception when the notifier tried to updateの原因

エラーメッセージを読み取る
```
The exceptions thrown are:

setState() or markNeedsBuild() called during build.
This UncontrolledProviderScope widget cannot be marked as needing to build because the framework is
already in the process of building widgets. A widget can be marked as needing to be built during the
build phase only if one of its ancestors is currently building. This exception is allowed because
the framework builds parent widgets before children, which means a dirty descendant will always be
built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
  UncontrolledProviderScope
The widget which was currently being built when the offending call was made was:
```

「ビルド中にstateの更新が行われています」という意味。

# 解決方法

ビルド直後にstateの更新を行なって欲しい場合は以下のメソッドを使用する
```dart
WidgetsBinding.instance.addPostFrameCallback((_) {
// 先ほどまでエラーメッセージで怒られていた更新処理
});
```

## 参考記事
https://flutter.salon/plugin/riverpod/
[Android] MediaStoreにメディアファイルを保存する

未だに何回実装しても覚えられないし、毎回調べてるので使う部分のコードだけメモ
（なんか説明がごちゃごちゃしすぎじゃない？）
細かいAPIの説明はネットに十分にあるので割愛。

コードは画像例（動画などの場合はMIME_TYPEやMediaURIなどを合わせて実装）
エラーハンドリングは各自でお願いします。

#### パターン1 ByteArrayで書き込む
```SaveMedia.kt
    private fun savedImageMediaStoreFile(imageUri: Uri, data: ByteArray, fileName: String){
                                        
        val collection = if (Build.VERSION.SDK_INT >= 29) {
            MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
        } else {
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        }

        val contentValues = ContentValues().apply {
            put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
            put(MediaStore.Images.Media.MIME_TYPE, "image/png")
            if (Build.VERSION.SDK_INT >= 29) {
                put(MediaStore.Images.Media.IS_PENDING, true)
            }

//      //※1　専用直下フォルダを作成したい場合
//            put(
//                MediaStore.Images.ImageColumns.RELATIVE_PATH,
//                Environment.DIRECTORY_PICTURES + "/MY_DIRECTORY/"
//            )
        }

        val contentResolver = applicationContext.contentResolver
        val contentUri = contentResolver.insert(collection, contentValues)

        //※2 ファイルを書き込む
        contentResolver.openFileDescriptor(contentUri!!, "w", null).use {
            FileOutputStream(it!!.fileDescriptor).use { output ->
                output.write(data)
            }
        }

        contentValues.clear()
        if (Build.VERSION.SDK_INT >= 29) {
            contentResolver.update(contentUri, contentValues.apply {
                put(MediaStore.Images.Media.IS_PENDING, false)
            }, null, null)
        } else {
            contentResolver.update(contentUri, contentValues, null, null)
        }
    }
```

※1
自分で指定したフォルダを作成して、その直下に保存してくれます。
ない場合はPictures/xxxxxx.pngだが、
/MY_DIRECTORY/を指定することによってPictures/MY_DIRECTORY/xxxxxx.png
に保存してくれる。

#### Uriで書き込む
```SaveMedia.kt
        //※2の部分のみ
        contentResolver.openInputStream(imageUri).use { input ->
            val output = contentResolver.openOutputStream(contentUri, "w")!!
            input!!.copyTo(output)
        }
```

#### 感想
byteArrayで書き込むサンプルとUriで書き込むサンプルが混同してるので余計混乱していたように感じる。
DBにおける日付型と時刻型のデータ一覧（５種類）

# はじめに
データベース設計してるとデータ型で頭がいっぱいになったので、
ここに書き出しておくぜ

より詳しく書き直した記事がこちらにあります

https://qiita.com/takekota/items/e8385d3c59bc6dc44ed2

### DATE：日付
‘1000-01-01’から‘9999-12-31’
フォーマット：’YYYY-MM-DD’
入力可能フォーマット：YY-MM-DD, YYYYMMDD, YYMMDD, YYYY/MM/DD, YYYY.MM.DD

### DATETIME：日時
‘1000-01-01 00:00:00.000000’から‘9999-12-31 23:59:59.999999’
フォーマット：’YYYY-MM-DD HH:ii:ss’
入力可能フォーマット：YY-MM-DD HH:ii:ss, YYYYMMDDHHiiss, YYMMDDHHiiss, YYYY/MM/DD HH*ii*ss, YYYY.MM.DD HH+ii+ss

### TIMESTAMP：タイムスタンプ
エポック（‘1970-01-01 00:00:00’ UCT）からの秒数
‘1970-01-01 00:00:00.000000’ UCTから‘2038-01-19 03:14:07.999999’ UCT

### TIME：時間
‘-838:59:59.000000’から‘838:59:59.000000’
フォーマット：’HH-ii-ss’

### YEAR：2桁または4桁の年
‘1901’から‘2155’と’0000’
‘70’から‘69’（1970から2069に相当）
フォーマット：’YYYY’ or ’YY’


***
### DATE_FORMAT関数について

date型やdatetime型の値をフォーマットで指定した文字列に整形する

DATE_FORMAT(date, format)
TIME_FORMAT(date, format)

https://www.javadrive.jp/mysql/function/index49.html


***
### 現在日時の取得方法

select current_time();
select current_date();
select curtime();
select curdate();
select now();
DBにおける数値型

# はじめに
データベース設計してるとデータ型で頭がいっぱいになったので、
ここに書き出しておくぜ

より詳しく書き直した記事がこちらにあります

https://qiita.com/takekota/items/e8385d3c59bc6dc44ed2

### INTEGER = int
|タイプ|サイズ(byte)|最小値|最大値|
|-|-|-|-|
|TINYINT|1|-128, 0|127, 255|
|SMALLINT|2|-32768, 0|32767, 65535|
|MEDIUMINT|3|-8388608, 0|8388607, 16777215|
|INT|4|-2147483648, 0|2147483647, 4294967295|
|BIGINT|8|-9223372036854775808, 0|9223372036854775807, 18446744073709551615|

Mysql(MariaDB) では、 INT(4) のように型指定の後に整数値を指定できるが、これは「バイト数」ではなく「表示桁数」である。
しかし、カラムに格納できる値の範囲が制限されたりすることはなく、実際には11桁（INT型の最大値）まで登録できる。

では、 (?) のカッコ内の数字は何なのか？
オプション属性の ZEROFILL を使用すると、足りない桁は 0（ゼロ） が埋め込まれる（ゼロパディングされる）。 (?) のカッコ内の数字は、その際の桁数である。
INT(4) ZEROFILL に 9 という数字を格納すると 0009 と表示されるということ。


[数値データ型まとめ(old)](https://www.mk-mode.com/blog/2014/02/04/mysql-type-of-integer/)
[数値データ型まとめその２](https://pentan.info/sql/mysql/mysql_type_num.html)
[幅広いデータ型](https://runebook.dev/ja/docs/mariadb/data-types/index)
[公式ドキュメント](https://dev.mysql.com/doc/refman/8.0/ja/numeric-types.html)


### boolean
内部的には int(11) となる。
tiny(1) を利用するとよい。
完全に0or1にする場合は、bit(1) を利用する。
正規表現2023/03/03

# はじめに
正規表現は裏切らない！
正規表現を一回学習して覚えておけば、マークダウン並みに役立つ

って聞きました
でも、使う機会が少なすぎて忘れちまうんだよ
とりあえずまとめとくか

## メタ文字
正規表現で用いられる特殊文字
###### https://userweb.mnet.ne.jp/nakama/
```
. ^ $ [ ] * + ? | ( )
```
|メタ文字|意味|
|---|---|
| . | any one string |
| ^@ | 行の先頭 |
| @$ | 行の末尾 |
| @* | 直前の文字が０個以上連続する |
| @+ | 直前の文字が１個以上連続する |
| @? | 直前の文字が０個もしくは１個 |
| @\|@ | いずれかの文字 |
| [@@@] | 指定した文字のどれか |
| () | グループ化 |

***

### [ ]の中

|正規表現|意味|備考|
|---|---|---|
| [^@@@] | 指定した文字以外のどれか | 先頭にある ^ |
| [@-@] | 指定した文字範囲のどれか | 文字コード上の範囲指定 |
| [\\]^.*] | ]^.*のどれか | 先頭の^,\\,]以外の特殊文字が使えない |

***

## 実用例など

###### https://qiita.com/Yametaro/items/36493c107053ae996b47
###### https://www-creators.com/archives/5332

|<div style="width:260px;">正規表現</div>|意味|備考|
|---|---|---|
| ```^@@@$``` |完全一致|  |
| ```^[1-9][0-9]*$``` | 最初に1〜9が1つ、その後ろに0〜9が0個以上 |  |
| ```^.{1,30}$``` | 1〜30文字 | ```0 < userInput.length && userInput.length <= 30``` |
| ```^(@@@)$``` | グループ化 |  |
| ```^(大正\|昭和\|平成\|令和)$``` | 大正・昭和・平成・令和だけOK | ```["大正", "昭和", "平成", "令和"].includes(userInput)``` |
| ```(?!@@@)``` | 否定先読み |  |
| ```^(1st\|2nd\|3rd\|[4-9]th)$``` | 1桁の英語の序数 |  |
| ```^(([0-9]*[02-9])?(1st\|2nd\|3rd)\|([0-9]*([04-9]\|1[1-3])th))$``` | 英語の序数(先頭の0が許容される) | 1st, 2nd, 3rd, 4th, 11th, 12th, 13th, 14th, 21st, 22nd, 23rd, 24th |
| ```(?=@@@)``` | 肯定先読み |  |
| ```(?=@@@)(?=@@@)``` | AND | 含むかつ含む |
| ```(?=@@@)(?!@@@)``` | AND | 含むかつ含まない |
| ```(?=^(([0-9]*[02-9])?(1st\|2nd\|3rd)\|([0-9]*([04-9]\|1[1-3])th))$)(?=^[1-9])``` | 英語の序数 | 1st, 2nd, 3rd, 4th, 11th, 12th, 13th, 14th, 21st, 22nd, 23rd, 24th |

***
## テストサイト
[リンク](https://regexr.com/)
ディレクトリ内の該当ディレクトリのシンボリックリンクの削除方法

### 概要
ディレクトリ内の該当ディレクトリのシンボリックリンクを間違って作成したため消そうとしたところ、ディレクトリ以下も消されそうになったため、シンボリックリンクのみを削除する方法を探した。

#### フォルダは下記の通り

```
 - Laravel -> /var/www/Laravel/
   - Laravel -> /var/www/Laravel/
   - app
   - artisan
   - etc...
```

上記はLaravelのフォルダ内にLaravelのシンボリックリンクを作成してしまった例

### 結論

Laravelのフォルダ内で下記を実施
```
$ rm Laravel

or

$ unlink Laravel
```

初めに削除対象フォルダを`rm -rfi Laravel/`と打ったところ下位ディレクトリまで削除
されそうになった。

削除するのはディレクトリではなくシンボリックリンクであるため`/`はいらない。
WSL2 の docker-compose を v2 にアップデートする

通常であれば Docker Desktop for Windows を利用されていると思いますが、大人の事情によりそれを利用することが出来ない場合があると思います。人が多いとか売上が多いとか…

v2 を基準に書かれた docker-compose.yml は v1 では動かせない場合も多いため、早めにバージョンアップしましょう



# docker compose アップデート

その場合に docker compose をアップデートするには、下記のコマンドを実行するだけ

```bash
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
```

この2つのコマンドが正常終了すればアップデート完了

# 注意事項

バージョン確認してみると…なんと v1 のまま！などと焦ってはいけません

```bash
docker-compose -v
# docker-compose version 1.26.0, build d4451659
```

<br>

v2 からはコマンドが変わっています

```bash
docker compose version
# Docker Compose version v2.18.1
```


ややこしいので、docker-compose コマンドは削除しておきましょう

```bash
sudo rm -rf /usr/local/bin/docker-compose
```

使えなくなればOK

```bash
docker-compose --v
# bash: /usr/local/bin/docker-compose: No such file or directory
```