20201128のRubyに関する記事は21件です。

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のレコードが更新出来なかった時の解決法

最後まで読んでいただきありがとうございます!

何かご指摘などございましたら、コメントいただけると嬉しいです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで、現在時刻・日時を表示させる方法

はじめに

 Rubyでは標準ライブラリなど、様々なものがあるが、その中で、日付についてのものがあり、現在制作中のアプリに日付を表示させようと思い、ここにいたる。

Rubyでは…

require 'date'

を記述することで、そのライブラリを使用できる。

Railsでは…

 調べが足りないので、定かではないが、デフォルトで使用できるみたい。私は、上記の記述を書いた覚えがないが、日時を表示できた。

現在の時刻

Time.now
#=>2020-11-28 23:33:32 +0900

今日の日付

Date.today
#=>2020-11-28

最後に

 どこの記述されているのか、わからないが使える機能がたくさん出てきた。Railsは便利だが、ブラックボックスな部分が私にはまだまだあり、難しい。開いたことのないファイルがたくさんある…
 表示方法を変えたいなぁ…ja.ymlあたりをいじればいいのかなぁ…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

非ツイッター民が黒い画面からツイートしてみた件について

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ルーティング設定

ルートへのルーティング設定とは

Vagrant環境では、http://localhost:3000
にアクセスした時に、デフォルトで表示される「Welcome aboard」のページではなく、自分で作ったページなり、アクションなりを表示させたい時にする設定のこと。

コントローラーとアクションを作る

ターミナルに下記のコードをうちます。

$ rails g controller コントローラー名 アクション名

ルートファイルを編集する

「config/routes.rb」ファイルに下記のコードを書き込む。

root  to: 'コントローラ名#アクション名'

例えば、http://localhost:3000/にアクセスした時にindexページを表示させたい場合は、

root to: 'コントローラ名#index'

と書く。

出典記事

http://localhost:3000/に特定のページを表示させたい(ルートへのルーティング設定)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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-vbguest

vagrant停止

>vagrant halt

生成されたVagrantfileに下記を加える

config.vm.network "forwarded_port", guest: 3000, host: 3000

再度vagrant起動確認

>vagrant up

RubyとRailsをインストールする

rbenvをインストール

$ sudo yum -y install git
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv

rbenvコマンドを汎用的に使えるようにパスを通します。

$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

設定反映のため、シェルに入り直す

パスの変更が反映されるように実行

$ type rbenv

ruby-buildをインストール

rbenv同様にGitHubのリポジトリからパッケージをダウンロードします。

$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

Rubyをインストール

利用可能な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 bundler

bundlerのバージョン確認

$ bundle -v

gemを通してRailsをインストール

$ gem install -v 5.2.3 rails

インストールの確認

$ rails --version

MySQLのインストール

MariaDBライブラリを削除

$ sudo yum remove -y mariadb-libs

MariaDBのディレクトリを削除

$ sudo  rm -rf /var/lib/mysql/

MySQL8.0のリポジトリをインストール

$ sudo yum localinstall -y https://dev.mysql.com/get/mysql80-community-release-el7-2.noarch.rpm

MySQLをインストール

$ sudo yum install -y mysql-community-server

MySQLクライアントのバージョンを確認

$ mysql --version

MySQLサーバのバージョンを確認

$ mysqld --version

以下の記事にバージョン確認後の手順あり
https://qiita.com/nooboolean/items/7efc5c35b2e95637d8c1

Railsサーバを立ち上げる

共有ディレクトリに移動する

$ cd /vagrant

Railsのプロジェクトファイルを生成(DBはmysql)

$ rails new アプリケーション名 --database=mysql
$ sudo yum install -y mysql-devel

Gemfileにgem 'therubyracer'を追記

$ cd /vagrant/アプリケーション名
$ bundle install

config配下のdatabase.ymlに設定したパスワードを書き加える

Centos7のポート開放 (Railsで使う3000番を解放)

sudo firewall-cmd  --permanent --add-port=3000/tcp

firewalldをリロードします。

sudo firewall-cmd --reload

Railsプロジェクトのconfigディレクトリの中にあるdatabase.ymlを読み込み,そのファイルに基づいてデータベースを作成

$ rails db:create

Railsプロジェクトの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環境でホストのブラウザからサーバ接続できなかった時の対処

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】NewsAPIを使ってクリスマスニュースを収集するアプリを作る

はじめに

chrisapp.png
(初投稿です)
Railsでクリスマスに関連するニュースを集めるごくシンプルなアプリを作ります。

NewsAPIのキーを取得し、英語の公式ドキュメントを参照してみたものの、Ruby client libraryのサンプルコードの使い方が自分にはいまいち分からず、あれこれ調べた結果自分に合った方法を見つけたので記事を書いてみました。

環境

  • Ruby 2.6.5
  • Rails 6.0.3.4

NewsAPIとは

https://newsapi.org/

世界中のニュースソースからデータを集めて提供してくれるAPI。
3種類のエンドポイントURIが用意されており、

リクエストパラメータ内で欲しいニュースの国とカテゴリーを指定すると最新のニュースデータを返してくれる「Top headlines」/v2/top-headlines
50,000を超えるさまざまな情報源から数百万ものニュースデータを返してくれる「Everything」/v2/everything
ニュースの情報元の情報を取得する「Sources」/v2/sourcesがあります。

APIキーの取得

register.png
名前、メールアドレス、パスワード、個人か商用かを記入し、規約に同意することで登録出来ます。
登録を済ませたらアカウント画面にある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、最大値が100
page 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、最大値が100
page 結果をページングする
apiKey 取得したAPIキー (※必須)

このうちqqInTitleは、""で囲うことで完全一致検索が出来たり、
必ず出現して欲しい文字列には先頭に+を付けたり、
逆に除外したい文字列には-を付けたりと、欲しい記事の情報に合わせてパラメータの値を工夫することが出来ます。

Sources

上2つのエンドポイントURIにあるsourcesパラメータの「どんな情報元があるか」を調べたい場合は、このSourcesのエンドポイントURIを使うか、国別情報元一覧で調べることが出来ます。

パラメーター名 パラメーターの値
category ニュース記事のカテゴリー
business entertainment general health science sports technologyの7つ
初期値では全てのカテゴリーが出る
langage 記事の言語
やはり日本語(ja)は使えない模様
country 特定の国の記事を指定する
日本はjp
apiKey 取得したAPIキー (※必須)

リクエストパラメーターの作成が終わったら、ブラウザのURL欄に入力し、アクセスしてみましょう。するとこのように大量のJSONデータ(レスポンスオブジェクト)が返却されます。

スクリーンショット 2020-11-28 14.43.16.png
(なぜかエンコード済みの日本語部分がデコードされてる)

レスポンスオブジェクト

画像のレスポンスオブジェクトを要約するこんな感じになります。

レスポンスオブジェクトの例(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 記事の情報元(idnameで指定する)
author 記事の著者
title 記事のタイトル
description 記事の本文
url 記事のURL
urlToImage 記事の画像URL
publishedAt 記事が投稿された日付と時刻(UTC/協定世界時での表示)
content 引用元や記事の内容(最初の200文字のみ表示)

Sources

キー名 内容
id 記事の情報元の識別子
name 情報元の名前(主にドメイン名)
description 情報元の情報
url サイトURL
category 情報元から推測されたニュースのカテゴリー
langage 記事の言語
country 記事の発信元の国、あるいはその国についての記事

アプリでの実装

通常通りにコントローラーとビューを作成します。モデルは必要ありません。

app/controllers/news_controller.rb
class 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
end

credentials.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で装飾をして完成です。

app.gif

CSS(一応載せます)
app/assets/stylesheets/news/index.css
body {
  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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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 mariadb 
nginxの再起動
sudo systemctl restart nginx
unicornの状態確認
ps aux | grep unicorn
unicornの起動
RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

タグ振り分け機能の実装

はじめに

今回は、zipang(漢字、ひらがな、カタカナをローマ字に変換するもの)というgemを用いてタグを頭文字ごとに0~9、A~Zに振り分けて索引のようなものになるよう実装いたしました。その際、予め ancestry を用いて親カテゴリーとして0~9、A~Zをデータに保存するseedを作成しております。
スクリーンショット 2020-06-24 20.57.21.png

機能の実行順序

大まかな流れといたしましては、

① タグを入力する

② 入力されたタグを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

④ 表示する際は、頭文字の子要素として出力する。

以上のことを応用すると、あいうえお順に振り分けることもできるかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyで2進数を計算する方法はとても簡単

ruby 2.7.0の記事です

10進数を2進数に変換する方法

以下、私の試行錯誤の結果です

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 = 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] 公式リファレンスより
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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyで2進数を計算したい時には、**演算子を使う

ruby 2.7.0の記事です

10進数を2進数に変換する方法

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 = 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] 公式リファレンスより
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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Index】マルチスケールシミュレーション特論

Mac OS X-10.15.7 ruby-2.7.1p83

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.

  1. 【Memo】github & Qiita

  2. 【Memo】Emacs key-bind

  3. 【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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.rb
class 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.rake
namespace :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.rb
class 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.rb
post '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 までご連絡ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveRecordでデータを月毎に集計

[ActiveRecord]でデータを月毎に集計する方法

今回は入力した数値を月毎に集計して合計値を出力させる方法を投稿します。
ネットで検索しても、なかなか良い方法がなかったので、参考になればと思います。

使用テーブル(incomesテーブル)↓↓↓
スクリーンショット 2020-11-28 14.03.26.png
このテーブルから2019年-月分・・・2020年-月分としたいと思います。
実行コード↓↓↓

Income.group("YEAR(fill_date)").group("MONTH(fill_date)").sum(:price)

YEAR(fill_date)で年毎にグルーピングして、さらにMONTH(fill_date)で月毎にグルーピングした後、sum(:カラム名)でpriceを合計しています。
このときのターミナルのログ↓↓↓
スクリーンショット 2020-11-28 14.05.28.png
結果、年と月を区別して合計値を出力させることができました。

今回の記事が誰かの役に立てればと思います。またもっと良い方法があればコメントしてもらえると助かります!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FFmpegをAWS Lambdaで動かす

ffmpegをAWS Lambdaで動かしてみました
何を思ったかRubyのラッパー使ってみました
途中結構躓くことが多かったので面倒でした

Dockerfile

Dockerfile
FROM 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

fix
RUN bundle config set --local path 'vendor/bundle'

ffmprobeの実行権限がないと言われた

windowsでzipファイルの作業してたらはまった。Lambdaのコンテナ上で圧縮しないと
実行権限の付与も忘れずに
RUN chmod -R a+x ../../ffmpeg_dist

x265のリポジトリがない

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.ExitError

START 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.ExitError

START 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で実行するにはこれらを含めなければならなかった。
つくづく動画をデジタル信号に変えるのはあらゆる処理が必要なのだなと感じた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を使ってターミナルに表示するには、文字列なら「""」や「''」の中に記述する、数値の場合はそのまま記述する。
・基本的には「文字列」 + 「数値」の計算はできない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】parent_idとchild_idを持つ中間テーブルを作って親子関係を実装する

はじめに

階層構造を持つモデルを実装することがあったのでその時のメモ書きです。

例として部署を考えます、以下の画像のようなイメージですね。

( ※ 適当に拾ってきました。)

admn0035.gif

テーブルは 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.rb
belongs_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.rb
def 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 # 子孫
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】mark_for_destruction を 使って特定の条件のレコードを削除する。

使用ケース

「特定のカラムが空である場合にはそのレコードを削除したい」みたいなときに便利です。
そのレコードに削除マークをつけておくと削除されるイメージで実装できます。

サンプルコード

前提として users は name カラムを持つとします。

name の値が nil か 空文字であればそのレコードを削除したいみたいな時は、以下のように実装できると思います。

今回はインスタンスメソッドを定義し、before_validationのコールバックで実行しました。

app/models/user.rb
before_validation :delete_user_if_name_blank

def delete_user_if_name_blank
  self.mark_for_destruction if name.blank?
end

if 文を使えば色んな条件で適用できますし、アソシエーションしているレコードも削除できたりするので色々と応用が効きそうですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

新規投稿機能を作成した際にNoMethodError【Ruby on Rails】

Railsで一般的な新規投稿機能を作成した際にNoMethodErrorが発生した。

原因

一口にノーメソッドエラーと言っても原因は色々考えられる。

私の場合は、UserモデルとPostモデルの間で1対多の関連付けをしていたのだが、app/model/User.rbに

has many :posts

を記述していない事が原因だった。

教訓

判明してみると、なんでこんなミスをしたのだろうと思ってしまうような凡ミスではあるが、

ノーメソッドエラー
→メソッドが定義されていない
→メソッドを一生懸命確認する

という安易な発想で、原因にたどり着くまでに30分くらい費やしてしまった。

エラー画面の情報はあてにならない(この言い方は大いに語弊があるかもしれないが)場合が多々あるため、周辺情報も含めて行うべき設定は正しく行っているか確認するように心がけたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
とても分かりやすかったです!ありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ターミナルの出力結果をテキストに出力する

0.背景

ターミナルで出力した結果をテキストに出力できたら便利だと思い、まとめました。

1.使用環境

mac.os バージョン10.15.6
Ruby2.6.6

2.実際やってみる

Rubyのバージョンを追加する際にインストール可能なバージョンを確認したいと思い、
rbenv install --list-allを実行したところ、見切れてしまいました。
ターミナル.png
新たにインストールを行う、2.5.1のバージョンを探していますが見当たりません。
※rbenvとは、Rubyのバージョンを簡単に切り替えてくれるツールのことです。
参考リンク:rbenvの使い方と仕組みについて

そこで下記のサイトを元に出力してみました。
[Tool] Mac標準コマンドで出来る!ターミナルの出力結果をテキストファイルにも保存する方法

$ script sample.txt           #ファイル名を指定する
$ rbenv install --list-all    #実行したい処理
...
$ exit                       #書き出しを終了

実行するとsample.txtの名前でテキストに出力できたことが分かります。
無事探していた2.5.1も見つかりました。

sample.txt.png

3.どこに保存されているのか?

先ほどのコマンドは保存場所を指定していません。ではどこに保存されているのか?というと
ホームディレクトリに保存されます。ホームディレクトリはmacの場合、Finderを開いてShift + command + Hで飛べます。

補足:ファイル名を指定しない場合

ファイルを指定しない場合、typescriptに保存されます。
参考リンク:Linuxコマンド【script】
typescriptもホームディレクトリに保存されます。
screen typescript.png

4.応用先

Gitでlogを出力したいときなど、いろんなところで活用できるのではないかと思います。
読んで頂き、ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.slim
  svg 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"

21.png

クリック(タップ)した場所をオセロの座標に変換して

app/assets/javascripts/board.js
    let 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.rb
class 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.js
function 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分で作れちゃうからねぇ。

次回はビットボードをクローズアップしていきます!
ではまた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む