- 投稿日:2020-11-28T23:51:48+09:00
RSpecを実行すると、 Lock wait timeout exceeded; try restarting transaction mysqlというエラーがでる
ある日、RSpecのテストの実行中に、Ctrl+Cでキャンセルし、再びテストを走らせた。すると、いつまでたっても処理が止まったままだったのでそのまま放置していたら以下のようなエラーが出ました。
Lock wait timeout exceeded; try restarting transaction mysqlなぜこうなったのか
おそらく、MySQLのトランザクション(システムスペックは自動でテストデータのトランザクション処理をしてくれる)がコミットされる前にCtrl+Cでキャンセルしてしまったからだと思いますが、はっきりした原因は不明です(超ピンポイントでCtrl+Cを押してしまったため発生した?)
解決策
①mysqlに
mysql -u root -p
でログインする。
②SHOW ENGINE INNODB STATUS
を実行し、TRANSACTIONSの部分を確認する。TRANSACTIONS ------------ Trx id counter 60949 Purge done for trx's n:o < 60941 undo n:o < 0 state: running but idle History list length 4 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 421573119466232, not started 0 lock struct(s), heap size 1136, 0 row lock(s) ---TRANSACTION 60934, ACTIVE 151 sec 7 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 6 MySQL thread id 3, OS thread handle 140097739974400, query id 78 172.20.0.3 kiyo Trx read view will not see trx with id >= 60934, sees < 60934③すると、151秒コミットされないままのトランザクションがありました!
thread id 3 とあるのでこのプロセスを kill 3 で削除します。④再度実行したら、RSpecが無事実行されるようになりました。
おそらくそう起こるエラーではないですが、同じ状況になった方の手助けになれば幸いです!
参考
トランザクションを強制的に終了させる
rakeタスクでDBのレコードが更新出来なかった時の解決法最後まで読んでいただきありがとうございます!
何かご指摘などございましたら、コメントいただけると嬉しいです!
- 投稿日:2020-11-28T23:37:00+09:00
Railsで、現在時刻・日時を表示させる方法
はじめに
Rubyでは標準ライブラリなど、様々なものがあるが、その中で、日付についてのものがあり、現在制作中のアプリに日付を表示させようと思い、ここにいたる。
Rubyでは…
require 'date'を記述することで、そのライブラリを使用できる。
Railsでは…
調べが足りないので、定かではないが、デフォルトで使用できるみたい。私は、上記の記述を書いた覚えがないが、日時を表示できた。
現在の時刻
Time.now #=>2020-11-28 23:33:32 +0900今日の日付
Date.today #=>2020-11-28最後に
どこの記述されているのか、わからないが使える機能がたくさん出てきた。Railsは便利だが、ブラックボックスな部分が私にはまだまだあり、難しい。開いたことのないファイルがたくさんある…
表示方法を変えたいなぁ…ja.yml
あたりをいじればいいのかなぁ…
- 投稿日:2020-11-28T23:02:55+09:00
非ツイッター民が黒い画面からツイートしてみた件について
- 投稿日:2020-11-28T21:28:57+09:00
ルーティング設定
ルートへのルーティング設定とは
Vagrant環境では、http://localhost:3000
にアクセスした時に、デフォルトで表示される「Welcome aboard」のページではなく、自分で作ったページなり、アクションなりを表示させたい時にする設定のこと。コントローラーとアクションを作る
ターミナルに下記のコードをうちます。
$ rails g controller コントローラー名 アクション名ルートファイルを編集する
「config/routes.rb」ファイルに下記のコードを書き込む。
root to: 'コントローラ名#アクション名'例えば、http://localhost:3000/にアクセスした時にindexページを表示させたい場合は、
root to: 'コントローラ名#index'と書く。
出典記事
- 投稿日:2020-11-28T21:28:36+09:00
ruby on railの環境をcentos7.2で構築
vagrant boxを利用してcentosの仮想環境を構築する
>vagrant box add 名前 urlもしくはローカルパス※OSのボックスURLの一覧サイト
http://www.vagrantbox.es/
centos7.2:https://github.com/CommanderK5/packer-centos-template/releases/download/0.7.2/vagrant-centos-7.2.box>vagrant init 名前vagrant起動確認
>vagrant upもし、
mount -t vboxsf -o uid=1000,gid=1000 vagrant /vagrantというエラーが出たら、
>vagrant plugin install vagrant-vbguestvagrant停止
>vagrant halt生成されたVagrantfileに下記を加える
config.vm.network "forwarded_port", guest: 3000, host: 3000再度vagrant起動確認
>vagrant upRubyとRailsをインストールする
rbenvをインストール
$ sudo yum -y install git $ git clone https://github.com/rbenv/rbenv.git ~/.rbenvrbenvコマンドを汎用的に使えるようにパスを通します。
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile $ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile設定反映のため、シェルに入り直す
パスの変更が反映されるように実行
$ type rbenvruby-buildをインストール
rbenv同様にGitHubのリポジトリからパッケージをダウンロードします。
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-buildRubyをインストール
利用可能なRubyのバージョン一覧を確認します。
$ rbenv install -lインストールしたいバージョンを選んでインストール(例:バージョン2.6.6)
$ sudo yum install -y openssl-devel readline-devel $ rbenv install -v 2.6.6以下のように表示されればインストール完了
Installed ruby-2.6.6 to /home/vagrant/.rbenv/versions/2.6.6ruby使用するRubyのバージョンを設定してあげましょう。
$ rbenv global 2.6.6バージョンの確認(以下のようにでたらRubyを使用するバージョンの設定OK)
$ ruby --version ruby 2.6.6p146 (2020-03-31 revision 67876) [x86_64-linux]Railsをインストール
bundlerのインストール
$ gem install bundlerbundlerのバージョン確認
$ bundle -vgemを通してRailsをインストール
$ gem install -v 5.2.3 railsインストールの確認
$ rails --versionMySQLのインストール
MariaDBライブラリを削除
$ sudo yum remove -y mariadb-libsMariaDBのディレクトリを削除
$ sudo rm -rf /var/lib/mysql/MySQL8.0のリポジトリをインストール
$ sudo yum localinstall -y https://dev.mysql.com/get/mysql80-community-release-el7-2.noarch.rpmMySQLをインストール
$ sudo yum install -y mysql-community-serverMySQLクライアントのバージョンを確認
$ mysql --versionMySQLサーバのバージョンを確認
$ mysqld --version以下の記事にバージョン確認後の手順あり
https://qiita.com/nooboolean/items/7efc5c35b2e95637d8c1Railsサーバを立ち上げる
共有ディレクトリに移動する
$ cd /vagrantRailsのプロジェクトファイルを生成(DBはmysql)
$ rails new アプリケーション名 --database=mysql $ sudo yum install -y mysql-develGemfileにgem 'therubyracer'を追記
$ cd /vagrant/アプリケーション名 $ bundle installconfig配下のdatabase.ymlに設定したパスワードを書き加える
Centos7のポート開放 (Railsで使う3000番を解放)
sudo firewall-cmd --permanent --add-port=3000/tcpfirewalldをリロードします。
sudo firewall-cmd --reloadRailsプロジェクトのconfigディレクトリの中にあるdatabase.ymlを読み込み,そのファイルに基づいてデータベースを作成
$ rails db:createRailsプロジェクトのdb/migrateディレクトリの中にあるスクリプトファイルに基づいてデータベースにテーブルを作成
$ rails db:migrateサーバーを立ち上げる
$ cd /vagrant/myapp $ rails s -b 0.0.0.0ブラウザ等で検索
localhost:3000
出典記事
【CentOS7(+Ubuntu16)】Ruby / Rails のインストールから Rails サーバの起動までの(ほぼ)完全ガイド
CentOS 7にMySQLをインストールして初期設定するまで
Vagrant(CentOS7) + Rails環境でホストのブラウザからサーバ接続できなかった時の対処
- 投稿日:2020-11-28T20:13:35+09:00
【Rails】NewsAPIを使ってクリスマスニュースを収集するアプリを作る
はじめに
(初投稿です)
Railsでクリスマスに関連するニュースを集めるごくシンプルなアプリを作ります。NewsAPIのキーを取得し、英語の公式ドキュメントを参照してみたものの、Ruby client libraryのサンプルコードの使い方が自分にはいまいち分からず、あれこれ調べた結果自分に合った方法を見つけたので記事を書いてみました。
環境
- Ruby 2.6.5
- Rails 6.0.3.4
NewsAPIとは
世界中のニュースソースからデータを集めて提供してくれるAPI。
3種類のエンドポイントURIが用意されており、リクエストパラメータ内で欲しいニュースの国とカテゴリーを指定すると最新のニュースデータを返してくれる「Top headlines」
/v2/top-headlines
50,000を超えるさまざまな情報源から数百万ものニュースデータを返してくれる「Everything」/v2/everything
ニュースの情報元の情報を取得する「Sources」/v2/sources
があります。APIキーの取得
名前、メールアドレス、パスワード、個人か商用かを記入し、規約に同意することで登録出来ます。
登録を済ませたらアカウント画面にあるAPIキーを保存しましょう。
(APIキーはGitなどに映らないようcredentials.yml.encか環境変数内に格納しておきます)リクエストパラメーター
APIキーの取得が完了したら次はリクエストパラメーターを使ってエンドポイントURIを作成します。
今回はEverythingを使用し、
https://newsapi.org/v2/everything?
の後にパラメーター名をいくつか指定し、「クリスマス」についての記事を人気順に並び替えた情報をリクエストします。エンドポイントURIの種類によって使えるパラメータは違うようなので、下の表を参考にしてください。ちなみにいずれのエンドポイントURIでもAPIキーのパラメーターは必須です。
エンドポイントURI例https://newsapi.org/v2/everything?q=%E3%82%AF%E3%83%AA%E3%82%B9%E3%83%9E%E3%82%B9&sortBy=popularity&apiKey=(取得したAPIキー)
Top headlines
パラメーター名 パラメーターの値 country どこの国の記事を取得するか
ISO 3166-1で定められたアルファベット2文字の国名コードを使う
日本ならjp
(※sources
と同時使用不可)category 記事のカテゴリー business
entertainment
general
health
science
sports
technology
の7つ (※sources
と同時使用不可)sources 記事の情報元などを指定する
文字列を使い、,(コンマ)で区切ることで複数の情報源を指定できる
(※country
category
と同時使用不可)q 取得したいニュース記事についての任意の文字列または単語 pageSize 一度に取得したい記事の数を指定できる
初期値で20、最大値が100page pageSizeでの指定数よりも多くの記事が出てきた場合はこれでページングをする apiKey 取得したAPIキー (※必須) Everything
パラメーター名 パラメーターの値 q 取得したい記事のタイトルや本文についての任意の文字列または単語
必ずURLエンコードをするqInTitle 取得したい記事のタイトルについての任意の文字列または単語
必ずURLエンコードをするsources 記事の情報元などを指定する
最大20字までの文字列を使い、,(コンマ)で区切ることで複数の情報源を指定できるdomains 取得したい記事のドメインを指定する
,(コンマ)で区切ることで複数のドメインを指定できるexcludeDomains 除外したい記事のドメインを指定する
,(コンマ)で区切ることで複数のドメインを指定できるfrom 指定した日付の中で最も古い記事を表示する
ISO 8601の日付表記で記述する(例:2020-11-27
2020-11-27T20:15:25
)to 指定した日付の中で最も新しい記事を表示する
ISO 8601の日付表記で記述する(例:2020-11-27
2020-11-27T20:15:25
)language 記事の言語の指定
ISO-639-1で定められたアルファベット2文字の国別コードを使う
Top headlinesのcategoryで使用したISO 3166-1とは別だが
2020年11月現在、日本語(ja
)は指定できない模様sortBy 記事の並び替えを指定できる。
オプションはrelevancy
(q
の値により関連する順)popularity
(人気順)publishedAt
(投稿順)の3つ
初期値はpublishedAt
pageSize 一度に取得したい記事の数を指定できる
初期値で20、最大値が100page 結果をページングする apiKey 取得したAPIキー (※必須) このうち
q
とqInTitle
は、""
で囲うことで完全一致検索が出来たり、
必ず出現して欲しい文字列には先頭に+
を付けたり、
逆に除外したい文字列には-
を付けたりと、欲しい記事の情報に合わせてパラメータの値を工夫することが出来ます。Sources
上2つのエンドポイントURIにある
sources
パラメータの「どんな情報元があるか」を調べたい場合は、このSourcesのエンドポイントURIを使うか、国別情報元一覧で調べることが出来ます。
パラメーター名 パラメーターの値 category ニュース記事のカテゴリー business
entertainment
general
health
science
sports
technology
の7つ
初期値では全てのカテゴリーが出るlangage 記事の言語
やはり日本語(ja
)は使えない模様country 特定の国の記事を指定する
日本はjp
apiKey 取得したAPIキー (※必須) リクエストパラメーターの作成が終わったら、ブラウザのURL欄に入力し、アクセスしてみましょう。するとこのように大量のJSONデータ(レスポンスオブジェクト)が返却されます。
レスポンスオブジェクト
画像のレスポンスオブジェクトを要約するこんな感じになります。
レスポンスオブジェクトの例(Everything){"status":"ok", "totalResults":20, "articles":[ {"source":{"id":null,"name":"情報元名"}, "author":"著者", "title":"記事タイトル", "description":"ニュース記事の本文", "url":"記事URL", "urlToImage":"記事の画像URL", "publishedAt":"記事の投稿日", "content":":記事の引用元URL" }, {(中略)} ] }上のJSONデータの3行目の
"articles"
の値がハッシュ入りの配列として返却されているのが分かります。ビューファイル内でニュースの見出しを作りたい場合は、繰り返し処理(eachメソッド)を使い
配列articles
の中から一つずつ記事のハッシュを取り出します。
<%= article["記事のハッシュのキー名"] %>
と記述することで取得した記事についての任意の情報を表示できます。以下がその配列キー名とその内容です。
Top headlines / Everything
キー名 内容 source 記事の情報元( id
やname
で指定する)author 記事の著者 title 記事のタイトル description 記事の本文 url 記事のURL urlToImage 記事の画像URL publishedAt 記事が投稿された日付と時刻(UTC/協定世界時での表示) content 引用元や記事の内容(最初の200文字のみ表示) Sources
キー名 内容 id 記事の情報元の識別子 name 情報元の名前(主にドメイン名) description 情報元の情報 url サイトURL category 情報元から推測されたニュースのカテゴリー langage 記事の言語 country 記事の発信元の国、あるいはその国についての記事 アプリでの実装
通常通りにコントローラーとビューを作成します。モデルは必要ありません。
app/controllers/news_controller.rbclass NewsController < ApplicationController require "open-uri" def index api = Rails.application.credentials.news_api[:api_key] uri = "https://newsapi.org/v2/everything?q=%E3%82%AF%E3%83%AA%E3%82%B9%E3%83%9E%E3%82%B9&sortBy=popularity&apiKey=#{api}" article_serialized = open(uri).read @articles = JSON.parse(article_serialized) end endcredentials.yml.encに格納されたAPIキーを取り出してURIに式展開で取り付けて、
それを変数化した後にopenメソッドでアクセスし、readメソッドでJSONを読み込みます。
parseメソッドでJSON形式の文字列をRubyの文字列に変換しています。app/views/news/index.html.erb<header> <h1>⭐️クリスマスニュース⭐️</h1> <p>Powered by <a href="https://newsapi.org">News API</a></p> </header> <hr> <div class="articles"> <% @articles["articles"].each do |article| %> <div class="article"> <div class="title"> <%= link_to article["title"], article["url"] %> </div> <div class="wrapper"> <div class="date"> <%= article["publishedAt"] %> </div> <div class="source"> <%= article["source"]["name"]%> </div> <div class="image"> <%= image_tag article["urlToImage"],:size =>'240x160' %> </div> <div class="content"> <%= article["description"] %> </div > </div> </div> <% end %> </div>レスポンスオブジェクトとして返却された記事のハッシュの中には、さらに
"source"
キーの値として、もう一つ記事の情報元を示すハッシュが格納されてます。
なので、情報元についての表記をしたいときは<%= article["source"]["name"]%>
と記述することで情報元のサイト名などを取り出すことが出来ます。あとはCSSで装飾をして完成です。
CSS(一応載せます)
app/assets/stylesheets/news/index.cssbody { background-color: #588C73; } header { text-align: center; } hr{ border: none; border-top: 2px dashed #ff5c00; } .articles { display: flex; flex-wrap: wrap; } .wrapper { padding: 5px; } .title { border-radius: 10px 10px 0 0; padding: 10px 5px 5px 5px; background-color: #a20a0a; height: 4.5em; } .title > a { color: #f2ae72; text-decoration: none; } .date { font-size: 0.8em; color: #588C73; } .source { font-size: 0.8em; color: #588C73; } .image { text-align: center; padding-top: 5px; padding-bottom: 5px; } .article { border-radius: 10px; width: 30%; margin: 20px 15px; color: #d96459; background-color: #f2e394; border: 1px solid #d96459; } .content { font-size: 0.9em; }
備考
NewsAPIを使用した際は、帰属を示すために"Powered by News API"という文字列でhttps://newsapi.orgへのリンクを貼るよう規約内に指示があるので忘れずに記載しておきましょう。
まとめ
公式ドキュメントでは、NewsAPIのgemをインストールして記事を取得する方法が記されていましたが、どういうことかそのライブラリ自体が非公式だとされていました。
なので結局どうすればいいのか分からずあれこれ調べた結果このやり方に行き着きました。この方法ならば新しいgemを追加する必要もなく、コントローラーに
require "open-uri"
を記述することでエンドポイントURIを開き、簡単にニュース記事を収集するアプリを作ることが出来ます。参考
Documentation - News API
open uri - trying to use a news API with rails application - Stack Overflow
- 投稿日:2020-11-28T20:05:34+09:00
[rails]突然自動デプロイが反映されなくなった
何が起きたのか
よくわかりませんが、調べたところEC2に問題があった
自動デプロイしてたら突然変更が反映されなくなった解決方法
よくわかりませんが突如起こった出来事ですので
とりあえずAWSのマネジメントコンソールにログインしてEC2インスタンスを再起動
再起動後は以下のコマンドでNginxとdbを起動。$ sudo service nginx start $ sudo systemctl restart mariadbその後 自動デプロイする 完 (勝手にunicornは起動されるであろう )
bundle exec cap production deploy補足
EC2に問題がある場合の確認すべきところ
基本的に以下のどれかがどうにかこうにかなっていることが多い説
( unicorn,db,nginx...)なのでエラーログみてもよくわからなかったらコマンドでいろいろ確認してみよう
データーベースの状態を確認 sudo systemctl status mariadbデーターベースの起動 sudo systemctl restart mariadbnginxの再起動 sudo systemctl restart nginxunicornの状態確認 ps aux | grep unicornunicornの起動 RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D
- 投稿日:2020-11-28T19:18:36+09:00
タグ振り分け機能の実装
はじめに
今回は、zipang(漢字、ひらがな、カタカナをローマ字に変換するもの)というgemを用いてタグを頭文字ごとに0~9、A~Zに振り分けて索引のようなものになるよう実装いたしました。その際、予め ancestry を用いて親カテゴリーとして0~9、A~Zをデータに保存するseedを作成しております。
機能の実行順序
大まかな流れといたしましては、
① タグを入力する
② 入力されたタグをzipangでローマ字に変換
③ 変換した文字の頭文字を取得し、その頭文字を親としてタグをその子要素に保存する。(タグのテーブルにはancestryを用いているので、知らない方は検索してみてください)
②③の流れをメソッドでまとめると以下の通りになります。(このメソッドはbefore_saveにて実行しております。)
def find_or_create_tag self.tags = self.tags.map do |tag| word = Zipang.to_slug tag[:name] parent = Tag.find_by( ancestry: nil, name: word[0].upcase ) parent.children.find_or_create_by(name: tag.name) end end④ 表示する際は、頭文字の子要素として出力する。
以上のことを応用すると、あいうえお順に振り分けることもできるかと思います。
- 投稿日:2020-11-28T19:00:20+09:00
Rubyで2進数を計算する方法はとても簡単
ruby 2.7.0の記事です
10進数を2進数に変換する方法
以下、私の試行錯誤の結果です
- 演算子については、公式リファレンスを参照
- ** で二乗
公式ドキュメント(Numericクラス)def Binary(num) arry1 = [] #1になる桁を左から数えた数が入ります 44の場合(6,4,3) つまり(101100) while num > 0 arry2 = [] #2で割った数が入ります n_i = num while n_i > 0 n_i = n_i / 2 arry2 << n_i end num = num - (2 ** (arry2.length - 1)) #arry2.lengthで桁を出します 10進数に戻して、最初の数から引きます arry1 << arry2.length #1になる桁数を加えます end i = 1 b_a = [] arry1[0].times do #arry1[0]に桁が入ります。1か0を配列b_aに入力します if arry1.include?(i) b_a << 1 else b_a << 0 end i += 1 end b_a.reverse.each do |s| #配列をprintで連続して表示します print s end end Binary(num)以下@kts_hさんのコメントを元に追加
- 整数を進数で出したければ、すごく簡単にできる
- num.to_s(2-36の数値が入る)
公式ドキュメント(Integerクラスinspect)num = 44 num.to_s(2) #=> "101100" puts "2進数にしたい値を入力してください" num = gets.to_i # 整数にする 仮に44と入力した場合 puts Binary = num.to_s(2) #=>101100 文字列として出力 puts "取り出したい桁を入力してください" n = gets.to_i #例えば6桁目”6”と入力 puts Binary.slice(-n) #今回はn=6となり、”1”が表示される #メソッドとして定義したい場合 def Binary(num) puts num.to_s(2) end
inspectメソッドを使わず計算式で定義したい場合
divmodメソッド
divmod(other) -> [Numeric]
self == other * q + r
other > 0 のとき: 0 <= r < other,
other < 0 のとき: other < r <= 0,
q は整数
公式ドキュメント(Numericクラスdivmod)11.divmod(3) #=> [3, 2] 公式リファレンスより
- reverse!
公式ドキュメント(Stringクラスreverse!)- join
公式ドキュメント(Arrayクラスjoin)def Binary(num) acc = [] while num > 0 num, mod = num.divmod(2) #numには2で割った数が入ります acc << mod #2で割った余りを配列に加えます つまり1or0です end answer = acc.reverse!.join #accの値をreverse!で逆にする joinで値を続けて表示する puts answer end
- 投稿日:2020-11-28T19:00:20+09:00
Rubyで2進数を計算したい時には、**演算子を使う
ruby 2.7.0の記事です
10進数を2進数に変換する方法
- 演算子については、公式リファレンスを参照
公式ドキュメント(Numericクラス)def Binary(num) arry1 = [] #1になる桁を左から数えた数が入ります 44の場合(6,4,3) つまり(101100) while num > 0 arry2 = [] #2で割った数が入ります n_i = num while n_i > 0 n_i = n_i / 2 arry2 << n_i end num = num - (2 ** (arry2.length - 1)) #arry2.lengthで桁を出します 10進数に戻して、最初の数から引きます arry1 << arry2.length #1になる桁数を加えます end i = 1 b_a = [] arry1[0].times do #arry1[0]に桁が入ります。1か0を配列b_aに入力します if arry1.include?(i) b_a << 1 else b_a << 0 end i += 1 end b_a.reverse.each do |s| #配列をprintで連続して表示します print s end end Binary(num)以下@kts_hさんのコメントを元に追加
- 整数を進数で出したければ、すごく簡単にできる
- num.to_s(2-36の数値が入る)
公式ドキュメント(Integerクラスinspect)num = 44 num.to_s(2) #=> "101100" puts "2進数にしたい値を入力してください" num = gets.to_i # 整数にする 仮に44と入力した場合 puts Binary = num.to_s(2) #=>101100 文字列として出力 puts "取り出したい桁を入力してください" n = gets.to_i #例えば6桁目”6”と入力 puts Binary.slice(-n) #今回はn=6となり、”1”が表示される
簡潔に記述する
divmodメソッド
divmod(other) -> [Numeric]
self == other * q + r
other > 0 のとき: 0 <= r < other,
other < 0 のとき: other < r <= 0,
q は整数
公式ドキュメント(Numericクラスdivmod)11.divmod(3) #=> [3, 2] 公式リファレンスより
- reverse!
公式ドキュメント(Stringクラスreverse!)- join
公式ドキュメント(Arrayクラスjoin)def Binary(num) acc = [] while num > 0 num, mod = num.divmod(2) #numには2で割った数が入ります acc << mod #2で割った余りを配列に加えます つまり1or0です end answer = acc.reverse!.join #accの値をreverse!で逆にする joinで値を続けて表示する puts answer end
- 投稿日:2020-11-28T17:57:37+09:00
【Index】マルチスケールシミュレーション特論
Introduction
マルチスケールシミュレーション特論の講義メモです.Rubyの講義メモを中心に,その他備忘録などを投稿していきます.
間違えている部分があれば修正・加筆するのでコメントいただけると幸いです.
Class_Assignments
- To Do
[2/4]
- [X] qiita link: 学習メモへのlinkがqiitaにある
- [ ] roman numerals, google recruit or both: どちらかあるいはどちらもが解けている
- [X] qiita public: 学習メモがpublicになっていて,LGTMをもらえている
- [ ] pull_request or issue on my_help or qiita_org: my_helpかqiita_orgにissueかpull req.を投げた,認められた
Class_Articles <2020-11-25 Wed>
edit_check
[4/8]
- [X] 1. 【Week 5】bash, ruby_first
- [X] 2. 【Week 6】my_help, ruby_second
- [X] 3. 【Week 7】gem, ruby_third
- [X] 4. 【Week 8】rake, ruby_fourth
- [ ] 5. 【Week 9】rubular, ruby_fourth
- [ ] 6. 【Week 10】recursion, ruby_fifth
- [ ] 7. 【Week 11】Coming soon…
- [ ] 8. 【Week 12】Coming soon…
Public_Articlds
公開記事 (Public) は【Public】マルチスケールシミュレーション特論公開記事リストにまとめています.
※ private と public は同一内容ですが,アクセス数や LGTM によるトレンド浮上,受講者以外の限定共有記事へのアクセスを避けるために念の為分けています.
Memorandums <2020-11-11 Wed>
Memorandums are currently under edit.
【Memo】github & Qiita
【Memo】Emacs key-bind
【Memo】Emacs Org-Mode
Directory_tree
$ pwd ~/grad_members_20f/members/e79a93e5b7b1 $ tree . ├── README.md ├── README.org ├── Rakefile ├── codes │ └── 講義プログラム(.org, .md) └── posts ├── class │ └── 講義メモ(.org, .md) └── memo └── 備忘録(.org, .md).org ファイルを整理しようとディレクトリにまとめたら,講義ページの『作業達成の目安』から弾かれてた.
そういえば各ユーザのディレクトリ直下に README.org をおかないといけないんだっけ.
どうしようもないので上記の構成で整理していく.
- source ~/grad_members_20f/members/e79a93e5b7b1/README.org
- 投稿日:2020-11-28T16:08:06+09:00
Railsの機能をcronから実行する
この記事は、Railsの機能をcronから実行する方法について解説しています。
Railsの機能をcronから実行する方法はいくつかありますが、単純に実装してしまうと、サーバーのリソースを無駄に消費し、実行完了までとても時間のかかるものになってしまいます。
ある程度アクセスのあるサービスを運営している場合は、上記のリソースの無駄使いを防ぐために、Railsの機能をcronから実行するときに少し工夫が必要になります。
最終的な完成形は、「バックグラウンドジョブとして実装し、起動しているRailsサーバーからジョブをキューイングする」というものになります。順を追って解説していきます。
既存の方法の何が問題なのか
既存のよくある方法は、cronからRailsサーバーを起動するものがほとんどです。この方法だと、Railsサーバーの起動に10秒ほど時間がかかります。時間だけでなく、100MBほどのメモリが消費されます。規模が大きい場合はもっとたくさんの時間とメモリが必要になります。
cronから毎分実行する場合は、このリソースの無駄使いを避けたいところです。cronから呼び出したい機能をバックグラウンドジョブとして実装し、ジョブのキューイングをRailsサーバーから行うことで、このリソースの無駄使いを避けることができます。
バックグラウンドジョブとして実装する
まずは、cronから呼び出したいRailsの機能をバックグラウンドジョブとして実装します。ジョブキューシステムにはSidekiqを利用している想定です。
このジョブは、
UpdateSomethingWorker.perform_async(user_id)
というコードで実行することができます。app/workers/update_something_worker.rbclass UpdateSomethingWorker include Sidekiq::Worker sidekiq_options queue: 'misc', retry: 0, backtrace: false def perform(user_id, options = {}) user = User.find(user_id) # 数秒よりも長い時間がかかると想定 user.update_something end end実装したジョブの動作テスト用として、Rakeタスクもついでに用意します。ここは必須ではありません。
このRakeタスクは、
rails users:update_something
というコードで実行することができます。lib/tasks/users.rakenamespace :users do desc 'Update something' task update_something: :environment do user_id = ENV['USER_ID'] UpdateSomethingWorker.perform_async(user_id) end end起動しているRailsサーバーからジョブをキューイングする
Railsでアプリケーションを実装している場合、ほとんどの場合はWebサーバーが起動している思います。このWebサーバーに、ジョブをキューイングするためのエンドポイントを用意します。
必ず、リクエストの正当性チェックをする必要があります。そうしないと、第三者に意図しないタイミングでジョブをキューイングされてしまう脆弱性が残ってしまいます。
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action do # !!! リクエストの正当性を必ずチェックすること !!! end def update_something user_id = params[:user_id] UpdateSomethingWorker.perform_async(user_id) render json: {status: 'ok'} end endこのルーティングを追加することで、コントローラーで実装したコードが有効になります。
ビューの中では、
users_update_something_path
というコードでこのルーティングに対応するパスを取得できます。config/routes.rbpost 'users/update_something', to: 'users#update_something'ここがポイントです。実装したコントローラーにアクセスするためのRubyコードを書きます。RailsではなくRuby単体で動くようにしている点が重要です。こうすることで、cronからRailsの機能を実行するときの不要なオーバーヘッドを減らすことができます。
このRubyスクリプトは、
ruby bin/users_update_something.rb
というコードで実行することができます。bin/users_update_something.rb#!/usr/bin/env ruby require 'net/http' require 'uri' if __FILE__ == $0 # users_update_something_url url = 'https://yourweb.com/users/update_something' puts Net::HTTP.post_form(URI.parse(url), {}).body endさらに、現実的にはcronから実行したときのログも記録したくなると思います。そのためのシェルスクリプトを用意します。
このシェルスクリプトは、
sh bin/cron_ruby.sh [実行したいRubyファイル]
というコードで実行することができます。bin/cron_ruby.sh#!/usr/bin/env bash exec >>/var/yourapp/log/cron.log 2>&1 cmd="/usr/local/bin/ruby $1" SECONDS=0 echo -e "$(date) $cmd started" cd /var/yourapp && $cmd echo -e "$(date) $cmd finished elapsed=${SECONDS}"crontabを更新する
後は、上記のシェルスクリプトをcronから実行するようにすればOKです。こうすることで、Rais起動時のオーバーヘッドを避けつつ、cronからRailsの機能を呼び出すことができます。
crontab* * * * * /bin/sh -c "/var/yourapp/bin/cron_ruby.sh /var/yourapp/bin/users_update_something.rb"Railsの機能をcronから実行する他の方法
毎分ではなくもっと長いスパンでの実行であったり、サーバーのスペックに余裕があるのなら、今回のような複雑な仕組みは不要かもしれません。そういう場合は、下記の方法を使うことができます。
両方とも、crontabに直接書くことでcronから実行できます。
# Rubyのコードをコマンドラインから実行する方法 rails runner "実行したいRubyコード" # Rakeタスクをコマンドラインから実行する方法 rails "実行したいRakeタスク"質問の連絡先
質問や分からない点はお気軽に @ts_3156 までご連絡ください。
- 投稿日:2020-11-28T14:23:52+09:00
ActiveRecordでデータを月毎に集計
[ActiveRecord]でデータを月毎に集計する方法
今回は入力した数値を月毎に集計して合計値を出力させる方法を投稿します。
ネットで検索しても、なかなか良い方法がなかったので、参考になればと思います。使用テーブル(incomesテーブル)↓↓↓
このテーブルから2019年-月分・・・2020年-月分としたいと思います。
実行コード↓↓↓Income.group("YEAR(fill_date)").group("MONTH(fill_date)").sum(:price)YEAR(fill_date)で年毎にグルーピングして、さらにMONTH(fill_date)で月毎にグルーピングした後、sum(:カラム名)でpriceを合計しています。
このときのターミナルのログ↓↓↓
結果、年と月を区別して合計値を出力させることができました。今回の記事が誰かの役に立てればと思います。またもっと良い方法があればコメントしてもらえると助かります!!
- 投稿日:2020-11-28T14:20:10+09:00
FFmpegをAWS Lambdaで動かす
ffmpegをAWS Lambdaで動かしてみました
何を思ったかRubyのラッパー使ってみました
途中結構躓くことが多かったので面倒でしたDockerfile
DockerfileFROM lambci/lambda:build-ruby2.7 RUN gem install bundler -p WORKDIR /var/task RUN export TASK=/var/task RUN mkdir ffmpeg_sources RUN mkdir ffmpeg_dist RUN mkdir bin RUN yum install autoconf automake bzip2 bzip2-devel cmake freetype-devel gcc gcc-c++ git libtool make pkgconfig zlib-devel -y RUN yum install -y libpng-devel WORKDIR /var/task/ffmpeg_sources RUN curl -O -L https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.bz2 RUN tar xjvf nasm-2.14.02.tar.bz2 WORKDIR nasm-2.14.02 RUN ./autogen.sh RUN ./configure --prefix="$TASK/ffmpeg_build" --bindir="$TASK/bin" RUN make RUN make install RUN WORKDIR /var/task/ffmpeg_sources RUN curl -O -L https://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz RUN tar xzvf yasm-1.3.0.tar.gz WORKDIR yasm-1.3.0 RUN ./configure --prefix="$TASK/ffmpeg_build" --bindir="$TASK/bin" RUN make RUN make install WORKDIR /var/task/ffmpeg_sources RUN git clone --depth 1 https://code.videolan.org/videolan/x264.git WORKDIR x264 RUN PKG_CONFIG_PATH="$TASK/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$TASK/ffmpeg_build" --bindir="$TASK/bin" --enable-static RUN make RUN make install WORKDIR /var/task/ffmpeg_sources RUN git clone https://bitbucket.org/multicoreware/x265_git.git WORKDIR /var/task/ffmpeg_sources/x265_git/source RUN cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="$TASK/ffmpeg_build" -DENABLE_SHARED:bool=off . RUN make RUN make install WORKDIR /var/task/ffmpeg_sources RUN git clone --depth 1 https://github.com/mstorsjo/fdk-aac WORKDIR fdk-aac RUN autoreconf -fiv RUN ./configure --prefix="$TASK/ffmpeg_build" --disable-shared RUN make RUN make install WORKDIR /var/task/ffmpeg_sources RUN curl -O -L https://downloads.sourceforge.net/project/lame/lame/3.100/lame-3.100.tar.gz RUN tar xzvf lame-3.100.tar.gz WORKDIR lame-3.100 RUN ./configure --prefix="$TASK/ffmpeg_build" --bindir="$TASK/bin" --disable-shared --enable-nasm RUN make RUN make install WORKDIR /var/task/ffmpeg_sources RUN curl -O -L https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz RUN tar xzvf opus-1.3.1.tar.gz WORKDIR opus-1.3.1 RUN ./configure --prefix="$TASK/ffmpeg_build" --disable-shared RUN make RUN make install WORKDIR /var/task/ffmpeg_sources RUN git clone --depth 1 https://chromium.googlesource.com/webm/libvpx.git WORKDIR libvpx RUN ./configure --prefix="$TASK/ffmpeg_build" --disable-examples --disable-unit-tests --enable-vp9-highbitdepth --as=yasm RUN make RUN make install WORKDIR /var/task/ffmpeg_sources RUN curl -O -L https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2 RUN tar xjvf ffmpeg-snapshot.tar.bz2 WORKDIR ffmpeg RUN PATH="$TASK/bin:$PATH" PKG_CONFIG_PATH="$TASK/ffmpeg_build/lib/pkgconfig" ./configure \ --prefix="$TASK/ffmpeg_build" \ --pkg-config-flags="--static" \ --extra-cflags="-I$TASK/ffmpeg_build/include" \ --extra-ldflags="-L$TASK/ffmpeg_build/lib" \ --extra-libs=-lpthread \ --extra-libs=-lm \ --bindir=../../ffmpeg_dist \ --enable-gpl \ --enable-libfdk_aac \ --enable-libfreetype \ --enable-libmp3lame \ --enable-libopus \ --enable-libvpx \ --enable-libx264 \ --enable-libx265 \ --enable-nonfree RUN make RUN make install RUN chmod -R a+x ../../ffmpeg_dist RUN mkdir /var/task/dist RUN cp -r ../../ffmpeg_dist/* /var/task/dist RUN yum install -y yum-utils rpmdevtools WORKDIR /tmp RUN yumdownloader unixODBC.x86_64 libtool-ltdl.x86_64 gnutls.x86_64 \ bzip2-libs.x86_64 freetype.x86_64 libpng.x86_64 RUN rpmdev-extract *rpm RUN cp /tmp/*/usr/lib64/* /var/task/dist WORKDIR /var/task/dist COPY . . RUN bundle config set --local path 'vendor/bundle' RUN bundle install RUN zip -r dist.zip . RUN mkdir /var/task/output CMD cp dist.zip /var/task/output上のDockerfileと一緒のディレクトリにlambda_functionをおいて
docker build . -t lambda_ruby_ffmpeg docker run -v "{PWD}":/var/task/output lambda_ruby_ffmpegでstreamio-ffmpegが使えます
githubの方にも上げておいたのでlambda_function.rbを編集してdocker上でビルドして走らせるだけでデプロイパッケージのzipが出てくると思います。
ただrequireはbundleに依存するのでrequire 'bundler' Bundler.requireは忘れないでください。
それとバイナリにパスは通っていないはずなのでカレントディレクトリ(/var/task)にパスを通してください躓いたところ
以下文体崩壊
--pathが古い
bundle install --path vendor/bundleを実行したら--pathが古いといわれた。AWSの公式サンプルにも乗っていたのにそれでいいのかAmazon
fixRUN bundle config set --local path 'vendor/bundle'ffmprobeの実行権限がないと言われた
windowsでzipファイルの作業してたらはまった。Lambdaのコンテナ上で圧縮しないと
実行権限の付与も忘れずに
RUN chmod -R a+x ../../ffmpeg_distx265のリポジトリがない
MercurialというGitに似たものがあったらしい(無知)。
これを見ればわかる通りffmpegの公式のビルド手順にもx265のビルドにはMercurialを使うように書かれているが、
公式(かどうかはわからないが)のx265のビルド方法を見てみると gitを使っていた。
Mercurialからgitに乗り換えるという歴史的な瞬間に立ち会ってしまった。
考えてみればGitもBitKeeperに触発それて作られているわけでほかにも分散型バージョン管理システムがあっても不思議じゃない。
AWSにせよなんにせよクラウドサービスの多くがgithubフレンドリーな姿勢になるほどGitはデファクトスタンダードになっているが、その一方で他の分散型バージョン管理システムのシェアを奪っているんだなあと寂寥感に苛まれた。
そして苦労して入れたところでおそらく使われない。
ffmpegで必要なライブラリ、例えばLameとかもgitで管理されているわけではなくsourceforgeで進められていて、tarボールをcurlするような形になっていて常に最新のバージョンをとってこられるよう指定できない。そう考えたらgitもMercurialもいい感じなのになあ。* .so. * エラー
「2021年までにモバイルトラフィックの80%は動画で占められます」
https://media.kaizenplatform.com/n/n51e413c8087c
これほどまでに動画は我々の世界に身近になったけれど、動画を再生するまでに何があるのか知っている人はほとんどいない
ffmpegはビルドすると30MBほどになる巨大なプログラムだが、動かすにはまだ足りない。
これらの
START RequestId: effdb447-3a9a-4fa4-832e-e7359370d9d0 Version: $LATEST
ffprobe: error while loading shared libraries: libfreetype.so.6: cannot open shared object file: No such file or directory
END RequestId: effdb447-3a9a-4fa4-832e-e7359370d9d0
REPORT RequestId: effdb447-3a9a-4fa4-832e-e7359370d9d0 Duration: 2249.69 ms Billed Duration: 2300 ms Memory Size: 128 MB Max Memory Used: 113 MB Init Duration: 724.21 ms
RequestId: effdb447-3a9a-4fa4-832e-e7359370d9d0 Error: Runtime exited with error: exit status 127
Runtime.ExitErrorSTART RequestId: 51a2ee5f-3cfc-4647-a1cf-58096076e7b6 Version: $LATEST
ffprobe: error while loading shared libraries: libbz2.so.1: cannot open shared object file: No such file or directory
END RequestId: 51a2ee5f-3cfc-4647-a1cf-58096076e7b6
REPORT RequestId: 51a2ee5f-3cfc-4647-a1cf-58096076e7b6 Duration: 2024.82 ms Billed Duration: 2100 ms Memory Size: 128 MB Max Memory Used: 111 MB Init Duration: 748.18 ms
RequestId: 51a2ee5f-3cfc-4647-a1cf-58096076e7b6 Error: Runtime exited with error: exit status 127
Runtime.ExitErrorSTART RequestId: 47eaba0a-f985-4103-a452-935a78e4e6e1 Version: $LATEST
ffprobe: error while loading shared libraries: libpng15.so.15: cannot open shared object file: No such file or directory
END RequestId: 47eaba0a-f985-4103-a452-935a78e4e6e1
REPORT RequestId: 47eaba0a-f985-4103-a452-935a78e4e6e1 Duration: 2530.37 ms Billed Duration: 2600 ms Memory Size: 128 MB Max Memory Used: 128 MB Init Duration: 887.27 ms
RequestId: 47eaba0a-f985-4103-a452-935a78e4e6e1 Error: Runtime exited with error: exit status 127
Runtime.ExitErrorエラーメッセージは実行しようとしたときに必要になった共通ライブラリ。lambdaで実行するにはこれらを含めなければならなかった。
つくづく動画をデジタル信号に変えるのはあらゆる処理が必要なのだなと感じた。
- 投稿日:2020-11-28T14:01:28+09:00
Rubyのputsメソッドについて簡単に
Rubyには「puts」と呼ばれるメソッドが用意されています。
呼び方は人によりますが「プッツ」だったり「プットエス」と呼ばれていたりします(僕はプッツ派です)。
このputsメソッドはターミナルに値を出力するためのメソッドになります。
記述方法は簡単でputsの後ろに
「" "」(ダブルクォーテーション)または、
「' '」(シングルクォーテーション)と呼ばれる記号の中に文字列を記述するだけです。
数値はそのまま「" "」や「' '」で囲まずに記述します。「""」はshift + 2
「''」はshift + 7
でそれぞれ表示することができます。putsは数値を足すこともでき、文字列同士を連結することもできます。
puts "値" #ターミナルの出力は「値」 puts 10 #ターミナルの出力は「10」数値なので「""」はいらない #数値の足し算、文字列の連結 puts 10 + 10 #ターミナルの出力は「20」 puts "これは" + "文字列です" #ターミナルの出力は「これは文字列です」ただし、「数値の足し算」「文字列の連結」は同じ数値同士、文字列同士のみの話です!
Rubyには「文字列」と「数値」の概念があります!
Rubyでは「数値 + 文字列」の計算は基本的にはできません。
以下に例をあげます。puts 100 + "歳です" #数値と文字列の計算 #ターミナルの出力は Traceback (most recent call last): 1: from array.rb:1:in `<main>' array.rb:1:in `+': String can't be coerced into Integer (TypeError)このようにエラーが表示されます。
この時のRubyは「文字列を数値に変換できませーーん」
とエラーを返してくれます。この数値の「100」と文字列の「歳です」という文字を連結させるにはどうすればいいのか、、、
答えはこうです。
puts "100" + "歳です" #「100」を文字列として記述 #出力は「100歳です」他にも記述方法はあるんですが今回は簡単な記述方法にします。
え、さっき数値は「" "」いらんゆうたやん、、、
と、思った方そうです、数値は「""」や「''」を必要としないです。
ただし、数字を文字列として扱う場合は別で、この場合「文字列同士の連結」になります。
ややこしや〜
まとめ
・putsメソッドはRubyに予め用意されているメソッドである。
・putsを使ってターミナルに表示するには、文字列なら「""」や「''」の中に記述する、数値の場合はそのまま記述する。
・基本的には「文字列」 + 「数値」の計算はできない。
- 投稿日:2020-11-28T13:15:04+09:00
【Rails】parent_idとchild_idを持つ中間テーブルを作って親子関係を実装する
はじめに
階層構造を持つモデルを実装することがあったのでその時のメモ書きです。
例として部署を考えます、以下の画像のようなイメージですね。
( ※ 適当に拾ってきました。)
テーブルは departments と department_pathsの二つを用意します。
departments id integer name string
department_paths id integer parent_id string child_id string association の実装
まず assciation の実装です。
- department.parent で自分の親部署を取得
- department.children で自分の子部署を取得
ができることを目指します。
まずは DepartmentPath の parent_id から親部署を, child_id から子部署を取得できるように association を実装していきます。
app/models/department_path.rbbelongs_to :parent, class_name: 'Department' belongs_to :child, class_name: 'Department'次に Dpartment です。
DepartmentPath を throughして親部署・子部署を取得できるように association を実装します。
ちなみに子部署は複数持ち得るので has_many ですね。app/models/department.rb# 親との association has_one :parent_path, class_name: 'DepartmentPath', foreign_key: :child_id, has_one :parent, class_name: 'Department',through: :parent_path, source: :parent # 子との association has_many :child_paths, class_name: 'DepartmentPath', foreign_key: :parent_id, dependent: :destroy has_many :children, class_name: 'Department', through: :child_paths, source: :child以上で association は実装完了です!
先祖や子孫を階層構造で取得する
association は 実装できましたが、これだけだと自身の直近の親と子しか取得できないの自分の子孫の末端までを取得したり、親を遡って部署の頂点から自身までを取得することはできませんね。
ここでは以下の二つができることを目指します。
- 部署の頂点から順に自身までの name を配列で取得
- 自身から子孫までの末端を Hash で取得
前者は画像中の「経理課」でいうと以下のような感じですね。
( ※ 説明しやすいように「富士通株式会社」も部署の一つとさせてください。)['富士通株式会社', '経理部', '経理課']後者は画像の中の「富士通株式会社」でいうと以下のような感じですね。
{ '富士通株式会社' => [ '人事総務部' => [ '人事課', '総務課', '教育課' ], '経理部' => [ '経理課' ], '開発企画部' => [ '企画課', '開発課', 'システム課' ], ] }まずは部署の頂点から順に自身までの name を配列で取得できるように実装していきます
再帰関数を使えば簡単ですね。app/models/department.rbdef ancestors(array = [self]) if parent.present? # 親が存在すれば instanceを配列の先頭に挿入 array.unshift(parent) parent.ancestors(array) else # 親が存在しなければ配列の instance をそれぞれ name に変換して返す array.map do |department| department.name end end end次にできるように実装していきます。
これも再帰関数を使えば簡単ですね。app/models/department.rb# まず子孫の Hash を取得するメソッドを定義し、 # そのメソッドを使って { 自身 => 子孫のHash } を返せるように実装します def get_nested_children_hash return if children.blank? children.map do |child| child.children.present? ? { child.name => child.get_nested_children_hash } : child.name end end def descendants children.present? ? { name => get_nested_children_hash) } : name endまとめ
以上の実装で以下のように部署構造が取得できるようになりました。
department.parent #親 department.children #子 department.ancestors # 先祖 department.descendants # 子孫
- 投稿日:2020-11-28T11:54:49+09:00
【Rails】mark_for_destruction を 使って特定の条件のレコードを削除する。
使用ケース
「特定のカラムが空である場合にはそのレコードを削除したい」みたいなときに便利です。
そのレコードに削除マークをつけておくと削除されるイメージで実装できます。サンプルコード
前提として users は name カラムを持つとします。
name の値が nil か 空文字であればそのレコードを削除したいみたいな時は、以下のように実装できると思います。
今回はインスタンスメソッドを定義し、before_validationのコールバックで実行しました。
app/models/user.rbbefore_validation :delete_user_if_name_blank def delete_user_if_name_blank self.mark_for_destruction if name.blank? endif 文を使えば色んな条件で適用できますし、アソシエーションしているレコードも削除できたりするので色々と応用が効きそうですね。
- 投稿日:2020-11-28T05:23:15+09:00
新規投稿機能を作成した際にNoMethodError【Ruby on Rails】
Railsで一般的な新規投稿機能を作成した際にNoMethodErrorが発生した。
原因
一口にノーメソッドエラーと言っても原因は色々考えられる。
私の場合は、UserモデルとPostモデルの間で1対多の関連付けをしていたのだが、app/model/User.rbに
has many :posts
を記述していない事が原因だった。
教訓
判明してみると、なんでこんなミスをしたのだろうと思ってしまうような凡ミスではあるが、
ノーメソッドエラー
→メソッドが定義されていない
→メソッドを一生懸命確認するという安易な発想で、原因にたどり着くまでに30分くらい費やしてしまった。
エラー画面の情報はあてにならない(この言い方は大いに語弊があるかもしれないが)場合が多々あるため、周辺情報も含めて行うべき設定は正しく行っているか確認するように心がけたい。
- 投稿日:2020-11-28T04:11:24+09:00
rails db:migrateでalready existsというエラーの対処方法
rails g modelでモデルを作った後、rails db:migrateしたらエラーが出たのでその時の対処法をメモしておきます。
エラー内容
-------------------------------------------------------------------------- rails aborted! StandardError: An error has occurred, all later migrations canceled: Mysql2::Error: Table 'notifications' already exists: CREATE TABLE `notifications` (`id` bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, `visiter_id` int NOT NULL, `visited_id` int NOT NULL, `post_id` int, `comment_id` int, `action` varchar(255) DEFAULT '' NOT NULL, `checked` tinyint(1) DEFAULT FALSE NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL) --------------------------------------------------------------------------もうすでにこのファイルは存在していると言われているみたいです。
1回モデルを消したときにテーブルが残っていたのかも?対処方法
最初にモデルを削除します
$ rails destroy model Notification次にテーブル削除のために削除用のmigrationファイルを作成します(ファイル名はなんでも良いです)
$ docker-compose run web rails generate migration drop_table_notifications作成したmigrationファイルにテーブル削除を記述する
class DropTableNotifications < ActiveRecord::Migration[5.2] def change drop_table :notifications end end最後にマイグレーションを実行します
$ rails db:migrateこれでちゃんとテーブルも削除できて、無事モデル作成後、rails db:migrateも通りました!
参考にした記事
https://note.com/oreno/n/n45f8208ade29
とても分かりやすかったです!ありがとうございました!
- 投稿日:2020-11-28T01:50:30+09:00
ターミナルの出力結果をテキストに出力する
0.背景
ターミナルで出力した結果をテキストに出力できたら便利だと思い、まとめました。
1.使用環境
mac.os バージョン10.15.6
Ruby2.6.6
2.実際やってみる
Rubyのバージョンを追加する際にインストール可能なバージョンを確認したいと思い、
rbenv install --list-all
を実行したところ、見切れてしまいました。
新たにインストールを行う、2.5.1のバージョンを探していますが見当たりません。
※rbenvとは、Rubyのバージョンを簡単に切り替えてくれるツール
のことです。
参考リンク:rbenvの使い方と仕組みについてそこで下記のサイトを元に出力してみました。
[Tool] Mac標準コマンドで出来る!ターミナルの出力結果をテキストファイルにも保存する方法$ script sample.txt #ファイル名を指定する $ rbenv install --list-all #実行したい処理 ... $ exit #書き出しを終了実行すると
sample.txt
の名前でテキストに出力できたことが分かります。
無事探していた2.5.1も見つかりました。3.どこに保存されているのか?
先ほどのコマンドは保存場所を指定していません。ではどこに保存されているのか?というと
ホームディレクトリ
に保存されます。ホームディレクトリはmacの場合、Finderを開いてShift + command + H
で飛べます。補足:ファイル名を指定しない場合
ファイルを指定しない場合、
typescript
に保存されます。
参考リンク:Linuxコマンド【script】
typescript
もホームディレクトリに保存されます。
4.応用先
Gitでlogを出力したいとき
など、いろんなところで活用できるのではないかと思います。
読んで頂き、ありがとうございました。
- 投稿日:2020-11-28T00:51:57+09:00
Websocket通信で作るリアルタイムオセロ対局 - GORO解体真書Chapter1
皆さんおひさし!!
エンジニアオセラーのはじぽんです!
さて、今年のアドベントカレンダー1番乗りをGETしちゃいましたので、
今年は割と技術的にも真面目にやっていこうかなって思います。と!いうのも!
今年はコロナでオセロ大会が開けないなんてことがあったので、なんとなんと!
日本オセロ連盟公式でオンライン対局できるものを作っちゃったのです!!はい、テンションも上げ上げなのはこれのおかげで技術ネタが豊富で記事がスラスラ書けちゃうからなのですわ。
というわけでこのシリーズを『GORO解体真書』と名付けて書いていこうと思います。第一弾 Websocket通信で作るリアルタイムオセロ対局
Websocketとは
サーバ、クライアント間を双方向通信するプロトコル。
httpと比較すると、リクエスト送ってレスポンス返ってくるという1セットに対して、1回コネクション繋いだら後はどっちからでも通信を自由に送り合うやつ。どう使うの?
オセロの盤面の通信に使っていきます。
F5に打つ → クライアントからサーバに着手座標の送信
盤面の更新 ← サーバからクライアントに最新盤面の送信
これは対局してる人にとっての流れだけど、観戦してる人には、盤面の更新だけ受信し続ければよい。でも(ハードルが)お高いんでしょ?
世の中のWebアプリなんて、めんどくさいことは全部フレームワークがやってくれるんですわ()
GOROはRuby on Railsで作成しているので、フレームワークに標準で入っているWebSocketライブラリのActionCableを使って簡単実装しちゃうよ!!
※Ruby on RailsとかActionCableの基本編みたいなことはやらないよ!着手送信
まぁ座標を送るだけです。クライアントでは一切判定処理とか入れずに全部サーバでやらせちゃいます。
盤面の絵をhtmlのSVGタグだけで簡単にお絵描きして、(Viewテンプレートにslimを使って動的に書いてますが、これがHTMLになる)
show.html.slimsvg width="#{outer_board}px" height="#{outer_board}px" viewBox="0 0 #{outer_board} #{outer_board}" class="board img-fluid" defs filter id="white_shadow" feDropShadow dx="1" dy="1" stdDeviation="0" flood-color="white" filter id="black_shadow" feDropShadow dx="1" dy="1" stdDeviation="0" flood-color="black" rect x="0" y="0" width="#{outer_board}" height="#{outer_board}" fill="#666" rx=10 ry=10 rect x="4" y="4" width="#{outer_board - 8}" height="#{outer_board - 8}" fill="#444" rx=10 ry=10 rect x="13" y="13" width="#{outer_board - 26}" height="#{outer_board - 26}" fill="#333" rect x="16" y="16" width="#{outer_board - 32}" height="#{outer_board - 32}" fill="#444" rect x="#{outer}" y="#{outer}" width="#{inner_board}" height="#{inner_board}" fill="forestgreen" rect x="#{outer_board / 2 - cell / 2 - 8}" y="#{outer_board - 25}" width="#{cell + 16}" height="25" fill="#555" rx=5 ry=5 rect x="#{outer_board / 2 - cell / 2 - 8}" y="#{outer_board - 4}" width="#{cell + 16}" height="4" fill="#666" text x="#{outer_board / 2 - cell / 2}" y="#{outer_board - 8}" font-size="18" stroke="none" fill="gold" Othello text x="#{outer_board / 2 - cell / 2 + cell - 4}" y="#{outer_board - 14}" font-size="11" stroke="none" fill="gold" R circle cx="#{point(2)}" cy="#{point(2)}" r="#{4}" fill="#000" circle cx="#{point(2)}" cy="#{point(6)}" r="#{4}" fill="#000" circle cx="#{point(6)}" cy="#{point(2)}" r="#{4}" fill="#000" circle cx="#{point(6)}" cy="#{point(6)}" r="#{4}" fill="#000" - (0..8).each do |i| line x1="#{point(i)}" y1="#{from}" x2="#{point(i)}" y2="#{to}" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="1" line x1="#{from}" y1="#{point(i)}" x2="#{to}" y2="#{point(i)}" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="1"クリック(タップ)した場所をオセロの座標に変換して
app/assets/javascripts/board.jslet board = document.getElementsByClassName('board'); $(board).on('click', function (event) { let offset =$(this).offset(); let x = Math.floor((event.pageX - offset.left - border) / cell_px_absolute()); let y = Math.floor((event.pageY - offset.top - border) / cell_px_absolute()); let point = String.fromCharCode(x + 'A'.charCodeAt(0)) + String(y + 1); // マス以外も無理やり座標文字列できちゃうけどサーバ側ではじくようにしてる。 App.game.put_stone(point); });ActionCableの通信として送るだけ。
app/assets/javascripts/channels/game.js$(function() { // ActionCable送受信をしてくれるインスタンス生成 App.game = App.cable.subscriptions.create({channel: 'GameChannel', room: 'room'}, { connected: function() { }, disconnected: function() { }, received: function(data) { }, // 送信用メソッドは好きなようにいくつでも作れる。preformを呼び出すことで実際の送信。 put_stone: function(point) { return this.perform('put_stone', {point: point, game_table_id: 1}); } }); });着手受信
サーバ側で座標を受信して盤面判定処理とかかませる。
盤面ロジックはビットボードのアルゴリズムを採用してるけど、Ruby版の実装として別エントリーにしようかな。着手送信の受け取り口とリアルタイムに盤面見てる人に送信(ブロードキャスト)
app/channels/game_channel.rbclass GameChannel < ApplicationCable::Channel def subscribed stream_from params['room'] end def unsubscribed; end def put_stone(data) game = GameLoader.load(data['game_table_id']) # 着手処理&置けたかどうかの戻り値 return unless GameService.put_stone(current_user, game, data) # コネクション繋がってる人に対してブロードキャスト ActionCable.server.broadcast "game_channel_#{data['game_table_id']}", game: game.to_h end end盤面情報の中身
ビットボードで持ってる値をあんまり加工せずそのまんま渡してます。つまり黒と白でそれぞれ配置情報をビットで持つと。
しかしruby側は64ビットの変数に対応できるけどクライアント側のJavaScriptはそういうわけにもいかないので、
盤をパキッと割って32ビット変数2つな感じでやり取りしてるという歪さは残ってしまった…!
rubyで使うデータ(64bit)
{ black_stones: 0x0000000810000000, white_stones: 0x0000001008000000 }
javascriptで使うデータ(32bit)
{ black_stones: [0x00000008, 0x10000000], white_stones: [0x00000010, 0x08000000] }
最新の盤面情報を受信
さっき送信の時に作ったActionCable専用クラスに受信時の処理を書く。
app/assets/javascripts/channels/game.js$(function() { App.game = App.cable.subscriptions.create({channel: 'GameChannel', room: 'room'}, { connected: function() { }, disconnected: function() { }, // このメソッドが受け取り口 received: function(data) { receive_game(data); }, put_stone: function(point) { return this.perform('put_stone', {point: point, game_table_id: 1}); } }); });受信した盤面情報で画面を書き換える
黒と白の配置のビットをそれぞれ描画していきます。32ビット二つなのでちょっとだけかっこ悪い
app/assets/javascripts/board.jsfunction receive_game(data) { black_stones = change_32bits(data['game']['black_stones']); white_stones = change_32bits(data['game']['white_stones']); update_board(); } function change_32bits(bit_stones) { let bit_32 = bit_stones.match(/.{8}/g); return [parseInt(bit_32[0], 16), parseInt(bit_32[1], 16)]; } function update_board() { clear_board(); let board = document.createDocumentFragment(); for (let i = 0; i < 32; i++) { let point_bit = (base_point_bit >>> i); if ((point_bit & black_stones[0]) !== 0) { board.appendChild(stone(i, color_num.black)); } if ((point_bit & black_stones[1]) !== 0) { board.appendChild(stone(i + 32, color_num.black)); } if ((point_bit & white_stones[0]) !== 0) { board.appendChild(stone(i, color_num.white)); } if ((point_bit & white_stones[1]) !== 0) { board.appendChild(stone(i + 32, color_num.white)); } } document.querySelector('#view_board > .board').appendChild(board); }基本はこんなもん
これでリアルタイム対局のコア部分は出来上がり。割とあっさりできちゃうもんだけど、素のWebSocketを触ってないから楽なだけっていうのもありそう。チャットとかなんて30分で作れちゃうからねぇ。
次回はビットボードをクローズアップしていきます!
ではまた!