maturinをワークスペースで運用してみる

どうも、ナナオです。 最近PythonからRustを呼び出す実装をすることがありまして、pyo3にお世話になることがありました。 pyo3のビルドにはmaturinというCLIを使うのですが、これをRustのワークスペース機能と併用できるのかどうか気になったので、検証してみたいと思います。 準備 とりあえず適当にpyo3を使用したライブラリを作ります。 ryeを使っていれば以下のコマンドでmaturinをビルダーに指定したプロジェクトを作成できます。 rye init maturin-workspace-playground --build-system maturin maturinをインストールしていなかったので、以下のコマンドでインストールしておきます。 rye install maturin 作ったプロジェクトに移動して、ワークスペースのメンバーになるプロジェクトを作成しておきます。 cd maturin-workspace-playground mkdir rust && cd rust cargo init --lib app cargo init --lib python-api 作成したプロジェクトをワークスペースのメンバーになるように設定をしていきます。 ルートディレクトリのCargo.tomlを以下のように編集します。 [workspace.package] name = "maturin-workspace-playground" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] members = ["rust/python-api", "rust/app"] rust/appのCargo.tomlは以下のように編集します。 [package] name = "app" version.workspace = true edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rust/python-apiのCargo.tomlは以下のように編集します。 ...

2024年8月26日 · にあえん

Gitに代わる新たなバージョン管理ツール「jujutsu」を使ってみる

お久しぶりです。ナナオです。 今回は今注目を集めているコードのバージョン管理ツール「jujutsu」を触ってみて、実際Gitと何が違うのか?を検証してみようと思います。 ちなみに、私は普段git-flowを使用したブランチ運用をしており、主に使用しているコマンドはこれくらいです。 init add branch commit checkout (swith) remote stash merge status インストール Windowsを使用している場合、cargoを使用してインストールします。 cargo install --locked --bin jj jj-cli --features vendored-openssl インストールできました。 > jj --help Failed to spawn pager 'LESSCHARSET=utf-8 less -FRX': program not found Jujutsu (An experimental VCS) To get started, see the tutorial at https://github.com/martinvonz/jj/blob/main/docs/tutorial.md. Usage: jj.exe [OPTIONS] [COMMAND] Commands: abandon Abandon a revision backout Apply the reverse of a revision on top of another revision branch Manage branches cat Print contents of a file in a revision checkout Create a new, empty change and edit it in the working copy [aliases: co] chmod Sets or removes the executable bit for paths in the repo commit Update the description and create a new change on top [aliases: ci] config Manage config options describe Update the change description or other metadata diff Show changes in a revision diffedit Touch up the content changes in a revision with a diff editor duplicate Create a new change with the same content as an existing one edit Edit a commit in the working copy files List files in a revision git Commands for working with the underlying Git repo init Create a new repo in the given directory interdiff Compare the changes of two commits log Show commit history merge Merge work from multiple branches move Move changes from one revision into another new Create a new, empty change and edit it in the working copy obslog Show how a change has evolved operation Commands for working with the operation log [aliases: op] rebase Move revisions to different parent(s) resolve Resolve a conflicted file with an external merge tool restore Restore paths from another revision show Show commit description and changes in a revision sparse Manage which paths from the working-copy commit are present in the working copy split Split a revision in two squash Move changes from a revision into its parent [aliases: amend] status Show high-level repo status [aliases: st] util Infrequently used commands such as for generating shell completions undo Undo an operation (shortcut for `jj op undo`) unsquash Move changes from a revision's parent into the revision [aliases: unamend] untrack Stop tracking specified paths in the working copy version Display version information workspace Commands for working with workspaces help Print this message or the help of the given subcommand(s) Options: -h, --help Print help (see a summary with '-h') -V, --version Print version Global Options: -R, --repository <REPOSITORY> Path to repository to operate on By default, Jujutsu searches for the closest .jj/ directory in an ancestor of the current working directory. --ignore-working-copy Don't snapshot the working copy, and don't update it By default, Jujutsu snapshots the working copy at the beginning of every command. The working copy is also updated at the end of the command, if the command modified the working-copy commit (`@`). If you want to avoid snapshotting the working and instead see a possibly stale working copy commit, you can use `--ignore-working-copy`. This may be useful e.g. in a command prompt, especially if you have another process that commits the working copy. Loading the repository is at a specific operation with `--at-operation` implies `--ignore-working-copy`. --at-operation <AT_OPERATION> Operation to load the repo at Operation to load the repo at. By default, Jujutsu loads the repo at the most recent operation. You can use `--at-op=<operation ID>` to see what the repo looked like at an earlier operation. For example `jj --at-op=<operation ID> st` will show you what `jj st` would have shown you when the given operation had just finished. Use `jj op log` to find the operation ID you want. Any unambiguous prefix of the operation ID is enough. When loading the repo at an earlier operation, the working copy will be ignored, as if `--ignore-working-copy` had been specified. It is possible to run mutating commands when loading the repo at an earlier operation. Doing that is equivalent to having run concurrent commands starting at the earlier operation. There's rarely a reason to do that, but it is possible. [default: @] [aliases: at-op] -v, --verbose Enable verbose logging --color <WHEN> When to colorize output (always, never, auto) --no-pager Disable the pager --config-toml <TOML> Additional configuration options jujutsuリポジトリの初期化 まずはリポジトリを作成してみましょう。 ...

2024年2月14日 · にあえん

haskellに入門してみる

いろんな言語が気になる時期。ナナオです。 今回はHaskellに入門してみようと思います。 (以前「すごいHaskell」は読んだことがあるのですが、読むだけでコード書いてなかったのでほぼ忘れてしまいました。。もったいない!!) Haskellのセットアップ まずはHaskellを使える環境を作っていきます。 Windows11の環境でやります。(WSLは使わないのでお気をつけて!) GHCupを使ってStackやGHCなどをまとめてインストールしちゃいましょう!! (僕は最初Stackだけインストールすればいいか~とやっていたら、VSCodeの拡張機能がGHCupを使う感じになっていたので詰みました) https://zenn.dev/mod_poppo/articles/haskell-setup-2023#ghc%E3%82%92%E4%BD%BF%E3%81%86 windowsの場合はかなり長いコマンドですが、以下でインストール可能です。 Set-ExecutionPolicy Bypass -Scope Process -Force;[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; try { Invoke-Command -ScriptBlock ([ScriptBlock]::Create((Invoke-WebRequest https://www.haskell.org/ghcup/sh/bootstrap-haskell.ps1 -UseBasicParsing))) -ArgumentList $true } catch { Write-Error $_ } チュートリアルをやってみる とりあえずこのページのチュートリアルをやってみましょう。 10分で学ぶHaskell - HaskellWiki ghciは以下のように起動します。 stack ghci ghciでは:tと打つことで、変数や関数の型について知ることができます。 ghci> :t filter filter :: (a -> Bool) -> [a] -> [a] これはなんかいい機能ですね。 まずは関数定義からやっていきましょう。 適当なプロジェクトを作成します。 stack new haskell-playground これをVSCodeで開きます。 拡張機能もインストールしておきましょう。 Haskell Extension Pack - Visual Studio Marketplace では関数を定義してみましょう。 factorial 0 = 1 factorial n = n * factorial (n - 1) main = do print $ factorial 2 こんな感じでfactorial関数を定義しました。 ...

2024年1月17日 · にあえん

rubyに入門する

Rubyってどうなの?ナナオです。 今回はあまり触ってこなかったRubyに入門してみようと思います。 きっかけとしてはある会社と面談したときに面白そうな会社だな~~と思ったのですが、Rubyをメインで使っている会社だったからです。 私のRubyのレベルは若干しかできないくらいのレベルなので、基礎はできるようにしておこうと思い、今回備忘録もかねて入門してみようと思います。 Rubyのセットアップ まずはRubyのインストールを行っていきましょう。 バージョン管理はしっかりやっておきたいので、rbenvを使います。 私の開発環境はWindowsですが、anyenvを使いたかったのでWSL2で作業はしています。 また、すでにanyenvをダウンロード済みの環境ですが、anyenvのインストールは公式リポジトリを参照してください。 ではanyenvでrbenvをセットアップしていきます。 anyenv install rbenv これで問題なくrbenvはインストールできます。 あとは適当に安定版の3.2.2くらいをインストールします。 rbenv install 3.2.2 ただここでエラーが起きてしまいました。かなしいね。 ==> Downloading openssl-3.1.4.tar.gz... -> curl -q -fL -o openssl-3.1.4.tar.gz https://dqw8nmjcqpjn7.cloudfront.net/840af5366ab9b522bde525826be3ef0fb0af81c6a9ebd84caa600fea1731eee3 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 14.8M 100 14.8M 0 0 10.0M 0 0:00:01 0:00:01 --:--:-- 10.0M ==> Installing openssl-3.1.4... -> ./config "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl" "--openssldir=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl/ssl" zlib-dynamic no-ssl3 shared -> make -j 24 BUILD FAILED (Ubuntu 22.04 on x86_64 using ruby-build 20231225-4-g33168b3) You can inspect the build directory at /tmp/ruby-build.20240115225829.19003.NXfhFA See the full build log at /tmp/ruby-build.20240115225829.19003.log ログを見てみると、zlibがインストールされていないのが原因だったようです。 ...

2024年1月15日 · にあえん

gasでCheerioを使うとスクレイピングが捗る

あけましておめでとうございます。ナナオです。 年が明けてからだいぶ経ってしまいましたが、最近GAS(Google App Script)でスクレイピングする方法が分かったのでやっていこうと思います。 どうやるか jqueryライクにHTMLを操作できるcheerioを利用します。 GASではnpm installはできないので、こちらを使用します。 GitHub - tani/cheeriogs: Cheerio/ jQuery for Google Apps Script このライブラリをGASでインポートします。 まず新しいGASのプロジェクトを作成します。 作成したらライブラリにスクリプトIDとして1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0を貼り付け、検索します。 Cheerioが表示されました! これを追加ボタンから追加すれば、Cheerioが使用可能な状態になります。 以下のような実装を追加してみます。 function myFunction() { const content = Cheerio.load("<div>example</div>") console.log(content) } 実行してみます。 ちゃんとCheerioのオブジェクトとして読み込まれていますね! クエリによる検索が可能かどうかも検証してみましょう。 function myFunction() { const $ = Cheerio.load(` <div class="1">example</div> <div class="2">example</div> `) const div_2 = $("div.2") console.log(div_2.length) } これを実行してみます。 ちゃんと検索できていますね! さらに、UrlFetchAppと組み合わせて検索してみます。 何の意味もないですが、divがHTML内に何個あるか出力するプログラムを作りました。 function myFunction() { const response = UrlFetchApp.fetch("http://www.google.com/") const $ = Cheerio.load(response.getContentText()) const search_area = $("div") console.log(search_area.length) } では実行します。 いくつあるか出力されました!意外と少ないな。。 まとめ ということでCheerioをGASで使用するという記事でした。 応用することで簡易なスクレイピングができると思います。 ではまた。

2024年1月13日 · にあえん

WSLでzshの導入&zshのプラグイン管理にZnapを導入してみる

こんばんは。ナナオです。 Windowsに切り替えてから半年近く経ちますが、まだwslのセットアップが全然できていませんでした。 普段仕事ではMacを使っているのでこちらもzshを使おうと思っていたんですが、せっかくなら新しいものを使いたいなと思って調べてみたら、プラグイン管理にznapというツールが使用できることを知ったのでzshと一緒に導入してみようと思います。 ZSHの導入 私のWSLはUbuntuを使用しているため、その設定でやります。 まずは以下のコマンドでzshをインストールします。 sudo apt install zsh 次にchshで起動するシェルをZSHに変更します。 chsh -s /bin/zsh はい、これで導入終わりです。 ホームディレクトリに空の.zshrcを配置したら設定はこれで終わりです。 Znapの導入 続けてznapの導入です。 リポジトリをクローンします。 git clone -depth 1 -- https://github.com/marlonrichert/zsh-snap.git ~/.znap そしたらznapを読み込む設定を.zshrcに追加します。 # .zshrc source ~/.znap/znap.zsh はい、これで終わりです。 とりあえずhelpコマンドを表示してみましょう。 % znap --help Usage: znap <command> [ <argument> ... ] Commands: clean remove outdated .zwc binaries clone download repos in parallel compile compile zsh scripts and functions eval cache & eval output of command fpath install command output as a function function create a set of lazy-loading functions help print help text for command ignore add local exclude patterns to repo install install executables & completion functions multi run tasks in parallel prompt instant prompt from repo pull update repos in parallel restart (DEPRECATED) validate dotfiles & safely restart Zsh source load plugins status fetch updates & show git status uninstall remove executables & completion functions For more info on a command, type `znap help <command>`. ちゃんとznapコマンドが使えていますね。 ...

2023年12月22日 · にあえん

LineのOAuth認証を試してみた

OAuth触るの5年ぶり。ナナオです。 今回はLINEのOAuth認証を、実際にサーバーを実装しながら試していきたいと思います。 LINEログイン v2.1 APIリファレンス | LINE Developers そもそもOAuthとは?といったところは↓ 一番分かりやすい OAuth の説明 #OAuth - Qiita OAuthでログイン画面表示まで まずLINE Developersコンソールからログインチャネルを作成します。 これはMessaging APIなどとは別で作成する必要があります。 以下のように設定しました。 続けて、「LINEログイン設定」からコールバックURLを設定します。 コールバックURLのリクエスト先はまだ実装していなくても大丈夫です。 今回はウェブアプリから使用する設定で実装します。 ここまで出来たらブラウザからログインの様子を確認することができます。 認可リクエストのドキュメントを確認し、URLを作ってブラウザからリクエストしてみましょう。 うまくいけば以下のようなLINEのログイン画面に遷移します。 コールバックURLを実装する 今のままだとリダイレクトURLの先には何もないので、自分で実装しましょう。 Cargoパッケージを作成します。 cargo new rust-line-oauth-playground 必要な依存関係も追加しておきます。 [dependencies] axum = "0.7.1" dotenv = "0.15.0" reqwest = { version = "0.11.22", features = ["json"] } serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" tokio = { version = "1.34.0", features = ["macros", "rt", "rt-multi-thread"] } では実際にリダイレクト先の実装をしてあげましょう。 これを実施すると、アクセストークンが取得できるようにになります。 以下のように行いました。 use axum::{extract::{Query, State}, routing::get, Router}; use serde::Deserialize; use reqwest::Client; use serde_json::{Value, json}; #[derive(Debug, Deserialize)] struct LineCallbackQuery { code: String, state: String, friendship_status_changed: Option<bool>, liffClientId: Option<String>, liffRedirectUri: Option<String>, } #[derive(Debug, Deserialize)] struct LineCallbackError { error: String, error_description: Option<String>, state: Option<String>, } #[derive(Debug, Deserialize)] struct AccessTokenResponse { access_token: String, expires_in: i32, id_token: String, refresh_token: String, scope: String, token_type: String, } #[derive(Debug, Clone)] struct BaseSetting { redirect_uri: String, client_id: String, client_secret: String, } #[derive(Debug, Clone)] struct AppState { http_client: Client, setting: BaseSetting, } async fn line_callback( Query(query): Query<Value>, State(state): State<AppState>, ) { // エラーの場合 // https://developers.line.biz/ja/docs/line-login/integrate-line-login/#receiving-an-error-response if query.get("error").is_some() { let query: LineCallbackError = serde_json::from_value(query).unwrap(); eprintln!("リダイレクト時にエラーが発生しました: {:?}", query); return } // 正常な場合 // https://developers.line.biz/ja/docs/line-login/integrate-line-login/#receiving-the-authorization-code let query: LineCallbackQuery = serde_json::from_value(query).unwrap(); let params = json!({ "grant_type": "authorization_code", "code": query.code, "redirect_uri": state.setting.redirect_uri, "client_id": state.setting.client_id, "client_secret": state.setting.client_secret, }); // アクセストークンを取得する // https://developers.line.biz/ja/docs/line-login/integrate-line-login/#get-access-token let res = state.http_client.post("https://api.line.me/oauth2/v2.1/token") .form(&params) .send().await.unwrap(); let res_json: AccessTokenResponse = res.json().await.unwrap(); println!("res: {:?}", res_json); } #[tokio::main] async fn main() { // 環境変数をdotenvで取得する dotenv::dotenv().ok(); let setting = BaseSetting { redirect_uri: std::env::var("REDIRECT_URI") .expect("REDIRECT_URI must be set"), client_id: std::env::var("CLIENT_ID") .expect("CLIENT_ID must be set"), client_secret: std::env::var("CLIENT_SECRET") .expect("CLIENT_SECRET must be set"), }; let http_client = reqwest::Client::new(); let state = AppState { http_client, setting, }; let app = Router::new() .route("/callback", get(line_callback)) .with_state(state); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .unwrap(); println!("listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app).await.unwrap(); } あとはルートパッケージ直下に.envを配置します。 ...

2023年11月30日 · にあえん

Rustでかんたん動的サイト構築

いろんなライブラリを触りたい。ナナオです。 今回はPythonのHTMLテンプレートエンジンで有名なJinjaに影響を受けたTeraを使って、簡単な動的サイトを作ってみたいと思います。 プロジェクトの作成 とりあえず実験用のプロジェクトを作成します。 cargo new rust-ssr-playground 必要なライブラリをインストールしてあげます。 今回はWebサーバーとしてAxum、HTMLのテンプレートエンジンとしてTeraを使用します。 [dependencies] axum = "0.7.1" serde = { version = "1.0.193", features = ["derive"] } tera = "1.19.1" tokio = { version = "1.34.0", features = ["full"] } じゃあとりあえずaxumを起動できるところまで実装します。 use axum::{Router, routing::get}; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "hello world" })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } 起動してアクセスしてみましょう。 ちゃんと起動しましたね。 次にプロジェクトにHTMLを追加してみましょう。 templatesというディレクトリを作成して、その中にindex.htmlを作成しましょう。 作成したHTMLは以下の通りです。 (Teraのテンプレート構文についてはこちらを参照してください) hello {{ name }}! nameの部分はTeraで入れるようにしました。 HTMLの準備ができたので、これをTeraで描画できるようにします。 use axum::{Router, response::Html, routing::get}; use tera::{Context, Tera}; use serde::Serialize; #[derive(Serialize)] struct Index { name: String } #[tokio::main] async fn main() { let tera = match Tera::new("templates/**/*.html") { Ok(t) => t, Err(e) => { println!("Parsing error(s): {}", e); ::std::process::exit(1); } }; let index = Index { name: String::from("test") }; let page = tera.render("index.html", &Context::from_serialize(&index).unwrap()).unwrap(); let app = Router::new().route("/", get(|| async move { Html(page.to_owned()) })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } できました。nameにはtestと描画できるようにします。 ...

2023年11月27日 · にあえん

調整さんで絶対に回答させるline botを作ってみた【7】

どうも、ナナオです。 結構長いこと書いていますが、今回で完成かな…?(前回も同じこと言ったような気がするけど いや、完成させます! 期日設定によって送られるメッセージ頻度を変化させる 最初の記事で想定していたように、このBotは全員が回答するまでメッセージを送り続けるようにしたいです。 その頻度は以下のように期日からの経過時間によって変わります。 期日まで 未回答者に1日に一回催促のDMが送られる 期日経過~1日 未回答者に1時間に一回催促のDMが送られる 期日経過1日~2日 未回答者に30分に一回催促のDMが送られる 期日経過3日~ 未回答者に15分に一回催促のDMが送られる これを実装していきます。 pub struct MultipleService { router: Router, scheduler: JobScheduler, } impl MultipleService { pub async fn new(pool: PgPool) -> Result<MultipleService, shuttle_runtime::Error> { let state = AxumState { pool: pool.clone() }; let router = Router::new() .route("/webhook", post(webhook)) .route("/healthcheck", get(healthcheck)) .with_state(state); let scheduler = JobScheduler::new().await.unwrap(); // 時間ごとにバッチを追加 // 1日に一回 let pool_clone = pool.clone(); let daily_job = Job::new_async("0 0 0 * * *", move |_, _| { let inner_pool = pool_clone.clone(); Self::send_message_with_deadline(inner_pool, DailyCondition) }).expect("ジョブの作成に失敗しました"); // 1時間に一回 let pool_clone = pool.clone(); let hourly_job = Job::new_async("0 0 * * * *", move |_, _| { let inner_pool = pool_clone.clone(); Self::send_message_with_deadline(inner_pool, HourlyCondition) }).expect("ジョブの作成に失敗しました"); // 30分に一回 let pool_clone = pool.clone(); let every_thirty_minutes_job = Job::new_async("0 */30 * * * *", move |_, _| { let inner_pool = pool_clone.clone(); Self::send_message_with_deadline(inner_pool, EveryThirtyMinutesCondition) }).expect("ジョブの作成に失敗しました"); // 15分に一回 let pool_clone = pool.clone(); let every_fifteen_minutes_job = Job::new_async("0 */15 * * * *", move |_, _| { let inner_pool = pool_clone.clone(); Self::send_message_with_deadline(inner_pool, EveryFifteenMinutesCondition) }).expect("ジョブの作成に失敗しました"); scheduler.add(daily_job).await.expect("スケジューラへジョブを追加するのに失敗しました。"); scheduler.add(hourly_job).await.expect("スケジューラへジョブを追加するのに失敗しました。"); scheduler.add(every_thirty_minutes_job).await.expect("スケジューラへジョブを追加するのに失敗しました。"); scheduler.add(every_fifteen_minutes_job).await.expect("スケジューラへジョブを追加するのに失敗しました。"); Ok(Self { router, scheduler, }) } // バッチ内の処理はほぼ同じことをやっているので共通関数として定義 fn send_message_with_deadline(pool: PgPool, cond: impl JobCondition + Send + 'static) -> Pin<Box<(dyn Future<Output = ()> + Send)>> { let inner_pool = pool.clone(); let http_client = HttpClient::new(); Box::pin(async move { let line_group_repository = LineGroupRepository::new(inner_pool); let all_line_group = line_group_repository.get_all() .await .expect("全てのline_group取得に失敗しました"); for line_group in all_line_group.iter() { if let Some(chousei_id) = line_group.chousei_id.clone() { let group_count = http_client.count_group_members(line_group.id.clone()) .await .expect(format!("line_groupの人数取得に失敗しました。id: {}", line_group.id).as_str()) .try_into() .expect("グループカウントをusizeに変換できませんでした"); let res = http_client.get_chousei_csv(chousei_id) .await .expect("調整さんのCSV取得に失敗しました"); // グループ内の人数より調整さんの回答者数が少ない場合はメッセージをプッシュします if res.member_info_map.len() < group_count { if let Some(deadline_date) = line_group.deadline_date { if cond.check(deadline_date) { // 実行時間ごとに条件が異なるため、JobConditionトレイトを使用して条件を入れ替え可能にしています。 let message = "調整さんに回答してください!".to_string(); http_client.push_message(line_group.id.clone(), vec![message]) .await .expect("lineのメッセージ送信に失敗しました"); } } } } } }) } } 少しだけテクいのは、send_message_with_deadline関数でJobConditionトレイトを使用しているあたりです。 ...

2023年11月20日 · にあえん

調整さんで絶対に回答させるline botを作ってみた【6】

会社で発表したら緊張しすぎて手汗がすごい、ナナオです。 今回でおそらく完成させたい、LineBotのヤバいさんを早速実装していきます。 line_groupテーブルに調整さんのIDを設定できるようにする まずはマイグレーションを追加します。 sqlx-cliを使用しましょう。 sqlx migrate add add_column_chousei_id 作成されたマイグレーションファイルを実装します。 PostgreSQL: Documentation: 18: ALTER TABLE 調整さんのIDは小文字の英数字32桁からなる文字列なので、そのように定義します。 -- 調整さんのイベントIDを設定できる列を追加 ALTER TABLE "line_group" ADD COLUMN "chousei_id" varchar(32); この定義だと1グループにつき一つのイベントまでしか設定できませんが、そんなに立て続けに同じグループでイベントの出欠を取ることはないと信じています。 一旦この状態でshuttleを起動して、マイグレーションが問題なく適用されるか確認します。 cargo shuttle run DBを確認してみます。 postgres=# select * from line_group; id | deadline_date | chousei_id -----------------------------------+---------------+------------ C480b2f8b56ecaf62c2033867e2ff78b2 | 2023-11-12 | (1 行) 既存データにchousei_idが追加されています! 続けて、LineGroupのモデルにchousei_idプロパティを追加してあげましょう。 #[derive(Debug, FromRow, Serialize, Deserialize)] pub struct LineGroup { pub id: String, pub deadline_date: NaiveDate, pub chousei_id: Option<String>, } 期日設定を行うCommandの実装にLineGroupの初期化処理があるので、ここにchousei_idの初期化も追加しておきます。 impl BotSetDeadlineCommand { async fn new( pool: Arc<PgPool>, group_id: &str, deadline_date: &str, ) -> Self { let repository = LineGroupRepository::new(&pool); let id = group_id.to_string(); let line_group_option = repository.select(id.clone()) .await .expect("期日設定中のline_groupの取得に失敗しました"); let deadline_date = NaiveDate::parse_from_str(deadline_date, "%Y-%m-%d") .expect("日付型への変換に失敗しました"); let line_group = match line_group_option { Some(mut line_group) => { line_group.id = id; line_group.deadline_date = deadline_date; line_group }, _ => LineGroup { id, deadline_date, chousei_id: None, } }; Self { pool, line_group } } } 期日設定によってchousei_idがリセットされないように、line_groupの初期化処理の前に既存のline_groupを取得するようにしました。 ...

2023年11月17日 · にあえん