OmniAICreator/Qiita-1.07M huggingface dataset:
NimでSelenium

## 概要
仕事上でNimを使ってSeleniumを書く必要があったため、その時の知見を共有します。

HaloniumというSelenium互換ライブラリを作ってくださっている方がいたのですが
Testディレクトリにサンプルがない操作が多かったため参考になると嬉しいです。

## WebDriverの設定
まず他のSeleniumと同様にChromeを使って自動操作をするため、
Chromeのバージョンに合ったWebDriverをダウンロードして
環境変数に設定する必要があります。

お使いのChromeバージョン確認　
[chrome://settings/help](chrome://settings/help)

WebDriverのダウンロード場所
https://chromedriver.chromium.org/downloads

ダウンロードしてきたらwebdriverのファイルパスを環境変数へ設定してください（割愛）

## 各種操作

### ライブラリのimport 

```nim:test_selenium.nim
import halonium
```

### ブラウザの起動と遷移

```nim:test_selenium.nim
# ひとつのセッションとしてChromeを起動
var session = createSession(Chrome)

# 起動するとデフォルトは小さくウィンドウモードで起動するため全画面表示
for w in session.windows:
  w.maximize()　

# QiitaのURLに遷移
session.navigate("https://qiita.com/")
```

session.navigateをすると通常通りネットワーク接続の上、
ページ遷移するため次までの処理を待つためsleepを入れることをおすすめします。
こちらは要素のクリックによって読み込みが発生するような場合にも必要です。
waitForElementで取得できるまで待機する方法もあるかと思います。

```nim:test_selenium.nim
# 3秒スリープ
sleep(3000)
```

### オブジェクトの取得（例：ログイン処理）

要素の取得は通常のSeleniumのように書くことができます。

```nim:test_selenium.nim
# ログイン処理
let userName = "input[name=\"userName\"]"
let element = session.waitForElement(userName).get()　# 要素が取れるまで待機
element.sendKeys("user", Key.Tab) # ユーザー名入力後にTabを入力してフォームを移動

let password = "input[name=\"password\"]"
let element2 = session.waitForElement(password).get()　# 要素が取れるまで待機
element2.sendKeys("password", Key.Enter) # パスワード入力後にEnterを押す
```

#### 要素取得（ClassNameSelector）

上記の取得の他、例として複数の要素をclass名で取得する場合が以下になります。

```nim:test_selenium.nim
let elements = session.findElements("class-name",　LocationStrategy.ClassNameSelector)

# 複数要素の単体をクリックする場合の例
elements[i].click()
```

LocationStrategyには以下の種別があるので、
idやnameで取得したい場合はそちらを選択することも可能です。

```nim:test_seleniun.nim
LocationStrategy* = enum
    IDSelector = "id"
    XPathSelector = "xpath"
    LinkTextSelector = "link text"
    PartialLinkTextSelector = "partial link text"
    NameSelector = "name"
    TagNameSelector = "tag name"
    ClassNameSelector = "class name"
    CssSelector = "css selector"
```

### オブジェクトのクリック

こちらは関数自体シンプルなので要素の取得と合わせてサンプルを置いておきます。

```nim:test_selenium.nim
let button1 = session.waitForElement("#button_1").get()
button1.click()
```

### おまけ：ローカルにダウンロードしたファイルの操作
こちらはselenium処理とは異なりますが、
自動操作した場合ファイルをダウンロードしてきて〜なんて話が多いのでこちらも例を。

```Nim:test_selenium.nim
# こちらはnimの標準ライブラリなので基本追加でもOK
import options, os, times, strutils, sequtils

# 移動先のフォルダが無ければ作成する
let path = "保存フォルダのパス"

# フォルダがなければ作成
if not existsDir(path):
  os.createDir(path)

let dl_path = os.getHomeDir() & "Downloads"
# ホームディレクトリのDownloadsからcsvを検索
for f in walkFiles(dl_path & "/*.csv"):
  # 更新日時が１時間以内のものを保存フォルダに移動させる
  if getLastModificationTime(f) > (now() - minutes(60)).toTime:
    # ファイル名をいじる場合には、walkFilesでは
    # fにフルパスが渡ってくるためextractFilenameでファイル名だけにします。
    # let now_dir = dl_path & "/" & extractFilename(f)　
    os.moveFile(f, path & "/" & extractFilename(f))
```
エンジニアこそ、できっこないをやらなくちゃ

#はじめに

この記事はトラストバンクアドベントカレンダー11日目の記事です。

みなさんこんにちは。 トラストバンクCTO やまけん ／ 山崎賢 （ @yamaken66 ）です。

突然ですが、私はサンボマスターが大好きです。このブログのタイトルも当然、大好きなサンボマスターの代表曲
「できっこないをやらなくちゃ」からいただいてます！

今日はエンジニアの苦悩と、そこから来るジレンマ、そこに対する提案を記載していきたいと思います。

#エンジニアの苦しみ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242212/221e5a8b-d1b2-68b1-e411-9557ff0b275b.png)

これは有名は絵です。

多くのプロジェクトが伝言ゲームで要件が受け渡されるため、エンジニアに対しては本来の顧客の課題が明確でない状態で、

「**こんな感じのものを1ヶ月で作って欲しい。できる？**」

というコミュニケーションがされることが多いです。

「**<font color="red">まぁできるんじゃないですか？</font>**」

と答えようものなら、要件がもりもりになったものが後で渡されて

「**<font color="red">できるっていいましたよね？ もう納期変えれません</font>**」

とか言われたり。 涙

エンジニアはいつだって最終工程なので、結局納期を守るためには時間を捧げるしか無くなることが多いです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242212/c4ad6c0b-ccd1-b69e-5846-eb88e2859cf5.png)

これも有名な
「不確実性のコーン」です。
要件が定まっていない時点の見積もりなんて、4倍の誤差が発生しますよってことです。

#この繰り返しによってエンジニアは自分を守り始める
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242212/586ad8ec-633c-2783-9f2a-0af48fa9aefa.png)

こういったやりとりが続くと、エンジニアは簡単に「**できます**」と言えなくなります。
「**できます**」という代わりに自分を守るためのステルスなバッファを盛り込んだり、工数を多めに見積もることをやり始めます。これは過去の経験から自分と仲間を守るための行為です。

#エンジニアが「できない」と言うこと
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242212/2b4fa665-81ea-0e06-8560-e95b061d8155.png)

特に事業会社において、

「**それはできません**」
「**今月は入りません**」

というコミュニケーションが飛び交います。

これは本当に出来ないケースも多く含んでいますが、場合によっては保守的なコミュニケーションに起因して発言されているケースもあります。

考えてみてください。エンジニアはスペシャリストです。エンジニアに相談する側は多くの場合、非エンジニアです。

例えば、あなたが所有している車が故障したとして、ディーラーに修理に持っていきます。
ディーラーのエンジニアが

「**これは部品の取り寄せなども必要なので、2週間修理に掛かります**」


と言われて、どう思いますか？
特別に車の構造に詳しい人でない限り、ディーラーの言葉を鵜呑みにするしかないですよね。

エンジニアの言う「できない」という言葉は同じ重みを持ちます。

言われる側からすると、できないと言われた言葉をそのまま持ち帰るしか出来ないことが多いです。
なぜならエンジニアの脳内の見積もりに対して議論する術を持っていないから。

#だからこそ。できっこないをやらなくちゃ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242212/91848927-e313-53e2-6992-84e873494fe8.png)

エンジニアは魔法使いではありません。物理的に不可能なことを可能にする必要はありません。

ただ、エンジニアが発信する「**できない**」という魔法は、とてつもない破壊力を持っています。
その破壊力は多くのビジネスの可能性を破壊します。

もし、あなたが言う「**できない**」が少しでも保守的な要素をはらんでいるのだとしたら、出来る方法を一緒にビジネスサイドと考えてみてください。

冒頭で出てきたブランコの絵のように、本当に顧客が望むものはもっと違うところあり、エンジニアだったらもっと簡単に解決できるものも存在します。

抽象度の高い依頼で今後の工数の増大を恐れるなら、それをしっかりと説明し、ジャッジポイントを設けて見積もりと納期に調整余力を持たせて依頼元と握ってください。

「**難しいけど出来る方法を一緒に考えたい**」

と言ってみてください。 その瞬間に周囲の全ては仲間になり、課題はみんなのものになり、プロジェクトはチームの宝になります。

そういったコミュニケーションをするエンジニアは、自然と周囲の信頼と信望を集め、結果としてその人が発信する言葉は多くの熱量を持ち周囲を巻き込み、結果として苦難が待ち構えていたとしても、その苦難は最終工程のエンジニアが時間を捧げるべき苦難でなく、チーム／プロジェクトの共通の課題になるでしょう。

「**できっこないをやらなくちゃ**」

スペシャリストであるエンジニアこそ、誰よりも柔軟に出来る方法を考える人でありたいと思っています。
NCMBのFlutter SDKを使ってニュースアプリを作る（その1：画面の仕様とSDKの初期化）

NCMBでは公式SDKとしてSwift/Objective-C/Java/Unity/JavaScript SDKを用意しています。また、それ以外にもコミュニティSDKとして、非公式ながらFlutter/React Native/Google Apps Script/C#/Ruby/Python/PHPなど幅広い言語向けにSDKが開発されています。

今回はコミュニティSDKの一つ、Flutter SDKを使ってニュースアプリを作ってみます。まず画面の仕様とSDKの初期化について解説します。

[ncmb | Dart Package](https://pub.dev/packages/ncmb)

## 完成版のコード

作成したデモアプリのコードは[NCMBMania/flutter_news: Flutter SDKを使ったニュースアプリデモです。](https://github.com/NCMBMania/flutter_news)にアップロードしてあります。

## ベースを作成する

`flutter create` コマンドでベースを生成します。

```
flutter create news_app
```

## lib/main.dartについて

今回のコードはすべて main.dart に記述しています。クラスは次の5つです。

- MyApp extends StatelessWidget
- MyHomePage extends StatefulWidget
- _MyHomePageState extends State<MyHomePage>
- MyDetailPage extends StatefulWidget
- _MyDetailPageState extends State<MyDetailPage>

### MyApp

アプリ全体の設定を行っています。

### MyHomePage

_MyHomePageStateを呼び出しています。

### _MyHomePageState

![localhost_53010_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/3c5a7f48-1598-02cb-5fb0-3b0c0e2068b5.png)


NCMBに保存されているニュース記事を取得して、一覧表示しています。お気に入りの記事だけを表示する機能もあります。

### MyDetailPage

記事一覧をタップした際に呼ばれます。_MyDetailPageStateを呼び出しています。

### _MyDetailPageState

![localhost_53010_ (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e69c36a0-18c7-7295-c187-cdffcd8b6bed.png)


記事の詳細を表示します。HTMLをレンダリングして本文を表示しています。

## NCMB SDKのインストール

NCMB SDKは `flutter pub add ncmb` でインストールできます。

```
flutter pub add ncmb
```

記事執筆時点での最新版は `2.2.0` です。

## NCMB SDKの初期化

まず main.dart にてNCMB SDKをインポートします。

```dart
import 'package:ncmb/ncmb.dart';
```

main関数にて初期化します。アプリケーションキーとクライアントキーは `assets/.env` 内に記述しています。今回はdotenvライブラリ `flutter_dotenv` を使っています。

```dart
Future main() async {
  await dotenv.load(fileName: '.env');
  var applicationKey =
      dotenv.get('APPLICATION_KEY', fallback: 'No application key found.');
  var clientKey = dotenv.get('CLIENT_KEY', fallback: 'No client key found.');
  NCMB(applicationKey, clientKey);
  runApp(const MyApp());
}
```

これでNCMB SDKの利用準備が整います。

## 匿名認証の利用準備

![FireShot_Capture_328_-_ニフクラ_mobile_backend_-_console_mbaas_nifcloud_com_20211210105942_-_328.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/291bfb1e-6687-1679-940e-014b4364d0c7.jpeg)
FireShot_Capture_328_-_ニフクラ_mobile_backend_-_console_mbaas_nifcloud_com_20211210105942_-_328.jpg

匿名認証はスマートフォンアプリ内部でUUIDを生成し、それを使って認証を行います。IDやパスワードを入力することなく、認証機能が利用できます。匿名認証はまず、NCMBの管理画面で有効にしなければなりません。


## まとめ

今回は各画面の説明とNCMB SDKの初期化までを行いました。次回は[ニュース記事の取得と表示](https://qiita.com/goofmint/items/2f0de85c4f59d1785473)を行います。
[Salesforce]ログインフローからファイルをアップロードできるか？

#やりたかったこと
ユーザが初回ログイン時に書類(免許証とか)をUploadするよう強制したい。
ログインフローを使えばできそうだが、果たしてそんなことはできるのか？

#結論
コツ(下記)が必要だが、できる。

#コツ

#####前提情報

ログインフローを実行中のセッションは、ログインフロー以外の通信ができない制限付きセッションとなっている。
そして、ファイルアップロードはログインフロー以外の通信と判定されてしまうため、以下の方法で普通に実装してもブロックされ、エラーとなってしまう。
- 案1: ログインフローにフローを選び、標準の「ファイルのアップロード」コンポーネントを画面に配置する
- 案2: ログインフローにフローを選び、LWCで作ったファイルアップロード用コンポーネントを画面に配置する
- 案3: ログインフローにVisualforceを選び、ファイルアップロード用Visualforceを指定する

#####ではどうすればエラーを回避できるか？
対応として、Visualforceの場合のみ呼び出せる制限解除用メソッド[Auth.SessionManagement.finishLoginFlow()](https://help.salesforce.com/s/articleView?id=sf.security_login_flow_visualforce.htm&language=ja&r=https%3A%2F%2Fwww.google.com%2F&type=5)をファイルアップロードに先立って呼び出すことでファイルアップロードが可能となる。

なお、Winter '22時点では、このメソッドをVisualforceではなくLWCで使うと以下のエラーが出る。そのため、案3をベースにVisualforceのコードを改良して利用することになる。

```
Class.Auth.SessionManagement.finishLoginFlow: line 79, column 1
Class.AuthController.finishLoginFlow: line 5, column 1
01:45:48.0 (5193470)|FATAL_ERROR|System.NullPointerException: Attempt to de-reference a null object
```

#注意点
Auth.SessionManagement.finishLoginFlow()を呼んだ時点でログインフローは完了したものとみなされる。
よって、ログインフローでカスタムのMFAを実行する場合は、ファイルアップロードの前にその処理を持ってくる必要がある。
また、ファイルアップロードを終える前にホーム画面等のURLを直打ちされてしまうとブロックされずに普通に開ける。ここは制約として考える必要がある。

#ソース
・ログインフロー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573815/d84960b1-d9ff-6a7c-d2f3-42948f8ccbc8.png)

````xml:FileUploader.page
<apex:page controller="FileUploaderController" showHeader="false" applyHtmlTag="true" applyBodyTag="false">
    <head>
        <apex:slds />
    </head>
    <body class="slds-scope" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
        <apex:messages id="msg"/>
        <apex:form id="theForm">
            <apex:inputFile value="{!file}" />
            <apex:commandbutton action="{!upload}" value="Upload"/>
        </apex:form>
    </body>
</apex:page>
````

````apex:FileUploaderController.cls
public class FileUploaderController {

    public FileUploaderController () {
        //ポイント：コンストラクタでfinishLoginFlowを実行すること！！
        Auth.SessionManagement.finishLoginFlow();
    }
    
    public blob file { get; set; }

    public PageReference upload() {
        //ContentVersionをInsert
        ContentVersion v = new ContentVersion();
        v.versionData = file;
        v.title = 'testing upload';
        v.pathOnClient ='/somepath.txt';
        insert v;
        //ホーム画面に遷移
        return new PageReference('/cs/s');
    }
}
````

#動作

まずは普通にログインする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573815/bb6a4058-72e7-1796-f67b-87d4ce36391a.png)

ログインフローが起動し、Visualforceが表示された。 ここでファイルを選択してUploadボタンを押す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573815/0b366f97-4ec1-1ffb-145e-0bc08e87cb4f.png)

無事トップ画面に遷移した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573815/1d843d72-2dd9-d0c9-7ade-de9f1afcafd8.png)

ファイルタブにちゃんとUploadされた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573815/33dd94a2-1c5a-aafb-9139-ef89f3a467b9.png)
気になるRustのユースケースを調査してみた

株式会社エイチームフィナジーの[Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ateam-finergy)13日目の記事です。
@murajun1978 が担当します。


# はじめに
最近、普段使っているライブラリがRustで書き直す！みたいな事例が多くなってきた気がします。
なので、どんなところでRustが使われているのか調べてみることにしました。
ほんの一部ですが、Rustを本腰入れて学ぶモチベーションになればいいな:sunglasses:

↓ 公式サイトの事例

https://www.rust-lang.org/ja/production

## Cloudflare Workers
https://blog.cloudflare.com/workers-rust-sdk/

100％RustでCloudflare Workersを作成することができるようになっています。
wranglerを使って簡単に開発、デプロイできます。

https://github.com/cloudflare/wrangler

initialize時にtypeにrustを指定することで、Rustのworkerを作成できます。
こんな感じ

```sh
wrangler init <worker-name> --type=rust
```

## Yew
https://github.com/yewstack/yew

JSXライクなRust製Webフレームワークです。
WebAssemblyでマルチスレッドで実行可能で、既存のアプリケーションに組み込むことが可能です。
[Examples](https://github.com/yewstack/yew/tree/master/examples)のコードを見たけど、普段Reactを使っている自分には違和感はそんなに感じなかったですね。

## コマンドラインツール
### exa
https://github.com/ogham/exa

`ls`コマンドに相当するツールです

gitのステータスを表示できたり、オプションがが豊富です

### Command line apps in Rust

Rustでコマンドラインツールを作成するドキュメントもありました。

https://rust-cli.github.io/book/index.html

## Prisma
Prismaもv2からRustで書き直されています。

Prismaに関しては、ちょっと面白い記事が公開されていたので紹介します。
こちらです。

https://www.prisma.io/blog/vscode-extension-prisma-rust-webassembly

PrismaのCoreエンジンはRustで作られています。
そのCoreエンジンをOSごとにコンパイル、ダウンロードして利用してたらしいです。
でも、バイナリをダウンロードするって、セキュリティ的に問題があります。

そこで、このCoreエンジンをWebAssemblyにコンパイルして利用するように設計を変更したみたいです。
なるほど、VSCodeはnode.jsで動いてるので、WebAssemblyを拡張機能に差し込むことが可能です。

CoreエンジンをWebAssemblyにコンパイルすることで、OSごとのコンパイルが不要になったとのこと。

普段、フロントエンドとバックエンド開発に携わっているので、この記事を読んだときになるほど！:zap:ってなりました。

おもしろそうだったので、実際に試してみました:point_down:

```rs:hello_world.rs
pub mod words {
  pub fn hello_word() -> &'static str {
    return "Hello World!";
  }
}
```

```rs:main.rs
mod hello_world;

pub use crate::hello_world::words;

fn main() {
  println!("{}", words::hello_word());
}
```

```rs:lib.rs
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;
pub use crate::hello_world::words;

#[wasm_bindgen]
extern {
  pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn say_hello() {
    alert(&format!("{}", words::hello_word()));
}
```

`Hello World!`という文字列を返す単純な関数ですが、mainとlibで同じ関数を使うことができます。
あとは、このlib.rsをwebassemblyにビルドして、JavaScriptから呼び出すだけです。

# まとめ
ひとまず、自分が興味のある分野だけPickUpしてみました。
Rustは組み込み系ってイメージがありましたが、今回の調査ですごく身近に感じることができました。

個人的にはPrismaのユースケースがRustを学ぶモチベーションになるかなと思いました :thinking: 
バックエンド、フロントエンド(WebAssembly)で利用できるのは嬉しい人が多いかと。
あとは、Cloudflare Workersで重たい処理をエッジサーバで処理できるのも魅力的ですね。

Cloudflare + Rustの可能性は無限大かもしれない。。。

[Rustの公式サイト](https://www.rust-lang.org/ja)は日本語翻訳もされていますし、Rustと仲良くなれるようにがんばってみます！
npm install時にnode-sassでcommand failed

## 前提

数年前にiOSエンジニアをやっていたのでネイティブ系はなんとなく分かるが、
フロントエンドは何となく改修は出来るが、ちょっとよく~~分かってない~~わかろうとしてない系のバックエンドエンジニア。

## 現象

npmインストールすると下記エラーが出る。
node-sassの確認で失敗してるっぽい。

```shell
$ npm install

〜省略〜

npm ERR! code 1
npm ERR! path /Users/mitsuaki1229/Documents/01_REX/wos-front/node_modules/node-sass
npm ERR! command failed
npm ERR! command sh -c node scripts/build.js
npm ERR! Building: /Users/mitsuaki1229/.nodebrew/node/v16.5.0/bin/node /Users/mitsuaki1229/Documents/01_REX/wos-front/node_modules/node-gyp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
npm ERR!   c++ '-DNODE_GYP_MODULE_NAME=libsass' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_GLIBCXX_USE_CXX11_ABI=1' '-D_DARWIN_USE_64_BIT_INODE=1' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DLIBSASS_VERSION="3.5.4"' -I/Users/mitsuaki1229/.node-gyp/16.5.0/include/node -I/Users/mitsuaki1229/.node-gyp/16.5.0/src -I/Users/mitsuaki1229/.node-gyp/16.5.0/deps/openssl/config -I/Users/mitsuaki1229/.node-gyp/16.5.0/deps/openssl/openssl/include -I/Users/mitsuaki1229/.node-gyp/16.5.0/deps/uv/include -I/Users/mitsuaki1229/.node-gyp/16.5.0/deps/zlib -I/Users/mitsuaki1229/.node-gyp/16.5.0/deps/v8/include -I../src/libsass/include  -O3 -gdwarf-2 -mmacosx-version-min=10.7 -arch x86_64 -Wall -Wendif-labels -W -Wno-unused-parameter -std=c++11 -stdlib=libc++ -fno-strict-aliasing -MMD -MF ./Release/.deps/Release/obj.target/libsass/src/libsass/src/ast.o.d.raw   -c -o Release/obj.target/libsass/src/libsass/src/ast.o ../src/libsass/src/ast.cpp

〜省略〜

```

## 原因

サイトを見ると、node-sassのバージョンに対応しているnodeのバージョンは決まってる様子。

https://www.npmjs.com/package/node-sass


```shell
$ grep node-sass package.json
    "node-sass": "^4.13.1",
    "node-sass-glob-importer": "^5.3.1",
```

```shell
$ node -v
v16.5.0
```

`4.13.1`の場合、Node 13にする必要がありそう。

## 対応

バージョン管理出来そうなrenv的なツールがあったので入れ替え。

```shell
$ brew uninstall --force node
$ brew install nodebrew
```

nodeをv13.14.0に切り替え。

```shell
$ nodebrew ls-remote | grep v13 | tr -s ' ' '\n' | tail -n 1
v13.14.0
$ nodebrew install v13.14.0
```

```shell
$ nodebrew list
v13.14.0
v16.5.0

current: v16.5.0
```

```shell
$ nodebrew use v13.14.0
use v13.14.0
```

```shell
$ node -v
v13.14.0
```

## 確認

問題なく取得出来る事を確認。

```shell
$ npm install

〜省略〜
```

## その他

フロントエンドはなんとなく苦手意識が強い。
ネイティブとかFlutterとかは割と好きなので読めるんだが、食わず嫌い的なもんなのかな。
Ruby on Rails 備忘録

https://prog-8.com/

##新規プロジェクト
* 入力したアプリケーション名と同名のフォルダが作成され、その中に開発に必要なフォルダやファイルが用意されます。

```
rails new tweet_app
```
##サーバーの起動
*　開発中のアプリケーションをブラウザで表示するためには、サーバーというものを起動する必要があります。サーバーの起動は、「rails server」というコマンドを実行するだけで完了です。
サーバーを起動した後、ブラウザで「localhost:3000」というURLにアクセスすると、右図の初期画面が表示されるようになります。

```
rails server
```
##ビュー /トップページの作成

* 新しいWebページが自動で作られ、「localhost:3000/home/top」というURLにアクセスできるようになります。

```
rails generate controller home top
```
|ポイント|説明|備考|
|----|----|----|
|top.html.erb|ディレクトリ：app/views/home||
|erb|≒HTML||

##コントローラー
###アクション
「rails generate controller home top」を実行したとき、
「home_controller.rb」というコントローラのファイルが作成され、ファイルの中に「topメソッド」が追加されます。
コントローラ内のメソッドを「アクション」と呼びます。

###アクションの役割
コントローラ内のアクションは、ブラウザに返すビューをviewsフォルダの中から見つけ出す役割を担っています。
具体的には、アクションは、コントローラと同じ名前のビューフォルダから、アクションと同じ名前のHTMLファイルを探してブラウザに返します。

```home_controller.rb
class HomeController < ApplicationController
  def top
  end
end
```
* homeコントローラーのtopアクションに対応するHTMLファイルを探す。

##ルーティング
* Rails内ではコントローラを経由してビューを返していますが、ブラウザとコントローラを繋ぐ役割を担うのがルーティングです。
ページが表示されるまでに、ルーティング→コントローラ→ビューという順で処理が行われている
* ルーティングは、送信されたURLに対して「どのコントローラの、
どのアクション」で処理するかを決める「対応表」のことです。
ブラウザでURLを入力すると、ルーティングがURLを見て、適切なコントローラのアクションを呼び出します。

|URL|コントローラー|アクション|
|----|----|----|
|home/top|home|top|

* ルーティングは「config/routes.rb」に定義され,
「get "URL" => "コントローラー名#アクション名"」という文法で
書かれます。
これによって、ブラウザから「localhost:3000/home/top」というURLが
送信されたときに、homeコントローラーのtopアクションで処理されるようになります。

```routes.rb
Rails.application.routes.draw do
  get "home/top" => "home#top"
end
```
|ポイント|説明|備考|
|----|----|----|
|routes.rb|ディレクトリ：config/routes.rb||
【Jbuilder】うまくJSONを返せないときに確かめること

##その1：名前一致しているか
ファイル名やルーティングが一致してないとエラーが出たり、JSON表示できない。
特に2回くらい```複数形s```忘れしてるのでもう忘れない。

```routes.rb
namespace :api, format: 'json' do
  namespace :v1 do
    resources :post_infos, only: [:index]
  end
end
```

```app/views/api/v1/post_infos/index.json.jbuilder
json.result 'success'
json.post_infos do
  json.date @post_infos[:date]
  json.genre @post_infos[:genre]
  json.like_count @post_infos[:like_count]
end
```

##その2：APIアプリケーションコントローラーを継承しているか

```app/controllers/api/v1/post_infos_controller.rb
class Api::V1::PostInfosController < Api::ApplicationController
  def index
    ...
  end
end
```
AWS Lambda+SNSでSMSを送付してみた！ ~社内で大不評の【誰得？】シリーズ~

[パーソンリンク アドベントカレンダー](https://qiita.com/advent-calendar/2021/personlink)13日目です！🎉

# 内容
SMSを送付する際にめんどくさいなーと思ったので備忘録がてら書いてみました。
AWS Lambda と SNSでSMSを送付するサンプルコードです。

利用言語やバージョンライブラリはこんな感じ。
Python 3.8
boto3

権限は適宜作ってくださいなー

コードはこちら

```python:lambda_function.py
import boto3

def lambda_handler(event, context):
    client = boto3.client('sns')
    response = client.publish(
        PhoneNumber='+8190XXXXXXXX', #E.164形式電話番号 +81(日本国コード)から始めて、日本国内の電話番号の頭の0を取った内容
        Message='Sample Message', #送付メッセージ内容
        MessageAttributes={
            'AWS.SNS.SMS.SMSType': {
                'DataType': 'String',
                # 'StringValue': 'Transactional' #信頼性を重視する場合はこちらを選択
                'StringValue': 'Promotional' #コストを重視する場合はこちらを選択
            },
            'AWS.SNS.SMS.SenderID': {
                'DataType': 'String',
                'StringValue': 'PLTEST' #表示名で、誰からとかの名称を設定可能
            }
        }
    )
    return response
```


実際に送付した際のキャプチャ
![IMG_7658.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230393/352096a2-a9eb-17a3-c356-49219c2cb59b.png)



# なにこのシリーズ
社内で不評なたまに呟く「誰の得になるの！？」シリーズです。

さようならー
iOSの Auto-Renewable Subscription(自動更新サブスクリプション)の値上げ対応は簡単だけど不安になる

# はじめに
本記事は [with Advent Calendar 2021](https://qiita.com/advent-calendar/2021/with) 16日目の記事です。

こんにちは [with](https://with.is/)で iOSエンジニアをしている増田です。

以前、弊社の with iOSアプリにて Auto-Renewable Subscription(自動更新サブスクリプション)の値上げを行いました。
実際対応は簡単ですが、細かい情報が探し辛く想定と違う仕様があったりもして不安になる事が多かったので、いろいろ書いておきます。

所感としては、良い意味でも悪い意味でも:apple:神が全ていい感じにやってくれるので、やらねばならない事が全然無い一方、やれる事も無い。
正しく課金商品の実装が出来ていれば、何も問題は起こりません。

[実際の手順は既にわかりやすい記事を書いてくれている方がいる](https://qiita.com/kirihata/items/d194db14fdb8d9ddb7a6#ios編)ので、本記事では不安になる点や気をつけたほうがいい点についての備忘録のような内容になります。


# 対応した内容
- 1ヶ月プランの価格を 3400円 -> 3900円に値上げ
- 価格変更の際に、現在契約中のユーザはそのまま価格を据え置きする
  - ただし、現在の契約を一旦停止した後で、再契約した場合は新価格にする

## 実際やった事
- iTunesConnectの管理画面上で、価格変更の予約を設定する
- 以上！

# 知っておきたい事
- [自動更新サブスクリプション > 価格の管理](https://developer.apple.com/jp/app-store/subscriptions/#managing-prices-for-existing-subscribers)
- [AppStoreConnectヘルプ > 自動更新サブスクリプションの価格を管理する](https://help.apple.com/app-store-connect/#/devc9870599e)



## 既存ユーザに対する値上げ方法の種類
現在契約中のユーザに対する値上げの方法は２種類の手段から選ぶ事が出来ます。

- 次回の自動更新支払い時から値上げする。この場合、ユーザに Appleからメールと Pushで通知され、同意があれば契約継続されるが、応答が無かったり同意がなければ契約継続は打ち切られる
- 現在契約中のユーザは自動更新を継続する限りそのまま価格を据え置きする。一度自動更新をやめた場合、次回からの課金時には新価格が適用される

この度、withでは後者の方法を採用しました。

# 気をつけた方が良い点

## 事前に価格変更の動作テストをする事は出来ない
以下、:apple: の人に質問した時の返事
>App 内課金の購入が問題なくできるかは Sandbox でテストが可能となっておりますが、価格変更の動作確認に関しましては Sandbox または TestFlgith などをご利用いただき、テストを行う事もいたし兼ねます旨、何卒ご理解賜りますよう、お願いいたします。

withではリリースしないテスト環境用のアプリを別途 AppStoreConnect上に登録してあるので、こちらで価格変更の設定をする事で試せる・・・？ と思いきや、審査承認済みでない課金商品は`再設定`する事は可能ですが`価格変更`は出来ませんでした😇

単純に課金商品を別価格で登録しなおす事で、課金ボタン上等の提示価格が想定と違うものになっていないかのテストは可能なので、せめてもの確認として行っておきましょう。


## 変更日の予約が出来るが・・・

### 正確な価格変更時刻は決まっておらず、ユーザ個別で異なる
予約出来るのは日付だけで、何時何分という時刻は設定出来ません。
実際の結果としては、当日朝には価格が変わっているアカウントもあったし、16時頃にやっと変わったアカウントもありました。
さらに課金履歴のログを見てもらった所、前日の23時から新価格で契約されたものもあった模様です。

つまり、ユーザに価格変更の正確な時刻を事前にアナウンスはする事は出来ませんし、予定日よりも早く変わってしまう可能性もあります。
ユーザに不利益を与えない事を最優先するのなら、AppStoreConnect上での価格変更設定日を実際の予定日の次の日としておく選択も有り得ると思います。

### 今日を価格変更日には出来ない
あくまで予約という事で、未来の日付を予約日として設定する事は出来ますが、価格を変えたい当日に価格を変更するように設定することは出来ません。


## `保存`を押さないと予約が完了しない
これは本当はあえて書くような内容ではないのかもしれないですが、実際に私が嵌ったので書いておきます😅
価格変更の予約の操作を終えると、`価格変更`の欄にそれが追加されるので、それを見て予約が出来たと思ってしまうかもしれませんが、実際は右上の`保存`ボタンを押さないと完了しません。
![スクリーンショット_2021-12-01_19_12_32.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39844/60ce0bdc-1905-b844-ae5d-024bb8d18af0.jpeg)

設定後は実際に登録されているのかを確実に確認する為に、AppStoreConnectのトップから
`マイ App` > `対象のアプリ` > `App内課金` > `対象の課金商品` > `サブスクリプション価格の変更を計画` と開き直し、予定した価格変更が登録されているのかを確認しておきましょう。

私は価格変更日の直前に登録されていない事に気づいて非常に焦りました😇

# 事前に確認出来る事
上記のような状況により、事前に確認出来そうな対応は以下のようなものに限られます。

- 価格に関するものでアプリのリソースに、画像・テキスト等でハードコードされたものが無いことを確認
- 価格に関するもので自社APIから取得した値で、価格等を表示していないことを確認
- テスト環境用の別アプリがあるのなら、そちらで課金商品を別価格で登録しなおして、課金ボタン上等の提示価格が想定と違うものになっていないことを確認
- AppStoreConnect上で予約した価格変更の設定がちゃんと登録されている事を確認

つまる所、課金商品の実装を行う上で当然やっているべき事が正しく出来ていれば良いという事ですね。
MySQLのMetadata Lockで困るケースと解決策的なもの

この記事は、[Wano Group Advent Calendar 2021](https://qiita.com/advent-calendar/2021/wano-group) の記事です。

## MySQL の Metadata Lockとは？

その前に`Metadata`って何?という話ですが、Metadata自体の説明はMySQLのドキュメントを探せませんでした。
まぁ、普通に考えて、information_schemaに入っている、tableとかcolumnとかtriggerとかの情報かと思います。

Metadata Lockというのは、Metadataを変更する際にかかるロックと思っておけばいいでしょう。

## どんなときに問題になるか？

端的に言うと、Metadata Lockでselectが待たされれるということが問題となります。
待たされる条件は、書いてしまえばすごいシンプルです。

1. 誰かがtransactionを発行している最中に
1. transaction内で使わているtableに`alter table`を行うと
1. クエリキャッシュが効いていない`select`は、Metadata lock を待つ

短いtransaction中であれば、問題にならないケースも多いでしょう(扱ってるアプリケーションによると思いますが)。
なので、問題となる具体的な条件は、下記になるかなと思います。

1. バッチ処理等で長いトランザクションをはっている
1. トランザクションの中で使われている(selectでも)テーブルは、他の処理からもよく使われている

1のトランザクションが、2がアプリケーションとして待てる時間以上の長さのトランザクションであれば、問題となります。

要は、

長めのトランザクションを発行している最中にalterを行うと、長めのトランザクションが終わるまで、alterは待たされるし、そのalterが終わるまで、他のselectが待たされるという問題が出る可能性があります。

## 具体的な再現方法

下記のような手順を行うと再現できます。

1. (接続1) begin
2. (接続1) 対象テーブル(a)をselect
3. (接続2) alter table(a) ... 待たされる(Waiting for table metadata lock)
4. (接続3) 対象テーブル(a)をselect(クエリキャッシュの効かないselect) ... 待たされる(Waiting for table metadata lock)
5. (接続1) commit
6. (接続2) alter成功
7. (接続3) selectの結果が返る

## どうやったら避けられるか？

起こる条件はわかるのですが、これを避けるためにはどうしたら良いのか？

下記で、トランザクションで行われていることはわかるのですが、その前に別のテーブルがselectされているかもしれないので、あまり使えません。

```sql
select trx_mysql_thread_id,command,state,info 
  from informatio_schema.INNODB_TRX t 
  join PROCESSLIST p on p.id = t.trx_mysql_thread_id;
```
他にも使えるものはあるかもしれませんが、ちょっと探しきれませんでした。

ただ、Web系のシステムによっては、下記で十分なケースもあるかなと思います。

Web系のシステムであれば、長いトランザクションがあるのは、基本的には、バッチ処理とかデーモン化されている処理とかだと思います。もしかしたら、管理画面の処理とかもあるかもしれません。
ですが、一般ユーザーのWebブラウザからの処理であれば、1秒もかかるような処理はないはずかと思います。

なので、バッチ処理やデーモンを一時的に止めてから行う、管理画面を使わないように周知してから行う、というようなことでも大方避けられるのではないかと思います。

もちろん携わっているアプリケーションしだいですので、0.1秒も止められないみたいなシステムだともっと別の方法が必要になるかもしれません。

## 終わり

もうちょっと明確に問題を避けられる(というか、確認できる)方法があれば良かったんですが、時間の関係もあり、そこまで調べきれませんでした。ちょっとでも助けになれば幸いです。
Xcode Version 13.1の info.plistの場所

# 事のきっかけ

アクセス権限を要求する際に必要ということを知り、何処にあるのかと色々探していた。

# 場所

従来までなら、info.plistというファイルになっていたのが、少し前にinfoタブで見れるようになった。
しかし、13.1ではそのタブが存在しない。
じゃぁどこにあるかと探すと、Buils Settingsに埋もれててた。気づけんよAppleさん(´・ω・)

![スクリーンショット 2021-12-10 10.41.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149437/bcbcf253-c386-135d-1612-838449ff43b2.png)
Javaを使用してPDF内の指定されたテキストを検索して置換する方法

##Javaを使用してPDF内の指定されたテキストを検索して置換する方法

##背景
PDFファイルの内容は簡単に変更されない、エクスポートしやすい、互換性の問題も存在していませんので、データの保存などにふさわしいです。でもPDFドキュメント内の特定のテキストを置き換えたい場合もよくあります。この記事では、Javaプログラムを介したPDF内の指定されたテキストコンテンツのバッチ置換を紹介します。

##プログラム環境の下準備。

コードコンパイルツール：IntelliJ IDEA
Jdkバージョン：1.8.0
Jarパッケージ：**[free Spire.Pdf.jar 3.9.0](https://www.e-iceblue.com/Download/pdf-for-java-free.html)**

本記事では無料版のPDF Jarを使用しました、また、JDKバージョンの上位バージョンを使用することをお勧めします。
jarファイルがインポートされた後、Spire.PDFによって提供されるインターフェースとメソッドを呼び出してPDFを操作できます。次のインポート結果を参照してください。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/366ac42c-88d2-46ce-2523-070305dcab1c.png)
**注：jarパッケージは手動でダウンロードできます。ダウンロード後、ファイルを解凍し、libフォルダー内のSpire.Pdf.jarファイルをJavaプログラムにインポートします。**

##Javaコード
```
import com.spire.pdf.*;
import com.spire.pdf.general.find.PdfTextFind;
import com.spire.pdf.general.find.PdfTextFindCollection;
import com.spire.pdf.graphics.PdfBrushes;
import com.spire.pdf.graphics.PdfRGBColor;
import com.spire.pdf.graphics.PdfSolidBrush;
import com.spire.pdf.graphics.PdfTrueTypeFont;

import java.awt.*;
import java.awt.geom.Rectangle2D;

public class FindAndReplaceText {
    public static void main(String[] args) {
        //PDFドキュメントをロードする
        PdfDocument pdf = new PdfDocument();
        pdf.loadFromFile("C:\\Users\\Administrator\\Desktop\\Java.pdf");

        //ドキュメントの各ページをトラバースする
        for (int i = 0; i < pdf.getPages().getCount(); i++)
        {
            //すべてのページを取得する
            PdfPageBase page = pdf.getPages().get(i);

            //指定されたテキストを検索する
            PdfTextFindCollection textFindCollection;
            textFindCollection = page.findText("Java",false);

            //ブラシ、フォントを作成する
            PdfSolidBrush brush1 = new PdfSolidBrush(new PdfRGBColor(Color.red));
            PdfTrueTypeFont font1= new PdfTrueTypeFont(new Font("Yu Mincho",Font.PLAIN,9),true);

            //元のテキストを新しいテキスト文字に置き換える
            Rectangle2D rec;
            for(PdfTextFind find: textFindCollection.getFinds())
            {
                rec = find.getBounds();
                page.getCanvas().drawRectangle(PdfBrushes.getWhite(), rec);
                page.getCanvas().drawString("C#", font1, brush1, rec);
            }

        }
        //ドキュメントを保存する
        pdf.saveToFile("FindAndReplaceText.pdf");
        pdf.close();
    }
}
```
**テキスト置換前後の結果の比較**
![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/67ce2542-d222-cffb-a5a3-6118d16fdad0.png)
今回の記事はここまでです、最後まで読んでいただきありがとうございます。
オレオレゲームエンジン作ってた時の話

[「Applibot Advent Calendar 2021」](https://qiita.com/advent-calendar/2021/applibot) 10日目の記事になります。
株式会社Applibotサーバサイドエンジニアのsana37です。

# この記事を書いた背景
学生の頃に勢いだけで自作したゲームエンジンがあるのですが、多分もう使われることがないので供養させてください。

# 作ったもの
3Dオブジェクトの衝突判定や物理演算をc++で実装しました。
3D空間の描画にはQtとOpenGLを使っています。

実際に動かしてみた様子が[こちら](https://youtu.be/yHlamilXWac)です。
（少し挙動が不安定なので物体がずっとプルプルしています）

一応githubにもソースコードあげてます。
かなりの自己満足コードなのでちょっと恥ずかしいですが気が向いたら見てみてください。
https://github.com/sana37/PeculiarEngine

# どうやって作ったの？
イチから全部説明すると長くなってしまいそうなので、衝突判定のアルゴリズムについてのみ軽くお話ししようと思います。

## 衝突判定

主に、
1. オブジェクトを内包する球体同士の衝突判定
2. ポリゴンと頂点の衝突判定
3. 辺と辺の衝突判定

の流れで衝突判定を行います。

#### 1. オブジェクトを内包する球体同士の衝突判定
少ない計算量で、オブジェクト同士が確実に衝突していないことを検証できます。

球体同士が重なる場合、オブジェクト同士が衝突している可能性があります。
![スクリーンショット 2021-12-09 19.28.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267360/3d8c6ae3-a005-425d-ed8d-34800705489d.png)

逆に重ならない場合は、オブジェクト同士が確実に衝突していないことになります。
![スクリーンショット 2021-12-09 20.51.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267360/50f401eb-9a09-f90d-d7fc-c980bd6ed40f.png)

もし球体同士が衝突していなければ後続の衝突判定をスキップできるので、計算量の削減につながります。

#### 2. ポリゴンと頂点の衝突判定
オブジェクトAのポリゴンとオブジェクトBの頂点で衝突判定を行います。

![スクリーンショット 2021-12-09 20.56.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267360/d66163ce-7f80-17f0-67f7-52f6fda5cb28.png)

オブジェクトBの頂点がオブジェクトAのポリゴンを突き刺しているかどうかを調べるイメージです。

#### 3. 辺と辺の衝突判定
各オブジェクトの辺が互いを貫いているかどうかを判定します。

![スクリーンショット 2021-12-09 21.04.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267360/9f2f4e05-f5ca-b1f9-79bd-bc253aaed20c.png)


以上の流れでオブジェクト同士の衝突を判定することが可能です。


# 振り返ってみて

”それっぽく動いてるように見える”ところまで持っていくのは結構大変だった記憶がありますが、自分が実装した法則でものが動き出すのが見られた時は感動しました。

# 最後に
本記事ではオレオレゲームエンジンの一部仕組みについて簡単にお話しさせていただきました。

以上、[「Applibot Advent Calendar 2021」](https://qiita.com/advent-calendar/2021/applibot)10日目の記事でした。
neosでlogix その20

# 概要

niosでlogixやってみる。
共有フォルダ作ってみた。

# 写真

![pub.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18104/3d8128ad-901d-3670-e690-af64cb86e890.jpeg)


# 手順

- インベントリーを開く。
- プラスボタンを押し、フォルダを作る。
- メールアイコンを押す。
- セッションに、フォルダーがスポーンされる。
- それをグラブして、tでコンテキストメニューを開く。
- 「リンクのアドレスをコピー」をクリックする。

# 成果物

```
neosrec:///U-ohisama/R-0ee1ec21-db19-425b-ba63-9a699caff48e

```



以上。
custom settingの使用例（custom setting的使用）

homeタブの下で、custom settingを検索する。custom settingを新規して、typeはlistに設定します。（もし設定できない場合、権限の設定は必要です）
在setup -》custom setting里面新规一个custom setting，type设置成list(如果没法作成，需要事先打开权限的设定)


下記の例を参照してください

```
 @AuraEnabled(cacheable=false)
    public static void insertPop(){
        system.debug('insertpop   '+UserInfo.getUserId());
        LpsPopList__c pop = LpsPopList__c.getValues(UserInfo.getUserId());
        if(pop == null){
            LpsPopList__c myCustomSetting = new LpsPopList__c();
            myCustomSetting.Name = UserInfo.getUserId();
            //myCustomSetting.openFlag__c = true;
            insert myCustomSetting;
        }
        system.debug('insertpop2');
    }

    @AuraEnabled(cacheable=false)
    public static Boolean checkPop(){
        LpsPopList__c pop = LpsPopList__c.getValues(UserInfo.getUserId());
        if(pop != null && pop.Name == UserInfo.getUserId()){
            return true;
        }else{
            return false;
        }
    }

    @AuraEnabled(cacheable=false)
    public static void deletePop(){
        LpsPopList__c cs = LpsPopList__c.getValues(UserInfo.getUserId());
        if(cs != null) {
            delete cs;
        }
    }
```
業界未経験のプログラミング初心者が実務で自動化スクリプトを作る際にぶつかった壁

# はじめに
### 概要
この記事では、学習コンテンツでPythonの基礎を勉強しただけの超絶プログラミング初心者である筆者が、勢いだけで飛び込んだゲームQA業界で、実際に使う自動化スクリプトを作る際にぶつかった壁について、また、その壁の乗り越え方について振り返っていきたいと思います。

### 自己紹介
・前職では携帯ショップ勤務（プログラミングなんてかけらも触ったことはなかった）
・プログラミングに興味を持ったのはお客様との何気ない会話から
・重度のゲーマー（酷いときは週2で新しいゲームを購入）
・プログラミングへの興味とゲーマーとしての興味が合体した結果、Pythonの基礎を勉強後、勢いでゲームQA業界に転職、自動化エンジニアへ

# 実際に使うスクリプトを作る際にぶつかった壁
### 何をどう作ればいいか分からない
インターネット上には様々なPythonの学習コンテンツが存在しており、その殆どがプログラミング初心者でも理解できるように分かりやすく作られています。

しかし実務では、用意された題材も、使う関数の指示も、行き詰った時の解説も存在しません。

なので、テストケースから自動化できる項目、出来ない項目を選別し、どうやったらその項目を確認できるスクリプトを作成することが出来るか、自ら考えながら作る必要がありました。

### なんのエラーなのか分からない
学習コンテンツを利用している際に発生するエラーというのは、作成するスクリプトが初歩的な内容で、なおかつ短いものであるという事もあり、発生が想定しやすいエラーであることが多いです。

なので初心者向けに「よくあるエラー」としてブログ等で詳しくまとめてくれている方が多く、エラーが発生した際にネットで調べれば簡単に対処が可能でした。

しかし、実際にスクリプトを作る際に発生するエラーは「よくあるエラー」ではない場合である事が多く、ネットに情報が少ない為、その発生原因を突き止めるのにかなりの時間が必要であることが多かったです。

# 壁を乗り越える為に
### まずは先輩に相談する！！
もちろん、分からなかったらすぐに相談するのではなく、自分で打破出来るか考えてみる必要があります。
しかし、初心者である以上限界があります。その時には先輩に相談をします。
初心者がぶつかった壁という事は、先輩もかつてぶつかって乗り越えたであろう壁であることが多いです。壁を乗り越える為の答え、あるいはヒントを貰えます。
貰えたのならば感謝をし、拝みましょう。

### いったん後回しにしてみる！！
自分で考え続けていると、思考が固まって一つの発想しか出てこなくなる場合があります。
そういったときはいったん問題を後回しにして思考をリセットしましょう。
新しい発想が生まれて、「どうしてこんな問題で詰まっていたんだろう」と、あっさりと問題を解決できることがあります。

# 最後に
学習コンテンツで勉強している際には簡単に出来ていたことも、実際にゼロからスクリプトを作る時には非常に困難であることが多く、1つ問題を解決したらさらに2つ3つ問題が出てくるような状況でした。

しかし、その分スクリプトが完成した際の達成感は大きく、自ら考え何かを作ることの楽しさというも実感できました。

まだまだ少しの壁を乗り越えただけですが、今後も壁を乗り越え続け自身のスキルを向上させ、ゲームQAの一助となれるよう、努力していきたいと思います。
.idと_id　違いについて　ruby rails ルーティングのネスト　親子関係

〇〇.idでしかうまくいかない時と〇〇_idでしかうまくいかない時ありますよね

一度は学習したんですがふと疑問に思い説明できなかったので自分の解釈で説明してみます。




まず基本は〇〇.idなど「.」を使うことで解決できます。

ではなぜ〇〇_idのような時が発生するんでしょうか？




それはルーティングのネストにあります。

ルーティングにおけるネスト（親子関係を示す）とは、コントローラーのルーティングの中に、別のコントローラーのルーティングを記述することです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274768/596e9b6c-4dbe-211e-4ce3-47c3c0061d65.png)


こうなると「 _ 」が発生してしまいます。

itemsの中にordersがいますよね、これで親がitems　子がorders になります。

なぜかというと、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2274768/a8000328-dccd-689d-9ed9-53c53306fdd4.png)
大きくできないので虫眼鏡使ってください笑




真ん中にいますよね「 _ 」が！！！こいつのせいなんです。

ここで決められているんです！

この場合　:item_id　としなければ値を取得できません。

つまりルーティングのネスト(親子関係)によって「 _ 」が生まれ

子が親のidを受け取るためには「 _ 」を使う必要があるのです。




じゃーネストなんかしないでいいじゃん




と思いますよね。
ルーティングをネストさせる一番の理由は、アソシエーション先のレコードのidを

paramsに追加してコントローラーに送るためです。

つまりどうアソシエーションするかで決まってしまってるんです。




偉そうにまとめましたが初めて見たような気持ちで復習してます。

間違っていたら何かこうした方がいいなど指摘してください。
【Python】リストの空の要素を消す方法

## 環境
+ Microsoft Windows 10.0.22000.318（21H2）
+ Python 3.8.12.final.0


## 方法
### 1. filter関数を使う
`list(filter(None, LIST_NAME))` でリストの空の要素を削除できる。

```python:実行例
list_a = [[1, 2], [], [3, 4]]
list_b = list(filter(None, list_a))
print(list_b)
# [[1, 2], [3, 4]]
```

### 2. 内包表記を使う
`[x for x in LIST_NAME if x != '']` でリストの空の要素を削除できる。

```python:実行例
list_a = [[1, 2], [], [3, 4]]
list_b = [x for x in list_a if x != '']
print(list_b)
# [[1, 2], [], [3, 4]]
```

## 参考
https://wp.tekapo.com/2020/04/20/python-how-to-remove-empty-element-from-list/

https://teratail.com/questions/92477
Ruby 備忘録

##コメント　コンソールログ 
```rb
#コメント
puts "hello world"
```

##変数　文字列との連結
```rb
name = "阿部寛"
puts name
#○
puts "私は#{name}です"
#× エラー
puts "私は" + name + "です"
```
|ポイント|説明|
|----|----|----|
|#{変数}|特に無し||
|""|ダブルクウォーテーションで囲う||

##配列
```rb
languages = ["ラーメン", "カレー", "寿司"]
#ラーメンを出力
puts languages[0]
```

##if
```rb
score = 73
if score > 80
  puts "優"
elsif score > 60
  puts "良"
else
  puts "不可"
end
```
ChromeDevToolsのrecorderを試してみたのと有効活用できそうなことを妄想してみた

[食べチョク](https://www.tabechoku.com/)を運営する[ビビッドガーデン Advent Calendar 2021](https://qiita.com/advent-calendar/2021/vivid-garden) の10日目の記事です。


少し前にTwitterで見かけたChromeDevToolsの新機能、
**recorder**が色々なことに有効活用できるのではと思い試してみました。


https://twitter.com/ChromeDevTools/status/1455936412646199296?s=20

## 前提
recorderは2022年1月に登場予定のGoogle Chrome 97から利用可能になるそうです。
Google Chrome 96では利用できません。
試したい場合はdev版やbeta版をDLしましょう。

## recorderで何ができるの？

- ユーザーが操作したことを記録して再現できる
- ユーザー操作のパフォーマンス測定ができる
- 記録した手順は後から編集することも可能

などになります。
詳細は[こちら](https://developer.chrome.com/docs/devtools/recorder/)をご確認ください。


## 実際に使ってみた
ChromeDevToolsで紹介されているデモの内容と少しかぶるのですが、
せっかくなら自社プロダクトで何か試してみたいよねってことで食べチョクで商品を購入する操作を記録して再現させてみました。


### Step1：記録
ChromeDevToolsを開きrecorderを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282373/da4a29c5-074b-989e-35c2-84f67bc09417.png)

recorderが開いたら `Start new recording` で操作の記録ができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282373/f7d09eb3-1bda-ee7d-ba90-ca7eb1aa3992.png)

今回操作した流れはこんな感じになります。

1. 予めブックマークに保存していた食べチョク商品詳細ページに遷移
2. `今すぐ注文` をクリック
3. `確認画面へ進む`　をクリック
4. `注文を確定する` をクリック

![qiita11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282373/0d5a05e2-e40a-7c79-c1e2-405113f3a791.gif)





最後に停止ボタンを押すと記録が完了します。
非常に簡単ですね。


### Step2：再現
それでは記録の再現をしていきましょう。
Replayを押すと。。。

![qiita30.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282373/97fd0fa7-9772-ba91-513c-0a75b75a9eac.gif)





なんということでしょうか。
全く同じ商品を簡単に買うことに成功しました。

少しの操作で全く同じ商品を購入することが可能というわけです。
かなり汎用的に活用できる予感がしますね。


## 使ってみた感想
ぽちぽちしているだけで記録できるので非常に簡単でした。
エンジニアはもちろん非エンジニアにも使って欲しい機能です。
単純な操作を記録しておけば簡単にライフハックできそうですね。


## 有効活用できそうなことを妄想する
まずテストに活用できそうだと妄想しました。
例えばテスト時に出たバグの再現を記録して開発者に共有するとかです。
あとはもしこの記録の再現をブラウザの外から動かせれば、簡易的なE2Eテストとしても活用できそうな予感がします。

他のシーンだとルーティン化している作業に活用できそうです。
出勤連絡とか多分どこの会社でもあると思うのですが、そういう操作を記録しておけば多少の負荷軽減になりそうですね。
さらに毎朝コーヒーをUberEatsで頼んでるならボタン一つでコーヒーの注文をすることも可能になるわけです。

そして記事を書いた時点では使えなかったのですがアップロードもできるようになりそうなので、
記録した操作の共有ができればさらに夢は広がる気がします。


---

といった感じでChromeDevToolsの新機能、**recorder**に興味を持っていただいた方はぜひ試してみてください。


そんな食べチョクではエンジニアを大募集しています。
recorderに興味を持ったけど食べチョクにも興味持っていただけたらまずはカジュアルに話しましょう。


https://www.wantedly.com/projects/636843#_=_
システム導入におけるユーザーとのコミュニケーションの重要性


こんにちは。GxPの村木です。
この記事は[グロースエクスパートナーズ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/gxp)の12日目です。

本日はとある情報管理システム（以下、システムと記載）をチームで客先に導入したときの話を書きます。

全く同じシステムを複数のユーザーに導入するも、システム導入後の反応は様々。
導入して良かったと言って頂けたり、不満を口にされたり、同じシステムを導入しているのになぜ導入後の反応が異なるのか。
そこにはシステムの機能面だけではない、ユーザーとのコミュニケーションが大きな要因になっているのではないかというお話になります。

# なぜシステムを導入するのか
まず始めになぜシステムを導入するのか考えた時、思い浮かぶのは

- 現行の業務をもっと効率良くしたい
- 作業ミスを減らしたい
- 運用コストを抑えたい

といったような事項ではないでしょうか。
実際、自分がシステム導入に関わったユーザーから聞こえてくる声も上記内容が多かったです。
つまりユーザーがシステムを導入する目的としては現行業務の不満点・問題点を解決したいから、と言えます。

これらの不満点・問題点を解決するために開発したシステムを導入する。
この流れではあればユーザーのニーズにあったシステムの導入になるので、これは満足頂けるであろうことが期待出来ます。


# いざ、システム導入！が…
ユーザーのニーズにあったシステムの導入であるはずなのですが、冒頭でも記載した通り導入後の反応は様々でした。
満足頂けたケースは良いとして、ここでは不満を持たれるケースとはどういった場合だったのかを書いていきます。

----
それはある客先にてシステム導入後、何日間か客先で立会をし問い合わせ対応などしていた時のこと。
たまたまお客様の立ち話が耳に入ってきました。

お客様A「このシステム、使いづらいよね」
お客様B「〇〇も出来ないし☓☓も微妙だし」
お客様C「システム使い始めてから疲れる…」
※実際の内容とは異なりますが、だいたいこんな感じの会話でした。

----

どういうことだ？というのが率直な感想でした。
ニーズにあったシステムの導入であるはずだし、導入前にはシステムの説明会も複数回実施している。
不明点などの質疑応答も終えシステム運用が始まったのになぜ不満を持たれてしまっているのか…。

後日、関係者にヒアリングし情報収集した結果、ユーザーがいくつかの不満な点を挙げた中で一番大きかったのは

- 現行業務の問題点がユーザーが期待していた程改善されなかった。

でした。

結果としてユーザーが思っていたニーズとシステム導入側が思い描いていたニーズが異なっていた、ということになります。

# ユーザがシステム導入後に期待していた結果とは？

ではユーザーが期待していた問題点の改善はどのようなものだったのか。
今回のケースでは以下のようなものでした。

- いままで手動でやっていた為ミスが多かった作業が、システムを導入すれば自動で出来ると思っていた
- 紙で運用していた部分がすべてペーパーレスになると思っていた

などヒアリングした結果、システムで出来ることとユーザーの期待値との乖離が大きい部分について特に不満と感じている様子でした。

事前に操作説明やデモンストレーションを行っているものの、ユーザーはそれだけでシステムの全てを理解することは難しく、現在の業務とシステムとの関連が曖昧な部分ほど、都合良い方向に捉えてしまう（きっと出来るんだろう）傾向にあることも感じました。


# コミュニケーションを密に現場の業務を理解しシステムを導入する
システムで出来ることとユーザーの期待値との乖離が問題なのであれば、解決するにはどうすれば良かったか。

それは現行業務を運用フロー、問題点を把握しているユーザーとシステムの内容を把握している自分たちとで
一緒にシステム導入後の運用や問題点を考えることがより良い解決方法ではないかと考えます。

そしてこれを実現するためにはコミュニケーションを密に取りユーザーのやりたいこととシステムが出来ることを確認しながら運用を考えることが必要になります。
システムが何でも出来るわけではないことを理解頂き、システムに出来ないものは100%要望を叶えられないとしても、少しでも要望に近い代案がないかを模索しユーザーと共にシステム導入後の運用を完成させるような導入が出来れば、不満の少ない、満足度の高い結果に繋がることになります。


# 最後に
ユーザーはこんな機能も欲しい、あんな機能も欲しいなどシステムに多くを求めがちになります。
システムで全て対応出来ることがベストとは思わず、システムの運用をユーザーと共に組み立て問題点を解決することが重要であり、システム導入とはただ機能説明をし収めるのではなく、ユーザーとのコミュニケーションを取り一緒に導入後の運用を完成させることが重要ではないかと考えます。

またシステム開発にてユーザーからの要望をどのように形としていくかは
以下の記事が非常に参考になりますのでご紹介しておきます。

・システム開発で曖昧な要望を形にしていく方法
https://arclamp.hatenablog.com/entry/2021/12/09/230643
anyenvでrbenv自体をアップデートして、最新のRubyのバージョンをインストールする


rubyやpython, nodeなどのプログラミング言語の異なるversionを管理できる[anyenv](https://github.com/anyenv/anyenv)
このanyenvを利用してrubyのバージョン管理をするrbenvを使ってrubyをインストールする

このときインストール可能なリストの中にRubyの最新版が見つからない場合に、rbenv自体を更新して最新版のrubyを取得する方法です。


### rbenvの更新コマンド

```sh
anyenv update rbenv
```


### 更新後のリスト

```sh
rbenv install --list
2.6.9
2.7.5
3.0.3
jruby-9.3.2.0
mruby-3.0.0
rbx-5.0
truffleruby-21.3.0
truffleruby+graalvm-21.3.0

Only the latest stable releases for each Ruby implementation are shown.
Use 'rbenv install --list-all / -L' to show all local versions.
```

定期的にググるので自分でまとめておきました。
【Unity】古いバージョンの AndroidビルドがNDKが無くて出来なかったら

:::note info
本記事で使ったUnityのバージョン
Unity 2021.1.27f1 Mac版
Unity 2021.2.5f1 Mac版
Windows版や他のバージョンでは検証していません。
:::
Unity 2021.1.27f1でAndroidビルドしようとしたところ、エラーで出来ませんでした。
![スクリーンショット 2021-12-10 9.26.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1772485/c30bf5d7-8e98-0a47-0659-22310b342695.png)
SDKマネージャーでコンポーネントが見つからないらしいのですが、Unity > Preferences > External Tools で確認するとNDKで警告が出ているようです。
![スクリーンショット 2021-12-10 10.01.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1772485/6dd95eec-2409-0c3e-4223-b2be71080210.png)
実際にFinderで確認すると、確かにNDKがありません。
![スクリーンショット 2021-12-10 9.28.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1772485/cf5fe45e-bbb6-6f8a-a40c-7c62d91bd2b8.png)
そこで最新版のUnity 2021.2.5f1ではどうなっているかというと。
![スクリーンショット 2021-12-10 9.29.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1772485/4474e887-1bde-a0a7-a57e-00e9b79d162e.png)
こっちにはNDKがあるようです。
なので Unity 2021.2.5f1 > Preferences > External Tools でCopy Pathボタンを押してパスをコピーし、2021.1.27f1側のパスを指定し直します。
2021.2.5f1の方がSDKが新しく、対象SDKもレベル30なので、OpenJDK、SDK、NDK、Tools/gradleと全てのコンポーネントを更新しましょう。
これでビルド出来ました。
モータードライバを利用する


**マイクロステップ**
https://www.microstep.co.jp/
4軸ワンボード低振動型5相ドライバー DC24V　MC-S0514-4L
https://www.microstep.co.jp/stepping/mc_s0514_4l.html
取扱説明書
https://www.microstep.co.jp/file/mc_s0514_4l/JMC-S0514-4L.pdf
5相ステッピングモーター 2K-S544
https://www.microstep.co.jp/motor.html

**コスモテック**
http://www.cosmotechs.co.jp/
pukiwiki dumpプラグインからの添付ファイルを復元する方法

#Pukiwiki dumpからファイルが取り出せない
dumpプラグインの「エンコードされているページ名をディレクトリ階層つきのファイルにデコード」を使うと，ファイルサイズが数KBしかなく壊れてしまうという現象が発生。

この記事を参考にcygwin経由でperlで復元してみました。

https://anatofuz.hatenablog.com/entry/2020/11/05/131412

```perl:conv
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Encode qw/decode encode_utf8/;
use File::Basename;

while (my $file = <*>) {
  chomp $file;
  $file =~ s/\*//;
  my $ufile = $file;
  $ufile =~s/([0-9A-F]{2})/chr(hex($1))/ge;
  $ufile = decode('utf-8', $ufile);      #pukiwikiのエンコードに合わせる(euc版ならeuc-jp)
  $ufile = encode_utf8 $ufile;
  $ufile =~ s/_/\//g;                    #_区切りをスラッシュ区切りに変更
  my $ufiledir = dirname($ufile);

  print "mkdir -p $ufiledir\n";
  print "mv $file $ufile\n";

  system("mkdir", "-p", $ufiledir);
  system("mv", $file, $ufile);
}
```
dumpしたattachフォルダの中に保存して

```shell
$ conv
```
でOK
Go 言語の reflect : 最初の一歩

reflect package に苦手意識がある人は多いのでは？と思います。
自分自身もあまり使ってなかったのですが、 encoding/json 的な package として excel に入出力するものを作った時にある程度理解が進みました。
ここでは、最低限のコードにより reflect package を少しだけ説明します。


# reflect package とは

> Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types. The typical use is to take a value with static type interface{} and extract its dynamic type information by calling TypeOf, which returns a Type.
>
> A call to ValueOf returns a Value representing the run-time data. Zero takes a Type and returns a Value representing a zero value for that type. 
> https://pkg.go.dev/reflect@go1.17.5

reflect は実行時の reflection のための package です。
雑な理解としては `interface{}` に対しての型情報や値の読み書きをすることができる package です。
詳細は godoc を参照してください。

# サンプルコードと出力結果

この記事では、以下のコードを例とします。

```go
package main

import (
	"fmt"
	"reflect"
)

type XXX struct {
	Fullname string `json:"name"`
	Number   int    `json:"number,omitempty"`
}

func main() {
	x := XXX{Fullname: "aaa", Number: 123}

	rt := reflect.TypeOf(x)
	rv := reflect.ValueOf(x)

	fmt.Printf("%s{\n", rt.Name())
	for i := 0; i < rv.Type().NumField(); i++ {
		k := rt.Field(i)
		v := rv.Field(i)

		switch v.Kind() {
		case reflect.String:
			fmt.Printf("    %s: string(%q), // tag %q\n", k.Name, v.String(), k.Tag.Get(`json`))
		case reflect.Int:
			fmt.Printf("    %s: int(%d), // tag %q\n", k.Name, v.Int(), k.Tag.Get(`json`))
		default:
		}
	}
	fmt.Printf("}\n")
}
```

出力結果は以下になります。

```
XXX{
    Fullname: string("aaa"), // tag "name"
    Number: int(123), // tag "number,omitempty"
}
```

# 構造体の各 Field の型情報 `reflect.Type`

上記コードの `rt.Field(i)` の戻り値は、構造体の各 Field の情報です。
すなわち `var x XXX`に対して `x.Name` とか `x.Number` とかで参照される Field 自体の情報です。
型の情報なので、ここで取り出した `reflect.Type` には Tag 情報が含まれていて `k.Tag.Get()` で取り出すことができます。

上記のコードの TypeOf に関する部分はおおよそ以下のようなものです。

```go
	x := XXX{Fullname: "aaa", Number: 123}
	rt := reflect.TypeOf(x)
	k := rt.Field(0) // Fullname string `json:"name"` に対応する部分
	fmt.Printf("%q : tag %q\n", k.Name, k.Tag.Get(`json`))
	// -> "Fullname" : tag "name" と表示される
```

# 構造体の各 Field の値情報 `reflect.Value`

上記コードの `rv.Field(i)` の戻り値は、構造体の各 Field で保持される値の情報です。
`reflect.Value` からは例えば `x.Fullname` でアクセス可能な `aaa` という値や、その型 (string 型) を取り出すことができます。

```go
	x := XXX{Fullname: "aaa", Number: 123}
	rv := reflect.ValueOf(x)
	v := rv.Field(0) // x.Fullname に対応する部分
	fmt.Printf("%s : %q\n", v.Kind().String(), v.String())
	// -> string : "aaa" と表示される

```

# 何が出来るようになるか

上記の情報を元に以下のようなコードが動作する `encoding/excel` 的な package の初期実装を作りました。

```go
package main

import (
	"io/ioutil"

	"github.com/sago35/go-eexcel"
)

type XXX struct {
	Name   string `eexcel:"name"`
	Number int    `eexcel:"number"`
}

func main() {
	x := XXX{Name: "aaa", Number: 123}
	b, _ := eexcel.Marshal(x)
	ioutil.WriteFile("out.xlsx", b, 0644)
	// -> out.xlsx に出力される
}
```

上記コードで出力されるファイルは以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63649/ff20b123-0b79-3c9c-595b-3bc35092799d.png)

コードは以下にあります。

https://github.com/sago35/go-eexcel


# まとめ

reflect を使ったコード例と実装例を示しました。
Go において reflect は避けて通ることもできますが、触ってみるととても強力で面白いです。
reflect package を使ったことが無い人は是非使ってみてください。
Laravel7｜1分ごとに古いデータを削除するスケジュール

投稿する記事が毎日数件ある状況。

このままだと増えていく一方。

古いデータはもういらないから、それを定期的に削除するスケジューラーを作ってみた。


1.コマンドファイル作成
2.カーネルファイル修正
3.クーロンタブ修正

この手順で解説


## 1.コマンドファイル作成

まずはコマンド実行ファイルの作成

```
$php artisan make:command delOldPost
```

\App\Console\Commandsに
delOldPost.phpができるので修正

コマンド名をつけてやりたい処理を描かく

```delOldPost.php
protected $signature = 'command:deloldpost';//コマンド名

public function handle()
    {
       //処理内容（一番古いものを削除する）
       $post=Post::oldest()->first()->delete();
    }
```

### 手動実行してみる
```
$php artisan command:deloldpost
```
ちゃんとデータが消えていればOK

## 2.カーネルファイル修正
\App\Console\Kernelファイル修正

動かすコマンドファイルを指定

```Kernel.php
protected $commands =
   \App\Console\Commands\delOldPost::class
];

protected function schedule(Schedule $schedule)
{
    //コマンド名と実行タイミング
    $schedule->command('command:deloldpost')->everyMinute();
}

```

## 3.クーロンタブ修正
```
cronrab -e
* * * * * cd アプリまでのパス && php artisan schedule:run >> /dev/null 2>&1
```

:wで保存
:qで抜ける
crontab -lで確認
自作キーボードでスペースバーを押しても元に戻らない不具合が出た話

[遊舎工房](https://yushakobo.jp/)にて販売されている[Choco60](https://keys.recompile.net/projects/choco60/)というキットを利用してキーボードを自作したところ、スペースバーを押しても元に戻らない(押したままになる)不具合が発生した際の原因と対応について書きます。

## 原因
スタビライザーの差し込み不良でした。
![stabilizer.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/645470/fdfdab56-ea09-0e1c-44d0-06891650bebf.jpeg)
## 対応
すでに組み上がってしまっており、今更バラす気にもならないので、差込部を上から押してみたもののビクともせず…
そのまま力を入れすぎると基盤が壊れる可能性もあり、どうしたものかと…  

押して駄目なら引いてみようということで、100均で購入した精密ではない精密ドライバーの一番小さいマイナスドライバーを穴に入れて引いてみたところ…直りました！

部品を組んだときは、きちんと目視確認をするべきという、ごくありふれた教訓を得たミスでした。