好きなものだけ書く。ポジティブに。

好きなことを楽しく。プログラミング、写真、音楽、ガジェットとか。

Durable Functionsで引数を受け取る方法を解説

おつかれさまです!のぶじゃすです。

Azure 関数アプリ で Timeoutの制限や複雑な処理を整理するために Durable Functions という機能があります。とても便利なのでAzure 関数アプリを使っている人は是非試してみると良いと思います。

learn.microsoft.com

今回は、Durable Functions でHTTPトリガーやService Bugトリガーで処理に引数に受け渡す方法が明記されている記事が見つけられなかったので備忘録的な記事です。さくっと結論のみ書きますー

引数の渡し方

learn.microsoft.com

const client = df.getClient(context);
const instanceId: string = await client.startNew(
  request.params.orchestratorName,
  { input: "input string!" }
);

startNew 関数*1 の第2引数の input プロパティ*2 にJSONシリアライザブルな値を渡せる。

引数の受け取り方

const someHandler: OrchestrationHandler = function* (
  context: OrchestrationContext
) {
  const input = context.df.getInput();

OrchestrationContextdf.getInput() 関数*3 で渡した値が受け取れる。

アクセスありがとうございます🙇‍♂️

ここまで読んでいただき誠にありがとうございました。もしこの記事が役に立ったらはてブや、Twitterのフォローしていただけると大変喜びます😊

BigQuery の js api ライブラリ @google-cloud/bigquery を JWT 認証で実行したい

おつかれさまです!のぶじゃすです。

@google-cloud/bigquery を実行する際にJWT認証で実行出来るようにしたい。サービスアカウントを使って利用していたりするとJWT認証じゃないと厳しい場面が発生する。(credentialファイルを置けないとか)

「@google-cloud/bigquery を JWT 認証で実行したい」という文字が書いてある。鍵をもってこちらを見ている女性が背景。

具体的に今回やりたいのは、googleapis と同様に

const auth = new google.auth.JWT(
  env.GOOGLE_EMAIL,
  undefined,
  env.GOOGLE_PRIVATE_KEY, 
  [
    "https://www.googleapis.com/auth/drive",
  ]
);
export const driveService = google.drive({ version: "v3", auth });

みたいな感じで BigQuery のAPIを実行したい。

結論

まずは結論から。これで動きました。調べた事を後述していますので気になる方はどうぞ。

const auth = new google.auth.JWT(
  env.GOOGLE_EMAIL,
  undefined,
  env.GOOGLE_PRIVATE_KEY, 
  [
    "https://www.googleapis.com/auth/drive",
  ]
);

new BigQuery({
  authClient: auth,
  projectId: `{env.GCP_PROJECT_ID}`
});

BigQuery の API がどうなっているのか調べてみる

BigQuery クラスの constructor は @google-cloud/commonGoogleAuthOptions を継承している
export interface BigQueryOptions extends GoogleAuthOptions

nodejs-bigquery/src/bigquery.ts at main · googleapis/nodejs-bigquery · GitHub

@google-cloud/commonGoogleAuthOptions の実体は google-auth-libraryGoogleAuthOptions
export {GoogleAuthOptions} from 'google-auth-library';

nodejs-common/src/index.ts at main · googleapis/nodejs-common · GitHub

google-auth-libraryGoogleAuthOptions の実体はこれ

google-auth-library-nodejs/src/auth/googleauth.ts at main · googleapis/google-auth-library-nodejs · GitHub

googleapis のAPIがどうなっているのか調べてみる

Drive クラスの constructorgoogleapis-commonGlobalOptions を利用している
 constructor(options: GlobalOptions, google?: GoogleConfigurable) {

google-api-nodejs-client/src/apis/drive/v3.ts at main · googleapis/google-api-nodejs-client · GitHub

googleapis-commonGlobalOptionsauthgoogle-auth-library の OAuth2Client や GoogleAuth を渡せる
export interface GlobalOptions extends MethodOptions {
  auth?: GoogleAuth | OAuth2Client | BaseExternalAccountClient | string;
}

nodejs-googleapis-common/src/api.ts at main · googleapis/nodejs-googleapis-common · GitHub

new google.auth を探ってみる

googleapis-commonAuthPlusgoogle.auth の実体
export class GoogleApis extends GeneratedAPIs {
  private _discovery = new Discovery({debug: false, includePrivate: false});
  auth = new AuthPlus();

google-api-nodejs-client/src/googleapis.ts at main · googleapis/google-api-nodejs-client · GitHub

AuthPlusJWTgoogle-auth-libraryJWT
export class AuthPlus extends GoogleAuth {
  JWT = JWT;

nodejs-googleapis-common/src/authplus.ts at main · googleapis/nodejs-googleapis-common · GitHub

google-auth-libraryJWTOAuth2Client の継承
export class JWT extends OAuth2Client

google-auth-library-nodejs/src/auth/jwtclient.ts at main · googleapis/google-auth-library-nodejs · GitHub

ということで GoogleAuthOptionsJWT を渡せる手段がありそうか探してみる

export interface GoogleAuthOptions<T extends AuthClient = JSONClient> {
  /**
   * An `AuthClient` to use
   */
  authClient?: T;

JSONClient という型がある

export type JSONClient =
  | JWT
  | UserRefreshClient
  | BaseExternalAccountClient
  | ExternalAccountAuthorizedUserClient
  | Impersonated;

JWT が渡せそうだ!

動いた

ということで以下のようにしたら動きました。納得感持って繋げられたのでよかった。

new BigQuery({
  authClient: auth,
  projectId: `{env.GCP_PROJECT_ID}`
});

アクセスありがとうございます🙇‍♂️

ここまで読んでいただき誠にありがとうございました。もしこの記事が役に立ったらはてブや、Twitterのフォローしていただけると大変喜びます😊

Next.JS App Router でURLクエリー文字列を取得するのはクライアントコンポーネントとpage.tsxで出来る

きっとURLクエリー文字列を使ってなにか魅力的なものを検索していたりするはずの女性のイラスト
なにか検索していますね。クエリー文字列をきっと使っているはずです

おつかれさまです!のぶじゃすです。

URLクエリー文字列を取得する方法

https://example.com/anypage?search=abcdefg の様なURLのクエリー文字列をNext.jsのApp Routerを用いて取得する方法について解説します。

Next.JS App Router ではクエリー文字列を取得する2つの主な方法があります。

  1. クライアントコンポーネントで useSearchParams を使う方法
  2. page.tsxsearchParams を参照する方法

Next.jsのApp Router導入後、ClientとServerの両方を考慮してコーディングする必要が出てきました。 書き味が少し違うので何が何に対比していて、どこに制限があるのかを意識しないと素早く書けないです。 この辺りが頭に入っているのといないのでは、設計時に見通せる世界が広がります。

1. クライアントコンポーネントで useSearchParams を使う方法

"use client";
import { useSearchParams } from "next/navigation";

export const PresentationSearchPanel = () => {
  const sParams = useSearchParams();
  const search = sParams.get("search");
  return <div>{search}</div>;
};

詳細についてはNext.js公式ドキュメントを参照してください。 nextjs.org

2. page.tsxで searchParams を参照する方法

export default async function AnyPage({
  searchParams,
}: {
  searchParams: z.infer<typeof formSchema>;
}) {
  return <div>{searchParams.search}</div>
});

詳細についてはNext.js公式ドキュメントを参照してください。 nextjs.org

検証環境

package.json 抜粋

"next": "^13.5.2",

next.config.mjs 抜粋

const config = {
  reactStrictMode: true,
  experimental: {
    serverActions: true,
  },
};

アクセスありがとうございます🙇‍♂️

ここまで読んでいただき誠にありがとうございました。もしこの記事が役に立ったらはてブや、Twitterのフォローしていただけると大変喜びます😊

Phaser 3.60.0 で六角形(Hexagon)を描く方法、あるいは本業以外のプログラミングを書くということ

Phaserを試してみたくてやってみた。 sinとcosが思い出せなくて泣いた。手触り感的には結構悪くない。きっとこういうメソッドあるんだろうなーが当たる確率が高い。

stability.ai で生成したプログラミングで正六角形を書く女性
stability.ai で生成したプログラミングで正六角形を書く女性

全然まったく、決して本業とは関係ない。本業はそれなりに忙しくさせてもらっているが、スキマで違うプログラミングを書いたりすると気分転換になるし楽しい。

完成がいつになるのかは分からない。それでもいい。本業でも発想が柔らかくなったりいい効果も少しだけある。

「今日は夜1時間だけやれるように時間作るぞ」と考えて生きていると少し前のめりに動ける。ほんのちょっとだけ潤う。

See the Pen Phaser Hexagon Example by noblejasper (@noblejasper) on CodePen.

Rails7 で button_to の data-confirm の挙動が変わって data.confirm から form.data.confirm に変わってました

おつかれさまです!のぶじゃすです。

最近Rails7でWebアプリケーションを開発しているのですが、削除の処理などの後戻りができないときや、事前に確認が必要な処理を書く際にブラウザのconfirm機能を使って確認ダイアログを出す処理を出したい場面がありました。

Rails7でこの機能の処理が変わってしまっていてハマりました。今回はある程度詳細な理由も調べてみました。

想定以上にアタフタ困ってしまった動画はこちら

実は動画撮影中にこの変更にハマってしまいました。まんまとハマって困りまくり焦りまくりの動画はこちらです。面白かったら高評価おねがいしますw


www.youtube.com

Rails7より前の button_to で confirm を出す方法

Railsでは button_to などで簡単にconfirmを出す機能がありました。

<% button_to "削除", any_path(any), { method: :delete, data: { confirm: "本当に削除してもいいですか?" } } %>

のように書くことで確認ダイアログが表示されていました。

Rails7 ではこれだと出ない

Rails7ではこの方法では確認ダイアログが表示されず、そのまま処理されてしまいます。これでは簡単に削除できすぎてしまいます。

そこでいくつか調べた所、 解決策を見つけました。

Rails7 で button_to で confirm を出す方法

<%= button_to "削除", rabbit_weight_path(@rabbit, weight), { method: :delete, form: { data: { turbo_confirm: "本当に削除しますか?" } } } %>

data: { confirm: "確認" }form: { data: { turbo_confirm: "確認" } } に直す事で正しく表示されます。

挙動としてはこれで正しくなりましたがなぜなのかはよく分かりません。どういう処理でこれが動作しているのか少し調べてみました。

2023/02/15 追記: form をつけなくても confirmturbo_confirm にすると動く

id:JunichiIto さんにコメントを頂きました。ありがとうございます!

該当のPull Requestはこちらです Make data-turbo-confirm work with multiple submitters inside a form by feliperaul · Pull Request #564 · hotwired/turbo · GitHub

このPull Requestがmergeされた Turbo 7.2 (turbo-rails 1.3) 以降では <form data-turbo-confirm="xxx" でも動きますが、 <button data-turbo-confirm="xxx" のようにbuttonタグに設定する形でも反応してくれるようになりました。

具体的にはこんな感じです

<%= button_to "削除", rabbit_weight_path(@rabbit, weight), { method: :delete, data: { turbo_confirm: "本当に削除しますか?" } } %>

これはありがたいですね。出力されたHTMLを見た時に見つけやすくなりますし、1つのformの中で複数のボタンがある場合で別のconfirmを出したい場合などにも使いやすそうです。

form: でくくると何が起こるのか

button_to の第3引数で form: { data: を設定するとそれ自体が form タグで囲まれます。そして form タグのアトリビュートに data-turbo-confirm が設定されます。

具体的には

erbに下記のように書くと

<%= button_to "削除", rabbit_weight_path(@rabbit, weight), { method: :delete, form: { data: { turbo_confirm: "本当に削除しますか?" } } } %>

HTMLとしてはこのようにレンダリングされます。

<form data-turbo-confirm="本当に削除しますか?" class="button_to" method="post" action="/rabbits/1/weights/2">
  <input type="hidden" name="_method" value="delete" autocomplete="off">
  <button type="submit">削除</button>
  <input type="hidden" name="authenticity_token" value="..........................." autocomplete="off">
</form>

そしてどうやってconfirm処理が動いているのか

Rails7ではturboが動いているため、以前のバージョンと変わったようです。

ということで turbo のコードを読んでみましょう。

  1. confirm 処理を探して読んでみた
  2. form_submission.ts にそれっぽいコードを見つけました
  3. FormSubmission というクラスでは formElement を constructor で受け取ります
  4. confirmationMessagedata-turbo-confirm を探します
  5. needsConfirmation では confirmationMessage が存在した場合に true を返します
  6. needsConfirmationtrue の場合には confirmMethod が発火します
  7. confirmMethod ではJavaScript標準(ブラウザ標準?)の cofirm 処理を動かしています

この辺りの処理です

github.com

正しい対応っぽいので大丈夫そう

コードをざっと読んでみた所、特におかしな所はなく想定されている挙動のようだったのでこれで安心して開発が進められます。

アクセスありがとうございます🙇‍♂️

ここまで読んでいただき誠にありがとうございました。もしこの記事が役に立ったらはてブや、Twitterのフォローしていただけると大変喜びます😊

それではよいRails7ライフを!

Rails で多対多のリレーションをした時に、joinレコードも同時にnew & save したい

おつかれさまです!のぶじゃすです。

最近よくRails7でコードを書いています。modelsを作っていく中でどうしても多対多のモデルになることがあります。

多対多のリレーションをするときには中間モデル(join モデル)を作る必要があるのですが、create時に中間モデルも一緒にcreateする必要があります。自動でcreateされると思っていたのですが、されなくて困りました。色々試して解決できた&あまり明確に書いてある記事が見当たらなかったので備忘録的にBLOG書いておきます。

サーバの線につまづいて転びそうになる人のイラスト
モデル周りはよく躓きますよね

  • 今回使用したバージョン
  • うさぎと親が多対多
    • models/rabbit.rb
    • models/breed.rb
    • models/user.rb
  • has_many :through関連付け
  • 元々のうさぎ新規作成機能
    • controllers/rabbits_controller.rb#create
  • 間違った方法
  • 正しい方法
  • 最終的な controller は current_user.save にしました
    • controllers/rabbits_controller.rb#create
  • アクセスありがとうございます🙇‍♂️
続きを読む

【2022年版】今更ちゃんとRuby on Railsをキャッチアップしてみる。ついでに最新Ruby on Rails ver 7.0.0で!

おつかれさまです!のぶじゃすです。

今回はRuby on Railsの新規バージョン7.0.0が2021/12/15にリリースされたので、キャッチアップもかねて学習していこうと思います。

私は Ruby on Rails (以下Rails)を使っている会社に勤めているのですが、普段はSalesforce開発が主なので普段は全くと言っていいほどRailsに触れていません。先日偶然バージョン7が出たというツイートを見たのでスキマタイムで何かしらWebサービスを作る所までキャッチアップしていこうかなと思います。

対象読者

今回はそもそもの基本をやっていきます。

  • Ruby on Railsはほとんど触ったことない
  • プログラミングはざっくり分かる
  • なにか作ってみたい

という人むけに書いていきます。分かりづらい事などありましたらコメント頂けたら嬉しいです。

「読者」とか書きましたが今回は動画です

Ruby on Rails キャッチアップ動画を作成してみました。この動画を見ていただければ全部わかります。是非ご覧ください!

www.youtube.com

細かい説明や補足などを以下にテキストとして記載しています。

今後キャッチアップしていきたいこと

  • 体重削除機能を作る
  • 誕生日から年齢を計算して表示する
  • heroku に deploy して世界中からアクセス可能に
  • フォントを綺麗にする
  • firebase でログイン機能を作る
  • localeを設定してみる

撮影中にググったサイト紹介

action_text のミスで調べたサイト

blog.alea12.net

dependent オプション

qiita.com

本番で見ていた台本はこちら

本番はこんな台本を見ながら撮影していました。よく左側をみているのはこれですw

$ rails new demo
$ cd demo
$ rails g scaffold rabbit name:string birthday:date description:text
$ code .
# migration
# model
# controller
# view/index
$ rails db:migrate
$ rails s
# /rabbits
@application.html.erb
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
# /rabbits.json
# controller
@models/rabbits.rb
validates_presence_of :name, :birthday
$ rails c
>> Rabbit.first
>> Rabbit.create! name: 'aaaa'
$ rails action_text:install
$ bundle
$ rails db:migrate
@ models/rabbits.rb
has_rich_text :description
# create rabbit demo
$ ./bin/importmap pin local-time
@ application.js
import LocalTime from "local-time"
LocalTime.start()
@ _rabbit.html.erb
<p>Weights <%= time_tag rabbit.created_at, "data": {"local": "time-ago"} %></p>
$ ./bin/importmap pin local-time --download
$ rails g resource weight rabbit:references gram:integer note:string 
$ rails db:migrate
>> Rabbit.first.weights
@ models/rabbit.rb
has_may :weights
>> reload!
>> Rabbit.first.weights
>> Rabbit.first.weights.create! gram:1000
@ rabbits/show.html.erb
<%= render "rabbits /weights", rabbit: @rabbit %>
@ rabbits/_weights.html.erb
<h2>Weights<h2>

<div id="weights">
  <%= render rabbit.weights %>
</div>

<%= render "weights/new", rabbit: rabbit %>
@ weights/_weight.html.erb
<div id="<%= dom_id(weight) %>">
  <%= l(weight.create_at %>: <%= weight.gram.to_formatted_s(:delimited) %> g
</div>
@weights/_new.html.erb
<%= form_with model: [ rabbit, Weight.new ] do |form| %>
  <div>
    <%= form.number_field :gram %>g<br>
    <%= form.submit %>
  </div>
<% end %>
@rabbits/_rabbit.html.erb
<p>体重を記録した回数: <strong><%= rabbit.weights.count %>回</strong>
@weights_controller.rb
  before_action :set_rabbit

  def create
    @rabbit.weights.create! params.required(:weight).permit(:gram)
    redirect_to @rabbit
  end

  private

  def set_rabbit
    @rabbit = Rabbit.find(params[:rabbit_id])
  end
@routes.rb
  resources :rabbits do
    resources :weights
  end
# create weight demo
# turbo stream demo
@rabbit/show.html.erb
<%= turbo_stream_from @rabbit %>
@models/weights.rb
broadcasts_to :post
# live refresh demo
$ rails c
>> Rabbit.find(1).weights.last.destroy
>> Rabbit.find(1).weights.last.update! weight: 2000
$ rails test
dependent: :destroy 

Raills7いいぞ。JavaScriptの事考える時間がめっちゃ減る

いくつか動画外でもさわっていて、Rails7めっちゃ楽しいです。フロントエンドの事を考えなくていいのでかなり快適。 機能の開発に時間を注げる感じです。

今後もキャッチアップしつつBLOG, 動画出していきたいと思っています。

よいRails7ライフを!