OmniAICreator/Qiita-1.07M huggingface dataset:
PrismaでmongoDBを扱う際の備忘録

## Prismaとは？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2937091/1435dd94-61bf-0ffe-26a1-8fd4b081804d.png)
Prismaは、**データベース接続ツール** であり、データベースアクセスとスキーマ定義をシンプルにすることを目的としていて、TypeScriptやJavaScriptとシームレスに統合され、マルチデータベースサポートを提供している。クエリビルダーやデータモデル生成、マイグレーション機能などを備え、データセキュリティも重視している。Prismaを使うことで、安全で堅牢なデータベースアプリケーションを簡単に構築できる。
#### 簡単にまとめると、DBを扱いやすくするツール

## Prismaで悪戦苦闘
Next.jsでPrismaとmongodbを使おうと思ったとき、なかなか苦戦したので備忘録をここに残しておく。僕自身、まだまだ未熟者なのでかなり初歩的なことで躓いている。

>  **何が起きたのか？**

prismaで簡単にスキーマ(StringやIntやら型定義したやつ)を作成して、mongoDBにプッシュしようと思ったその時、、

```
The provided database string is invalid. An invalid argument was provided: Database must be
 defined in the connection string in database URL. Please refer to the documentation in 
https://www.prisma.io/docs/reference/database-reference/connection-urls for constructing a 
correct connection string. In some cases, certain characters must be escaped. Please check the 
string for any illegal characters.
```

何やらエラーが発生。色々調べた結果、.envファイルで使用していたmongoDBの接続URLが間違えていたのだ。具体的にどこが間違えていたのかを説明していく。

まず、mongoDB接続URLのフォーマットはこんな感じだ。

`
mongodb+srv://<username>:<password>@cluster0.--------.mongodb.net/<db-name>
`

僕はdb-nameをURL内に入れていなかったため、ずっと上記のエラーが出ていたのだ。さらに、db-nameすなわちデータベース名が永遠に分からずじまいでずっと苦戦していた。調べ上げた結果、db-nameは、デフォルトで`test`だ。実際、それをdb-nameに入れてみたところ上手くいった。では下記にまとめる。

#### mongoDB接続URLのDB名のデフォルトはtest`だ。忘れずに入れよう。

終わりに
-
改めて、かなり簡単なことで苦戦していることが身に染みて分かった。しかし最初の頃はプログラミングの右左もわからなかった。その頃と比べたらよい進歩だ。これからもプログラミングに精進したい。
いいコード悪いコードまとめ５章低凝集

# 凝集度とは
凝集度とはモジュール内における、データとロジックの関係性の強さを表す指標。これはクラス、パッケージ、レイヤーなどの場合も同じである。

この凝集度が高ければ変更に強い、望ましい構造と言える。

例えば、以下は低凝集なコードである。
一見、注文クラスの中だから、金額計算や注文管理のメソッドが混ざっていてもよさそうに思えるが、これはよろしくない。

なぜなら、コードが一つの関心事に集中していないため、理解しやすく保守しにくいからだ。このコードでは、金額の計算と注文の管理の両方の機能が混ざっている。

そのため、このコードを理解するには、金額の計算と注文の管理の両方の知識が必要になる。
また、このコードを保守するには、金額の計算と注文の管理の両方のコードを変更する必要もある。

このコードを改善するには、金額の計算と注文の管理の機能を分離すればよい。

例えば、金額の計算はOrderCalculatorクラスに、注文の管理はOrderManagerクラスに移譲することができる。これにより、コードが一つの関心事に集中し、理解しやすく保守しやすくなるわけだ。

```
class Order
  def initialize(amount, order_id)
    @amount = amount
    @order_id = order_id
  end

  def calculate_amount
    @amount * 1.1
  end

  def manage_order
    # 注文を管理する処理
  end
end
```

改善したコードは以下の通り。
```
class Order
  attr_reader :amount, :order_id

  def initialize(amount, order_id)
    @amount = amount
    @order_id = order_id
  end

  def calculate_amount
    order_calculator.calculate_amount(amount)
  end

  private

  def order_calculator
    @order_calculator ||= OrderCalculator.new
  end
end

class OrderCalculator
  def initialize
    @tax_rate = 0.1
  end

  def calculate_amount(amount)
    amount * @tax_rate
  end
end

class OrderManager
  def initialize
    @orders = []
  end

  def add_order(order)
    @orders << order
  end

  def manage_orders
    # 注文を管理する処理
  end
end
```

# CommonやUtilのような共通処理クラスを作っていないか
# ログ出力やエラー検出処理などの横断的関心事を共通処理にまとめているか
# 結果を返すために引数を使用していないか
# 引数は多すぎないか
# メソッドチェインしすぎていないか
仕様変更が生じた場合、呼び出しているすべての箇所の影響を調べる必要が出てくる
# 呼び出し側は尋ねずに命令しているか
これはifなどで条件分岐するのではなく呼び出す側は命じるだけ、メソッドを実行するだけにしようということ
悪い例
```
<% if current_user.admin? %>
  <%= current_user.admin_welcome_message %>
<% else %>
  <%= current_user.user_welcome_message %>
<% end %>
```
良い例
```
# 良いメソッド名が思いつかなかったが要は呼び出す側で条件分岐とかはしないようにしようということ。
<%= admin_or_regular_user_welcome_message(user) %>
```
```
def admin_or_regular_user_welcome_message(user)
  return 'hello, admin' if user.admin?
  return 'hello, regular user' unless user.admin?
end
【Laravel】Tinker(DBの手動操作)でトランザクションを実施する方法

LaravelでDB操作をしたい場合、TinkerでDBを操作することが可能です。
(いちいちMySQLコマンドを叩かなくて済みます)

その際、間違って更新したくないデータを書き換えてしまった場合、
MySQLのようにロールバックする必要が出てくるかと思います。

今回はTinkerでトランザクション処理をする実行する方法について解説します。

具体的には、以下のコマンドでできます。

```bash
## トランザクション開始
DB::beginTransaction()

## ロールバック
DB::rollBack()

## トランザクションをコミット
DB::commit()
```

MySQLコマンドを叩いたことがある人にとってはすぐわかるかと思いますが、具体例と共に説明します。

まずは、以下のUserデータがあるとします。
これのnameを更新して戻す操作を実施したいと思います。

```bash
> User::first()
= App\Models\User {#7421
    id: 1,
    name: "test-user", ## ★更新対象
    email: "test123@gmail.com",
    email_verified_at: null,
    #password: "$2y$10$tjDxajVvG8VDhFJyuMExxeckCKXFAnvpGqkcda62cFY/vCeZXUCwa",
    #two_factor_secret: null,
    #two_factor_recovery_codes: null,
    two_factor_confirmed_at: null,
    #remember_token: null,
    current_team_id: null,
    profile_photo_path: null,
    created_at: "2023-07-24 12:32:48",
    updated_at: "2023-07-24 12:32:48",
    +profile_photo_url: "https://ui-avatars.com/api/?name=t&color=7F9CF5&background=EBF4FF",
  }
```


まず、トランザクションを開始します。
次にUserデータを取得してnameを書き換え、更新(save)まで実施します。
これにより、Userデータのnameが更新されることになりますが、
次のDB::rollback()を実施することで、nameの更新が無かったことになります。
元のUserデータを取得してnameを確認すると、更新前のままとなっています。

```bash
## トランザクション開始
> DB::beginTransaction()
#= null

> $user = User::first()
> $user->name = "新しい名前のユーザーネーム"
> $user->save()
#= true

> $user->name
#= "新しい名前のユーザーネーム"

## ロールバック
> DB::rollBack()
#= null

## nameが元のままで、更新が無かったことになっている
> User::first()->name
= "test-user"
```

もし、更新するデータに問題がなくロールバックしないのなら、
以下のようにコミットすることで更新が適用されます。

```bash
> DB::beginTransaction()
= null

> $user = User::first()

> $user->name = "更新完了ユーザーネーム"
= "更新完了ユーザーネーム"

> $user->save()
= true

## コミット
> DB::commit()
= null

> User::first()->name
= "更新完了ユーザーネーム"
```


上記のトランザクション処理を実行することで、
本番環境でも安全に作業することができるようになります。



## 参考

https://readouble.com/laravel/10.x/ja/database.html?header=%25E3%2583%2588%25E3%2583%25A9%25E3%2583%25B3%25E3%2582%25B6%25E3%2582%25AF%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%25B3%25E3%2582%2592%25E6%2589%258B%25E4%25BD%259C%25E6%25A5%25AD%25E3%2581%25A7%25E4%25BD%25BF%25E7%2594%25A8
C#で文字列中の正規表現要素をエスケープする

# 1. はじめに
* 文字列をエスケープする時にエスケープが必要な文字が思い出せない時に簡単に確認したい
* ```Regex.Escape, UnEscape``` メソッドを使用してプログラムを使って機械的に確認したい

# 2. 開発環境
* C#
* .Net 6
* Visual Studio 2022
* Windows 11

# 3. 文字列をエスケープする
```C#
using System;
using System.Text.RegularExpressions;

public static void EscapeSample(){
    // 文字列中の記号をエスケープする
    var str = @"().*%!$$$";

    Console.WriteLine(str);
    Coonsole.WriteLine(Regex.escape(str));
}
```
```:実行結果
().*%!$$
$\(\)\.\*\%\!$$\$
```

# 4. 文字列をアンエスケープする
```C#
using System;
using System.Text.RegularExpressions;

public static void UnescapeSample(){
    // 文字列中のエスケープされている記号をアンエスケープする
    var str = @"\(\)\.\*\%\!$$\$";

    Console.WriteLine(str);
    Console.WriteLine(Regex.Unescape(str));
}
```
```:実行結果
\(\)\.\*\%\!$$\$
().*%!$$$
```

# 5. 参考文献

https://smdn.jp/programming/dotnet-samplecodes/regex/3a947f80024111eb907175842ffbe222/

https://learn.microsoft.com/ja-jp/dotnet/api/system.text.regularexpressions.regex.unescape?view=net-7.0
新しいPCがほしい

半分コラムです。

今は時期が良いので新しいPCがほしい！！！

最近、メモリ・SSD・もろもろ明らかに値段が低いので、
自作新しいPCがほしいです。

多分新しいPC買うので、
その選択をメモ形式で更新していきます。

# 用途とスペック検討

## 用途
(1)インフラ遊び
　(1-1)KVMとかVMwareとかいろいろなハイパーバイザーを動かして遊びたい
　(1-2)複数VMを動かして遊びたい
　(1-3)特定のハイパーバイザーでk8s動かして遊びたい
(2)インフラ開発
　(2-1)k8sに色々デプロイして遊びたい
　(2-2)ゲーム系のリモート母艦
(3)メタバース遊び
　(3-1)VRChat他を遊びたい
(4)高性能ゲーム
　(4-1)高性能ゲームを遊びたい
(5)その他開発・動作環境
　(5-1)LLMとか動かしたい
　(5-2)3D開発したい
(6)たまにいじる
　(6-1)いじりやすくしたい
　(6-2)電源容量を気にしない状態でいたい
(7)メイン機はノートであるので常用より開発性振り

## スペック検討
(1-2)の要件から、マルチコアCPU必須、大容量メモリ推奨 
(1-1)の要件から、マルチSSD推奨 → マザボがマルチSSD対応
(1-3)の要件から、k8s用SSD推奨

(2-1)の要件から、マルチコアCPU・メモリ多数
(2-2)の要件から、GPU＋リモート対応

(3-1)の要件から、GPU必須

(4-1)の要件から、GPU必須、高速CPU推奨

(5-1)の要件から、GPU必須、GPU大容量メモリ推奨
(5-2)の要件から、大容量メモリ推奨

(6-1)の要件から、ケースはこだわるべき
(6-2)の要件から、電源容量は大きくするべき

## 結論
こんな感じのスペック作る

・CPU：intel 12th CPU以降、コア数の多いやつ
　・20コア目標
・メモリ：64GBメモリ以上
　・デュアルチャンネル以上で構成
・GPU：なるべく早いやつ
　・Geforce 3060 以上のメモリの多いやつ
　・30世代と40世代の違いは調べないといけない
・マザーボード：拡張性へ降る
　・最低でマルチSSDをブートディスクに選べるやつ
　・拡張性重視
・電源：800Wくらい
　・大きめの安定したやつ
　・常用しないのでワット数は大きくて良い
・ケース
　・メンテナンス性の良い大きいやつ

## 次回以降
・BTOで相場を調べる
・BTOか自作で良い方を検討
　・パーツ検討
・制作・購入レポ
・ベンチマークれぽ
Javaの配列について学習してみた

# 概要
Javaの配列について学習してみました。以下のページを参考にしました。

配列

https://www.javadrive.jp/start/array/

配列の宣言と配列の作成

https://www.javadrive.jp/start/array/index2.html

配列要素へのアクセスとインデックス

https://www.javadrive.jp/start/array/index4.html

配列の要素の初期化と規定値

https://www.javadrive.jp/start/array/index5.html

配列の長さを取得する(length)

https://www.javadrive.jp/start/array/index6.html

多次元配列を作成する

https://www.javadrive.jp/start/array/index7.html

多次元配列の初期化

https://www.javadrive.jp/start/array/index8.html

多次元配列の長さを取得する(length)

https://www.javadrive.jp/start/array/index9.html

配列のすべての要素を指定した値で埋める(fill)

https://www.javadrive.jp/start/array/index12.html

```
Arrays.fill(a, 10);
```

配列をコピーする(シャローコピーとディープコピー)

https://www.javadrive.jp/start/array/index10.html

配列の指定範囲の要素から新しい配列を作成する(copyOfRange)

https://www.javadrive.jp/start/array/index13.html

2つの配列を連結して新しい配列を作成する

https://www.javadrive.jp/start/array/index17.html

```
System.arraycopy(src1, 0, dst, 0, src1.length);
```

2つの配列の要素の値を比較する(equals,deepEquals)

https://www.javadrive.jp/start/array/index11.html

配列の要素を逆順に並び替える

https://www.javadrive.jp/start/array/index15.html

配列の要素を昇順・降順にソートする(sort)

https://www.javadrive.jp/start/array/index14.html

配列の文字列表現を取得する(toString,deepToString)

https://www.javadrive.jp/start/array/index16.html

# まとめ
何かの役に立てばと。
DockerComposeを理解してみる

# はじめに
こんにちは。すっかり夏になりました。ponponnsanです。

さて、今日はなんとなく雰囲気で触っていたdocker　composeについて理解を深めようと思いました。


参考にした書籍は以下です。とてもわかりやすくておすすめです。さすがAmazonベストセラー。

https://www.amazon.co.jp/%E4%BB%95%E7%B5%84%E3%81%BF%E3%81%A8%E4%BD%BF%E3%81%84%E6%96%B9%E3%81%8C%E3%82%8F%E3%81%8B%E3%82%8B-Docker-Kubernetes%E3%81%AE%E3%81%8D%E3%81%BB%E3%82%93%E3%81%AE%E3%81%8D%E3%81%BB%E3%82%93-%E5%B0%8F%E7%AC%A0%E5%8E%9F%E7%A8%AE%E9%AB%98/dp/4839972745/ref=asc_df_4839972745/?tag=jpgo-22&linkCode=df0&hvadid=342438969336&hvpos=&hvnetw=g&hvrand=2923421224028769178&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009306&hvtargid=pla-1125756547188&psc=1&th=1&psc=1

# Docker Composeとは
dockerのコマンドをいちいち全部書くのは面倒ですよね。
Docker Composeは構築に関わるコマンド分の内容を一つのyamlファイルに書きこんで、一気に実行したり、停止・破棄することができるものです。

## Dockerfile との違い
Docker Compose: 「docker run」 の集合体。コンテナに加えてネットワークやボリュームなども作成できる。
Dockerfile: イメージを作成するもの。ネットワークやボリュームなどは作成できない。

＊ボリューム...ストレージの一領域を区切ったもの。長いカステラの一部分みたいなイメージ。

# docker-compose.yamlの書き方
```docker-compose.yaml
# バージョンを書く
version: '3'

# コンテナの情報
services: 
  mySqlService:
    image: mysql:5.7
    # macはこれがないと動かなかった。
    # M1のCPUは、linux/arm64/v8だが、使用しようとしたimageがこれに対応していない
    # platformを指定して、linux/amd64に指定をする
    platform: linux/amd64
    networks:
      - myNetwork
    volumes:
      - mySqlVolume:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: myrootpass
      MYSQL_DATABASE: myDb
      MYSQL_USER: myUser
      MYSQL_PASSWORD: myPass
  
  wordpressService:
    depends_on:
      - mySqlService
    image: wordpress
    networks:
      - myNetwork
    volumes:
      - wordpressVolume:/var/www/html
    ports:
      - 8085:80
    restart: always
    environment:
      WORDPRESS_DB_HOST: mySqlService
      WORDPRESS_DB_NAME: myDb
      WORDPRESS_DB_USER: myUser
      WORDPRESS_DB_PASSWORD: myPass

# ネットワークの情報
networks:
  myNetwork:

# ボリュームの情報
volumes:
  mySqlVolume:
  wordpressVolume:


```
## 定義ファイルの記述ルール
- 最初にDocker Composeのバージョンを書く
- 「services」「networks」「volumes」の順に書く
- 親子関係はスペースで字下げして表す
- 名前は、大項目の下に字下げして書く
- コンテナの設定内容は名前の下に字下げして書く
- 「-」が入っていたら複数指定できる
- 名前の後ろには「：」をつける
- 「：」の後ろには、空白が必要
- コメントアウトが使える
- 文字列はシングルクォートかダブルクォート


# 今後の課題
- ボリュームマウントとバインドマウントの違い
- ネットワークの設定の仕方
C#でEnumをFlagとして使用する

# 1. はじめに
* C#でEnumにFlag属性を追加して複数選択を判別したい
* 1つのEnum値で2進数のビット情報で複数の選択状態を管理してみたい

# 2. 開発環境
* C#
* .Net 6
* Visual Studio 2022
* Windows 11

# 3. Enumクラス
* EnumクラスにFlag属性を追加する
* Enumの数値は2進数で記載し、ビットの桁位置が被らないようにする
```C#:SelectItemEnum.cs
[Flags]
public enum SelectItemEnum
{
    // 「0b」から始めると２進数で書ける
    AAA = 0b0001,

    BBB = 0b0010,

    CCC = 0b0100
}
```

# 4. 動作確認
* 想定通り動いた
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/92bc68d2-ecf2-0650-2482-45ce68e55db6.png)


```C#:Form1.cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        // int flag = 1;    // 0b0001:AAA選択
        // int flag = 2;    // 0b0010:BBB選択
        // int flag = 3;    // 0b0011:AAA,BBB選択
        // int flag = 4;    // 0b0100:CCC選択
        // int flag = 5;    // 0b0101:AAA,CCC選択
        // int flag = 6;    // 0b0111:BBB,CCC選択
        int flag = 7;    // 0b0111:全件選択

        // int → Enumへ変換
        SelectItemEnum enums = (SelectItemEnum)a;

        // 選択状態を確認
        foreach (SelectItemEnum value in Enum.GetValues(typeof(SelectItemEnum)))
        {
            if (enums.HasFlag(value))
            {
                // 選択した値をコンボボックスへ追加
                comboBox1.Items.Add(value);
                comboBox1.SelectedIndex = 0;
            }
        }

    }
}
```

# 5. 参考文献

https://juraku-software.net/c-sharp-enum-multi-select/

https://qiita.com/4_mio_11/items/abf6373b0863ac2ebd5c
【Rails7 / ActionCable / 備忘録】「ArgumentError - wrong number of arguments (given 1, expected 2)」に対するエラー対処（備忘録）



# 開発環境

- Ruby 3.2.2
- Ruby on Rails 7.0.6
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117662/0c35f0b8-d878-485c-57c2-6468f7d40c17.png)


# エラー内容
ActionCableを用いたチャット機能を実装しようと、以下の実装の手順を書いてくれている記事を参考に進めていたのですが、テキストを送信しても実行されずサーバーログを確認してみると、
**`「ArgumentError - wrong number of arguments (given 1, expected 2)」`** 
となっており、直訳すると「２つの引数が渡されるのを期待しているが、１つの引数しか渡されていない」というエラーが発生していました。
そこでエラー箇所を確認してみるも以下のように`ActionCable.server.broadcast`には、`room_channel`と`message: data['message']`が引数として２つとして渡されているように見えますし、ネットを調べても、ChatGPTに尋ねてもほとんど情報が得られませんでした。
```Ruby:channels/room_channel.rb
class RoomChannel < ApplicationCable::Channel
    # 省略
  def speak(data)
    ActionCable.server.broadcast 'room_channel', message: data['message']
  end
end

```

【参考】実装手順の記事
----
- [【Rails6.0】ActionCableを使用したライブチャットアプリを実装する手順を解説](https://techtechmedia.com/action-cable-rails6/)
- [【Rails6.0】ActionCableとDeviseの欲張りセットでリアルタイムチャット作成(改正版)](https://qiita.com/rhiroe/items/4c4e983e34a44c5ace27)

# 解決方法
- **変更前**
```Ruby:room_channel.rb
ActionCable.server.broadcast 'room_channel', message: data['message']
```
- **<font color=red>変更後</font>**

```Ruby:room_channel.rb
ActionCable.server.broadcast 'room_channel', {message: data['message']}
```

キーにシンボルを利用したハッシュである`message: data['message']`を「{}」で囲みました。
- **変更前**
どうやら変更前の形式では、Rubyはこれを`ハッシュではなくキーワード引数`として解釈していたようです。
そうすると、`ActionCable.server.broadcast`メソッドから見ると２つ目の引数として、データ型がハッシュの値を期待していたにも関わらず、期待しているデータ型ではなかったため`「nil」`と解釈され、引数が１つしか渡されていない状態になったと考えられます。

- **<font color=red>変更後</font>**
明示的に{}で囲むことでハッシュとして認識され、broadcastメソッドには2つの引数が渡されたと解釈したため、エラーが発生せずに正常に動作しました。

# 終わりに

他の方ができているのになぜ今回ここでエラーとなったのか判明できていないですが、Rubyのメソッド呼び出しにおけるキーワード引数とハッシュの記法が似ているために今回のエラーが起きたと考えられます。
今後、ハッシュを引数として渡したい場合は、常に{}で囲むことにより明示的にハッシュであることを示していきたいと思います！
今回のエラー解決にかなり時間がかかったので、同じようなエラーで困っている方の助けに少しでもなればと思います:wink:


# 5. 参考文献

- ググり続けて、こちらの記事よりようやく解決法が得られました。ありがとうございました！

https://zenn.dev/chikaniki2/articles/d941343ca297e5
サービス開発でユーザー像を絞り込むときに気をつけていること

博報堂テクノロジーズの坂井です。
普段はメディアプラニングツールの企画・開発をやっています。

## ユーザー像は絞り込む？広げておく？

サービス開発で、ユーザー像を「絞り込むか」「広げておくか」を悩むことはないでしょうか。
一般的にはユーザー像を絞り込むことで

- ユーザー課題の理解やアイデア出しがシャープになる
- 開発チーム内での合意形成がスムーズになる

などのメリットがあると言われています。

一方でユーザー像を絞り込むことで

- ニッチ化することで幅広いユーザーに使ってもらえる機会を逸してしまう

ことを懸念して、なるべくユーザー像は広げておきたいんだという意見もあると思います。

私の個人的な好みは後者の立場ですが、サービスやチームの状況によってはやっぱりユーザー像は絞り込んだ方がいいよね、となるときもあります。

## 絞り込んだユーザー像に、サービスが不必要に依存しないように

ユーザー像を絞り込むときに私が気をつけているのが、**絞り込んだユーザー像にサービスが不必要に依存し過ぎないようにすること**です。

サービスが成功するとユーザーは必然的に広がっていくはずですし、うまくいかなければ別のユーザー像に絞り込み直して再チャレンジすることも考えられます。このとき、サービスの深い部分が当初想定したユーザー像に依存しすぎていると、これらサービスの設計し直し、作り直しが発生する確率が高くなってしまいます。

ユーザー像を絞り込む条件のうち、

- そうそう揺るがない条件には深めに依存してもよいが
- もしかしたら変わるかもしれない挑戦的な絞り込み条件には浅めに依存する（できればUIなどのプレゼンテーション層で対処できればベスト）

ことによって、将来の顧客像の変更に対してサービスがフットワーク軽く適応できる状態を維持するように心がけています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3509873/f3bec74c-1538-8fe1-d1f3-fdd6ae3569cb.png)

## 例: サイト解析サービスの場合

サイト解析サービスを開発したいとしましょう。汎用的なサイト解析サービスはすでに多くありますので例えば「（Qiitaのような）ソフトウェアエンジニア向け情報共有サイト」に一段階、ユーザー像を絞り込むことが考えられます。

これによって

> サイト来訪者はソフトウェアエンジニアが大半だから、GitHubアカウントを持っていて連携しているんじゃないか。記事の執筆閲覧とGitHub上のアクティビティの関係を分析できたら面白そうだ。


> サイト管理者は記事につけられる大量のタグのメンテナンスに苦労していそうだ。SNS上の会話をウォッチして、新しいタグをサイト管理者に提案する機能があると喜ばれるかもしれない。

など、一歩踏み込んだ課題洞察やアイデア出しが可能になります。

しかしいざサービスを作る場合は、サービスの最深部は汎用的なサイト解析サービスを前提にした機能開発に留め、「ソフトウェアエンジニア共有サイト」向けに特化した各種機能はその上に乗っかるような付加機能とすべきです。もし「ソフトウェアエンジニア共有サイト」という前提がサービスの最深部で必須条件になってしまうと、その上に乗っかる様々な要素が「ソフトウェアエンジニア共有サイト」向けに作られてしまう可能性があります。

将来ユーザー像が広がったり、別のカテゴリのサイトに変化した場合でも、付加機能をうまく切り離して作り変えることでビジネスの変化に耐えることができます。

## 最後に

今回紹介した指針は、あくまで絞り込んだユーザー像に**不必要に**依存することを避けるべきであって、必要であればサービスの深い部分で依存してしまうことは仕方ないと思います。

しかしドメイン理解が100%ではない状況では、不必要な依存関係を作ってしまったな〜という経験は私もよくやってしまうので、自分への戒めとして記事にしてみました。
【Windowsバッチ】パス情報取得の整理

Winodowsバッチでのパス情報の取得に関して、ほぼ個人的な整理。
```test.bat
rem C:¥test¥folder¥test.bat
rem バッチファイル自身
echo %0
rem ドライブ
echo %~d0
rem ドライブを除いたパス
echo %~p0
rem デイレクトリのフルパス
echo %~dp0
rem 拡張子
echo %~x0
rem 拡張子を除いたファイル名
echo %~n0
rem 拡張子を含むファイル名
echo %~nx0
rem ファイル属性
echo %~a0
rem ファイルの更新日時
echo %~t0
```

```text:実行結果
test.bat
C:
¥test¥folder¥
C:¥test¥folder¥
.bat
test
test.bat
--a--------
2023/07/24 21:00
```

%~a0で取得できる属性情報はattribで取得できる属性情報に対応している(出力結果は同じではない)。
上記はバッチファイル自身を指す%0で記載しているが、サブルーチン化して引数として指定する場合などは%1で対応する。
WordPressのメンテ画面のカスタマイズ【プラグイン使用】

wordpressのメモメモ。

すでに運用中のWebサイトをリニューアル・メンテナンスしたいとき、
`サイトの公開を停止していることを訪問者に知らせるページ` を表示させます。（以降、メンテ画面）
メンテナンス中はサイト内のどこをアクセスしても、メンテ画面が表示されます。

その方法は色々ありますが、今回はプラグイン `【wp-maintenance-mode】` （以下、メンテモード）を使用します。

※wp-maintenance-modeは現在、 [LightStart](https://ja.wordpress.org/plugins/wp-maintenance-mode/ "LightStart") という名前に変わっているそうですが、基本的には同じです（この記事では旧版を使用しています）

#### やりたいこと
プラグイン「WP Maintenane Mode」を使用すると簡単にメンテナンスモードに切り替えることができます。
実際の画面は至ってシンプルで、プラグインの設定画面からは簡単な文字の装飾や背景の指定程度しかできません。（カスタムCSSはあります）

▼設定画面 (※1)
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2718989/7e83448e-bac9-7c76-d52e-0c4be19ff030.png"> <img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2718989/5fa494db-9c8c-13b6-e70b-070e3df48b38.png">

▼実際の画面
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2718989/c68c298c-7b62-9f98-9363-073399e4246f.png">

「メンテナンス中画面だけどもう少し凝ったページにしたい」
「運用中のWebサイトに合わせた見た目にしたい」

## やり方

設定画面(※1)で設定した情報は下記テンプレートに反映されています。
`/plugins/wp-maintenance-mode/views/maintenance.php`

使用テーマ内に自分で用意したphpファイルを上記のテンプレートに上書きする形です。

使用テーマのパス (例: summer): `wp-content/themes/summer/`

- テーマのフォルダ直下に フォルダ `wp-maintenance-mode` を新規作成
- そのフォルダ内に `maintenance.php` を作成。

```php: /themes/summer/wp-maintenance-mode/maintenance.php
<?php get_header(); ?>

<header>
	<h1>ただいまメンテナンス中です</h1>
</header>

<div class="container">
	<p>ここに上書きしたコンテンツ</p>
</div>

<?php get_footer(); ?>
```

ヘッダー、フッターもメンテ画面独自のものにしたい場合は、
テーマのフォルダ直下（`wp-content/themes/summer/`）のheader.php, footer.phpをコピペし、名前を変更
`maintenance.php` の `get_header()`, `get_footer()` を以下のように修正する。

```php: 名前は任意(例: test)
/* header-test.php */
<?php get_header("test"); ?>

/* footer-test.php */
<?php get_footer("test"); ?>
```

メンテ画面のテンプレートを任意のファイルに上書きすることができました。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2718989/e6616b6f-c91b-7f80-5bbd-9fa5a0432002.png">


```txt: 上書きするメンテ画面のテンプレート格納場所まとめ
/wp-content/themes/{your_theme}/wp-maintenance-mode/maintenance.php
/wp-content/themes/{your_child_theme}/wp-maintenance-mode/maintenance.php
/wp-content/themes/{your_theme}/header-test.php
/wp-content/themes/{your_theme}/header-footer.php
```





参考: https://www.conoha.jp/lets-wp/wp-maintenancemode/
DockerでMariaDBを構築する

## 目的
Dockerを用いてMysqlを構築する時に、毎回webで調べてしまっているので、記事にまとめておこうと思う。

## ディレクトリ構成
- docker-compose.yml
- db/
  - Dockerfile
  - sql.sql 
  - my.cnf


## 構築
### docker-compose.yml
```docker-compose.yml
  db:
    container_name: db
    restart: always
    build:
      context: ./db
    environment:
      MARIADB_ROOT_PASSWORD: tmcit
      MARIADB_DATABASE: LESSON
      MARIADB_USER: tmcit
      MARIADB_PASSWORD: tmcit
      TZ: 'Asia/Tokyo'
    volumes:
      - ./db/data:/var/lib/mysql
    command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    ports:
      - 3306:3306
```
### Dockerfile
```Dockerfile
FROM mariadb:latest
RUN touch /var/log/mysql/mysqld.log

ADD  my.cnf /etc/mysql/conf.d/my.cnf
ADD sql.sql /docker-entrypoint-initdb.d
RUN chmod 644 /etc/mysql/my.cnf
```
### db/my.cnf
```db/my.cnf
[mysqld]
# 文字コード、整合順序の設定
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
```

## db/sql.sql
mariadbの起動時にデータベースの中身まで構築したい場合は以下のファイルを作成する。
```db/sql.sql
create database if not exists LESSON;
use LESSON;
create table LESSON_2023(LESSON_ID int auto_increment, LESSON_NAME text, ACCEPTED_GRADE int, PRIMARY KEY (LESSON_ID));
insert into LESSON_2023(LESSON_NAME,ACCEPTED_GRADE)VALUES("国語",1);
```

## ログイン
```
$ docker exec -it bf4 /bin/bash
$ mariadb -u root -h localhost -p
```
システムエンジニアのための今日から使える5つの仕事術

フリーランスのITエンジニアの話を聞くと感じますが、プログラミングができるよりも仕事全般の能力のほうが重要になってきます。
システムエンジニアとして働いていた時期もありました。
その中で大事だったと思うことをまとめていきます。

## システムエンジニアのための今日から使える5つの仕事術

### 1. 毎日やる仕事の管理
仕事とは、ルーティン作業です。
毎日やるタスク、毎週やるタスク、毎月やるタスクとあります。

決まったタイミングまでに、決まったタスクを終わらせることが大事。

いつまでにやるか？
が分かっていると、タスクの優先順位も付けられます。

仕事ができるようになっていけば行くほど、タスクの量も増えていきます。
一つずつ効率的に終わるように進めるスケジュールを作ってみましょう。

### 2. 問題解決能力
システム開発をしていると、どうしても問題・課題が出てきます。
そういう時に大事なのは、問題解決する能力です。

しかし、まだまだ歴が短いと、問題を解決する手札がなかったりしますね。
10分考えて分からなかったら、できる人に一回質問しましょう。
「自分だけで解決する」は置いといて、誰かに聞いてみて解決するのも大事な問題解決力だったりします。

### 3. チーム内のコミュニケーション
誰かに聞くためには、そもそも普段から話しやすい関係になっていることがキーです。
なので、チーム内のコミュニケーションは大事にしましょう。

まずは、自分から挨拶をする。
たった一言の挨拶だけで、話しやすくなります。
「挨拶」おすすめです。

### 4. 勉強を継続すること
当たり前のことですが、勉強を続けること。
システム開発はどこまでやっても学習が必要です。

案件が変われば、同じPHPでも全く違ったりします！
Laravelの代わりにCakePHPだったり、EC-CUBEの代わりにShopifyだったり。
似ているけど違うので、自分で勉強して理解する必要が常にあります。

定期的に勉強するのが苦手？
もしかすると、エンジニアに向いてないかもしれないですね。

### 5. コードをまず書く
とにかく書くこと！
Google検索で調査するよりも、まず書く！
分からないなら、まず書いてみて動くかどうか試しながら書く！

頭で考えるのも大事ですが、書くから気が付くことは多いです。
（ただ、個人差があります。人によっては考えてから効率よくできる人もいるかも。）

### さいごに
健康管理も大事ですね。
健康なほうが仕事のパフォーマンスも上がります。

**健康を維持するのに、大事にするポイントは「睡眠」「食事」「運動」です！**
私の経験上、どれか一つが欠けると、徐々に生活習慣がズレていきます。
健康、気を使いましょう！

https://qiita.com/ono-ayumu/items/2b7354b1b191de93e999
#0006 SHASUMとは

## はじめに
- dockerのファイルシステムのアップデートを最近考えている。
- いかに軽量でアップデートできるかが課題である。
- 今回は割愛するが、`rdiff`を使ったアップデート方式を考えている。
- アップデート前と後でファイルシステム（実際にはdocker imageの`xxx.tar`）の更新分を調べた。
- 調べるために、`shasum`を利用したので、それを記事にテーマとする。

## SHASUMとは

SHASUMはmessage digest algorithmsの一種で、詳細は[ブログ](https://linuxhint.com/shasum-linux/)に書いてあることを引用させていただく。

> The shasum is used for computing SHA message digest, cryptographic checksum, or cryptographic hashcode. For those who don’t know, a message digest is a fixed size hash value of a message. A message digest is encrypted with a private key to form a digital signature. 

- shasumは、SHAメッセージダイジェスト、暗号化チェックサム（後述）、暗号化ハッシュコードの計算に使われる。
- メッセージダイジェストとは署名付きのハッシュ値のメッセージ


> There are two important aspects of a message digest:
> 1. They produce hash values that are practically impossible to invert. Hence, they are unique. It is computationally impossible to find two files with the same MD(message digest) value.
> 2. If we slightly change the original message, the new MD value will significantly change.


- shasumはメッセージのたびに、署名が変更されることもあり、ユニークで改ざんされることがない

## その他のmessage digest algorithms

message digest algorithmsには、

- MD5
- SHA-1　（16bit）
- SHA-2
- SHA-256　（128bit）
- SHA-384 (192bit)
- SHA-512 (256bit)

などがある。現在最も使われているのが、`SHA-256`でそれ以前は`MD5`、`SHA-1`、`SHA-2`などが使われていたが、脆弱性が明らかとなり、使われていない。

SHA-に続く数字はビット数と相関がある。

## チェックサム

- チェックサムとは平たく言えば、ファイルの整合性を確かめること
- 手元にあるファイルと、それとをアップロードして再びダウンロードしたファイルの、整合性を測る手段
- アップロードとダウンロードの過程でもしファイルが壊れていたら、それがわかるということです。
- このチェックサムにSHASUMは活躍します


### 文字列のチェックサム

- `shasum XXX`でXXXのハッシュ値が算出される。

```
~/❯❯❯ shasum  #Enter                                                                    
JAPAN #control + d
8a2697779846e2045e9507892110c97083915535  -

~/❯❯❯ shasum  #Enter     
JAPAN  #Enter 意図的に改行
       #control + d
5a44c5ea80dbe3875e15b7100487b5b99f25fcc9  -
```

### fileのチェックサム

- `shasum XXX.yyy`でファイルのハッシュ値が算出される。

```
~/❯❯❯ shasum test.py
6392e9d66e70c20f311bb6183fc5427d9ab72a12  test.py
```

## おわりに
- SHA は Secure Hash Algorithm のこと
- `xxx.tar`のtarとは type archivesのことで、複数のファイルをまとめたもの
観測度数/期待度数/クロス集計/独立性検定/適合性検定

# Abstract
観測度数、期待度数、クロス集計、独立性検定、適合性検定について、概要を述べる。
# 観測度数と期待度数
観測度数$O = \lbrace O_{ij} \rbrace_{i=1,\cdots,m;j = 1,\cdots,n }$ が与えられている時、期待度数$E = \lbrace E_{ij} \rbrace_{i=1,\cdots,m;j = 1,\cdots,n }$を以下で定義する:
$\begin{align}
E_{ij} := \frac{O_{i \cdot} \cdot O_{\cdot j} }{O_{tot}}
\end{align}$
ただし、
$O_{i \cdot} := \sum_{j}O_{ij}$
$O_{\cdot j} := \sum_{i}O_{ij}$
$O_{tot} := \sum_{i}\sum_{j}O_{ij}$

# 定理
$T=\sum_{i=1,\cdots,m}\sum_{j=1,\cdots,n}\frac{(O_{ij} - E_{ij})^2}{E_{ij}} \sim \chi^2((m-1)(n-1))$

であり、
$m = 一般$の場合は、独立性検定に、
$m = 2$の場合は、適合性検定に使われる。

(ちなみに、独立性検定と適合度検定は、共に右側検定である)
# クロス集計
工事中
# 独立性検定
工事中
# 適合性検定
工事中
# 演習問題(解答解説付き)
【Windowsバッチ】nulを使う

「null」ではなく「nul」である。要するに空の出力先である。コマンドのリダイレクト先をnulにすることで、コマンド実行時に標準で出力されるメッセージを抑止することが可能。標準のメッセージが邪魔になる場合などにnulを使用する。また、空のファイルを出力する際にも使用できる。

```test.bat
rem 空のテキストファイルを出力
type nul > "out.txt"

rem 標準のメッセージ出力を抑止
mkdir "OutputFolder" > nul
move "out.txt" "OutputFolder" > nul
timeout /nobreak 10 > nul

rem エラーメッセージも抑止
del "OutputFolder¥out_2.txt" > nul 2>&1

rem 「続行するには何かキーを押してください . . . 」以外のメッセージでpauseさせる
echo キーを押すと終了します。
pause > nul
```

delコマンドでは少し書き方を変えているが、ファイルが存在せず空振りに終わった場合などに出力されるエラーメッセージの出力も抑止している。delコマンドの前にファイルの存在チェックをかけても良いが、処理が冗長となる場合などにはこの書き方でも良いかもしれない。
Whisper を Docker 化してみた

# はじめに

インフラ整備の一環とDockerの練習を兼ねて、文字起こしツールのWhisperをDocker化してみました。この記事ではDockerを利用してWhisperを実行する方法について説明しています。(Docker化する方法についての記事ではありません)

# Whisperとは

Whisperは、chatGPTで有名なOpen AIが提供する音声認識モデルです。Whisperを利用することで、英語や日本語など様々な言語の音声データを高精度にテキストデータに変換することが可能です。議事録の自動作成や翻訳に一役買うこと間違いなしでしょう。
今回利用するのは[こちら](https://github.com/ggerganov/whisper.cpp)のモデルです。


# Dockerの簡単な説明

Dockerはコンテナ型の仮想環境を簡単に作成・配布・実行できるプラットフォームです。GitHubがコードの共有を容易にするように、Dockerは環境の共有を可能にします。

Dockerは「イメージ」と「コンテナ」という2つの概念に基づいています。イメージは環境の設計図のようなもので、これを元に実行環境であるコンテナを作成します。イメージはDocker Hubで共有することができるため、誰でも同じ環境を手軽に再現できます。

Dockerを使えば「あの人のパソコンでは動いているのに自分のパソコンでは動かない！」というあるあるを減らすことができます。

興味がある方は、[こちらの動画](https://youtu.be/oHcLtX_DPko)などを参考にして、Dockerの基本を理解してみてください。

# 使い方

## ファイルの準備

まず最初に、文字起こししたいwavファイルを適当なディレクトリに入れて、わかりやすい場所に配置してください。
![IMG_0597.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3083819/88629b93-f1c4-623e-f496-ef3b5e3bae3b.jpeg)


## Whisperの実行

### 1.Dockerのインストール
Dockerをインストールしていない人は[この動画](https://youtu.be/OQ5m9iEbRu0)(windowsは[こちら](https://youtu.be/LSQKHW01ACo))を見てインストールしてください。

### 2.Dockerにログイン
ここからはターミナルを使用します。
Dockerにログインするために、以下のコマンドを実行し、ユーザーネームとパスワードを入力します。
```
docker login
```

### 3.イメージをダウンロード
Docker Hub([hidakakombu](https://hub.docker.com/u/hidakakombu))からイメージをダウンロードします。
```
docker pull hidakakombu/whisper:1.0
```

### 4.イメージからコンテナを起動 (2回目以降はここから実行)
次に、イメージからコンテナを起動します。
ここでは、自分のパソコンの"/Users/hogehoge/Desktop/whisper"ディレクトリと、コンテナの"/app"ディレクトリを同期させています。"/Users/hogehoge/Desktop/whisper"の部分には、先ほど準備したwavファイルが入っているディレクトリのパスを入力してください。
```
docker container run -it --rm -v /Users/hogehoge/Desktop/whisper:/app hidakakombu/whisper:1.0
```
コンテナが起動するとターミナルのプロンプト表示が次のようになります。
```
root@3ea5ef67e8dd:/app# 
```

### 5.Whisperの実行
以下のコマンドを使って、Whisperで文字起こしを行います。
```
../whisper/main -m ../whisper/models/ggml-large.bin -f sample.wav -l en -t 8 > sample.txt
```
- sample.wav : 文字起こししたいwavファイルの名前。
- sample.txt : 出力されるtxtファイルの名前。
- -l : 言語の指定。英語の場合は en 、日本語の場合は ja 、わからない場合は None を指定します。

### 6.コンテナの停止
文字起こしが完了したら、コンテナを停止します。
```
exit
```

## 結果の確認
文字起こしが成功していれば、こんな感じのtxtファイルがwavファイルと同じ場所に生成されているはずです。
```
[00:00:00.000 --> 00:00:04.000]   Alright, so here we are in front of the elephants.
[00:00:04.000 --> 00:00:12.000]   The cool thing about these guys is that they have really, really, really long trunks.
[00:00:12.000 --> 00:00:14.000]   And that's cool.
[00:00:14.000 --> 00:00:19.000]   And that's pretty much all there is to say.
```
![IMG_0598.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3083819/df560c95-5c54-dfb8-b294-cd7afd8dcc07.jpeg)



# さいごに
皆さんもwhisperを使って幸せな文字起こしライフを送りましょう！


# 参考

https://github.com/ggerganov/whisper.cpp

https://youtu.be/oHcLtX_DPko

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

https://youtu.be/LSQKHW01ACo

# DockerHub & Dockerfile

https://hub.docker.com/u/hidakakombu

https://github.com/hidakakubo/Dockerfiles/blob/main/whisper/Dockerfile
【PHP】変数($)とは表示方法や値の代入方法について

変数は簡単に言うと**値を入れて置ける箱**みたいな物です。

```php
$変数名 = 値;
```

```php
<?php
	$test
```

変数の場合は（＄）から書いていきます。

上に書いてある`$test`は箱みたいな物で、この`$test`の中に色々入れれるというイメージで良いです。

# ****代入演算子****

変数に値を格納するには代入演算子「=」を使います。代入演算子は左辺の値を右辺に代入します。

```php
<?php

$test = 123;

echo $test;
```

今回は`$test`の中に`123`を入れて`echo`で`$test`を表示させています。

### 表示内容

```php
123
```

このように変数の中に入れた文字や数字を`echo`から表示させることができます。

### 文字を変数に入れる場合

```php
<?php

$test = 'テスト';

echo $test;
```

シングルクウォーテーションで囲んであげれば文字で表示する事も出来ます。

### 表示内容

```php
テスト
```

# 変数を上書き

```php
<?php

$test = 123;
$test = 456;

echo $test;
```

`$test`の中に`123`を入れた後に変数に`456`を入れて`echo`で`$test`を表示させてみます。

### 表示内容

```php
456
```

このように変数は後から入れた物が上書きされるようになります。

# 変数の組み合わせ

```php
<?php

$test1 = 123;
$test2 = 456;

$test3 = $test1 . $test2;

echo $test3;
```

変数を組み合わせる時は（.）を付けてあげれば２つの変数を表示させることができます。

### 表示内容

```php
123456
```

※ピリオドでくっつける場合は文字として表示されます

`var_dump()`で確認してみるとわかりやすいと思います。

### var_dumpで表示内容確認

```php
int(123) int(456) string(6) "123456"
```

くっつけないとint「数字」で判断されていますが

くっつけるとstring「文字」で判断されるようになります。ので覚えておきましょう。
ソフトウェア工学

# 目次
1. [ソフトウェア工学とは](#ソフトウェア工学とは)
1. [ソフトウェ工学の詳細](#ソフトウェ工学の詳細)
1. [ソフトウェア工学の課題点](#ソフトウェア工学の課題点)

# ソフトウェア工学とは

品質の高いソフトウェアを低コストで期限通りに開発し，効率よく保守するためのさまざまな技術を扱う学問分野

# ソフトウェ工学の詳細
1. [ソフトウェアとは](#ソフトウェアとは)

### ソフトウェアとは
「コンピュータに対して命令を出すプログラム」を指す言葉
PCやスマホは、ソフトウェアのおかげで動かすことが出来る。

https://qiita.com/Cookie_Iwate/items/954ba0f06de666334050


# ソフトウェア工学の課題点
1. [とある研究結果](#とある研究結果)
1. [課題点](#課題点)

### とある研究結果
1) 2008年12月11日の[記事](https://xtech.nikkei.com/it/article/NC/20081126/319990/)(日経コンピュータ)
品質，費用，工期の全てが計画通りだったのは、全体の<font color="red">**31.1%**</font>だけ
2) 2018年3月1日の[記事](https://xtech.nikkei.com/atcl/nxt/column/18/00177/)(日経クロステック)
1745件に上るシステム導入/刷新プロジェクトのうち「成功」したのは<font color="red">**52.8%**</font>。「失敗」は<font color="blue">**47.2%**</font>で、わずかに成功が上回った。

### 課題点
まだまだ成功率が低い

学ぶ上で経験値が必要だから？学ぶ重要性が低い or 知名度が低いから？
【Python】Pandasを利用して、同じ数値から始まる二つのCSVファイルの中身を一瞬で結合する方法

# 概要
同じ数値から始まる二つのCSVファイルの中身を、横方向（列方向）に結合するコードを実装しました。

### 前提
`hoge`ディレクトリにある`XXX_output.csv`と、`foo`ディレクトリにある`XXX_2_output.csv`を結合して、一つのCSVファイルを作成します。`XXX`には同じ数値が入ります。

```
例：

1234567890_output.csv
1234567890_2_output.csv
↓
結合
↓
1234567890_data.csv
```

結合して作成されたCSVファイルは、ディレクトリ`combined`に`XXX_data.csv`の形で保存するものとします。

# サンプルコード解説

```py
import os
import pandas as pd

hoge_dir = 'hoge'
foo_dir = 'foo'
combined_dir = 'combined'

# 出力先ディレクトリを作成
os.makedirs(combined_dir, exist_ok=True)

# hogeディレクトリのファイルを処理
for file in os.listdir(hoge_dir):
    if file.endswith('_output.csv'):
        file_name = file.split('_output.csv')[0]
        output_file_name = file_name + '_data.csv'
        output_file_path = os.path.join(combined_dir, output_file_name)

        file_path = os.path.join(hoge_dir, file)
        file_path_2 = os.path.join(foo_dir, file_name + '_2_output.csv')

        # ファイルを結合して新しいCSVファイルに保存
        data = pd.read_csv(file_path)
        data2 = pd.read_csv(file_path_2)
        combined_data = pd.concat([data, data2], axis=1)
        combined_data.to_csv(output_file_path, index=False)

        print(f'{output_file_name} の作成が完了しました。')

```

## 補足
- `os.makedirs()`関数を使って、`combined_dir`で指定されたディレクトリを作成していますが、`exist_ok=True`を指定することで、既にディレクトリが存在している場合でもエラーを発生させずに処理を続行させることができます。
    - 上書きさせたくない時は外してください。`FileExistsError: [Errno 17] File exists:`エラーが表示されます。
- `os.listdir(hoge_dir)`は、`hoge_dir`で指定されたディレクトリ内のすべてのファイルとディレクトリの名前をリストとして取得。
- `file_name = file.split('_output.csv')[0]`により、`1234567890_output.csv`というファイル名を`_output.csv`を区切り文字として分割し、分割後の最初の要素（インデックス`0`の要素）を変数に代入します。`file.split`では、以下のように分割されます。
    - `['1234567890', '']`
- `pd.concat([data, data2], axis=1)`は、Pandasライブラリの`concat()`関数を使って、2つのデータフレームを横方向（列方向）に結合する操作。第1引数には結合したいデータフレームをリストとして渡し、`axis`引数で結合方向を指定します。`pd.concat()`関数によって、`data`と`data2`の列が横に連結され、新しいデータフレーム`combined_data`が作成されます。
    - 二つのCSVファイルで行数は一致している必要があります。一つのCSVで3行あっても、もう一つのCSVで1行しかない場合、1行までしか結合されません。
    - 縦方向（行方向）に結合する場合は、`axis=0`を指定します。デフォルトでは`axis=0`です。
    - ちなみに`sort=True`というように、結合したデータフレームのインデックス（行番号）が昇順にソートするオプションがありますが、デフォルトでは`True`が設定されており、Pandasバージョン0.23.0以降では非推奨となっているようです。
- `combined_data.to_csv(output_file_path, index=False)`で、`combined_data`をCSVファイルとして`output_file_path`に保存します。
    - `to_csv`は、DataFrameのデータをCSV形式のファイルに保存するPandasのメソッド。
    - `index=False`は、行番号をCSVに保存しないようにするためのオプション。行番号を含める場合は、`index=True`とするか省略することによって、含めることができます。
JavaScriptで正確な消費税の計算


## 前提

decimal.jsを使います。
npm, cdnお好きな方法でダウンロードしてください。

https://www.npmjs.com/package/decimal.js

https://cdnjs.com/libraries/decimal.js

なお、私はChromeで動作確認しています。

## 結論

```js
function strPriceInclTax(strPrice, strTaxRate = "1.08") {
    return (new Decimal(strPrice)).times(new Decimal(strTaxRate)).floor().toString();
}

strPriceInclTax("100"); // "108"
strPriceInclTax("123456789"); // "133333332"
```

切り捨てではなく四捨五入したい場合はfloorをroundに、切り上げたい場合はceilにしてください。

終わり。以下は読みたい人だけ。

## 数値を文字列で扱っている理由

数値を文字列で扱っているのは、目視している数値とプログラム内部の数値が異なるケースを懸念しているからです。

<img width=350 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/649673/0ade360e-360a-b9d9-3a69-f0ec72930ec9.png">

decimal.jsはコンストラクタの引数の数値は内部的にtoStringで文字列に変換しているため、このような数値はバグの原因になりえます。

https://github.com/MikeMcl/decimal.js/blob/7f01abd83d5632005d5f97ddbea41e069ee69a74/decimal.js#L4370

要するにこういうバグが嫌なのです。

```js
// 文字列（笑）という人用
function priceInclTax(price, taxRate = 1.08) {
    return (new Decimal(price)).times(new Decimal(taxRate)).floor().toNumber();
}

console.log(priceInclTax(10000000000000017));
// 出力：10800000000000016
// 正解：10800000000000018

console.log(priceInclTax(100000000000000000, 0.11111111111111111));
// 出力：11111111111111110
// 正解：11111111111111111
```

### 個人の感想

（Decimalコンストラクタの引数とDecimalから値を取り出すときの話です！！！！）

上記のpriceInclTaxの実装でも、
数値をtoStringに変換したときに目視したときの値と同じならバグらないでしょう。多分。
ただ、私にはどこからがその境界線なのか、はっきりと断言することができません。
0.11111111111111111が駄目で
0.1111111111111111がOKだということが私には分からないのです。
10000000000000017はNumber.MAX_SAFE_INTEGERを超える数値なのでまあ正確には扱えないだろうなーとは感覚的に分かりますが。
同等の理由で
new Decimal("1.08")をnew Decimal(1.08)と書くのは許されるのか？
仕様変更で1.08からXになった場合、
new Decimal("X")とnew Decimal(X)が同等なのか？
私には分からないのです。
もちろん、識者は分かるでしょう。
ただ、実際運用していくうえで、開発、保守に関わる人全員がそれを理解して扱えるのでしょうか？
そうでないなら確実性を重視して数値ではなく文字列を使うべきなのではないでしょうか？
もちろん途中で値が壊れる可能性があるので、入力から出力、サーバーへの連携、すべてをです。

ぶっちゃけ個人の日常生活の範囲の金額や小数点以下数桁程度なら普通に数値を使ってもバグらないと思うのでこれが問題化することはないと思います。ただ、潜在バグがあることは頭の片隅に入れておいてください。
【Windowsバッチ】カレントフォルダのフルパスを保持する

```test.bat
echo %0
echo %~d0
echo %~p0
echo %~d0%~p0
echo %~dp0
echo.
echo %1
echo %~1
echo.
set CurrentFolderPath=%~dp0
echo %CurrentFolderPath%
```

```text:実行結果
>test.bat "a"
test.bat
C:
¥test¥folder¥
C:¥test¥folder¥
C:¥test¥folder¥

"a"
a

C:¥test¥folder¥
```

%0はバッチファイル(text.bat)自身を示す環境変数で、自動で設定される。「~」を付与すると環境変数(もしくは引数)のダブルクォートを除去する。(%0にはダブルクォートが含まれていない。)
「d」を付与するとドライブ名のみ、「p」を付与するとドライブ名とファイル名を除いたパス情報となる。この組み合わせでカレントフォルダのドライブ名からのフルパスを保持できる。%~d0%~p0と%~dp0は同じ結果になる。
%~dp0を環境変数に設定しておけば、バッチファイルの処理中にcdによる移動を行なっても元のパスを容易に参照することができる。
ChatGPT For Python

## はじめに
-  当記事ではpythonでOpenaiを使って、ChatGPTの機能を実現する方法を紹介する
-  参考先：https://fuji-pocketbook.net/chatgpt-api-python/

## openaiライブラリをインストール

- ターミナルで以下のコマンドを打つ
  - 前提条件：ローカルにpip、pythonインストールされていること。 
```コマンドフロント
pip install openai
```
## pythonコードを書く。
- ライブラリのimport
```python
import openai
```
- apiキーの設定
　-　https://platform.openai.com/account/api-keys　で設定したAPIキー 


```python
import openai

# openaiのキーを設定する
openai.api_key = "sk-xxxxxxxx"
```
- `ChatCompletion.create`でAPIを呼び出す
- リクエストパラメタ設定
- responseで結果を受け取る  
```python
# openaiから質問を聞いてみる
response = openai.ChatCompletion.create(
    # モデル設定、gpt-3.5-turbo-xxxとの感じで異なるバージョンのモデル指定できるがバージョンなしで書けば最新のもの取ってくれる
    model = "gpt-3.5-turbo",
    # メッセージ設定、userに設定しているのはChatGptから聞く質問
    messages = [
        {"role": "system", "content": "ChatGPTへの指示"},
        {"role": "user", "content": "日本の面積は"}

    ]
)
```
- 返した結果を画面印刷してみる
```
# responseの型を確認してみる
print(type(response))
```
- `OpenAIObject`が返されている
```
<class 'openai.openai_object.OpenAIObject'>
```
- responseの中身を見る
```python
# responseを出力してみる
print(response)
```
- json形式のデータが戻ってきました
```
{
  "id": "chatcmpl-7fo3WLxHRgwVvjxxZkVDw3rAcTlmg",
  "object": "chat.completion",
  "created": 1690198218,
  "model": "gpt-3.5-turbo-0613",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "\u65e5\u672c\u306e\u9762\u7a4d\u306f\u7d04377,972\u5e73\u65b9\u30ad\u30ed\u30e1\u30fc\u30c8\u30eb\u3067\u3059\u3002"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 26,
    "completion_tokens": 21,
    "total_tokens": 47
  }
}
```
- `content`のみ出してみる

```
print(response["choices"][0]["message"]["content"])
```
- ChatGPTは回答してくれましたね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148710/735f1b55-a005-a57f-513d-56fffb316da0.png)
Alpine Linuxのパッケージマネージャで一部のパッケージが入らないときの対処法

Alpine Linuxをインストールし、パッケージマネージャのapkでpy3-pipやffmpegのパッケージをインストールしようとしたらno such packageを吐かれた。
自戒と備忘録を兼ねて

# 原因
apkのリポジトリファイルにコミュニティリポジトリが読まれてないせいだった。

# 対処
何らかのeditorで/etc/apk/repositoriesを編集。リンクは差異がある事があるので適宜読み替えてください。

```terminal:/etc/apk/repositories
#/media/cdrom/apks
http://link.to.repository/alpine/ver/main
#http://link.to.repository/alpine/ver/community
```
/communityへののコメントアウトを外して、apk update。
apkのコミュニティリポジトリが使えるようになります。
ITエンジニアこそマーケティングの勉強をしよう！

経営企画やマーケティング部門の人たちだけでなく、
マーケティングの勉強をしている人はかなり広がってきていると思います。

私も、たまにマーケティングをテーマにした本を読む事がありますが、
エンジニアの方にこそマーケティングをどんどん勉強してもらいたいと思います。

よく、面接や面談でもエンジニアの方から、これからどんな勉強をした方が良いか質問や相談を受けますが、「マーケティング」をお勧めすることが良くあります。

これからの時代、課題解決が出来るエンジニア、ビジネスが創造出来るエンジニアが求められています。

ITのアーキテクチャー、PM、ITコンサルの仕事の境目はどんどん曖昧になり、AIがどんどん発展する中、技術だけのエンジニアや、技術が弱いITコンサルは淘汰される時代になりつつあります。

要件定義や、基本設計などの上流工程を進めて行くときにも、顧客やエンドユーザーの業界における立ち位置や、戦略を理解した上で提案ができるエンジニアとそうでないエンジニアでは提供できる価値に差が出てきてしまうでしょう。

まだまだ開発がメインで、上流工程まで出来ていないというエンジニアの方も、どんな目的のシステムを開発しているかを理解しながら、開発していけば、上流工程へのステップアップにも繋がるし、仕事のモチベーションもアップ出来ると思います。

このシステムの目的や、ビジネス理解を進める上で、マーケティングの勉強は理解を助ける、大きな武器になるでしょう。


個人のキャリアを考えるときにもマーケティングの考え方は非常に役立つと思います。

例えば、SWOT分析。

自身の強みと弱みを整理して、IT業界でキャリアアップするにあたっての、自身にとっての脅威や機会を整理していけば、どのようなスキルセットを身に付ければ良いのか、どんな勉強をすれば良いのか。
SWOT分析のフレームワークを使えば、自分の市場価値を高める自身のキャリア戦略が立てられると思います。

もはや、マーケティングは全てのビジネスパーソンが学ぶべきとも言われています。

ITエンジニアもマーケティングを勉強して、課題解決が出来るエンジニアを目指していきましょう！
WSL2 にて Git Clone がうまくできなかった話

Fatal Error とか吐かれて結構苦しんだのでメモ。
**Git Credential Manager がカギ。** 

## 環境
Windows11 Home
WSL2 (Ubuntu 22.04.2 LTS)

## やらないといけないこと
### windows に Git を入れる
**この時 Git Credential Manager のインストールの確認をされるのでそれを入れておく**
デフォルトだと有効なので連打しておけばとりあえずインストールされる。

-----

### windows上で適当なレポジトリなんかをクローンする
これをすると初回には Github との連携が求められるので対応する。
これによって資格情報が保存される。

変に資格情報が保存されているのであれば、一度削除してからやってみるといい。

----

以下 WSL 上での作業

### ubuntu 上の git バージョンを確認
```terminal:terminal
git version
> git version 2.34.1
```
**バージョンによって次の手順が変わるらしいので注意**

-----

### Git 認証情報を保存する
こちらを参照

https://learn.microsoft.com/ja-jp/windows/wsl/tutorials/wsl-git#git-credential-manager-setup

wsl 上にて以下コマンドを実行
```terminal:terminal
git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager-core.exe"
```

**リンク先にあるが、Git のバージョンによってコマンドが若干違うので注意**

------

これでなんか Clone できたよ。

## 感想
開発環境と通常の OS 環境分けようとして Windows 上に Git 入れるのをめんどくさがったのが罠だった。
普通に入れたほうがいいね。
IMDS Packet Analyzerについて調べてみた

# はじめに
IMDS Packet Analyzerが6月に登場したので、どんなサービスか調べてみました。

https://aws.amazon.com/jp/about-aws/whats-new/2023/06/imds-packet-analyzer-simplifies-migration-imdsv2/

そもそもIMDSが何かすらわかっていなかったので、以下を参考にIMDSについてまとめます。

https://dev.classmethod.jp/articles/ec2-imdsv2-release/

https://dev.classmethod.jp/articles/imds-packet-analyzer-simplifies-migration-imdsv2/

# Instance Metadata Service（IMDS）とは
インスタンスメタデータは、OS上から取得できるEC2のメタデータ情報です。
インスタンスIDやインスタンスタイプ、AZなどを確認できます。
IMDSは、169.254.169.254というリンクローカルIPで動作しています。curlコマンド等でアクセスしてインスタンスメタデータを取得可能です。

※リンクローカルアドレス

> リンクローカルアドレスは、特定のネットワーク内で使われるもので、そのネットワークの中で通信やデータのやり取りをするために使います。例えば、職場や家庭の中にあるコンピュータやスマートフォンが、そのネットワーク内でデータを送受信する場合、リンクローカルアドレスが使われます。これにより、同じネットワーク内にある機器同士が互いを認識し、データを送り合うことができます。リンクローカルアドレスは、特別な番号のようなもので、一般的なインターネット上のIPアドレスとは異なります。

> コンピュータがネットワークに接続すると、まずDHCPDISCOVERパケットを送信してDHCPサーバーを探します。 DHCPサーバーが見つからない場合、コンピュータは自動プライベートIPアドレスアドレス自動構成（APIPA）プロセスを使用してリンクローカルアドレスを割り当てます。 APIPAは、DHCPサーバーに接続できないコンピュータにIPアドレスを自動的に割り当てるプロセスです。コンピュータは、リンクローカルアドレスを使用してネットワーク上の他のコンピュータと通信できます。リンクローカルアドレスは、インターネットに公開されていないため、インターネットとの通信には使用できません。

https://jousys-force.deepapex.com/blogs/what-is-link-local-address

## Instance Metadata Service（IMDS） v1のリスク
EC2は基本的に、メタデータの中にあるインスタンスプロファイルを利用し、他のAWSサービスと連携します。

※インスタンスプロファイル
・IAMロールを格納するコンテナ
・EC2に割り当てられた権限が利用できる一時的なアクセスキーを発行する機能を有する

例えば、S3へのアクセスを許可したIAM ロールをEC2に割り当てると、EC2内のスクリプト等からS3にアクセスすることができるようになります。
上記の方法は、スクリプト内にIAM ユーザのアクセスキーを埋め込む方法よりはセキュアですが、インスタンスプロファイルを外部から取得することが可能な脆弱性と組み合わせられる事によりクレデンシャルが漏洩し、そのEC2に割り当てられた権限を悪用することが可能になってしまいます。

### 米金融大手 Capital Oneの事例
米金融大手のCapital Oneでは、独自運用のWAFの設定ミスによって、SSRF攻撃を受け、インスタンスメタデータからクレデンシャルを取得されてしまいました。
結果、その権限でアクセスできるS3上の、1億人以上の個人情報が流出してしまいました。

※SSRF攻撃
クラウド上にある外部に公開されているサーバから、内部のサーバに送られるリクエストを偽造し、本来公開されてないサーバにアクセスするという手法


## Instance Metadata Service（IMDS） v2の登場
上記のCapital Oneの個人情報流出を受け、SSRF攻撃の対策として、AWSよりIMDS v2が発表されました。


IMDS v2により、アプリケーションレイヤーやIAM ロールの設計・ポリシーで、SSRF攻撃の対策が必要だったところを、IMDS側で対策を強化することができるようになりました。

IMDS v2による改善点のは以下を参考にしてください。

https://aws.amazon.com/jp/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service/

## IMDS Packet Analyzer
本題のIMDS Packet Analyzerについてですが、どのようなものかというと、IMDSとのTCP通信をトレースするツールです。
GitHub上に公開されていて、都度必要なEC2インスタンスにインストールして使用します。（IMDSv1が使用されているか判断するために一時的に使用するツールです。分析が完了したら削除することが推奨されます）

https://github.com/aws/aws-imds-packet-analyzer

### IMDS Packet Analyzerでできること
IMDSv1を使用しているかどうか判断し、SSRF攻撃対策をとることができます。

ほとんどのEC2では、デフォルトでIMDS v1、v2がどちらも有効化されています。
どのプロセスがどちらを使っているのか判断するメトリクスがないため、これまでは確認が簡単でない状況でした。
    
IMDS Packet Analyzerを利用することで、トークンを取得せずにIMDSに直接リクエストしたプロセス（IMDS v1）とそうでないプロセス（IMDS v2）をロギングすることができます。

これにより、プロセスがIMDS v1、v2のどちらを使用しているか判断することができます。


# おわりに
IMDSについて、聞いたことはあるけど何かは知らない状況でした。
v1からv2への背景を知り、とても勉強になりました。
Tauri を WSL2 で動かす

今年に入って 10 回ぐらい windows11 入れなおして、毎回環境構築するのめんどくさくなってきたのでメモ。
OS 入れなおしてまっさらな状態から ``wsl --install`` した直後を考慮

## 環境

- Windows11 Home
- WSL2 (Ubuntu 22.04.2 LTS)

## Rust 環境を作る
公式通り

https://www.rust-lang.org/ja/learn/get-started


```terminal:terminal
sudo apt update
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```

## npm をインストールする
### npm のインストール
公式を参照

https://learn.microsoft.com/ja-jp/windows/dev-environment/javascript/nodejs-on-wsl

```terminal:terminal
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
これをやったら ``command -v nvm`` にて動作確認。 nvm と帰ってきたら多分大丈夫。

(VSCode + Remote WSL使っている人向け)
上記コマンドのインストールが正常に行われたにも関わらず、 nvm と帰ってこない場合、vscode を開きなおすと解決することがある。**window reload だと意味がないので注意** 

### node.js の lts リリースを入れる
```terminal:terminal
nvm install --lts
```


### npm run tauri dev ができる状態にする
詳細は公式参照

https://tauri.app

新しくプロジェクト作るのであれば以下コマンド
```terminal:terminal
npm create tauri-app@latest
```

### いろいろ怒られるので必要なやつをいっぱい入れる
----------------
#### error: linker `cc` not found
これは普通に gcc 入ってなかった
```terminal:terminal
sudo apt install build-essential
```
--------------------------
#### なんかいっぱい出るエラー
```terminal:terminal
error: could not compile `quote` (build script) due to previous error
                warning: build failed, waiting for other jobs to finish...
                error: could not compile `serde` (build script) due to previous error
                error: could not compile `proc-macro2` (build script) due to previous error
                error: could not compile `libc` (build script) due to previous error
                error: could not compile `futures-core` (build script) due to previous error
                error: could not compile `futures-task` (build script) due to previous error
                error: could not compile `getrandom` (build script) due to previous error
                error: could not compile `memchr` (build script) due to previous error
                error: could not compile `target-lexicon` (build script) due to previous error
                error: could not compile `syn` (build script) due to previous error
```

これらを入れてみる
```terminal:terminal
sudo apt install pkg-config \
                libdbus-1-dev \
                libgtk-3-dev \
                libsoup2.4-dev \
                libjavascriptcoregtk-4.0-dev \
                libwebkit2gtk-4.0-dev
```
---------
#### failed to run custom build command for `alsa-sys v0.3.1`
```terminal:terminal
sudo apt install libasound2-dev
```
これで多分通る

----------------

#### あとなんか足りないやつ

```terminal:terminal
sudo apt install libwebkit2gtk-4.0-dev \
            build-essential \
            curl \
            wget \
            file \
            libssl-dev \
            libgtk-3-dev \
            libayatana-appindicator3-dev \
            librsvg2-dev
```

これぐらいでビルド通ると思う。

## 感想
なんで週に 1 回 OS 入れなおしてるんだろ。
次は Manjaro でもいれよっかな。

## 参考

https://penner.me/getting-tauri-working-in-wsl
レゴブロックで作ったエレベーターの組立図 書いてみた　その３　カウンタウェイト編

# カウンタウェイト編
## 全体

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/b0703c0a-3f87-3473-d8c2-cb0c7e6153b4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/29476705-e535-13cd-a77f-5846e52cb136.png)





![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/e1fe7adc-2b19-9297-42f7-4d84c1989287.png)




## 小分け分解
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/7e5aea64-0e22-f645-fe75-48ca39863c3c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/890c7a6b-37c4-508f-5ca5-e2f747867690.png)




![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/a2c63faf-7afd-cf3b-8f89-c4e34cfd1d2e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/2d567adc-26c7-45ab-a6fe-581f00ffc014.png)



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/157a19df-1f07-1dbc-b829-a77d88540d6a.png)



### 最後に
部品名等が小さ過ぎるためこの冊子には記載しておりませんが、
「秋葉原ロボット部」のサイトにて公開したいと思います。
詳細はこちら　https://akbrobot.gitlab.io/blockelevator/
letter_opener_webって？ 

# letter_opener_webってなに？？
タイトルの通り`letter_opener_web`について解説したいとおもいます。
#### letter_opener_webとは
開発環境でメールを送信した際に、ブラウザでそのメールを確認するためのツールです。
本番環境で実際にメールを送る代わりに、このツールを使ってメールの内容や送信先を確認することができます。
#### 使用方法
Gemfileにletter_opener_webを追加
```ruby:Gemfile
gem 'letter_opener_web', '~> 1.0'
```
```
$ bundle install
```
これで使えるようになります！
レゴブロックで作ったエレベーターの組立図 書いてみた　その1　フロア編

# フロア編
## 全体
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/dd43f5c3-60ee-0cc0-f952-4d917e0bce99.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/5910930c-705c-5547-fa48-5ef2cccab83e.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/a58c7c18-e8ad-ee7f-94e4-0fbef3728e7f.png)

## 小分け分解
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/5681bc3e-7626-9335-178c-bba91bbfb4ee.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/e18300f1-1249-a010-d229-271c2087857b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/7887f27f-8a0d-96d7-de62-8a961c240e18.png)



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/06dbb016-3ff5-7e39-9fa4-2b58ccf409da.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/6a0612a4-2a1f-96db-1a42-b8ca06f781ed.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/0a3f7504-4a24-2b53-6364-37d9776cd944.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/ee30ec40-1094-a769-c287-22bf21de6a68.png)




![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3010655/1582a577-dcef-fabe-d495-630b74c3a92a.png)




### 最後に
部品名等が小さ過ぎるためこの冊子には記載しておりませんが、
「秋葉原ロボット部」のサイトにて公開したいと思います。
詳細はこちら　https://akbrobot.gitlab.io/blockelevator/