respond_to |format| は複数のフォーマットがなくても便利です(翻訳)

July 21, 2016 blog.arkency.com 原文です。英語の勉強のために興味ある記事を日本語訳しています。間違いあればご指摘ください。(#)は私の補足です。


controllerのactionでHTMLのレンダリングしかしない場合、respond_toを使う理由が少ないと考えられるかもしれません。それは恐らくRails Scaffoldの影響です。

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

しかし、このコードが例外に繋がる非常に厄介な状況が1つあります。それは、クライアントが間違ってあなたのページをXML形式でリクエストしたときです。
試してみてください。

curl -H "Accept: application/xml" localhost:3000/posts

例外が発生します。

Missing template posts/index, ... :formats=>[:xml]

クライアントは、サーバ側に問題あることを示す500エラーを受け取ります。

<?xml version="1.0" encoding="UTF-8"?>
<hash>
  <status>500</status>
  <error>Internal Server Error</error>
</hash>

この問題(# エラー)は、Railsアプリの例外トラッカーによってログに書き出されます。ただし、クライアントがアプリのランダムなページでXMLを取得できると考えている事実に対して、私たちが対応できることは何もありません。私たちはこれが起こっても通知を受け取りません。そしてあなたのWebサイトが大きくなればなるほど、そのようなバカバカしいリクエストが起こります。

しかし、これらのエラーが起こっている事実を完全に無視したいわけじゃない。そのエラーは本当の問題(即ち、リファクタリングのミスなど)を見つけるのに役立つかもしれません。

どうすれば今の状況を修正できますか?対応するフォーマットを示すために、respond_to セクションを追加するだけです。html呼び出すのにブロックを渡す必要さえありません。(# format.htmlの後に引数を渡す必要がないということ)

class PostsController < ApplicationController
  def index
    @posts = Post.all
    respond_to do |format|
      format.html
    end
  end
end

あるいは、respond_to :htmlを使うこもできます。しかし、それだけでは十分ではなく、respond_withを一緒に使う必要があります。

class PostsController < ApplicationController
  respond_to :html

  def index
    @posts = Post.all
    respond_with @posts
  end
end

上記の変更後、サポートされていないフォーマットでアクセスした場合、クライアントは406エラーを受け取ります。

<?xml version="1.0" encoding="UTF-8"?>
<hash>
  <status>406</status>
  <error>Not Acceptable</error>
</hash>

サポートされているMINEタイプ(この例ではHTML)のテンプレートに本当に問題がある場合にのみ、テンプレートにミスがあることを示す500エラーを表示します。

P.S. Post-Rails Way Book Bundleについてお知らせです。55%の割引であなたの開発現場で多いに役立ちます。


わたしの見解

複数のフォーマットの場合のみ respond_toを使うものだと思ってた。確かにscaffoldの影響だなって思う。エラー原因を区別したエラーメッセージを出すためにもrespond_toを使っていこう。

routes.rbを部分ファイルに分割する方法(翻訳)

原文(How to split routes.rb into smaller parts? | Arkency Blog / February 27, 2015)です。英語の勉強にいいと思って、興味ある記事を日本語訳しています。間違いあれば教えてください。(#)は私の補足です。


Ruby on Railを使っているアプリケーションは、ルーティングエンジンとルーティングを定義するconfig/routes.rbファイルがあります。routes.rbは開発していると非常に大きくなることがよくあります。1行追加するたびに、routes.rbの保守が難しくなります。また、開発段階で特定のパスを探すことが、どんどん困難になります。現在、routes.rbが約5000LOC(# lines of code)含まれているアプリケーションで作業しています。かなり多いですよね。解決策はとてもシンプルです。routes.rbファイルをいくつかの小さいファイルに分割することです。

ファイルの読み込み順

リクエストが来ると、routes.rbファイルは「上から下に」順番に処理されます。最初に適切にマッチしたルーティングが見つかると、リクエストは適切なコントローラーに転送されます。ファイル内で一致するパスが見つからない場合、Railsは404エラーになります。(# 分割された)ファイルの読み込む順が並び替わる可能性があるため、(# ファイルを分割した場合)名前空間の優先順位を定義できなければなりません。

解決策

次の例はroutes.rbの一部です。

ActionController::Routing::Routes.draw do
  root to: "home#index"
  get "/about
  get "/login" => "application#login"


  namespace :api do
    #nested resources
  end

  namespace :admin do
    #nested resources
  end

  namespace :messages do
    #nested resources
  end

  namespace :orders do
    #nested resources
  end
end

いくつかのデフォルト(# namespaceの指定がないルーティングのこと?)の名前空間(/home, /about, /loginのルーティングを指定した場合 )とその他 4つの名前空間があります。これらの名前空間は、私たちのアプリケーションは存在するコンテキスト(# 人間が分かりやすい形にってこと。adminは管理者だったり)をうまく定義します。よって、それらの namespace はファイルを分割するための最適な候補です。そのため、api.rbadmin.rbmessages.rb, orders.rb を作成しました。通常は、この分割ファイルをconfig/routes/ディレクトリに置きます。次のステップは上記のファイルを読み込むことです。これを行うにはいくつかの方法があります。Rails 3 をベースにしたアプリケーションでは、アプリケーション設定からルートファイルを世も込むのが一般的な方法です。最終的にapplication.rbに次の行を追加します。

config.paths["config/routes"] += Dir[Rails.root.join('config/routes/*.rb’)]

ファイルの読込み順を制御したい場合は、次のようにしてください。

config.paths["config/routes"] = %w(
      config/routes/messages.rb
      config/routes/orders.rb
      config/routes/admin.rb
      config/routes/api.rb
      config/routes.rb
    ).map { |relative_path| Rails.root.join(relative_path) }

ただし、Rails 4 以降では上記の行を追加すると例外が投げられます。Rails 4 はRails::Engineに[config / routes] キーが提供されていません。Rails 3, 4 両方のバージョンで機能する別のオプションがあります。以下が別の解決方法です。

YourApplication::Application.routes.draw do

def draw(routes_name)
    instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb")))
end

  draw :messages
  draw :orders
  draw :api
  draw :admin

  root to: "home#index"
  get "/about
  get "/login" => "application#login" 

end

パスの読み込みはActionDispatch::Routingモジュールに新しいメソッドを追加することで可能になります。Rails 4 も最初は同様の解決策でしたが、削除されました。(# 前はRailsの機能として提供されていたが削除されてたということ。) Revert "Allow loading external route files from the router" · rails/rails@5e7d6bb · GitHub (# @dhh コメント「不透明になるのでRailsでは推奨したいものではないです。必要ならとても簡単なので自分で追加してください 」 )

結論

ファイルを分割することは、routes.rbファイルと日々の開発を改善するための非常に簡単な解決策です。本記事の解決策で、日々の開発が楽になります。


翻訳は以上です。こっからはワタシのひとり言

Railsがroutes.rbファイルの分割は不透明になるって言ってる、不透明ってなんだろう。
記述の重複(DRYじゃない)により小さく分割し共通化するのではなく、1ファイルの行数が増えたことにより分割することは、ただ全体像が追いづらい状態になるだけってことか。さすがにroutes.rbが5000行になったことないけど、namespaceごとにファイル分けるのはすごくいい。

Invalid route name, already in use: 'xxxxxxxx' (ArgumentError) エラーが出るときの条件を調査する

routers.rbで同じパス名を指定して以下のエラーがでたとき、ルーティングの記載順番を変えたらエラーが消えたので、エラーが出る条件を調査した。

Invalid route name, already in use: 'xxxxxxxx'  (ArgumentError)
You may have defined two routes with the same name using the `:as` option, or you may be overriding a route already defined by a resource with the same naming. For the latter, you can restrict the routes created with `resources` as explained here: 

routes.rbに以下のルーティングを書いてる

post "sign_up", to: "session#sign_up_process" 
get "sign_up", to: "session#sign_up", as: "sign_up" 

エラーになります。

記載順を入れ替えると・・

エラーになりません。

get "sign_up", to: "session#sign_up", as: "sign_up" 
post "sign_up", to: "session#sign_up_process" 

get が上に記載されてれば、重複しても大丈夫とかあるのか???

asを記載するルーティングを get / post で入れ替える。

エラーになります。

get "sign_up", to: "session#sign_up" 
post "sign_up", to: "session#sign_up_process", as: "sign_up" 

記載順を入れ替えると・・

エラーになりません。

post "sign_up", to: "session#sign_up_process", as: "sign_up" 
get "sign_up", to: "session#sign_up" 

結論

  • ①パス名(名前なし)のルーティングの下に、①のパス名と同じ名前でasを指定するとエラーになる。
  • ①パス名(名前あり)の下に、①のas名と同じパス名を指定してもエラーにならない。

その他

どちらもasを外した場合、エラーになりません。

get "sign_up", to: "session#sign_up" 
post "sign_up", to: "session#sign_up_process" 

autoload pathsを拡張するとき、eager_loadについて覚えておいてください(日本語訳)

Appフォルダ内に新たにフォルダ追加する際、お世話になった記事なので備忘録に日本語訳にしました。原文にはない補足は(*)付けています。訳が間違っていればご指摘頂けますと幸いです。 blog.arkency.com November 9, 2014


config.autoload_paths を知ってますよね。あなたが作った .rb ファイルを置くためのディレクトリを追加できる設定です(app/* のすぐに使える機能の他に)。ここでは config.autoload_paths + = %W(#{config.root}/extras) を例にお話しします。しかし、それを最も一般的に使うのはlibディレクトリを追加するときです(特にRails2 から Rails3 への移行後)。

別の(あまり一般的ではない)ユースケースは、トップレベルのディレクトリにいくつかのコンポーネント(例えば、notification_center など)を追加するときです。コンポーネントがあなたのAppに連携するために、通常は以下のように設定します。

config.autoload_paths += %W( #{config.root}/notification_center )

or

config.autoload_paths += %W( #{config.root}/notification_center/lib )

1つだけ言及し忘れていることを除けば、この設定で問題ないです。その1つとは、本番環境でどのように機能するのか、そしてそれをうまく機能させるために対応する方法についてです。

2つのファイルを見てみましょう。

# root/extras/foo.rb
class Foo
end

and

# root/app/models/blog.rb
class Blog < ActiveRecord::Base
end

設定ファイルの内容はこのようになります。

# root/config/application.rb
config.autoload_paths += %W( #{config.root}/extras )

開発環境では大丈夫。

開発環境時の動作を確認しましょう。

defined?(Blog)
# => nil
defined?(Foo)
# => nil

Blog
# => Blog (call 'Blog.connection' to establish a connection)
Foo
# => Foo

defined?(Blog)
# => "constant"
defined?(Foo)
# => "constant"

簡単な例からわかるように、最初は何も読み込まれていません。BlogもFooも定義されていない。
それらを使おうとするとrailsの 自動読込み(autoloading)が割り込まれます。const_missing(*クラスやモジュールで定義されていない定数(またはクラス)にアクセスしようとしたときに呼び出される)が実行され、規約とbangに基づいて(*const_missing(name) の引数でクラスを検索し、存在しない場合は例外を発生させる)適切なディレクトリ内のクラスを検索します。app/models/blog.rbが読込まれ、BlogクラスはBlog定数の下で定義されるようになりました。extras/foo.rbFooクラスも同様です。

本番環境(production)ではEager Loading が作動しますよね。

開発環境とは違い、本番環境では状況が少し異なります。

defined?(Blog)
# => "constant"

defined?(Foo)
# => nil

Blog
# => Blog (call 'Blog.connection' to establish a connection)
Foo
# => Foo

defined?(Blog)
# => "constant"
defined?(Foo)
# => "constant"

Blogを初めて使おうとする前から、Blogクラスは既に読み込まれており、定義されています。どうして?eager loadingのおかげです。

本番環境で処理を早くするためにRailsは(開発環境とは)少し違った方法を使います。アプリケーションは実行前に、できるだけ多くのコードを読み込むために*.rbファイルを必要とします。これによって、アプリケーションを実行開始したときに、規約に基づいてファイルからクラスを探すことに時間を費やさず、すぐにリクエストを処理できます。

もう1つ理由があります。webサーバ(unicorn, passenger, など)がworkerを生成するためにfork形式(*リクエストを並行で処理するために、プロセスを分岐して子プロセスを生成する)を使用している場合、メモリ管理のためにCopy-On-Writeを利用できます。masterにはすべてのコードが読込まれており、workerはmasterをfork(分岐)して作成されます。masterが変更されない限り、workerはメモリの一部をmasterと共有します。それはworkerがより少ないメモリ量しか使わないことを意味します。workerは自分がmasterとメモリを共有していることを認識しておらず、workerどうしでもやり取りもできません。スレッドのようには機能しません。オペレーティングシステムだけが今のところmasterプロセスのメモリ全体をforkプロセスにコピーする代わりに、それを省略していること(メモリを共有すること)を認識しています。少なくともworkerがメモリから読み込むまで。passengerの説明、またはunicornの説明を確認してください。

しかし、私が注目しても欲しいのはBlogが定数として定義されており、(アプリケーション起動前に)読込まれている点ではありません(これはRailsの前のバージョンから変わっていません)。私はFoo定数が本番環境に読み込まれていないことを注意して欲しいです。

defined?(Blog)
# => "constant"

defined?(Foo)
# => nil

なぜそれが問題なのでしょう?eager loadingの良くない理由として、Fooがeager loadingされないということは、以下を意味します。

  • アプリケーションはHTTPリクエストがきたときにFooを探し終える必要があり、それにより少し遅くなるでしょう。1つのクラスならそこまで遅くはなりませんが。アプリケーションはfoo.rbというファイルを見つけてクラスを読み込むので、もっと遅くなります。
  • (リクエストのときにFooを探すと)全てのworkerはメモリ内でFooが定義されているコードを共有できません。Copy-On-Writeの最適化を利用できなくなってしまいます。

すべてが1つのクラスに関するものであれば、それほど問題にはなりません。しかし、いくつかのレガシーrailsアプリケーションでは、開発者がconfig.autoload_pathsに多くのディレクトリを追加しているのを見ています。そしてこれらのディレクトリにある単一のクラスが本番環境で、実行前に読み込まれていることはありません。デプロイ後にこれらのクラスの動的に読み込む必要があり、初期リクエストのパフォーマンスが低下する可能性があります。あなたが継続的な開発を行う際に、特に痛ましいことでしょう。私たち(開発者)は自分たちのデプロイ(ソースの追加・更新)によって(アプリケーションの)利用者に影響が出ること望んでいません。

どうすれば修正できますか?

config.eager_load_pathsという、あまり知られていないrails設定を使うことで目標を達成できます。

config.eager_load_paths += %W( #{config.root}/extras )

それは本番環境にどのように作用しますか?見てみましょう。

defined?(Blog)
# => "constant"
defined?(Foo)
# => "constant"

extra/foo.rbのクラスFooが自動で読み込まれただけでなく、本番環境でも事前に読み込まれています。これで問題は解決しました。
待って、それではこれからファイルを読込むのに2行書く必要があるという意味ですか?

config.autoload_paths += %W( #{config.root}/extras )
config.eager_load_paths += %W( #{config.root}/extras )

autoloadingはeager_load_pathsも使用しています。

以下の書き方をすれば2行書く必要がありません。

config.eager_load_paths += %W( #{config.root}/extras )

開発環境と本番環境はうまく機能しているようです。 autoloadingはeager_load_pathsをチェックするように設定されているためだと思います。

def _all_autoload_paths
  @_all_autoload_paths ||= (
    config.autoload_paths   +
    config.eager_load_paths +
    config.autoload_once_paths
  ).uniq
end

in Rails::Engine code.

One more thing

残念ながら私たちは多くの人がこのように書いてるのを見ました。

config.autoload_paths += %W( #{config.root}/app/services )
config.autoload_paths += %W( #{config.root}/app/presenters )

app/*は既に追加されているので、これは全く必要ありません。app/に任意ディレクトリを追加して、app/controllersapp/modelsと同じように使うことができます。ただし、コンソール、サーバ、spring サーバ(spring stop)の再起動が必要です。以下はrails 4.1.7 のデフォルトパス構成です。

def paths
  @paths ||= begin
    paths = Rails::Paths::Root.new(@root)

    paths.add "app",                 eager_load: true, glob: "*"
    paths.add "app/assets",          glob: "*"
    paths.add "app/controllers",     eager_load: true
    paths.add "app/helpers",         eager_load: true
    paths.add "app/models",          eager_load: true
    paths.add "app/mailers",         eager_load: true
    paths.add "app/views"

    paths.add "app/controllers/concerns", eager_load: true
    paths.add "app/models/concerns",      eager_load: true

    paths.add "lib",                 load_path: true
    paths.add "lib/assets",          glob: "*"
    paths.add "lib/tasks",           glob: "**/*.rake"

    paths.add "config"
    paths.add "config/environments", glob: "#{Rails.env}.rb"
    paths.add "config/initializers", glob: "**/*.rb"
    paths.add "config/locales",      glob: "*.{rb,yml}"
    paths.add "config/routes.rb"

    paths.add "db"
    paths.add "db/migrate"
    paths.add "db/seeds.rb"

    paths.add "vendor",              load_path: true
    paths.add "vendor/assets",       glob: "*"

    paths
  end
end

paths.add "app", eager_load: true, glob: "*"globに注目してください。これはapp配下のサブディレクトリです。

設定はいつでもコンソールで確認できます。

Rails.configuration.autoload_paths
Rails.configuration.eager_load_paths

念のために。

config.pathsと結論

Rails :: Engine :: Configurationを見ると、これらのメソッドがどのように定義されているか分かります。

def eager_load_paths
  @eager_load_paths ||= paths.eager_load
end

def autoload_once_paths
  @autoload_once_paths ||= paths.autoload_once
end

def autoload_paths
  @autoload_paths ||= paths.autoload_paths
end

それら(* eager_load, autoload_once, autoload)はすべて、最初の呼び出しでRails.configuration.pathspathsを受け取ります。これによりextrasディレクトリをrailsと同じ方法で設定できるという結論に至ります。

config.paths.add "extras", eager_load: true

いい感じじゃない?

注意

eager_loading( 今回の説明で話したpathの話)とActiveRecordのeager_loading( N+1問題を回避するため、事前に必要なデータを読み込む)を混同しないでください。これについても記事があります。それらの処理方法は似ていますが、完全に異なるものです。

もっと学びたいですか?

あなたがこの記事を楽しんだなら、あなたが日々のRailsプログラマーの仕事に役立つ知識を得るために、あなたが常に最新の情報を得れるように私たちのニュースレターを購読してください。 コンテンツは主にRubyRails、Web開発、そしてRailsアプリケーションのリファクタリングに焦点を合わせています。 また、私たちの最新の本Domain-Driven Railsを必ずチェックしてください。 特に、大きくて複雑なRailsアプリケーションを扱う場合は特にそうです。


翻訳は以上です。

補足

  1. defined? メソッド
    式が定義されていなければ、偽を返します。定義されていれば式の種別 を表す文字列を返す。
    クラス/メソッドの定義 (Ruby 1.9.3)

  2. プロセスとスレッドの違い
    プロセスはCPUとメモリを仮想的に作成したもの(copy on write)でしたが、スレッドはメモリ部分だけは共通にCPU部分を分けたもの。
    各々の生き方: プロセスとスレッドの話

参考

rubyの定数が定義されているかをdefined?で確認して三項演算子に格納するときに()の有無でハマった - ツナワタリマイライフ

Mysql2::Error: Lock wait timeout exceeded; try restarting transaction

環境

エラー

19:27 ~ 20:33 15件同じエラーが発生(SQLは異なっていた)

Mysql2::Error: Lock wait timeout exceeded; try restarting transaction SQL・・

ロック待ちタイムアウトを超えました。transactionを再起動してくださいとエラー。

調べてみると同じような方がいました。
transactionの途中でトランザクションが切れてしまった時にそのトランザクションを殺す方法 - Garbage in, gospel out

原因

EC2の負荷によりDBへ接続中のtransactionが途中で途切れたが、DB内ではロックが残っており、次の接続がタイムアウトエラーになった。

調査

EC2

18:30 - 21:30 EC2 CPUUtilization

f:id:meikotan:20190115213354j:plain
ec2

19時から負荷が上がり始めて、19時30分で最高潮に。まさにエラーが続出したタイミングです。 まず、EC2の負荷を下げようということで、topコマンドで負荷を確認したら app サーバが高負荷だったので、appサーバを再起動しました。(20:50)

Aurora

18:30 - 21:30 EC2 DBconnection

f:id:meikotan:20190115215123j:plain
mysql

接続数は常に24接続されていて原因がよくわらないので、mysql 接続プロセスを確認します。

show processlist;

+--------+------------------------+-----------------+-------------------+---------+-------+------------+------------------+
| Id     | User                   | Host            | db                | Command | Time  | State      | Info             |
+--------+------------------------+-----------------+-------------------+---------+-------+------------+------------------+
| 350149 | user | 10.0.0.94:42244 | db_name | Sleep   | 15274 | cleaned up | NULL             |
| 350150 | user | 10.0.0.94:42246 | db_name  | Sleep   |  6461 | cleaned up | NULL             |
| 350151 | user | 10.0.0.94:42248 | db_name | Sleep   |  4458 | cleaned up | NULL             |
| 350152 | user | 10.0.0.94:42250 | db_name | Sleep   |  6461 | cleaned up | NULL             |
| 350153 | user | 10.0.0.94:42254 | db_name | Sleep   |  6461 | cleaned up | NULL             |
| 350154 | user | 10.0.0.94:42256 | db_name | Sleep   |  2434 | cleaned up | NULL             |
| 350155 | user | 10.0.0.94:42258 | db_name | Sleep   |  7037 | cleaned up | NULL             |
| 350156 | user | 10.0.0.94:42252 | db_name | Sleep   |  6461 | cleaned up | NULL             |
| 350157 | user | 10.0.0.94:42262 | db_name | Sleep   |  6546 | cleaned up | NULL             |
| 350158 | user | 10.0.0.94:42264 | db_name  | Sleep   |  6461 | cleaned up | NULL             |
| 350159 | user | 10.0.0.94:42266 | db_name | Sleep   |  7127 | cleaned up | NULL             |
| 350160 | user | 10.0.0.94:42268 | db_name | Sleep   | 15274 | cleaned up | NULL             |
| 350161 | user | 10.0.0.94:42260 | db_name | Sleep   |  6461 | cleaned up | NULL             |
| 350162 | user  | 10.0.0.94:42270 | db_name | Sleep   |  6461 | cleaned up | NULL             |
| 351723 | user | 10.0.0.94:47828 | db_name | Sleep   |   232 | cleaned up | NULL             |
| 351735 | user | 10.0.0.94:47858 | db_name | Query   |     0 | starting   | show processlist |
+--------+------------------------+-----------------+-------------------+---------+-------+------------+------------------+

Timeが長いのものがtransaction lockしてるんだと思う。
現在GAで見るとアクセス誰も来ていないので、Timeの長いプロセスをkillしました。

対処

Timeが1000を超えてるものをkillしました。

疑問

cleaned upはauroraだけのstatusなのかな。mysqlの公式サイトには掲載されてない。
MySQL :: MySQL 5.1 リファレンスマニュアル :: 12.5.4.24 SHOW PROCESSLIST 構文

参考

【JAWS DAYS 2016】ランサーズを支えるAurora

QRコードを生成(RQRCode)しをCarrywaveでS3にアップロードしたらEncoding::UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8)エラーになった

環境

Rails 5.1

利用gem

やりたかったこと

セミナーのチケット管理システムを作っており、今回はチケット登録後に参加申込みができるQRコードを生成し、S3にアップロード(carrywaveを使う)したい

処理の流れ

  • 自動生成したQRコードを tmpフォルダに一次保存。
  • uploderの要素に、tmpフォルダ内のファイルを読込む
  • 一次保存した tmpフォルダを削除する。

検証

# QRコードを生成
png_to_s = RQRCode::QRCode.new("【QRコードに変換する文字列】").as_png(size: 200).to_s

file_path = "#{QR_IMAGE_TMP_PATH}/qr_image_[ランダム文字列].png"
# ファイルをtmpフォルダに書き込む
File.write(file_path, png_to_s ) ←ここでエラー

self.qr_image = File.open(file_path)
self.save

# 削除処理
File.delete(file_path)

エラーメッセージ

Encoding::UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8)

class Encoding::UndefinedConversionError (Ruby 2.6.0)

QRコードの画像ファイルは[ASCII-8BIT]で、それを[UTF-8]に変換しようとしたときに、[UTF-8]に存在しない文字があった場合に起こるエラー。その[ASCII-8BIT]の文字は、[UTF-8]には対応してないよ!ということですね。

ネットにあまり情報が落ちてなかったので、IOクラス(Fileクラスの親クラス)のドキュメントを確認します。 class IO (Ruby 2.6.0) ※ドキュメント2.6ですが、検証環境は2.4です。

途中のサンプルで外部エンコーディングを指定できるのがわかります。

# nkfプロセスから得られる文字列を EUC-JP と指定する
# IO.new などと共通のオプションが指定できる
IO.popen("nkf -e filename", external_encoding: "EUC-JP"){|nkf_io|
  nkf_io.read
}

ソースコードを[ASCII-8BIT]で読み込めるようオプションを付けました。

File.write(file_path, png_to_s )
↓
File.write(file_path, png_to_s, external_encoding: "ASCII-8BIT" )

これでエラーはなくなり無事、tmpフォルダにファイルの保存ができました。
QRコードの保存方法は、もう少しスマートなやり方がありそうだけど今の私にはこれが限界。。頑張れ自分!

参考

Rails5のAPIモードで画像ファイルをS3にアップロードする。(carrierwave利用) - t-sanoブログ

Model内で route helper(*_url)を使うと、ArgumentError: Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true になる

model内で route helper ( *_url )であるRails.application.routes.url_helpers.root_url を呼び出したときに、エラーが出たのでその対処法を記載する。

エラーメッセージ

ArgumentError: Missing host to link to!
Please provide the :host parameter, set default_url_options[:host], or set :only_path to true

*_url によりホスト付きのパスをしたいんだけど、デフォルトでhostが設定されてないから、URLを生成できないよ。default_url_options を設定するか、only_path: true を設定してね。ってことが書いてある。

default_url_options の設定方法

enviroment に設定する

動的に変わらない場合は、こちらに設定しましょう。

Rails.application.routes.default_url_options = { host: "xxx.xx.xx" }

Rails.application.configure.action_mailer.default_url_options に設定する要領で記載します。

only_path: true とは?

ホストなしのURLを生成します。(*_path で生成されるパスと同じ)


参考

RailsでMissing host to link to!が出たときに。model内でURL組み立てる場合の設定 - Qiita