とーますメモ

Ruby on Rails / Goなどの学習メモ

【Flask】HTTPS環境で、Ajaxを使用するページを開いたら、エラーが出た。

件名の通り、以下のようなエラーが出た

The page at 'URL' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'URL'. This request has been blocked; the content must be served over HTTPS.

原因はスラッシュ漏れ。FlaskもDjangoと同じ構造みたい。
How to Make an AJAX HTTPS GET Request Using jQuery - Stack Overflow

間違い

url  : '/search'

正解

url  : '/search/'

【MySQL】SQLAlchemy内でMySQLの全文検索を試めしてみた

MySQLの設定は以下を参考
thoames.hatenadiary.jp

検索対象のインデックスがつけられる単語の最小長と最大長の設定

InnoDBなら「innodb_ft_min_token_size」、MyISAMなら「ft_min_word_len」で最小長を設定
InnoDBなら「innodb_ft_max_token_size」、MyISAMなら「ft_max_word_len」で最大長を設定

自分が使用するのはInnoDBなので、InnoDB用の設定を行う。

innodb_ft_min_token_sizeのデフォルトは「3」、innodb_ft_max_token_sizeは「84」
MySQL :: MySQL 5.7 Reference Manual :: 14.15 InnoDB Startup Options and System Variables
MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.9.6 MySQL の全文検索の微調整

/usr/local/etc/my.cnf

[mysqld]
innodb_ft_min_token_size=2
ft_min_word_len=2

設定の確認

mysql> SHOW VARIABLES like 'innodb_ft_min_token_size';

変更を有効にするためには、mysqldを再起動し、INDEXを再作成する必要がある

検索の仕方

ヒット率50%に準じた検索(全体の50%を超えたら出力されない)

SELECT * FROM products
WHERE 
 MATCH(search)
     against(ngram('protein'));

ヒット率を無視した検索

SELECT * FROM products
WHERE 
 MATCH(search)
     against(ngram('protein') in boolean mode );

検索語での結果に加えて、類似・関連語の結果も返す検索

SELECT * FROM products
WHERE 
 MATCH(search)
     against(ngram('protein') with query expansion );

以下の記事を参考
MySQLで全文検索2 - SQLer 生島勘富 のブログ
MySQLで全文検索 - FULLTEXTインデックスの基礎知識|blog|たたみラボ

Flask内で使用する

customers = session.query(Customer).filter(Customer.name.match("anomaly")).all()

現在は単一テーブルの単一カラムを対象にした検索で「BOOLEAN mode」のみしかデフォルトだと使用できない。
もし複数テーブルの複数カラム且つ「with query expansion」などを対象にした検索がしたい場合は
「ClauseElement」を使用し、自らクエリを組み立てる必要があるとのこと。
stackoverflow.com

将来的にはSQLAlchemyでサポートされるっぽい。

以下の記事を参考
Python SQLAlchemy + MySQL で 複数フィールド に対する 全文検索:MATCH AGAINST 文 を実行する - Qiita
MySQL full-text searches and SQLAlchemy; the present and a proposed future | Anomaly
MySQL — SQLAlchemy 1.3 Documentation



[他参考]
LIKE検索より50倍速い!?MySQLでラクラク高速な日本語全文検索 - bitA Tech Blog
innodb - MySQL FullText search on string shorter than 3 chars returns no rows - Database Administrators Stack Exchange

【Ruby】MacでRailsが突然動かなくなった話

原因が何だったのか定かではないが、
恐らくXcode関係かbrew関係のコマンドを弄ったかが原因で
Railsを起動しようとしたところ以下のようなエラーが表示された。

$ bundle exec rails s

...
LoadError - library not found for class Digest::SHA1 -- digest/sha1
...

ruby on rails - LoadError: dlopen(digest/sha1.bundle): Symbol not found: _rb_Digest_SHA1_Finish - Stack Overflow
ios - LoadError - library not found for class Digest::SHA1 -- digest/sha1 - Stack Overflow

どうやらOpenSSLが直接的な原因であったようで
/usr/local/opt/opensslのシンボリックリンクが「../Cellar/openssl@1.1/1.1.1d」を指していたのが原因っぽい。(あくまでも予想)

そしてこの問題が発生したときは、情報があまりなかったので
安易にRubyを再インストールすればよいと思ってしまい、使用していたRuby 2.3.1を消してしまったことから悲劇が始まった。

Rbenvで古いバージョン(2.3.1)がインストールできない

Ruby環境の構築にはrbenv及びbundlerを使用している。
そしてRubyを再インストールするために、以下のコマンドで2.3.1が以下のようなビルドエラーになるという状態になった

$ rbenv install 2.3.1

...
The Ruby openssl extension was not compiled.
ERROR: Ruby install aborted due to missing extensions
...

調べてみると自分の環境が、opensslのバージョンが1.1系になっており
2.4より古いRubyは、openssl@1.1がインストールされた環境ではビルドが失敗するとのこと。
なので古いopenssl経由でビルドする必要があるみたい。

自分の場合は/usr/local/opt/opensslのシンボリックリンクが、openssl@1.1を向いていたので
これを古いバージョン(以前使用していたのは1.0.2q)へ向け直す必要があったので、brew switchと組み合わせて以下のようなコマンドで、なんとか2.3.1がインストールできた。

ちなみに古いバージョンのopensslは「/usr/local/Cellar/openssl」に入っている。

$ brew switch openssl 1.0.2q
$ RUBY_CONFIGURE_OPTS="--with-openssl-dir=/usr/local/opt/openssl" rbenv install 2.3.1
$ rbenv rehash
$ rbenv global 2.3.1
$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin18]

[参考]
Mac環境でopenssl@1.1があると古いRubyのインストールに失敗する - Qiita
Cannot install Ruby versions < 2.4 because of openssl@1.1 dependency · Issue #1353 · rbenv/ruby-build · GitHub
[Ruby] rbenv で「The Ruby openssl extension was not compiled.」が発生したときの対処 - Qiita

Bundlerが原因でRailsが動かない。

bundle exec rails sを行うために、bundlerのインストールが必要だが、後々2系だと自分が使用しているgemと相性が悪いので
古いバージョンのbundlerをインストールした

$ gem install bundler -v 1.17.1
$ gem list

...
bundler (1.17.1)
...

Nokogiri(1.6.6.2)がインストールできない

どうやらmacOS SDK headerなるものが関係あるらしいので、以下の記事を参考にmacOS_SDK_headersをインストールしようとしたが
バージョンが古い?とのことでインストールされなかった。
mojaveでbundle installできない - Qiita

$ sudo open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg

どうやら最新のものがAppleのサイト(https://developer.apple.com/download/more/)からダウンロードできるらしい。
サイトに接続し、デベロッパーアカウントを作り、最新のpkgをダウンロードすることに成功し、無事にmacOK_SDK_headersのインストールに成功。
【macOS Mojave】macOS MojaveにアプデしたらCのヘッダーファイルが読み込まれなくなった件 - 空飛ぶセロ弾き

その後、以下のコマンドを叩いて、nokogiriをインストールすることに成功した。

$ bundle config build.nokogiri --use-system-libraries=true --with-xml2-include="$(xcrun --show-sdk-path)"/usr/include/libxml2
$ bundle install

[参考]
ruby — Mac OS Sierra 10.12にNokogiriをインストールする方法
ruby on rails - An error occurred while installing nokogiri (1.6.6.2), and Bundler cannot continue - Stack Overflow

lib8とtherubyracerがインストールできない

以下の記事と同じことを行ったらインストールできた。

$ brew install v8-315
$ bundle config --local build.libv8 --with-system-v8
You are replacing the current local value of build.libv8, which is currently nil
$ bundle config --local build.therubyracer --with-v8-dir=$(brew --prefix v8-315)
You are replacing the current local value of build.therubyracer, which is currently nil
$ bundle install

mysql2がインストールできない

以下の記事と同じことを行ったらインストールできた。

$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include"
$ bundle install

[参考]
[MacOS Mojave]mysql2のbundle installに失敗してハマった件 - Qiita
macOS Mojave環境でmysql2(0.5.2)のgemインストールが失敗した - Qiita

bundle exec rails serverでエラーがでる。

上述したエラーを全て回避し、bundle installは全て成功したのに、rails sでまさかのエラー。
以下の記事の参考にして、なんとか対応できた。

bundle updateするしかなかったのにで以下のコマンドで対応

$ bundle update therubyracer

【Ruby on Rails】dyld: lazy symbol binding failed: Symbol not found: というエラーが出たので - ハイパーニートプログラマーへの道

そしてついにサーバが立ち上がった!と思ったら・・・

最後に、Ignoring・・・という変なログがサーバを立ち上げたら出てきた。。。
サーバ自体はちゃんと立ち上がるようになったら、起動する度にこのエラーを見たくないので
gemがインストールされているフォルダを見に行き、ignoring・・・のgemのgemspecを削除することで
このメッセージを強制的に出ないようにした。

※色んなサイトには「gem pristine --all」しろと書いてあるが、だめだった。

場所: /vendor/bundle/ruby/2.3.0/specifications

[参考]
ruby - Ignoring GEM because its extensions are not built - Stack Overflow

【Ruby】Rubyバージョンが影響して、小数点計算が合わなくなることがある

以下の記事を見るとどうやらrubyのバージョンが2.4未満だとround関数が正しい値を返さないことがあるらしい。

irb(main):001:0> 1024.975.round(2)
=> 1024.97
irb(main):002:0> 1023.975.round(2)
=> 1023.98

Ruby - roundで四捨五入にならない場合の理由を教えてください|teratail
RubyとPythonとC#のround関数のバグっぽい挙動について - hnwの日記
floating point - Ruby Float#round behaviour change after update - Stack Overflow


以下の記事によると2.4未満のバージョンを利用しているときは、BigDecimalでroundすれば良さげ。

irb(main):001:0> BigDecimal('1024.975').round(2).to_f
=> 1024.98

floating point - Float round bug in ruby? - Stack Overflow

【Rails】Quickbooks Web Connector(QBWC)を使って、RailsとQuickBooks Desktopを接続してみた

中小企業会計ソフトの分野で、日本ではfreeeやマネーフォワードが普及しているが
海外ではQuickbooksが広く普及している。
このQuickbooks内に外部からデータをRailsアプリ経由で追加することで入力作業を省き、業務効率を上げようという話になった。

前提としてQuickbooks Onlineではなく、Quickbooks DesktopというWindowsアプリを使う。Onlineを使用している方にはこの記事は不要。
Quickbooks Onlineだと、すごく充実したREST APIドキュメント
あるので開発も簡単だが、Quickbooks Desktopでは「Quickbooks Web Connector(以降QBWC)」を使う。

色々と調べる中で、当たり前だが日本語の情報が壊滅的になかったので、将来の自分用に調べた内容をこの記事に残す。

QBWCの使い方

・QBWCは、QuickBooks Desktopがインストールされている端末にインストールする。
・APIはRESTではなく、SOAPという古いタイプのAPIで、XMLをやり取りすることでリクエストを行う。

以下のPDF内で、なぜQBWCは、シンプルなWeb APIではないのかについて説明があるが
一言でいうと、セキュリティを確保するため、不特定多数が使用するインターネット上からのアクセスを防ぎたいという意図があった模様。
※だったら、QuickBooks Onlineはどうなんだという疑問はあるが。
https://developer-static.intuit.com/qbSDK-current/doc/PDF/QBWC_proguide.pdf

・Railsとのやり取りの流れは以下
1)RailsでSOAP APIへのリクエスト(Job)を作成する
2)QBWC側からRailsにアクセスし、Jobがあればそれを実行する。
つまりRails側からJob実行のキューは送れない。Job実行のトリガーは必ずQBWCから。

・RailsでQBWCとやり取りするには、qbwcのgemを使用する。
github.com

Rails gem(qbwc)の使い方

・基本的な設定や使い方については上記の公式サイトを参照
・作成できるJobは「読込」と「書込」のどちらも可能
・Job作成にあたり、リクエスト用のHashを作成するのだが、それには以下のドキュメントを使用する。
しかし使いにくいし、説明不足感が否めない。

developer-static.intuit.com

このドキュメントを使用し、リクエストHashを作成する上で大事な点は以下
・Railsで使用するフォーマットは「qbXML」。
・QBEditionsは使用するQuickBooks Desktopの国を設定すること。
ドキュメントのXMLの「順番」は必ずそのとおりにすること。普通のHashで書くように、KEYの順番を入れ替えて作成すると「Parse Error」を返す。
・書込の際に「AMTTYPE」の値を送信する場合は、小数点第二位まで含んだ文字列を設定する必要がある。
※ ドキュメントにも書いてない。

✗: 123.4
○: 123.40

https://help.developer.intuit.com/s/question/0D50f00004rTfc6CAC/qbxml-amount-invalid-cant-convert
https://help.developer.intuit.com/s/question/0D5G000004Dk7HIKAZ/invoice-amount-error-3040-what-am-i-doing-wrong

・参照系のリクエストを送るときに、膨大なデータをリクエストするときは、一度にリクエストを送らず「Iterator」を使用したコードを作成すること。

自分は使用してはいないが、恐らく以下の記事を見ると、

最初のリクエストは以下のようにiteratorにStartを指定し

...
:sales_order_query_rq => {
  {:xml_attributes => { 'iterator'  => "Start" }}
}
...

それ以降のリクエストでは、iteratorにContinueを指定し、最初のリクエストの返り値であると思われるIteratorIDを設定する模様。

...
:sales_order_query_rq => {
  {:xml_attributes => { 'iterator'  => "Continue", 'iterator_id' => iteratorID }}
}
...

https://github.com/consolibyte/quickbooks-php/blob/master/docs/web_connector/example_web_connector_import.php#L214
https://github.com/Natejd04/QBWC_Test/blob/d23b42738eae05844e0e23375621fa48809ddb77/app/workers/z-customer_order_worker.rb
https://stackoverflow.com/questions/21757478/iteration-samples-in-quick-books-using-web-connector


未確認だが、使用する際に試したい。

・Mod系のAPI使用時に注意する点
例えば、Invoiceの内容を変更するInvoiceModを使用するとしよう。その時前回に送ったInvoiceのItemLine内容に、新しいItemLineを追加する必要があるとする。
InvoiceAddのAPIならば、InvoiceLineAddがあり名前の通りに、Itemを追加するだけでよいが
InvoiceModのAPIは、InvoiceLineModという変更前提のものしかない。。。それでどうするか。。。

「TxnLineIDを-1」に設定すれば良い
言わずもがなドキュメントにはこの記載はない。

またこのInvoiceModを使用時には、以前登録したデータも追加しないと、前のデータを上書きするみたいな内容になるので注意。

https://help.developer.intuit.com/s/question/0D50f00005gIodbCAC/how-can-i-get-txnid-and-editsequence-by-same-inovice-date-invoice-on-qb-for-checking-whether-add-new-invoice-or-add-new-line-in-same-invoice-like-an-attachment-and-how-can-i-add-new-line-in-same-invoiceaspnet-c
https://stackoverflow.com/questions/32817070/how-to-modify-an-invoice-in-quickbooks-using-qbxml-and-qbsdk13

・処理が遅いとき・・・
Rails側からデータを送信したときに、リクエストをQBWC側で受け取るのは早いが、それからの処理がものすごい遅いことがあった。
レジストリをいじって、キャッシュやログ出力の抑制などを試したが、あまり効果がなく、色々試して最後に効果があったのが
できるだけ、リクエストに必要なパラメータは全て送ること。

試したのは、InvoiceAddのAPIに、InvoiceLineAddで50個のデータを追加して送るとなんと3分もかかっていた。
またアイテムを追加するたびにn²で遅くなって行く模様。。。
このとき追加した情報はItemRef, Quantity, Amountの3つ。
そしてここにSalesTaxCodeRefを"E"で追加したら、20秒で処理が終わった。
恐らく送れるパタメータはできるだけ送ったほうが、QuickBooks Desktop側での処理をしなくてもよい?のかもしれない。

【Python】Flaskの本番環境について調べてみた

Flaskにはbuilt-inサーバがついているので、開発には全く困らないが
公式サイトを見ると以下のように記述があるので、本番環境は別に構築する必要がある。

While lightweight and easy to use, Flask’s built-in server is not suitable for production as it doesn’t scale well and by default serves only one request at a time.

by Deployment Options — Flask Documentation (1.1.x)

調べてみたが大体の本番環境構成は以下。

1)Flask + nginx + uWSGI
2)Flask + nginx + Gunicorn
3)Flask + Apache + mod_wsgi

自分の用途はとりあえずそれなりの本番環境が簡単に作れれば良いぐらいの考えなので
Flask + nginx + Gunicornの構成が一番簡単で良さげ。

参照した記事は以下
Which WSGI server should I use? — Django deployment
https://www.airpair.com/python/posts/python-servers
Web Applications & Frameworks — The Hitchhiker's Guide to Python
fcgi vs. gunicorn vs. uWSGI - Peterbe.com
Gunicorn vs uWSGI | What are the differences?

中でも以下の説明がわかりやすくてよかった。

Gunicorn and uWSGI Gunicorn and uWSGI are two different application servers.
Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It is very simple to configure, compatible with many web frameworks and its fairly speedy. This article by digitalocean shows how to configure gunicorn with nginx.


uWSGI is another option for an application server. uWSGI is a high performance and a powerful WSGI server. There are many configuration options available with uWSGI. This article by digitalocean shows how to configure uWSGI with nginx.


What I use I use Nginx because it is fast , light and I find the configuration to be much easy. Gunicorn is very simple to configure. So I use gunicorn. uWsgi is also used a lot instead of gunicorn.

一番高いパフォーマンスを目指すならuWSGIが良いけど、ただ設定が複雑。
それなりにちゃんと動作し、それなりに速く、設定がシンプルにものを選ぶならGunicorn。
他の記事を見るとWindowsサーバだとApache + mod_wsgiの一択っぽい。

ということで自分は上記の理由から「Flask + nginx + Gunicorn」の構成を選択する。

設定の仕方については以下のサイトを後ほど参考にする
https://medium.com/faun/deploy-flask-app-with-nginx-using-gunicorn-7fda4f50066a
https://martin-thoma.com/flask-gunicorn/

[その他参考]
・Flask + nginx + uWSGIについて
https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-uswgi-and-nginx-on-ubuntu-18-04
https://medium.com/@smirnov.am/running-flask-in-production-with-docker-1932c88f14d0
https://www.serverlab.ca/tutorials/containers/kubernetes/how-to-run-flask-docker-containers-in-kubernetes/

・Flask + nginx + Gunicornについて
https://www.toptal.com/flask/flask-production-recipes
https://www.linode.com/docs/development/python/flask-and-gunicorn-on-ubuntu/
https://blog.miguelgrinberg.com/post/running-a-flask-application-as-a-service-with-systemd
https://superset.incubator.apache.org/installation.html#a-proper-wsgi-http-server

【Vue.js】v-forの配列を削除すると、最後の行が消される問題

v-forで配列内をループで回して、表示させており
その中で以下のように、削除を行う機能を実装したのだが
削除すると選択した行が消されず、最後の行が消されるという変な現象が発生した。

[HTML]

<tbody>
  <tr
    is="sample-component"
    v-for="(item, index) in items"
    v-bind:index="index"
    v-bind:item="item"
    :key="index"
    v-on:remove="remove_item"
  ></tr>
</tbody>

[JS]

this.items.splice(index, 1);

原因を調べてみると、「:key」属性に「index」を設定していたのが原因だった。

In scenarios like this the most common problem is the v-for item keys. Your are keying each item by the index, but the index of each item is not constant because items can be removed from the list. Try using unique keys that are associated with item in the list.

by V-for and deleting components - Get Help - Vue Forum

なので設定する:keyをコンポーネントに属する唯一の値にすることで問題が解消された。
v-forのkeyにはindexを設定してはいけません!

[解決済みコード]

<tbody>
  <tr
    is="sample-component"
    v-for="(item, index) in items"
    v-bind:index="index"
    v-bind:item="item"
    :key="item.id"
    v-on:remove="remove_item"
  ></tr>
</tbody>