ActiveStorage の variant がずっとnilを返す
結論から先に書くと、添付ファイルを設定しているmodelの主キーをstringに設定していることが原因でした。
ActiveStorage用に生成されるmigrationファイルではpolymorphic関連先のid (active_storage_attachments tableのrecord_idカラム) がintegerであることを前提にしています。
なので以下のように t.reference で外部キー制約を設定しています。
# 20230328082729_create_active_storage_tables.active_storage.rb t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
今回、主キーをstringに設定したProduct モデルで
# model/product.rb has_one_attached :image
としていたので、以下ステップで関連が壊れていました。
- step.1 integerカラムのrecord_id にstringのprodct.id が渡される
- step.2 active_storage_attachments tableレコードにsaveする際にrecord_idが0に変換され保存される
- step.3 関連が壊れているのでvariant発行時のクエリでが添付ファイルを見つけられず nil を返す
他のモデルでもactiverecordを使うかもしれないのと、Productモデルのidをstringにする必要もなくなっていたので、今回はProductモデルの主キーをdefaultのintegerに戻して解決としました。
いやー
ハマった
Arcstar Smart PBX の端末にGRANDSTREAMのIP電話を設定する
ArcStart Smart PBXを使っています。
契約時に設定済みのIP電話 (Panasonic KX-HDV130) を1台買っていたのですが、新しい事務所ができたので追加で欲しくなりました。
調べてみるとKX-HDV130は中古しかなかったのと中古でも少々高かったので、Amazonでたくさん売っている GRANDSTREAMのIP電話を買って設定してみることにしました。
ちょっとハマったのですが以下で繋がりましたのでチャレンジされる方の参考になれば幸いです。
接続したIP電話機
GRANDSTREAM GXP1615www.amazon.co.jp
adminでログイン
初期設定では user名:admin / パスワード:admin でログインできるはず。
ログインするとパスワードの設定を促されるので、好きなパスワードに変更します。
アカウント → アカウント1 → 一般設定 でSIPサーバー情報を入力
アカウント名 / 名前
液晶ディスプレイに表示される名前です。内線番号とかで良いと思います
SIPサーバー
ここではまりました。
Arcstart Smart PBXの管理画面で表示されるSIPサーバー名 (FQDN)を入力してもなぜか名前解決できないようで、nslookupで調べてIPアドレスを直接指定する必要がありました。
ポートもwell know port番号ではなく "35790" を指定する必要があるので注意。
参考:Arcstar Smart PBX接続条件について (NTTコミュニケーションズ株式会社)
SIPユーザーID
Smart PBXのユーザID
認証パスワード
上で入力したSmart PBXユーザID用のパスワード
その他は入力なしでOKです。
これでつながるはず
SinatraでActiveRecordを使ってMigrationしたい on docker-compose
1. docker-compose.ymlの修正
Sinatraが動いているコンテナとdb (postgresql) が動いているコンテナをリンクできるようにする。
具体的にはSinatraコンテナ設定に enviroment, depends_on, links を追加
version: '3.4' services: sinatra_server: image: sinatra build: context: . dockerfile: ./Dockerfile volumes: - ./db:/app/db ports: - 26166:26168 environment: # デプロイ環境切り替え用 - APP_ENV=development depends_on: # postgresqlコンテナがアップしてから起動 - db # database コンテナの名前で指定 links: # sinatra_serverとdbをリンク - db # database コンテナの名前で指定 db: image: postgres:15 ports: - 5432:5432 environment: - POSTGRES_PASSWORD=postgres - POSTGRES_USER=postgres volumes: - dbdata:/var/lib/postgresql/data volumes: dbdata: # データ永続化用ボリュームコンテナ
2. SinatraからActiveRecord経由で使うDBを作成
# コンテナ起動 $ docker-compose up -d # クライアントソフトから接続 $ psql -h localhost -U postgres -d postgres # USER作成 postgresql# CREATE USER sinatraserver WITH PASSWORD 'postgresql'; # DB作成 postgresql# CREATE DATABASE sinatra_db OWNER = sinatraserver TEMPLATE = template0 ENCODING = 'UTF8' LC_COLLATE = 'C' LC_CTYPE = 'C';
3. database.ymlを用意
configディレクトリを掘ってその中に2で作ったDBと整合するconfig/database.ymlを作成
development: adapter: postgresql encoding: utf8 collation: C # LC_COLLATE ctype: C # LC_CTYPE database: sinatra_db host: db username: sinatraserver password: postgresql
4. Ruck up設定ファイル (config.ru) にActiveRecord設定を追加
# gem の bundle require 'rubygems' require 'bundler' Bundler.require # ActiveRecord設定 ActiveRecord::Base.configurations = YAML.load_file('config/database.yml') ActiveRecord::Base.establish_connection(ENV["APP_ENV"].to_sym) # to_symでシンボルに変換しないとエラーになる # 起動ファイル require './server.rb' # 起動 run Sinatra::Application
これでDBとの接続設定は終わり。
4. rakeコマンドを使えるようにRakefileを作成
ActiveRecordのコマンドを使えるようにRakefileをアプリケーションrootに作成。
require 'sinatra/activerecord' require 'sinatra/activerecord/rake'
5. migrationファイルを生成
# Sinatraコンテナに入る $ docker-compose up sinatra_server bash # migrationファイル生成rakeコマンドを発行 # volumes: - ./db:/app/db でマップしているので、このコマンドでmigrationファイルがホスト側のdbディレクトリに出てくる sinatra_server# bundle exec rake db:create_migration NAME=new_table
6. migrationファイルの中身を書く
ホストPCで5で生成されたmigrationファイル(db/migrate/yyyymmdd*****_new_table.rb)の中身を書く。
書き方はRailsのmigrationドキュメントに詳しく書いてあります。
7. migrate 実行
# Sinatraコンテナに入る $ docker-compose up sinatra_server bash # migrateコマンド発行 sinatra_server# bundle exec rake db:migrate == 20221124062056 CreateNewTable: migrating ============================= -- create_table(:new_tables) -> 0.0088s == 20221124062056 CreateNewTablesTable: migrated (0.0089s) ====================
参考記事
sinatra on docker-compose を Rubymine でデバッグしたい
Railsしかいじったことがないのですが、Stripeのサンプルアプリがsinatraで書かれていたのでdocker-composeで動かしてみました。
そこまではわりとすんなり行ったのですが、Rubymineによるデバッグ環境構築で手こずったので備忘録。
環境
Stripeのサンプル動かすのにはまったところ
Webドキュメントには "ruby server.rb" で起動しろと書いてあるのですがこれではだめで、サンプルアプリzipのReadmeにある "ruby server.rb -o 0.0.0.0" が正解です。
もういっこ。
vscodeのテンプレートからDockerfileとdocker-compose作った場合、Gemfile.lockを先に作っておかないとDocker buildでこけます。
今回はローカル環境を使わないので、以下のようにしてリモート環境 (Docker) で作ったGemfile.lockをローカルにコピーします (Gemfileの内容は下記参照)。
$ docker run --rm -v "$PWD":/app -w /app ruby:3.0.0 bundle install
Rubymineでデバッグするための準備
debug-ruby-ide と debase の2つのgemがインストールされている必要があります。
ただ2022.10.25現在、私の環境では3.0.0より新しいrubyだとdebug-ruby-ideのインストール失敗するので、rubyのimageには3.0.0を指定しています。
それと、3.0.0以上のRubyを使う場合、debaseのバージョンは0.2.5.beta2 以上でないとダメだそうです。
DockerfileとGemfileはこんな感じ。
Dockerfile
# ruby:slimだと gem isntallの native extensionのbuildで失敗する # 3.0.0 より新しいと debug-ruby-ide.gem のインストールで失敗する FROM ruby:3.0.0 LABEL Name=checkout_test Version=0.0.1 # throw errors if Gemfile has been modified since Gemfile.lock RUN bundle config --global frozen 1 WORKDIR /app COPY . /app # gem install時の develpment tools first 系エラー用 RUN apt-get update RUN apt-get install -y ruby-dev COPY Gemfile Gemfile.lock ./ RUN bundle install CMD ["ruby", "server.rb", "-o", "0.0.0.0"]
Gemfile
source 'https://rubygems.org/' gem 'sinatra' gem 'stripe' # Ruby 3.0 以降はWEBrickが入っていないようなので、アプリケーション・サーバーにpumaをインストール gem 'puma' group :development do gem 'debase', '>= 0.2.5.beta2' gem 'ruby-debug-ide' end
Rubymineの設定
※ docker-compose up -d で普通に動くところまでできているとします
Ruby 実行環境の設定
RubymineはRailsと違ってsinatraようのプリセットを持っていないので、RubymineでSinatraをdebugするときはruby scriptとして起動する必要があります。
なのでその実行環境を設定します。
2. Stripeのサンプルアプリ (sinatra) 起動コマンドを設定
デバッガー起動の前に微調整
上で設定したrubyの実行環境設定でデバッガーを起動すると、Rubymineがport設定を以下のように上書きしてsinatraが起動します。
ports: - 1234:1234 - 26166:26168
最初の1234はRubymineのデバッガーが使うようなので無視。
次の26166 (local):26168 (remote) が罠です。
ホストでは26166をlistenしてリモートの26168に繋ぐということなんですが、
Stripeのサンプルアプリはport 4242をlistenするようになっているのでこのままだとブラウザの要求が届きません。
なので下記のように変えてあげる必要があります。
- Sinatraはリモート環境 (docker) で動いているので、26168をlisten
- jsのRedirect (決済完了後に発生) はホスト環境のブラウザで発生するので 26166を指定
- デバッガー動かさないときも同じように動かせるように、docker-compose.ymlも最初から 26166:26168を入れておくとよい
具体的には以下のようになります。
server.rb
require 'sinatra' require 'stripe' # This is your test secret API key. Stripe.api_key = '' set :static, true set :port, 26168 # ← 4242から26168に変更
public/checkout.js
const { error } = await stripe.confirmPayment({ elements, confirmParams: { // Make sure to change this to your payment completion page return_url: "http://localhost:26166/checkout.html", // ←Redirect先のポート設定を4242から26166に変更 (26168じゃないので注意) }, });
docker-compose.yml (port設定)
ports: - 26166:26168
これで必要な設定全部終わりです。
PICを消去も書き込みできない子にしてしまった
やってしまいました。
なーんも考えないで参考コードをコピペコンパイル書き込みしたが最後、EraseもProgrammingもできない子になってしまいました。
私の環境は PIC12F675 + PICKIT4 ですが、10年以上前から同様の問題に関する質問が見つかるのでデバイスに関わらず共通の罠なんだと思います。
この状態になってしまうのは、CONFIGレジスタの設定を "内部クロック + /MCLRピンOFF (外部リセットを使わずにGPIOとして使用)" にしたFWを焼いた時です。
MCCが吐くコードだと以下の状態です。
#pragma config FOSC = INTRCCLK #pragma config MCLRE = OFF
なんでこうなるか
PICKITからReset制御ができないため、VPPの電圧がプログラミング電圧 (9V) になるまえにプログラムが走ってしまう (Resetが解除されてしまう)からと思われます。
復旧方法
step.1
/MCLRピンを9Vでプルアップする。
私は50kでプルアップしましたが10kでも良いと思います。
step.2
MPLABでPICKITからの給電に設定してFW書き込み。
これでPICが立ち上がる前に確実にVPPがプログラム電圧になるので、無事消去と書き込みができるようになります。
新しく書き込むFWでは必ず MCLRE=ON にしましょー
Cocoonでhas_oneな関連モデルのフォームを作りたいとき
ひさびさのRailsネタ。
has_manyな関連モデルを一挙に 生成 / 更新 / 削除 できるフォームを作るのにとても便利なcocoon。
has_oneな関連モデル用フォームでも使いたいのだけど、link_to_add_association ヘルパーを普通に使うとうまく動きません。
以下のように :force_non_association_create オプションを入れると期待どおりに動くようになります。
= link_to_add_association('add something', @form_obj, :comments, :force_non_association_create => true)
実際の動作は以下の感じです。
- 保存済みのレコードが無い場合は新規作成
- 保存済みのレコードがある場合は最後にlink_to_add_associationで追加された内容で登録済みレコードを更新
※ 参考サイト
github.com
Amazon DashボタンでiTunesを操作するぞ
うちにもDashボタンが来たので何かしてみようということで、
気分じゃない曲がかかったらDashボタンで次の曲へスキップ
をやってみることにしました。
超簡単だよ!
まずは以下サイトを参考にボタン検知できるようにします (takustaquさんあざます!)。
次に iTunesを操作するライブラリをインストール。
$ npm install --save itunes-remote
最後に上記サイトのtestコードに3行追加するだけ!
const DashButton = require("dash-button"); const PHY_ADDR = "xx:xx:xx:xx:xx:xx"; // 追加コード1行目 const itunesRemote = require('itunes-remote'); Let button = new DashButton(PHY_ADDR); button.addListener(() => { // 追加コード2行目 itunesRemote("play", () => {}); // 追加コード3行目 itunesRemote("next", () => {}); });