OmniAICreator/Qiita-1.07M huggingface dataset:
pygameでグラフ描画

日付：2021/06
分類：Python
サンプル

```py
import pygame
from pygame.locals import *
import sys
import math
import random

x0=50
y0=50
wx=500
wy=500

def event(): # =>void
    # イベント処理
    for event in pygame.event.get():#イベントキューからキーボードやマウスの動きを取得
        if event.type == QUIT:      # 閉じるボタンが押されたら終了
            pygame.quit()          # Pygameの終了(ないと終われない)
            sys.exit()             #終了（ないとエラーで終了することになる）

def rgen(m):
    ans=[]
    for i in range(m):
        r=random.randint(1,10)
        ans.append(r)
    #print(ans)
    return ans

# def hist(ar):#ar:int[]=>ans:{i:number : count:number}
#     ans={}
#     for a in ar:
#         if a in ans:
#             ans[a]+=1
#         else:
#             ans[a]=1
#     #print(ans)
#     return ans#ans:{i:number : count:number}

def hist2(ar):#ar:int[]
    ans=[0 for i in range(11) ]
    for a in ar:
        ans[a]+=1
    #print(ans)
    return ans#ans:int[]

#結果のグラフ表示 
def graph(screen, font, ar):
    ymax=max(ar)
    xmax=len(ar)+1
    #print("xmax=",xmax," ymax=",ymax)
    lbl=[str(a) for a in ar]
    #print("lbl=",lbl)
    pygame.draw.rect(screen, (255,0,0), Rect(0,0,500,500), 1)
    for i,a in enumerate(ar):
        my = a
        mx = i
        #1x1化
        sx = mx/xmax
        sy = my/ymax
        gx = sx * 300 + 100
        gy = -sy * 300+ 400
        gy2 = sy * 300
        #pygame.draw.line(screen, Infect, (gx, gy), (gx+4, y0 + wy), 4)
        pygame.draw.rect(screen, (255,0,0), Rect(gx,gy,10,gy2), 2)
        #y座標
        tx = lbl[i][:4]
        txt = font.render(tx, True, (0,0,0))   # 描画する文字列の設定
        screen.blit(txt, [gx, gy-30])# 文字列の表示位置
        #x
        tx = str(i)
        txt = font.render(tx, True, (0,0,0))   # 描画する文字列の設定
        screen.blit(txt, [gx, 405])# 文字列の表示位置

#ポアソン分布計算
def calc_p():
    lmd=5
    k=10
    ar=[]
    for k in range(12):
      ps=(lmd**k)*math.e**(-lmd)/math.factorial(k)
      ar.append(ps)
    return ar

#組み合わせ
def comb(n, r):
    return math.factorial(n) // (math.factorial(n - r) * math.factorial(r))

#2こう分布計算
def calc_2(p,n):
    ar=[]
    for k in range(n+1):
      ans = comb(n,k)*(p**k)*((1-p)**(n-k))
      ar.append(ans)
    return ar

def main():
    pygame.init()                                 # Pygameの初期化
    font = pygame.font.Font(None, 25) 
    screen = pygame.display.set_mode((800, 600))  # 800*600の画面

    #ans = calc_p()#ポアソン
    #ans = calc_2(0.3,5)#2こう分布
    ans  = hist2(rgen(100)) #ランダム

    while (1):
        screen.fill((255,255,255))  # 画面を白に
        graph(screen, font, ans)
        pygame.display.update()     # 画面更新
        event()
if __name__ == "__main__":
    main()
```
kind_of? (is_a?) と === の違い

# 検証内容
```ruby
RUBY_VERSION
# => "2.7.1"

# Integerの祖先
Integer.ancestors
# => [Integer, Numeric, Comparable, Object, Kernel, BasicObject]
```

## kind_of? (is_a?)
`is_a?` はエイリアス

### 定義

レシーバーが以下のいずれかに当てはまる場合、trueを返す。

- 引数のクラスあるいはそのサブクラスのインスタンス
- 引数のモジュールをインクルードしたクラスあるいはそのサブクラスのインスタンス

つまりレシーバーはインスタンス、引数はクラスあるいはモジュールである必要がある。


### 検証

```ruby
Numeric.new.kind_of?(Integer)
# => false
Numeric.new.kind_of?(Numeric)
# => true
Numeric.new.kind_of?(Object)
# => true

# インスタンスは引数に取れない
Numeric.new.kind_of?(1)
# TypeError (class or module required)
```

### 補足

```ruby
# レシーバーがクラスの場合
Numeric.kind_of?(Integer)
# => false
Numeric.kind_of?(Numeric)
# => false
Numeric.kind_of?(Object)
# => true
```

なぜこのようなことが起こるかというと、全てのクラスはClassクラスのインスタンスであるため。
以下のようにClassクラスはObjectクラスを継承しているため、true が返る。

```ruby
Numeric.class
# => Class
Class.ancestors
# => [Class, Module, Object, Kernel, BasicObject]
```

## ===
```ruby
# クラス同士を比較した場合全て false
Numeric === Integer
# => false
Numeric === Numeric
# => false
Numeric === Object
# => false

# インスタンス同士の比較も全て false
1 === Numeric.new
# => false
Numeric.new === Numeric.new
# => false
Object.new === Numeric.new
# => false

# 比較対象のインスタンスが比較元のクラスと同じかその子孫だと true
Numeric === 1
# => true
Numeric === Numeric.new
# => true
Numeric === Object.new
# => false

# 逆は成り立たない
1 === Numeric
# => false
Numeric.new === Numeric
# => false
Object.new === Numeric
# => false
```

# おまけ
## Boolean を判定したいとき
```ruby
# ruby に Boolean クラスは存在しないので怒られる
Boolean === true
# NameError (uninitialized constant Boolean)

val == true || val == false
# => true
```
gem も公開されている
https://rubygems.org/gems/boolean/versions/1.0.1

# 参考
https://docs.ruby-lang.org/ja/2.7.0/method/Object/i/kind_of=3f.html
https://docs.ruby-lang.org/ja/2.7.0/method/Module/i/=3d=3d=3d.html
https://qiita.com/fukumone/items/76e4f04a5188f4abb061
https://docs.ruby-lang.org/ja/2.7.0/class/Class.html
「Announcing TypeScript 4.4 Beta」を読む

https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-beta/

が出ていたので、読んでみて個人的に気になったものをメモしようかと思います。

---

### Control Flow Analysis of Aliased Conditions

```ts
function f(x: string | number | boolean) {
    const isString = typeof x === "string";
    const isNumber = typeof x === "number";
    const isStringOrNumber = isString || isNumber;
    if (isStringOrNumber) {
        x;  // Type of 'x' is 'string | number'.
    }
    else {
        x;  // Type of 'x' is 'boolean'.
    }
}
```

if文の外で型チェックしてもそれが引き継がれるようになる。素晴らしい、実際これに遭遇したことある気がする。

> Note that there’s a cutoff – TypeScript doesn’t go arbitrarily deep when checking these conditions, but its analysis is deep enough for most checks.

なるほど。

### Symbol and Template String Pattern Index Signatures

```ts
interface Colors {
    [sym: symbol]: number;
}
```

これを使うケースはなかったと思う。大抵はunique symbolを使うので。でも、これまで表現できなかったのができるようになったのは良い。

```ts
interface OptionsWithDataProps extends Options {
    // Permit any property starting with 'data-'.
    [optName: `data-${string}`]: unknown;
}
```

これは良い。stringにせざるを得なかったところが厳しくできる。

### Defaulting to the `unknown` Type in Catch Variables (`--useUnknownInCatchVariables`)

> TypeScript 4.0 allowed users to specify an explicit type annotation of `unknown`

すでにできてはいたのか。opt-inだけど。

> This flag is enabled under the `--strict` family of options

おお、それはよい。既存コードでいくつかエラーになりそう。思い当たるところいくつかあり。

### Exact Optional Property Types (`--exactOptionalPropertyTypes`)

```ts
interface Person {
    name: string,
    age?: number;
}

// With 'exactOptionalPropertyTypes' on:
const p: Person = {
    name: "Daniel",
    age: undefined, // Error! undefined isn't a number
};
```

なぜそういう型になっているのか不思議に思っていたものだ。直るのか。素晴らしい。

> This flag is not part of the `--strict` family

あら、残念。

### `tsc --help` Updates and Improvements

:raised_hands:

### Performance Improvements

:rocket: 

### Spelling Suggestions for JavaScript

最近は、JSあまり使わないので関係ない。

### Inlay Hints

VSCode使ってないので恩恵得られない。

# まとめると

4.4にすると、catchはunknownになる。今から `: unknown` つけておけば早期発見できるかな。exactOptionalPropertyTypesは自分で有効にしないといけない。
Python tkinterのウィジットの配置でハマった話

# はじめに
・Pythonでデスクトップアプリを開発する際に、tkinterライブラリを使用すると思います。

・ウィジットの配置にはpack,grid,placeがありますが、今回は細かいレイアウト調整が可能なplaceを使用しました。

・しかし、ウィジットが思い通りに配置できなかったのでメモを残します。

# やりたいこと
・Frameを入れ子して、親フレームの相対位置に子フレームを配置したい。

## NGコード

```python:test.py
import tkinter as tk

class Main(tk.Frame):

    def __init__(self, master=None):
        super().__init__(master)
        self.master.geometry("1000x1000") 
        self.master.title("サンプル")
        self.create_widgets()

    def create_widgets(self):
        self.frame1 = tk.Frame(self.master, height=500, width=500, bg="red").place(x=50,y=50)
        self.frame2 = tk.Frame(self.frame1, height=300, width=300, bg="green").place(x=50,y=50)
        self.frame3 = tk.Frame(self.frame2, height=100, width=100, bg="blue").place(x=50,y=50)

app = Main()
app.pack()
app.mainloop()


```

・NGコード実行結果
![NG画像.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1055916/0eaf0938-6caf-fda0-d023-19e0af1622fe.png)
・全フレームが絶対位置(50,50)の位置に配置されてしまった。


## 修正コード

```python:test.py
import tkinter as tk

class Main(tk.Frame):

    def __init__(self, master=None):
        super().__init__(master)
        self.master.geometry("1000x1000") 
        self.master.title("サンプル")
        self.create_widgets()

    def create_widgets(self):
        self.frame1 = tk.Frame(self.master, height=500, width=500, bg="red")
        self.frame1.place(x=50,y=50)
        self.frame2 = tk.Frame(self.frame1, height=300, width=300, bg="green")
        self.frame2.place(x=50,y=50)
        self.frame3 = tk.Frame(self.frame2, height=100, width=100, bg="blue")
        self.frame3.place(x=50,y=50)

app = Main()
app.pack()
app.mainloop()
```
修正コードの実行結果
![OK画像.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1055916/5d1f67e7-3840-2e25-c77b-9fb110eb4c31.png)
・親フレームの相対位置(50,50)に子フレームを配置できた。

## 修正したところ
```python:test.py
#一行のコードでウィジットの作成と配置
#self.frame1 = tk.Frame(self.master, height=500, width=500, bg="red").place(x=50,y=50)
#ウィジットの作成と配置を分けて記載
self.frame1 = tk.Frame(self.master, height=500, width=500, bg="red")
self.frame1.place(x=50,y=50)
```

## まとめ
どういうわけか詳細は不明ですが
・tkiterのレイアウトで(pack,grid,place)を使う際は、ウィジットの作成と描画コードを分けて書く。
・1行でまとめて書くと描画はされるが、レイアウトが上手くいかない。
WordPressテーマ「Lightning」の「Powerd by ～」を削除する方法

WordPressテーマ「Lightning」のフッター下部の「Powerd by ～」を削除するには、functions.phpに以下を追記すれば可能です。
※G2、G3共通です
※第3引数の999は、優先度を示しており省略していると「10」となりますが、そのままだと「Powerd by ～」が表示されてしまうので、10よりも大きい数値にして優先度を下げることで、後から実行してreturn falseすることで「Powerd by ～」を消しています。そのため、「11」でも「Powerd by ～」は削除されます。

```php:functions.php
// Powerd by の削除
add_filter( 'lightning_footerPoweredCustom' , function(){ return; } , 999 );
```

以下のように記載しても同義です。

```php:functions.php
// Powerd by の削除
add_filter( 'lightning_footerPoweredCustom' , '__return_false' , 999 );
```
WordPressテーマ「Lightning」の「Copyright ～」をカスタマイズする方法

WordPressテーマ「Lightning」のフッター下部の「Copyright ～」をのカスタマイズをするには、functions.phpに以下のように追記すれば可能です。
※G2、G3共通です

```php:functions.php
// Copyrightのカスタマイズ
function my_lightning_footerCopyRightCustom($content) {
  return $content = '<p> &copy; ' . Date('Y') . ' 株式会社サンプル</p>';
}
add_filter('lightning_footerCopyRightCustom', 'my_lightning_footerCopyRightCustom', 10, 1);
```

上記の場合、「　&copy; 2021 株式会社サンプル　」のように表示されます。※年数は動的に変わります。
WordPressテーマ「Lightning」のフッターの列数をカスタマイズする方法


以下で入力した列数の分、フッターウィジェットエリアが増えます。
※以下は4列に設定した場合の例です。

```php:functions.php
// フッターの列数変更(1,2,3,4,6列から指定)
add_filter( 'lightning_footer_widget_area_count' , function(){ return 4; } ); // 4列の場合の設定例
```
暗号化したRedis-clusterでマルチシャードにした時のデータ取得方法

## 前提

### Redis構成

- cluster
    - 0001シャード（slot：0-8191）
        - 0001-001マスターノード
        - 0001-002スレーブノード
    - 0002シャード（slot：8192-16383）
        - 0002-001マスターノード
        - 0002-002スレーブノード

### redis-cli設定

参考サイト
[ElastiCache for Redis転送時の暗号化 (TLS)](https://docs.aws.amazon.com/ja_jp/AmazonElastiCache/latest/red-ug/in-transit-encryption.html)

redis-cli.conf

```
fips = no
setuid = root
setgid = root
pid = /var/run/stunnel.pid
debug = 7
options = NO_SSLv2
options = NO_SSLv3
[redis-cli]
  client = yes
  accept = 127.0.0.1:6379
  connect = [クラスターのエンドポイント]
```


## 事象

##### 取得出来るデータと出来ないデータがある

```
get [キー名] 
```

上記コマンドを実行するとキーに紐づくバリューが取得できるのだが、バリューが取得出来る時もあるが、出来ない時もある。
取得出来ない時の動き：Redirected to slot コメントが表示されるが、一生リダイレクトされない。

```
localhost:6379> get hoge
-> Redirected to slot [9282] located at hoge-0002-001.hoge.zfxhaf.apne1.cache.amazonaws.com:6379
```

## 原因

##### シャード0001にはアクセス出来ているが、シャード0002にはアクセスできていない

Redirected to slot の動き 

1. 6379番ポートのノードでget hogeが実行される
- Redis-clusterは hogeをアルゴリズムにかける
- 結果として9282がでる
- 9282番スロットは6379番ポートのノードでは対応していないので、Redis-clusterがクライアントの向き先を6379番ポートのノードから6380番ポートのノードに変える
- 値をセットする
- セットできたらクライアントにOKと返す

「Redirected to slot の動き 」参考サイト
[[実演]Redis Clusterの仕組みを完全に理解する](https://qiita.com/keitatata/items/44678ad472e61a4606c5)

## 対応

##### redis-cli.confファイルを修正する

変更前

```
fips = no
setuid = root
setgid = root
pid = /var/run/stunnel.pid
debug = 7
options = NO_SSLv2
options = NO_SSLv3
[redis-cli]
  client = yes
  accept = 127.0.0.1:6379
  connect = clustercfg.hoge.zfxhaf.apne1.cache.amazonaws.com:6379
```

変更後

```
fips = no
setuid = root
setgid = root
pid = /var/run/stunnel.pid
debug = 7
options = NO_SSLv2
options = NO_SSLv3
[redis-cli shard0001]
  client = yes
  accept = 127.0.0.1:6379
  connect = hoge-0002-001.hoge.zfxhaf.apne1.cache.amazonaws.com:6379
[redis-cli shard0002]
  client = yes
  accept = 127.0.0.1:6380
  connect = hoge-0002-001.hoge.zfxhaf.apne1.cache.amazonaws.com:6379
```

- 変更点
    - 接続先にクラスターエンドポイントを指定していたが、マスターノードのエンドポイントを指定する。
    - ノード毎にポートを分ける（6379、6380）

###### セキュリティグループでポート6380を許容する

stunnel再起動

```
sudo pkill stunnel
sudo stunnel /etc/stunnel/redis-cli.conf
```

redisにアクセスし、バリューを取得しようとしたがスロット範囲がシャード0002の場合、リダイレクトは出来なかった。

```
redis-cli -h localhost -p 6379 -a hogehoge -c
localhost:6379> get hoge
-> Redirected to slot [10439] located at hoge-0002-001.hoge.zfxhaf.apne1.cache.amazonaws.com:6379
```

##### 問題点：クラスターを単一のシャードではなく、複数のシャードはどのように設定すればいいのか？

同じ問題を抱えている人が[stackoverflow](https://stackoverflow.com/questions/61723563/stunnel-for-elasticchache-rediscluster-mode-enabled)いたが答えが投稿されていなかった。

### 回避策

根本解決ではないが、別シャードのデータを取得する方法はある。

###### シャード0002の値を取得したい場合は、シャード0001からexitし、シャード0002にアクセスする（接続するポートを変更する）

```
redis-cli -h localhost -p 6380 -a hogehoge -c
localhost:6380> get hoge
"piyo"
```
【キャッチアップ】heroku

### 概要
サーバなどのインフラを構築せずに、アプリを公開できるもの

### 【参考】入門的な動画
https://www.youtube.com/watch?v=fNoFbt9cznM&t=924s

https://www.youtube.com/watch?v=4BMGAnC8oBA

https://www.youtube.com/watch?v=mBERWdb8NpM
TypeScript + React FCでのforwardRefの書き方

## やりたい事

Reactの関数コンポーネントにrefを渡してあげたい。

## やり方

こういうpropsから受けとったタイトルを表示するコンポーネントがあるとします。

```react
import React, {forwardRef} from 'react';

const Title = ({title}) => {
  return (
    <div>
      <h2>{title}</h2>
    </div>
  );
};

export default Title
```

こちらのコンポーネントをforwardRef関数で囲います。

```react
import React from 'react';

const Title = forwardRef(({ title }, ref) => {
  return (
    <div>
      <h2 ref={ref}>{title}</h2>
    </div>
  )
});

export default Title
```

後は型情報を渡してあげたら完成です。 `forwaredRef<Refの型, Propsの型>` という形式で渡してあげましょう。今回の場合はh2にrefを渡しているので`HTMLHEADINGElement`を指定しています。

```react
import React, { forwardRef } from 'react';

type Props = {
  title: string
}

const Title = forwardRef<HTMLHeadingElement, Props>(({ title }, ref) => {
  return (
    <div>
      <h2 ref={ref}>{title}</h2>
    </div>
  )
});

export default Title
```

最後に実際にrefが渡せているのか確認してみましょう。

```react
import React, { useRef } from 'react';
import Title from "~/components/Title"

const Main = () => {
  const ref = useRef(null)
  console.log(ref)

  return (
    <div>
      <Title title='タイトルだよー' ref={headingRef} />
    </div>
  );
};

export default Main;
```

コンソールを確認するとTitleコンポーネント内のh2要素が取得出来ているのが確認できると思います。

## 参考した記事

https://ja.reactjs.org/docs/forwarding-refs.html
Google Colaboratory 上で pyLDAvis が動かない問題の対処

現在 ( 2021/07/02 ) の Colab 上では pyLDAvis の最新バージョンが動かないのが問題のようです。

よって、最新ではないバージョンをインストールする必要があります。

Colab のセル内で以下を実行することでうまくいきました。

```
!pip install pyLDAvis==2.1.2
```
PostgreSQL で INSERT したら permission denied for sequence になったときすること

PostgreSQL に慣れている人だと当たり前かもしれないが，
最初は戸惑ったので，自分用に書いておく

## 前提条件

- PostgreSQL を使用
- 当該テーブルに serial 型の列を設定
    - サロゲートキーを作る場合など

## 結論

serial の列に紐づいている Sequence において，テーブルに INSERT するユーザに USAGE を GRANT する

## 理屈

- PostgreSQL には， Sequence (シーケンス)という概念がある
    - 整数を順番に重複なく出してくれる
    - シーケンスにも，ロールごとに権限を設定できる
- サロゲートキーのような重複のない連番を serial で作ると，デフォルト値としてシーケンスを利用するようになるらしい
    - 故に， INSERT 時にシーケンスを利用する権限が必要

シーケンスについての詳細は他記事に丸投げ．

https://qiita.com/5zm/items/da82cec73e097d2a97d0


## 例

PostgreSQL 12.6 で検証

- test_db : 作業中のデータベース名
- testuser : INSERT しようとしているロール名

```sql:テーブル作成
CREATE TABLE student (
	student_id serial NOT NULL,
	name varchar NOT NULL,
	age int4 NOT NULL,
	CONSTRAINT student_pk PRIMARY KEY (student_id)
);
```


|student_id |name  |age  |
|---|---|---|
|   |   |   |

```sql:INSERTできない場合
testdb=> INSERT INTO student(name, age) VALUES('HogeHoge', 20);
2021-07-02 11:45:53.359 JST [39624] ERROR:  permission denied for sequence student_student_id_seq
2021-07-02 11:45:53.359 JST [39624] STATEMENT:  INSERT INTO student(name, age) VALUES('HogeHoge', 20);
ERROR:  permission denied for sequence student_student_id_seq
```

```sql:対応
-- GRANT できるユーザで行う
testdb=# \ds
                  List of relations
 Schema |          Name          |   Type   |  Owner
--------+------------------------+----------+---------
 public | student_student_id_seq | sequence | hogehoge
(1 row)

testdb=# GRANT USAGE ON SEQUENCE student_student_id_seq TO testuser;
GRANT
```

```sql:対応後
testdb=> INSERT INTO student(name, age) VALUES('HogeHoge', 20);
INSERT 0 1
```
React Hook Form v7でMateril-UIのRadioGroupを使う

選択肢を数値で扱いたくてハマったのでメモ

# 環境

- react-hook-form 7.9.0
- @material-ui/core 4.11.3

# 結論

```typescript
const OneOrTwo = ({ control }: { control: Control<{ oneOrTwo : number }> }) => (
  <Controller
    name="oneOrTwo"
    control={control}
    render={({ field }) => (
      <RadioGroup
        name={field.name}
        onChange={(e) => {
          const value = parseInt(e.target.value)
          if (!isNaN(value)) {
            field.onChange(value)
          }
        }}
        value={field.value === undefined ? '' : field.value}
      >
        <FormControlLabel
          value={1}
          control={<Radio />}
          label="1"
        />
        <FormControlLabel
          value={2}
          control={<Radio />}
          label="2"
        />
      </RadioGroup>
    )}
  />
)
```

# 作業メモ

## 選択してもチェックされない？？

`field`をそのまま展開すると、ラジオボタンを選択したときにuseFormの内部では値が変化しているが、チェックが付かなかった。
`FormControlLabel.value`を文字列にしたらチェックが付くが、useFormの内部の値が文字列になっている。

```typescript:before
render={({ field }) => (<RadioGroup {...field}><FormControlLabel value={1} ...
```

```typescript:after
render={({ field }) => (<RadioGroup {...field}><FormControlLabel value="1" ...
```

## Material-UIのワーニングが出る

`A component is changing the uncontrolled value state of RadioGroup to be controlled.`と怒られる。
formとして未選択の状態を存在させたい場合は、`RadioGroup.value`の値を指定することで回避できる。

```typescript:before
render={({ field }) => (<RadioGroup {...field}> ...
```

```typescript:after
render={({ field }) => (<RadioGroup {...field} value={field.value === undefined ? '' : field.value}> ...
```

## useFormの内部で値が文字列として保持されている

`選択してもチェックされない？？`で`value="1"`としたので、当然こうなる。
v6では`valueAsNumber`を指定すれば良かったが、v7ではControllerでは使えないので、`onChange`を上書きする。（`結論`を参考）

# 参考

v7ではControllerではvalueAsNumber使えない
https://react-hook-form.com/advanced-usage#TransformandParse
画像データを MySQL に格納するとどうなるか

画像データを扱うとき、パス情報だけを MySQL に格納し、画像データ自体はファイルシステムや AWS S3 などに直接置くのが一般的です。なぜなら画像データは巨大で MySQL で扱うのには重いからです。かつてはコンピュータのリソースが貧弱でしたのでこの設計は適切だったと思うのですが、近年コンピュータは圧倒的に高性能になりました。もしかしたら現代のコンピュータなら画像データを MySQL で扱うことも出来るかもしれないと思い試してみました。

なぜ MySQL で画像データを扱いたいかというと、ひとえに ACID 特性のためです。画像データ処理もトランザクションに含めてしまうことができれば、画像データを含めて不整合のないデータを実現できます。迷子の画像データのことを考えなくて済むわけです。バックアップだって、DB ダンプ一発で全部まとめて取れるわけですし。

## スキーマ定義とテストデータ

こんな感じのテーブルを用意しました。images がパス名と画像データも格納するテーブルで、images_light が比較用にパス名だけを格納するテーブルです。

```sql
root@localhost [test]> CREATE TABLE images (
    -> id INTEGER PRIMARY KEY AUTO_INCREMENT,
    -> pathname VARCHAR(255),
    -> image LONGBLOB
    -> );
Query OK, 0 rows affected (0.28 sec)

root@localhost [test]> CREATE TABLE images_light (
    -> id INTEGER PRIMARY KEY AUTO_INCREMENT,
    -> pathname VARCHAR(255)
    -> );
Query OK, 0 rows affected (0.05 sec)
```

テストに使用する画像は定番でレナさんにご登場願おうと思ったのですが、21世紀になって20年も経過してるのにヌード画像というのもどうかという気もします。それにレナさん自身がもうこの画像は使わないで欲しいと仰ってるそうなので止めておきましょう。代わりに自分で撮った適当な写真で、私のソウルフードである[スパゲッティーのパンチョ](https://naporitanpancho.com/)のナポリタンメガ盛りにしました。ファイルサイズは 3.85MB です。

![20201112-174454.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86075/b1dabb34-57d9-be2b-76be-af6c71753080.jpeg)

テスト用のスクリプトはこんな感じで作りました。画像を100枚登録します。

```shell
$ cat gen1.pl
use strict;
use warnings;

print "INSERT INTO images (pathname, image) VALUES \n";
my $first = 1;
for(my $i = 0; $i < 100; $i++) {
  print ",\n" if(!$first);
  print "('pancho.jpg', LOAD_FILE('/var/db/mysql_secure/pancho.jpg'))";
  $first = 0;
}
print ";\n";
$ perl gen1.pl > import1.sql
```

比較用のパス名だけを格納するテスト用スクリプトも用意しました。

```shell
$ cat gen2.pl
use strict;
use warnings;

print "INSERT INTO images_light (pathname) VALUES\n";
my $first = 1;
for(my $i = 0; $i < 100; $i++) {
  print ",\n" if(!$first);
  print "('pancho.jpg')";
  $first = 0;
}
print ";\n";
$ perl gen2.pl > import2.sql
```

## 実験

それぞれ mysql コマンドで実行します。

```shell
$ /usr/bin/time mysql test -u root -pxxxxxxxx < import1.sql
mysql: [Warning] Using a password on the command line interface can be
insecure.
       83.38 real         0.02 user         0.00 sys
$ /usr/bin/time mysql test -u root -pxxxxxxxx < import2.sql
mysql: [Warning] Using a password on the command line interface can be
insecure.
        0.68 real         0.02 user         0.01 sys
```

画像1枚あたり1秒以下で格納できてますから、使い物にならないほど遅いわけではなさそうです。とはいえ、パス名だけを格納する場合に比べたら圧倒的に遅いですけれどね。

DB ファイルサイズも調べてみます。

```shell
$ sudo ls -lh /var/db/mysql/test/
total 401680
-rw-r-----  1 mysql  mysql   112K  5月 26 07:40 images_light.ibd
-rw-r-----  1 mysql  mysql   392M  5月 26 07:39 images.ibd
```

1枚あたり 3.92MB 程度ですから、ストレージ効率もめちゃめちゃ悪いわけではありませんね。

ということで、画像データを MySQL に入れるのは全くあり得ない選択肢でもないなという感想を持ちました。もちろん、システムの他の要件との兼ね合いによって最終的に採用するかどうかは変わってくるのですが。
【Rails】divse 新規登録とログインができない

## はじめに
この記事は備忘録として載せています。

新規登録とログインのフォーム画面のレイアウトを変更した際に、エラーが出てないのにも関わらず接続できない状態が続いていました。

## 環境
- rails6.1.3.2
- ruby2.7.3
- devise
- bootstrap4

## 結論

- formタグの中に、fromタグを作っていたことが原因でした。

　※フォームタグが2個ある状態


## 解決方法

【変更前】　formタグの中に、fromタグがあります

```
<%# ログイン画面 %>
<div class="sidenav">
  <div class="login-main-text">
    <h2><%= t('.sign_in') %></h2>
    <p>Login or register from here to access !</p>
  </div>
</div>
<div class="main">
  <div class="col-md-6 col-sm-12">
    <div class="login-form">
      <form> 　　<- ここ①
        <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 　　<-ここ②

          <div class="form-group">
            <i class="fas fa-envelope"></i> <%= f.label :email %>
            <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form-control', placeholder: "Email" %>
          </div>

          <div class="form-group">
            <i class="fas fa-unlock"></i> <%= f.label :password %>
            <%= f.password_field :password, autocomplete: "current-password", class: 'form-control', placeholder: "Password" %>
            <% if devise_mapping.rememberable? %>
              <div class="from-group">
                <%= f.check_box :remember_me %>
                <%= f.label :remember_me %>
              </div>
            <% end %>
          </div>

          <%= link_to "新規登録", new_registration_path(resource_name), class: 'btn btn-warning text-white' %>
          <%= f.submit t('.sign_in'), class: ' btn btn-primary' %>
      </form>
      <% end %>
    </div>
  </div>
</div>
```



【変更後】　不要なformタグを取り除きます　

```
<%# ログイン画面 %>
<div class="sidenav">
  <div class="login-main-text">
    <h2><%= t('.sign_in') %></h2>
    <p>Login or register from here to access !</p>
  </div>
</div>
<div class="main">
  <div class="col-md-6 col-sm-12">
    <div class="login-form">
        <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>

          <div class="form-group">
            <i class="fas fa-envelope"></i> <%= f.label :email %>
            <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form-control', placeholder: "Email" %>
          </div>

          <div class="form-group">
            <i class="fas fa-unlock"></i> <%= f.label :password %>
            <%= f.password_field :password, autocomplete: "current-password", class: 'form-control', placeholder: "Password" %>
            <% if devise_mapping.rememberable? %>
              <div class="from-group">
                <%= f.check_box :remember_me %>
                <%= f.label :remember_me %>
              </div>
            <% end %>
          </div>

          <%= link_to "新規登録", new_registration_path(resource_name), class: 'btn btn-warning text-white' %>
          <%= f.submit t('.sign_in'), class: ' btn btn-primary' %>
      <% end %>
    </div>
  </div>
</div>
```
今回は`<form></form>`を取り除きました。

## 最後に
これからも継続して学習を続けていきます！
【GAS】操作するスプレットシートを指定する方法

Google Apps Script(GAS)でスプレッドシートを読み込む方法には3つの方法が用意されています。今回は各々のスプレッドシートの取得メソッドについて紹介したいと思います。

#アクティブな（今開いている）スプレットシートを指定して取得する方法
今開いているアクティブなスプレットシートを読み込むために使うのが、SpreadsheetAppクラスのgetActiveSpreadsheetメソッドです。
getActiveSpreadsheet()の基本的なコードは以下の通りです。

```
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
```

getActiveSpreadsheetメソッドは、コンテナバインド型のGASスクリプト（GASファイルをスクリプトエディタから作成した場合）でのみ使用することが可能です。

そのため、GoogleドライブからGASファイルを作成するスタンドアロン型のGASスクリプトでは、getActiveSpreadsheet()メソッドは利用することができません。

#アクティブではないスプレットシートを指定して取得する方法
GASにアクティブではない別のスプレットシートを指定したい場合は、getActiveScreadsheet()は使わずに別の記述方法を用います。

openById(id)
openByUrl(url)
これら2つの記述方法があるので、1つずつ説明していきます。
##1. openById(id)
openbyId(id)は引数に指定したid(スプレッドシートキー)からスプレッドシートを読み込みます。
openById(id)の基本的なコードは以下の通りです。

```
var spreadsheet = SpreadsheetApp.openById(id);
```
スプレットシートキーとは、スプレッドシートのURLの/d/と/editに囲まれた部分です。
https://docs.google.com/spreadsheets/d/xxxxxxxxx/edit~~~

このxxxxxxxxxの部分がスプレッドシートキーで、世界中の人が利用しているGoogleスプレッドシートの中でユニーク(唯一)になっています。
openById(id)のidをxxxxxxxxxに変えると下記のようになります。

```
var spreadsheet = SpreadsheetApp.openById(“xxxxxxxxx”);
```
これでアクティブではないスプレッドシート、つまりスクリプトと直接紐付いていないスプレッドシートを指定して取得することが可能になります。
openbyIdメソッドはコンテナバインドでもスタンドアロンのどちらのスクリプトでも実行が可能です。
##2. openByUrl(url)
openbyUrl(url)は引数に指定したurlからスプレッドシートを読み込みます。
openbyIdと同様にコンテナバインドでもスタンドアロンでも利用可能なメソッドになります。
openByUrl(url)の基本的なコードは以下の通りです。

```
var spreadsheet = SpreadsheetApp.openByUrl(url);
```
このurlは、https://から始まるスプレットシートのURLを引数にしてます。
https://docs.google.com/spreadsheets/d/xxxxxxxxx/edit~~~

idの場合はxxxxxxxxxでしたが、urlなのでhttps://から最後までとなります。openByUrl(url)のurlを書き換えると下記のようになります。

```
var spreadsheet = SpreadsheetApp.openByUrl(“https://docs.google.com/spreadsheets/d/xxxxxxxxx/edit~~~”);
```

openById(id)とopenByUlr(url)を使用する際に気をつけなければいけないのが「””(ダブルクオーテーション)」をつけることです。これを忘れるとエラーで処理が実行できなくなります。

#まとめ
今回は、GASでスプレットシートを指定する3つの方法について紹介しました。

１．アクティブなワークブックを指定する(スクリプトエディタから作成した場合のみ可能)
２．スプレットシートのID(スプレッドシートキー)で指定する
３．スプレットシートのURLで指定する

個人的には複数のスプレッドシートを使うことを考慮して始めからopenById(id)を使用しています。urlだとidよりも引数の文字数が多いため、どうしてもスクリプトの可読性が下がってしまいますからね。getActiveSpreadsheet()はスプレッドシートを1つしか使わないときには便利ですので、用途によって使い分けてみてください。
VimのOperatorとtext-objectによるテキスト編集範囲指定

# Operator と text-object

テキストを選択して変更やヤンクを行う際、Operator + count + ojbect-select で動作とその範囲を指定できる。
(count は省略可能。省略時は 1 と同じ。)
各 Operator と text-object を覚えることで単語の置換や引用符の中身のコピーなどを楽に指定できる。

|Operator|意味|
|:-:|---|
|c|変更|
|d|削除|
|y|ヤンク|

|object-select|範囲<br>("a" で始まるコマンドはまとまりを WS を含めて選択)<br>("i" で始まるコマンドはまとまり内部を WS を含めずに選択 または WS のみを選択)<br>※WS:ホワイトスペース|
|:-:|---|
|aw<br>iw|**a** **W**ord, **I**nner **W**ord<br>count 単語|
|as<br>is|**a** **S**entence, **I**nner **S**entence<br>count 文|
|ap<br>ip|**a** **P**aragraph, **I**nner **P**aragraph<br>count 段落|
|a(<br>a)<br>i(<br>i)|**a** **()** block, **I**nner **()** block<br>() で囲った範囲<br>() を[], {}, <>, ", ', ` に変更した場合、それぞれに対応する範囲となる。<br>() は "b", {} は "B" でも代用可能 (ab, aB など)。|

# 使用例
## 文字列の置換
### ciw (カーソル上の単語を置換)
c (**C**hange) + iw (**I**nner **W**ord)
カーソル上の文字を削除して insert モードに移行する。

```
|：カーソル位置

[初期状態 (ノーマルモード)]
hello wo|rld!
        
[ciw 入力後 (insert モード)]
hello |!
```

### ci" ("" 内部を置換)
c (**C**hange) + i" (**I**nner quoted string)
"" 内部の文字を削除して insert モードに移行する。

```
|：カーソル位置

[初期状態 (ノーマルモード)]
print("hel|lo world!");

[ci" 入力後 (insert モード)]
print("|");
```

# 参考資料
[Vim日本語ドキュメント](https://vim-jp.org/vimdoc-ja/motion.html)
Twilioを利用してSMSテスト送信をしてみる

#はじめに
Twilioを業務で使うこととなり、いろいろと調べてSMSテスト送信までできるようになったのでそこまでの手順をまとめます。

#想定環境
私は以下の環境で試しました。
OS：Windows10
PHP：7.4.20

事前にTwilioで無料アカウントを作成しておき、SMS送信を行う電話番号を取得しておきます。
使うSIDとトークンはダッシュボードに記載のあるものを利用します。

#手順
###1.PHPを使えるようにする。
以下URLからPHPを入手して適当なフォルダに保存します。今回はC:\phpに保存しました。
https://www.php.net/downloads
php.iniはphp.ini-developmentをコピーして利用しています。
その後、pathを通してphpコマンドが使えるようにしておきます。

###2.PHPでCurlを使えるようにする。
php.iniを修正してcurlを実行できるようにします。
####変更前

```config:php.ini
;extension=curl
```

####変更後

```config:php.ini
extension=curl
```
###3.TwilioのSDKをダウンロードする。
以下URLからTwilioのSDKをダウンロードしました。
https://github.com/twilio/twilio-php/releases
composerを使ってもできるようなのですが、今回の環境的にそれが難しかったためgithubから入手しました。
これをテストコードを保存するフォルダ内に配置します。今回はTwilioフォルダをC:\appに保存しました。
###4.テストコードを書く
以下のようなコードを書きます。

```php:sendSMS.php
<?php
require __DIR__ . '/Twilio/autoload.php';
use Twilio\Rest\Client;

$sid = ＜ダッシュボードに記載のあるSID＞;
$token = ＜ダッシュボードに記載のあるToken＞;
$client = new Client($sid, $token);

$client->messages->create(
    ＜送り先電話番号＞,
    [
        'from' => ＜Twilioで取得した電話番号＞,
        'body' => ＜送りたいメッセージ＞
    ]
);
```

Twilioの公式に記載のあるコードとほぼ同じです。

###5.実行してみる
以下コマンドで実行します。

```cmd:cmd
php c:\app\sendSMS.php
```

うまくいけば指定した電話番号へSMS送信が行えるかと思います。
もし上手くいかない場合ですが、エラーメッセージにSSL certificate problemとかの記載がある場合、証明書の問題が考えられます。
その場合は以下URLに記載のある通り、証明書を指定すれば上手くいくようになるかと思います。
https://support.twilio.com/hc/en-us/articles/235279367-Twilio-PHP-helper-library-SSL-certificate-problem-on-Windows

#おわりに
証明書で引っかかるとは思っていなかったのでそこで時間がかかりましたが、それ以外は思った以上にすんなりいけました。

無料アカウントの場合、初期設定だとアカウント作成時に登録した電話番号にしかSMSを送ることができません。
そのため実際にサービス等で利用する場合はアップグレードする必要が出てきます。
無料アカウントは初期チャージ500円でそこから電話番号代が引かれた料金分だけ使えるようです。
また1通10円ほどかかりますので、使い過ぎには注意した方が良いと思います。
メールリーダーThunderbirdの件名(Subject)にゴミが混ざる

#はじめに
メールリーダーThunderbird(78.11.0/Windows10)でメールを受信したときに、ときどき件名(Subject)にゴミが混ざります。他のメールリーダーでは正常に表示されるので、どうやらThunderbirdの問題のようです。

具体的には、こんな感じで、ゴミ(&#xFFFD;)が混ざります

「学科Webメンテナ&#xFFFD;ンス(3/14)実施」
「ABCあい&#xFFFD;うえおEFG」

>  ![ThunderbirdSubject文字化け.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61370/ddbbadec-61cc-ed72-e6bd-99567c809422.jpeg)

同じSubjectでもゴミが混ざらない場合があります。それでメールのソースをみて差分を調べてみました。

補足: 本ブログの実験を試すにはLinuxにnkfとMewをインストールしてください。

#ゴミが混ざる条件(1)
日本語メールのSubjectの文字コードは多くの場合、JIS(iso-2022-jp)もしくはUTF-8です。

そして、Bエンコーディングされています。(Qエンコーディングの場合もあります。)

ゴミが混ざるのはJIS+Bエンコーディングの時によく起きるようです。少なくとも私の確認した範囲では。

どうもJIS固有の何かのようです。

#(余談)メールのSubjectのエンコーディング
すでにご存じの方は読み飛ばしてください。

例えばSubjectが「ABあいUE」のメールの場合、JISでBエンコーディングで記入されたメールのソースをみるとSubjectは次のようになってます。

> Subject: =?iso-2022-jp?B?QUIbJEIkIiQkGyhCVUU=?=

ここで「 =?iso-2022-jp?B?」がBエンコーディングの始まり指定で、「?=」が終了指定です。

なのでSubjectの本文は

> QUIbJEIkIiQkGyhCVUU=

です。上記はBASE64エンコーディングといいます。3バイトの8bitデータを4個のアスキーコードに置き換えるエンコーディングです。詳細はこちらをご覧ください。

https://ja.wikipedia.org/wiki/Base64

このBASE64をdecodeしてみますと以下のようになります。ESCはASCIIの27番です。＄は全角になってますが本当は半角です。空白は読みやすいように加筆したものです。

> A B ESC ＄ B ＄ " ＄ ＄ ESC ( B U E

ここで「ESC ＄ B」という並びはJISコードの始まりという意味です。 「ESC ( B」という並びはJISコードの終わり(ASCIIコードの始まり)という意味です。読みやすくするとこうなります。

> A B  (JIS開始) ＄ " ＄ ＄ (JIS終了) U E

次に「＄ "」はJISで「あ」です。「＄ ＄」はJISで「い」です。なのでまとめると

> A B あ い U E

になります。

#ゴミが混ざる条件(2)
さて本題に戻りまして、Subjectの文字化け発生条件ですが、無駄にJIS開始および終了が挟まると文字化けするようです。

たとえば「あいうえお」のJIS開始および終了は

> (JIS開始)あいうえお(JIS終了)

とするのがシンプルですが、次のように無駄にJIS開始および終了が挟まると文字化けするようです。

> (JIS開始)あい(JIS終了)(JIS開始)うえお(JIS終了)

試しにやってみましょう。nkf -jでJISコードに変換します。echo の -n は最後に改行を付けないという意味です。3行目のperlはBASE64 encodeです。4行目がBASE64のencode結果です。

```
$ echo -n "あい"| nkf -j > tmp.txt
$ echo -n "うえお" | nkf -j >> tmp.txt
$ cat tmp.txt | perl -MMIME::Base64 -0777 -ne 'print encode_base64($_)'
GyRCJCIkJBsoQhskQiQmJCgkKhsoQg==
```
上記の４行目のBASE64の結果の前後をBエンコーディングの開始終了を挟むと次のようになります。

> =?iso-2022-jp?B?GyRCJCIkJBsoQhskQiQmJCgkKhsoQg==?=

このSubjectでメールを送ればいいのですが、日本語対応のメールリーダーだと色々な加工が入り、そのまま送信はできません。

各種メールリーダーを確認すると、Mewだとそのまま送れます！。 MewでSubjectに上記文字列を張り付けて送信してみます。

予想通りThunderbirdで文字化けが再現しました。

> ![ThunderbirdSubject文字化け2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61370/f4d9aba7-cc66-a814-5a34-0db672aac636.jpeg)


#蛇足1
上記のような無駄なJIS開始およびJIS終了を入れるソフトの特定はできているのですが、それは本筋でないので省略します。時間があればdebugしたい。。

#蛇足2
Qエンコーディングでも試してみましょう。

https://ja.wikipedia.org/wiki/Quoted-printable

```
$ cat tmp.txt | perl -MMIME::QuotedPrint -0777 -ne 'print encode_qp($_)'
=1B$B$"$$=1B(B=1B$B$&$($*=1B(B=
```

上記で最後に現れる「=」はソフト改行なので除去してください(詳細はWikipedia参照)。Qエンコーディングの開始終了を追加すると以下のようになります。

```
=?iso-2022-jp?Q?=1B$B$"$$=1B(B=1B$B$&$($*=1B(B?=
```

これをMewでSubjectに上記文字列を張り付けて送信してみます。

すると同じ場所にゴミが混ざりました。

#蛇足3
このゴミ(&#xFFFD;)ですが、Thunderbirdで具体的には何の文字コードが割り当てられているか調べてみました。SubjectのゴミはJISのみで発生しますが、このメールにメールに返事を書くと、Subjectの文字コードがUTF-8でもゴミが温存されます。このSubjectのコードを調べると「%ef %bf %bd」のようです。これはUNICODEのU+FFFD(replacement character)のUTF-8表現のようです。

Ref: https://ja.wikipedia.org/wiki/Specials_(Unicode_block)

以上です。
LaravelのEloquentで実行SQLをプレースホルダの値を埋め込んで生SQLにして確認したい

# はじめに
こんにちは。昨日、高校の頃の友人にVBAわかると聞かれて、書いたことも見たこともないですが、相談に乗りました。意外とできるもんですね、筆者です :v:

さて今回は、私が業務で使用している便利な**古(いにしえ)のコード**を紹介します。
ぜひご活用くださいませ :smile:

# 経緯
実装している際に、**MySQL Workbench**等でDBに繋いで生SQL叩いて確認するケースが結構あります。

それをコードに落とし込む場合や、バグ等で期待しているものが表示されていな場合などの調査の際に、プログラムで実行されているSQLを確認するケースなどもあります。

その際に、生のSQLで出力して、コピペして**MySQL Workbench**等に貼り付けて実行するためには、プレースホルダの値も埋め込む必要があります。

**そんなときにこれ！**

# LaravelのEloquentで実行SQLをプレースホルダの値を埋め込んで生SQLで確認したい

これです↓

```php
$stmt->get();
$sql = preg_replace_array('/\?/', $stmt->getBindings(), $stmt->toSql());
dd($sql);
```


# 解説
SQL実行後、実行SQLを取得し、`?`の部分をプレースホルダの値で置き換えているだけです。
あとは`dd`などで出力すればOK！

# おわりに
これすごい便利なのでぜひ使ってください :smile: 
それでは！
フラグとステータスの話

[IDとコードの話](https://qiita.com/saoyagi2/items/4206d019b4d945697a2a)の親戚のようなコネタです。タイトルだけの出オチみたいなコネタで、わかる人には「ああ、あれね」とわかってしまうかもしれませんが、一応書きます。

フラグもステータスもレコードの状態を表すカラムには違いありません。違いは、フラグが2値なのに対して、ステータスは多値であることです。フラグって旗のことですから、旗が立ってるか立ってないかで状態を表してるわけですね。だから2値になってる。一方のステータスは多値ですから、複数の値を取りうる。もちろんシステムが定義してる範囲でですが。

問題は、これも「混ぜるな危険」に尽きるわけです。フラグという名前のカラムなのに何故か多値が入ってることがある。最初は会員/非会員の区別だけでフラグとして作成したカラムだったのに、途中から仮会員だとか強制退会だとか会員種別が増えてくる。それ自体はシステムの正常な成長なのでいいんですが、そこで既存のフラグカラムをそのまま流用してしまう実装者がいたりする。本人としては、スキーマ変更を伴わずにスマートに実装できてオレ頭いいって思ってるかもしれませんが、そうして複雑度を増したシステムは最終的に剣を刺しまくった黒ひげ危機一発のような、どこを変えても爆発してしまうアンタッチャブルなシステムになってしまいます。うがー！って、すいませんちょっと私怨入ってしまったかも。

```sql
root@localhost [test]> SELECT id, name, member_flg FROM members;
+----+--------+------------+
| id | name   | member_flg |
+----+--------+------------+
|  1 | 山田   |          1 |
|  2 | 田中   |          2 |
|  3 | 佐藤   |          3 |
+----+--------+------------+
3 rows in set (0.01 sec)
```

そして MySQL の場合にたちが悪いのは、BOOLEAN で定義されたカラムは TINYINT になるんですね。MySQL には厳密な BOOLEAN 型は存在しない。BOOLEAN で定義したカラムに多値が格納できるし、取り出すことも出来る。これを便利と思ってしまう愚か者（言い切っちゃった）がいることで、今日も黒ひげ危機一発の剣が1本また1本と刺さっていくわけです。ということで混ぜるな危険です。

```sql
root@localhost [test]> CREATE TABLE members (
    -> id INTEGER AUTO_INCREMENT PRIMARY KEY,
    -> name VARCHAR(255),
    -> member_flg BOOLEAN
    -> );
Query OK, 0 rows affected (0.13 sec)

root@localhost [test]> SHOW CREATE TABLE MEMBERS\G
*************************** 1. row ***************************
       Table: members
Create Table: CREATE TABLE `members` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `member_flg` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
```

もう一つ、NULLABLE 問題があります。BOOLEAN であろうとも NULLABLE であれば NULL が格納できます。つまり2値であったはずのカラムが3値になりうる。SQL における NULL というのはそれが特別な意味を持っているわけですが、これまた愚か者（また言い切っちゃった）が「会員フラグが NULL の場合は仮会員という扱いにしよう」と実装コード側を修正してしまう。こうして NULL の場合は何が起こるのか、いちいちコードを確認しないといけないデータベースが出来上がってしまう。黒ひげ危機一発ですね。

最後にフラグのカラム名の付け方ですが、いくつか流派があります。一つは locked や closed のように過去分詞を用いるもの。is\_ を前置するものもあります。is_lock とか is_close とかですね。

過去分詞も is_ を前置するのも真偽それぞれがどういう意味であるかカラム名から明瞭なのですが、困るのが _flag, _flg を後置するもの。lock_flag とか close_flg とかですね。真の場合にロックされてるのか、偽の場合にロックされてるのか、カラム名だけではちょっと言い切る自信が無い。仕様書なり実装なりを確認したくなります。ということで、_flag, _flg を後置するのは個人的にはあまりお勧めではありません。

どの流派を選ぶかは自由ですが、一つのシステムでは流派は統一しておいて欲しいですね。
特許強化手法：市場はめ込み方式

特許を有効に機能させる方法として、以下の市場はめ込み方式を提案します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/461483/d2e6c7e5-55ee-bdc6-e773-6367ec456b27.png)
Flow Builder「自動レイアウト」機能を使用時に起きた不具合と解消法

## はじめに
私がSalesforceを触り始めた頃は、ちょうど Classic から Lightning に切り替わっていくタイミングでもあったため、そこから様々なアップデートがあったり新しい機能が登場したりなど市場は現在も賑わっています。

Lightning の「Flow Builder」は、コーディング無しで複雑な自動処理を実装できることで推奨されていて、今でも使い勝手が良くなるような機能アップデートが適時行われています。

以前、そんなFlow Builderで実装中に、ある不具合によって実装内容がすべて水の泡になりかけたことがあったので、その事象について解消法と共にお伝えしようと思います。

## 自動レイアウト（ベータ）
まず不具合の原因となる「自動レイアウト」について軽く説明します。
Flow Builderで新たにフローを作成したことがある人は知っていると思いますが、去年ごろからフローの作成方法として「自動レイアウト」が選択できるようになっています。

通常はキャンパス内の要素やコネクタは自分で配置する必要があるため、要素の間隔がズレたり、コネクタが曲がったりで見た目が悪くなってしまいがちです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1683328/282527bc-9a53-e85d-ebb0-2caff0680a14.png)
一方、自動レイアウトによるフローの作成では、要素の配置・コネクタ接続がすべて自動的に行われるため、位置揃えなどを気にせずに実装できます。さらにはコネクタ中に要素を直接追加できるボタンなど、実装する上で非常に使いやすい仕様になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1683328/140a7c56-9abe-da46-f646-3f8b2b414d05.png)
但し！！
注意点として、この機能は表記の通りベータ版ということで、利用に関してまだ推奨されてはいないようです。Winter'21リリースノートにも、"自動レイアウトはベータ機能としてのプレビュー版であり、Salesforce とのマスターサブスクリプション契約における「サービス」には含まれません。" と記載があります。

しかし、この非常に便利な機能を使っている人は多いと思います。利用する／しないもスイッチひとつで容易に変更できるため、私も実際にSandboxでのフロー作成時にこの機能を使っていました。

## フローが開けない不具合発生
「自動レイアウト」を使ってフロー作成していたある日、作成中（完成間近）の最新バージョンを開こうとすると、画面がこんな状態でフリーズしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1683328/beb678c1-0db8-f75e-06d4-82db6769a8a7.png)
薄っすら見えるポップアップを確認したところ、どうやら読み込みエラーが発生したようです。
>Sorry to interrupt
>This page has an error. You might just need to refresh it.
>[Error]
>Failing descriptor: {markup://builder_platform_interaction:flcConversionUtils}

リフレッシュが必要とのことですが、過去のバージョンを開こうとしても同じエラーのため開けず・・・。
別環境へ移管してみてもエラーでリリースもできず・・・。
他のフローに異常はなかったが、作成中のこのフローだけがどうしても開けず操作できなくなってしまいました・・・。
原因不明。

## 解消法
当時この不具合について調べても何も手掛かりが無かったため、Salesforceサポートに問い合わせてみると、
「ベータ機能である自動レイアウトの機能が影響し、発生している事象であることが確認できました。その為、対象の機能をOffにすることで対応頂けたらと考えております。」と回答をいただきました。
しかし！そもそもFlow Builderで対象のフローを開くことができないので画面上でoffにすることはできません。

そこで、Visual Studio Code を使用してメタデータを直接変更することにしました。
思いのほか簡単に対処できたので、実施した作業内容を記載していきます。

まず、メタデータを取得するために「package.xml」でtypes内に対象フローを指定します。

```xml:package.xml
～略～
    <types>
        <members>*</members> <!-- 対象フローのAPI参照名 -->
        <name>Flow</name>
    </types>
```
「SFDX:マニフェストファイルのソースを組織から取得」を実行すると「flows」ディレクトリにメタデータのソースファイルが格納されるので、対象フローのソースファイルを開いて以下の部分に変更を加えます。

変更前：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1683328/702dd961-ad09-5624-4656-b911f452472c.png)
変更後：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1683328/29c2408e-61a3-fa63-d21c-f68084f2383b.png)
CanvasModeをFREE_FORM_CANVASにするというのが、自動レイアウトの機能をOffにするのと同じことになります。
その後「SFDX:組織へソースをデプロイ」を実行してメタデータの変更をSalesforceへ反映すると・・・
無事にFlow Builderで対象のフローを開くことができました！！！　
数日かけて作成してきたフローが水の泡にならずによかったです。

## おわりに
自動レイアウトは、あくまでベータ機能として提供されているものですが、既にSalesforce組織上では公開されており誰でも利用できる状態であるため、使っていて同じ境遇にあった人もいるのではと思い記事にしてみました。
この事象後は自動レイアウトを使うのは怖くなりましたが、素晴らしい機能であることは間違いないため、早く正式リリースしてほしいところです。


## 参考リンク
SALESFORCE WINTER '21 リリースノート
＞[自動レイアウトでフロー要素を自動的に配置 (ベータ)](https://help.salesforce.com/articleView?id=release-notes.rn_forcecom_flow_fbuilder_autolayout_mode.htm&release=228&type=5)
Meshlabでplyファイルが読めないときの対処法

# 問題
Meshlabでplyファイルが読めない。
今回は、Dockerで動かしたOpenMVSという３次元復元ソフトで作成した結果のplyファイルが読めなかった、という問題ですが、原因は一般的なものなので、このようなタイトルで記録しておきます。

# 環境
OS: Ubuntu 20.04
meshlab version: MeshLab_64bit_fp v2020.03+dfsg1

# エラーメッセージ
"Unabale to open file"
画面キャプチャはこちら：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/753103/6514360a-d230-27af-cbca-a735668e28a1.png)

# 原因
ユーザとグループがともにrootになっており、ログインユーザにファイルを読む権限がない。

# 対策
ownerを変更する

```shell
sudo chown $USER:$USER *.ply
```
もしくは、root ユーザとしてファイルを読む。

```shell
sudo meshlab <yourfile>.ply
```

#
laravelコマンドが呼び出せないと思ったらデフォルトログインシェルが変わっていた

# 準備
composerとLaravelはインストール済み。
.bash_profileにも以下の内容を書き込んだ。

```
export PATH=$HOME/.composer/vendor/bin:$PATH
```

.bash_profileを読み込ませるために以下を実行。

```
source ~/.bash_profile
```

これでlaravelが呼べるようになる

```
laravel -V
Laravel Installer 4.2.7
```

ここまでは良かった。

# ターミナルを再起動するとlaravelが呼べなくなる

ターミナルを再起動して以下のコマンドを実行。

```
laravel -V
zsh: command not found: laravel
```

コマンドが見つからない？

# 原因
MacOS Catalia以降は.bash_profileが読み込まれないらしい。
読み込まれないならそりゃ呼べないよね…。

## 詳細
原因を調べるうちに見つけたブログです。
詳しくはそちらを読んでください。
[MacOS Catalinaはデフォルトのログインシェルがbashではないらしい。](https://blog.officekoma.co.jp/2020/05/macos-catalinabash.html)

# 解決
先に用意していた.bash_profileを読み込まれるファイル名にリネームしただけ。

```
mv .bash_profile .zprofile
```

これでターミナル再起動後もlaravelが有効になりました。

```
laravel -V
Laravel Installer 4.2.7
```
【Docker】MongoDBのアクセス制御　no.23

![パンプキン カフェ (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1011305/c72c756e-9264-8db2-e61f-3ae5af3d4808.png)


こんにちは。まゆみです。

Dockerについての記事をシリーズで書いています。

[前々回の記事](https://qiita.com/Mayumi_Pythonista/items/11b412e59e045a2c5eb3)で、Databaseとバックエンド・フロントエンドのContainerをつなぐ方法を書いてきました。

今回の記事では、<font color="Red">Database(MongoDBを使用します)へのアクセス制御の方法</font>を書いていこうと思います

では、さっそく始めていきますね。

#MongoDBのドキュメント

<font color="Red">MongoDBはデフォルトでは、誰でもアクセスできる</font>ようになっています

MongoDBへのアクセスをコントロールする方法を、[ドキュメント](https://hub.docker.com/_/mongo)で見てみましょう

![2021-07-02_8-03-33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1011305/a1061d41-5d86-6526-f9fb-98ca5f00cd73.png)
引用元：docker hub

MongoDBイメージは2つの環境変数をサポートしています


>MONGO_INITDB_ROOT_USERNAME

>MONGO_INITDB_ROOT_PASSWORD

上記の2つの環境変数を利用して、アクセスコントロールをしていく具体的な方法を下記に書いていきます

##-eオプションでContainerを実行する

では上記の２つの環境変数を使ってユーザーネイムとパスワードを実際に設定してMongoDBのContainerをrunしましょう

```
docker run -e MONGO_INITDB_ROOT_USERNAME=mayumi -e MONGO_INITDB_ROOT_PASSWORD=secret mongo
```


また、[docker hub のmongoイメージのドキュメント](https://hub.docker.com/_/mongo)にどのようにrunするかの例も載っているので、抜粋して貼っておきますね。

![2021-07-02_8-16-39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1011305/fe3acf37-f798-5d34-24ca-6fd46c781574.png)

##mongodbとつながっているnodeのソースコードを書き換える

次にmongodbとつながっているnodeのソースコードを書き替えます。

[MongoDBのドキュメント](https://docs.mongodb.com/manual/reference/connection-string/#standard-connection-string-format)を見てみましょう

![2021-07-02_8-42-56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1011305/2a834b5e-f56f-9a0e-b34c-1821bd225bc6.png)

下記のようになります

![2021-07-02_8-45-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1011305/087dbee1-2ad7-448e-6280-705c66cea036.png)

上記のように実行して、エラーが出るようでしたら下記のセクションに進んでください。

#エラーが出る原因

もし、上記の通りに進めたのにエラーが出る場合、一番考えられる理由は『Volume』です。

最後のプロセスで、エラーが出る人は

```
docker volume ls
```

でボリュームのリストを表示してみてください。

そしてボリュームが存在していたら、一度削除してもう一度試してみてください。

```
docker volume rm
```

で削除できます。

ただし、エラーの原因が他にある場合は、何が原因なのか他を探してみてくださいね。　<(_ _)>

#まとめ

今回の記事では、データベースのアクセス制御について書かせていただきました。

今回、一番苦労したのはエラーが出た時の解決方法がなかなか分からなかったことです。

私の経験がみなさまのお役に立てれば幸いです。
小書きのルビを大書きに置換する

　日本の本のルビ（ふりがな）の文字には出版社や時代ごとに2つに大別されるルールがあります。「小書きのカナをそのまま使う」か「大書きのカナに変換して入れる」かです。大書きルールの場合には拗音・促音を「っ」→「つ」のように置換して入れます。これは元々は活版の時代にルビサイズの小書きのカナは潰れてしまって読めなくなる懸念があったことからできたルールではないかと推測しているのですが、現在のように印刷技術が発達してサイズの小さな文字でも十分な可読性が得られるようになっても依然として「大書きのカナに変換して入れる」ルールは残っていたりします。EPUBでも場合によっては要求されますので、機械的に処理できないかと思い、Xhtml内でのルビを対象とした小書きのカナ→大書きのカナの置換スクリプトをPerlで書いてみました。

 
<pre class="lang:perl decode:true " title="rubyKogaki2Oogaki.pl" >#! /usr/bin/perl
use utf8;
use Encode qw/encode decode/;

#変換Textファイルのパスを取得
$convertTextFilePath = $ARGV[0];
$convertTextFilePath = decode('UTF-8', $convertTextFilePath);

#各テキストファイルを展開、置換
	open(IN,"$convertTextFilePath");
		@importedTxts = &lt;IN&gt;;
		$importedTxt = join("",@importedTxts);
		$importedTxt = decode('UTF-8', $importedTxt);
	close (IN);
	#置換処理
	my $replacedTxt = &amp; textReplace ($importedTxt);
	#出力
	$replacedTxt = encode('UTF-8', $replacedTxt);
		open(OUT,"&gt; $convertTextFilePath");
		print OUT $replacedTxt;
	close (OUT);
exit;

sub textReplace {
	#置換テキストの取得
	my $targetText = $_[0];
	my @rtBlocks;
	@rtBlocks = $targetText =~ m/&lt;rt&gt;[^&lt;&gt;]+?&lt;\/rt&gt;/g;
	foreach $rtBlock(@rtBlocks){
		my $rtBlockConverted = $rtBlock;
		$rtBlockConverted =~ tr/ぁぃぅぇぉゕゖっゃゅょゎァィゥェォヵㇰヶㇱㇲッㇳㇴㇵㇶㇷㇸㇹㇺャュョㇻㇼㇽㇾㇿヮ/あいうえおかけつやゆよわアイウエオカクケシスツトヌハヒフヘホムヤユヨラリルレロワ/;
		$targetText =~ s/$rtBlock/$rtBlockConverted/g;
	}
	return $targetText;
}</pre> 



ターミナルで
 
<pre class="lang:sh decode:true " >perl このスクリプトのパス 処理対象ファイルのパス</pre> 

の順で指定すれば処理されます。

<a href="http://densyodamasii.com/wp-content/uploads/2021/07/column83_01.png" rel=lightbox[column83]><img src="http://densyodamasii.com/wp-content/uploads/2021/07/column83_01.png" alt="変換された" width="624" height="122" class="size-full wp-image-3993 thinborder" /></a>

　こんな感じ。

　なお、小書きのカナ→大書きのカナの場合には自動で処理できますが、大書きのカナ→小書きのカナの場合は小書きにしていい文字なのかそうでないのかの推測が機械的にはできませんので位置を特定しての手作業になるかと思っています。
【第3回】eager_loadとpreloadの挙動の違いをまとめた

前回の記事では、``joins``と``left_outer_joins``の挙動の違いについてまとめました。
前回の記事をまだ読んでいない方はこちらからどうぞ。
[【第1回】RailsのN+1問題と解決するためのメソッド完全版](https://qiita.com/kurokawa516/items/6c6e895843e199c52ac2)
[【第2回】Railsのjoinsとleft_outer_joinsの挙動と違いをまとめた](https://qiita.com/kurokawa516/items/5ffcfebed09e0d49bf43)

##本記事の内容

1. joins
2. left_outer_joins
3. eager_load <=（本記事ではここから解説）
4. preload
5. includes

##eager_load
関連モデルを一括読み込みするメソッドです。
SQLの``LEFT OUTER JOIN句``を発行し関連付けをキャッシュします。
キャッシュとは一度取得したデータをメモリ上に一時的に保存する事で、次回のデータ取得時により高速にデータ取得する仕組みです。
``eager_load``を使う事で、あるモデルを軸にその関連モデルの値を取得したい場合のN+1問題の解消ができる様になります。

```ruby:例1
モデル名.eager_load(:関連モデル)
```

**``eager_load``は、関連モデルのデータをメモリに一時的に保存することで、メモリからデータを取得します。**
関連モデルに存在する属性を、ループ処理の中で取得したい場合に使用します。

**``joins``と``left_outer_joins``との違いは、関連モデルを一括でキャッシュすることです。**
###使い方

```ruby:例2
User.eager_load(:post).each do |user|
 p user.posts.title
end
```

``Userモデル``と関連する``Postモデル``を一括読み込みしたうえで``eachメソッド``でループ処理を実行しています。
キャッシュされた``Postモデル``を使用するので、DBへのアクセスはされません。
そのためSQLが発行されず、N+1問題が解消されるわけですね。

##preload
同じく関連モデルを一括読み込みするメソッドです。
**``eager_load``との違いは、SQLをモデルごとに発行し、関連モデルをキャッシュすることです。**
つまり、``eager_load``のSQL発行回数が1回に対して、``preload``は軸となるモデルと関連モデルの２つに対してSQLを発行します。合計2回です。

**``preload``は対象テーブルのデータサイズが大きくJOINのコストが大きい場合に有効です。**

###使い方

```ruby:例1
モデル名.preload(:関連モデル)
```

関連モデルに存在する属性を、ループ処理の中で取得したい場合に使用します。

```ruby:例2
User.preload(:post).each do |user|
 p user.posts.title
end
```

**注意点としては、モデルごとにSQLを発行するので、``eager_load``と異なり絞り込み条件を指定できません。**

##includes
``includes``はSQL発行時の条件に合わせて、``eager_load``か``preload``どちらかを使用します。
N+1問題を解消するとき、とりあえず``includes``を使っているという人も多いのではないでしょうか。

たしかに便利なメソッドですが、``eager_load``か``preload``のどちらの挙動を狙った実装なのかわからないので、最終的にパフォーマンスが落ちてしまう可能性があることに注意が必要です。
個人的には``includes``は非推奨です。``eager_load``か``preload``の挙動を理解したうえで実装するのがベストです。

###使い方

```ruby:例1
モデル名.includes(:関連モデル)
```

**``includes``メソッドでは以下の条件に合致する場合にはeager_loadの挙動を、合致しない場合はpreloadの挙動を行います。**

1. 引数に指定した関連テーブルに対しjoinsメソッドを使用している場合
2. 引数に指定した関連テーブルに対しwhereメソッドを使用している場合
3. 引数に指定した関連テーブルに対しreferencesメソッドを使用している場合

```ruby:例2
class PostsController < ApplicationController

 def index
  @posts = Post.includes(:photos, :user).order('created_at DESC').page(params[:page]).per(3)
 end

end
```

このように実装した場合には、上記3つのメソッドは使用されていないので``preload``が実行されているということになります。

##まとめ
* ``eager_load``は、関連モデルのデータをメモリに一時的に保存することで、メモリからデータを取得する。
* ``preload``は、SQLをモデルごとに発行し、関連モデルをキャッシュする。
* ``includes``はSQL発行時の条件に合わせて、eager_loadかpreloadどちらかを使用する。

ふぅー。やっとすべて解説できました。
「まだよくイメージがわかない」という人も手元の環境で色々試してみると、理解が加速するはずです。
ぜひ試してみてください。
[Unity]VFXで滞留するスモークをだしてみる

# はじめに
こんな感じに煙が天井にあたって広がっていく感じのものをつくる．めちゃ簡単
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">なるほど <a href="https://t.co/hQd6OPhXnq">pic.twitter.com/hQd6OPhXnq</a></p>&mdash; よつば (@Yothuba3) <a href="https://twitter.com/Yothuba3/status/1410571833343057922?ref_src=twsrc%5Etfw">July 1, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

VFXの導入などは[こちら](https://qiita.com/Yothuba3/items/9ed1b611c2211339fa88)の記事を参考にして頂けると．
# 作り方
煙の元となる画像素材はUnity公式が配布しているものを使います．↓  
https://blog.unity.com/technology/free-vfx-image-sequences-flipbooks

## VFXグラフ
###Initialize  
SetLifetimeを長めにとると滞留してる感じを作れるのでもっとボリューム感だしたかったらSpawn量とここを大き目にすると多分良い
```Set Angle Z```は参考にしたブログ記事さんがやってた．これするとQuadが回転した状態で発生するので若干煙の模様にランダム性をつけれるが今回は誤差程度なので切りました．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411974/44cae0eb-7ef0-8afa-1eca-1667fd51779e.png)

### Update
```Flipbook Player```がキモ．
これを指定することで後述のOutputで指定するFlipbookを1秒あたり何枚コマ送りするか指定できる．
例えば8*8のフリップブックだった場合は，ここを8にすると64 / 8で8秒で1週することになる．  
Linear Dragはお好み  
```Collide with Depth Buffer```はその名の通りデプスバッファーを用いて当たり判定を行ってくれる．複雑なメッシュ形状などはCollide設定できないがデプスバッファー経由でのみVFXの当たり判定に使える．  
が，当然厚みなどはとれないためここで設定などを行う．(初期値は無限)
```Bounce```をあげすぎるとバスケットボールみたいなバウンドの仕方しだすので控え目に  
***煙が天井にあたった後に横に広がるような動きは```Roughness```を上げると出来る．***
```Roughness```は要は当たったパーティクルをどれだけランダム性を持たせて反射させるかっていう設定．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411974/42873fd9-be8c-3ffb-553f-af947a45ccfd.png)
```Camera```の項目をCustomにするとカメラの設定をしたうえで，専用でカメラを生成して使用してくれるぽい(未検証)ので
メインカメラの位置によっては判定がおかしくみえる場合はこれを利用するとよさそう．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411974/c763b8e8-4933-14ee-ceb1-8f540f87d4af.png)

### Output
LitQuadでOutputして，UVModeをFlipbook Blendに設定，```Base Color Map```に公式からDL済みのWispySmoke01を指定(2枚あると思うのでA要素がある方を選択)  
```Size```系のブロックはお好みで．
```Orient: Face Camera Plane```でカメラの方向にQuadを向かせる．
```Set Color over Life```についてもお好み
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411974/f3f50eec-37ae-bb91-506a-b50708f16ce8.png)

#　さいごに
いい感じにライトを置いたら完成！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411974/c75f82f0-e838-63f3-1954-d8457f13c072.png)