- 投稿日:2019-11-26T23:58:31+09:00
eager_load/preload/includes/joinsの違いメモ
動機
日頃Goしか書いてないGopherがRails書いたときにつまったActiveRecord、特にjoins周りの挙動が直感的によくわからなかったので実際にデータ用意して調べてみた
(メソッドの具体的な説明は他にたくさん記事があるのでそちらを参照してください。)実行環境
Rails公式Docのサンプルブログアプリ
のarticle.rb
とcomment.rb
のモデルを使用。
今回は、それぞれ約25万行を用意した。article.rbclass Article < ApplicationRecord has_many :comments, dependent: :destroy validates :title, presence: true, length: { minimum: 5 } endcomment.rbclass Comment < ApplicationRecord belongs_to :article endControllerから
preload/eager_load/joins/includes
を用い、それぞれの吐くSQLやパフォーマンスを比較してみる0. 素
Article.limit(100)
SELECT `articles`.* FROM `articles` LIMIT 100 -- Completed 200 OK in 154ms (Views: 127.1ms | ActiveRecord: 15.7ms | Allocations: 20418)まずは検証用にそのままで...
1. eager_load
Article.eager_load(:comments).limit(100)
SQL (1.9ms) SELECT DISTINCT `articles`.`id` FROM `articles` LEFT OUTER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` LIMIT 100 SQL (575.4ms) SELECT `articles`.`id` AS t0_r0, `articles`.`title` AS t0_r1, `articles`.`text` AS t0_r2, `articles`.`created_at` AS t0_r3, `articles`.`updated_at` AS t0_r4, `comments`.`id` AS t1_r0, `comments`.`commenter` AS t1_r1, `comments`.`body` AS t1_r2, `comments`.`article_id` AS t1_r3, `comments`.`created_at` AS t1_r4, `comments`.`updated_at` AS t1_r5 FROM `articles` LEFT OUTER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` WHERE `articles`.`id` IN (1, 2, 3, 4, 6, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19, 20, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157) -- Completed 200 OK in 4869ms (Views: 4280.1ms | ActiveRecord: 585.9ms | Allocations: 2103879)万能感ある。駆動表のキャッシュをしながら、絞り込みもできる。IN長すぎワロタ。
2. preload
Article.preload(:comments).limit(100)
Article Load (1.9ms) SELECT `articles`.* FROM `articles` LIMIT 100 Comment Load (405.8ms) SELECT `comments`.* FROM `comments` WHERE `comments`.`article_id` IN (1, 2, 3, 4, 6, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19, 20, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157) -- Completed 200 OK in 4304ms (Views: 3883.6ms | ActiveRecord: 416.9ms | Allocations: 1658640)eager_loadより省エネな感じ。ただ、最後のSELECT文がcommentsテーブルしか参照してないので絞り込みができない
3. includes
Article.includes(:comments).limit(100)
Article Load (1.5ms) SELECT `articles`.* FROM `articles` LIMIT 100 Comment Load (384.1ms) SELECT `comments`.* FROM `comments` WHERE `comments`.`article_id` IN (1, 2, 3, 4, 6, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19, 20, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157) -- Completed 200 OK in 3737ms (Views: 3339.1ms | ActiveRecord: 395.3ms | Allocations: 1652329)今回はpreloadと同じ挙動
(includes は場合によって挙動が変わる. 最後のSELECT文にないテーブルのwhereとかはできないので、そういう時はeager_loadになる)def eager_loading? @should_eager_load ||= eager_load_values.any? || includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) end4. joins
Article.joins(:comments).limit(100)
Article Load (2.1ms) SELECT `articles`.* FROM `articles` INNER JOIN `comments` ON `comments`.`article_id` = `articles`.`id` LIMIT 100 -- Completed 200 OK in 67ms (Views: 56.1ms | ActiveRecord: 7.0ms | Allocations: 21703)いつものアレ。安心感ある。
まとめ
そもそもActiveRecordは LazyLoad、すなわち 必要になったときにSQLが実行される 。
for文の中でSQLが複数回発行(N+1)されないために、こういう機構が必要になってくる。
(生SQLerの私にはこの感覚があんまりない)後は、where使いたいなら
eager_load
何もしないならpreload
って感じ。
頭使いたくない人は 「とりあえずincludes
」 しとけばなんとかなる。
ただ、複数紐づくテーブル扱い出すと最終的に吐き出されるSQLが予測困難になるので、ちゃんと使い分けたいところ。
ちゃんと使い分けたい人はこの記事とか読みましょう。実装追ってて勉強になります。
- 投稿日:2019-11-26T23:47:03+09:00
Herokuでデプロイした際にApplication error(エラーコードH10 (App crashed))が出た時の対処法
環境
Ruby 2.6.3
Rails 5.2.3現象
Railsアプリケーションを作成後、Herokuへデプロイした際に以下画像のエラーが発生。
その際の対処法を備忘録として残しておきます。【対処方法】エラーの原因を把握する
以下コードで状況を把握します。
$ heroku logs --tailエラーコードが確認できました。
2019-11-26T05:06:44.357974+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=agile-meadow-66722.herokuapp.com request_id=e2c36b19-f3d1-4544-9f84-5ddcb7027cd2 fwd="219.113.146.130" dyno= connect= service= status=503 bytes= protocol=https 2019-11-26T05:06:44.823379+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=agile-meadow-66722.herokuapp.com request_id=c5d323a0-b4be-497c-847b-2d3ebf4942d0 fwd="219.113.146.130" dyno= connect= service= status=503 bytes= protocol=httpsエラーコードの中身を読んでみると、code=H10でApp crashedとのこと。
ネットで調べてみたところ、以下のようにリスタートすれば直ることもあると情報があったため試しました。$ heroku restart -appもしくは
$ heroku restart -app application_nameしかし、こちらを試してもエラーのままでした。
コンソールを開いて詳細エラー情報を確認する。
エラーの詳細内容を確かめるために以下コードでHeroku上でコンソールを開きました。
$ heroku run rails cすると、以下エラーコードが出てきました。
Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? sqlite3 is not part of the bundle. Add it to your Gemfile. (LoadError)sqlite3がバンドルされていない?Gemfileに加えろとの指示。
しかし、sqlite3は本番環境に対応していません。どうやらrails newの時点でpostgresqlの指定を忘れていてsqlite3がデータベース作成時に指定されていたみたいです・・・
アプリ作成初期段階の凡ミスでエラーが発生することになりました。
SQLite3からPostgreSQLに変換
※参照
https://qiita.com/isotai/items/67bafc37a16ea5de5e3b上記記事を参考にし、
datebase.ymlのsqlite3時点の記載内容を全て消し、
postgresql指定でrails newした場合の記載に変更しました。gemfileに以下を追加しました。
gem 'pg', '>= 0.18', '< 2.0'以上。
- 投稿日:2019-11-26T23:33:30+09:00
RubyとAWS LambdaでサクッとSlackへポストしてみる
とりあえずAWS Lambdaを動かしてみたかった。
試しなのでブラウザ上で完結するようにしたが、割と時間がかかった。関数の作成
(AWSアカウントを作成してAWSコンソールにサインインするところまでは省略)
サービス一覧から「Lambda」 → 「関数」 → 「関数の作成」を選択。
関数の作成画面では「一から作成」を選択。関数名は任意。
ランタイムはRuby2.5 (2019/11/26現在) を選択。
ロールはデフォルトのまま。「関数を作成」ボタンを選択すれば関数が作成され、編集画面へ遷移する。
関数の実行
とりあえずサンプルを動かしてみる。
右上の「テスト」を選択し、「イベント名」を適当に入力して作成する。作成したイベント名が表示されていることを確認したら、「テスト」を押せば関数が動く。
Slackのincoming-webhookの設定
Slackにポストするためにwebhookを設定する。
(このあたりは初回設定時とそれ以外で表示が異なるかもしれない)通知したいチャンネルの設定アイコン → 「アプリを追加する」を選択して、「incoming-webhook」で検索。
ブラウザ側でAppディレクトリへ遷移するはずなので、「Slackに追加」を選択。webhookを追加するチャンネルを選択して、「Incoming Webhookインテグレーションの追加」を選択。
webhook URLが作成されるので、これをコピーしておく。
(このときに簡単な使い方なども表示されるので読むといいかも)LambdaからSlackへポストする
作成したLambda関数からポストしてみる。
試すだけなので、net/http
を利用。
user_ssl = true
を設定しないとBad Requestとなる。require 'json' require "net/http" URL = "https://hooks.slack.com/services/*******" def lambda_handler(event:, context:) uri = URI.parse(URL) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.post(uri.path, { text: "hello from lambda." }.to_json) end修正後、「保存」 → 「テスト」 と選択するとリクエストが実行される。
正常に処理された場合、Slackに通知が飛ぶ。その他
Lambda自体は無料枠に収まったが、ログを保存するS3の料金が僅かながら発生していた。
回避方法などあるかもしれないが、気にならない程度だったので今回は無視。次は何らかのトリガーで実行する部分と変数を渡す部分を調べる。
- 投稿日:2019-11-26T23:27:46+09:00
kaminariのlast_page?の動作に気をつけて!
主旨と結論
連番で管理するのではなく、「前へ」「次へ」ボタンを設置しての運用です。
記事のポイントは、データが入っていない状態でページネーション機能を使う際に気をつけること
常に何らかのデータが存在する場合は気にする必要はありません。下記を実現したい場合に参考になります。
- 最初のページは「前へ」ボタンを消す
- 最後のページは「次へ」ボタンを消す
- 最初で最後のページは両方とも消す←ここで次へ問題が発生します。
なので
当初問題のlast_page?の条件分岐は、
<% unless pagenates.last_page? %> <a href="<%= path_to_next_page(pagenates) %>">次のページ</a> <% end %>のようにしていました。
これだと、データがない状態では、最初で最後のページに次へボタンが表示されてしまうのです。最終的には下記のようにすればOK
<% unless pagenates.last_page? || paenates.out_of_range %> <a href="<%= path_to_next_page(pagenates) %>">次のページ</a> <% end %>まとめ
- last_page?は、現在ページと総ページ数が同じ時にtrueを返し、違う時にfalseを返す。
- データが存在しない際は、表示されるページ数(current_page)は、1となるが、total_pagesは、0となる。
- そのため、データが存在しない場合、表示されたページが最初で最後のページとなるものの条件分岐にはout_of_rangeを併用する必要がある。
最初で最後のページの戻り値は下記の通り
メソッド データあり データなし current_page 1 1 total_pages 1 0 last_page? true false first_page? true true out_of_range false true 解説
結論に書きましたが、last_page?は、最後のページの場合trueを返す、ではありません。
last_page?は、現在ページと総ページ数が同じ時にtrueを返し、違う時にfalseを返すです。kaminariのソースを確認します。
def last_page? current_page == total_page endこれだけではよくわからないので、
- current_pageメソッド
- total_pageメソッド
を確認します。
current_pageメソッド
def current_page offset_without_padding = offset_value offset_without_padding -= @_padding if defined?(@_padding) && @_padding offset_without_padding = 0 if offset_without_padding < 0 (offset_without_padding / limit_value) + 1 rescue ZeroDivisionError raise ZeroPerPageOperation, "Current page was incalculable. Perhaps you called .per(0)?" endこれは、offset_without_paddingの値が0になるのでしょうか。
データがないのでpaddingなど出ないはずです。
最後には、(offset_without_padding / limit_value) + 1として、0をlimit_valueで割って+1しているので、基本的に戻り値は1以上であることが想像できます。
そして、下記の実行結果を確認すると、1とでていましたのでよしとしましょう。
お分かりの方コメント頂けると幸いです、、、、!!!<%= @users.current_page %>total_pagesメソッド
def total_pages count_without_padding = total_count count_without_padding -= @_padding if defined?(@_padding) && @_padding count_without_padding = 0 if count_without_padding < 0 total_pages_count = (count_without_padding.to_f / limit_value).ceil max_pages && (max_pages < total_pages_count) ? max_pages : total_pages_count rescue FloatDomainError raise ZeroPerPageOperation, "The number of total pages was incalculable. Perhaps you called .per(0)?" endこれも
count_without_paddingが0になるのはわかります。
あとは単純な計算とif文ですね。
戻り値がtotal_pages_countになるのはわかりますので、答えは0。(お分かりの方コメンry...)そして、下記の結果は0でした。
<%= @users.total_pages %>まとめ、last_page?メソッドとout_of_rangeメソッド
def last_page? current_page == total_page endデータがない状態では、
- current_pageは1
- total_pageは0
となりますので、最初で最後のページで、last_page?を使って条件分岐してしまうと、falseが返ってきます。
そこで登場するのが
- out_of_rangeメソッド
これは
def out_of_range? current_page > total_pages endなので、データがない状態ではtrueが返ってきます。
現在ページの方が合計ページを上回ることって普通ないと思うのですが、今回のようなケースを想定していたのでしょうか。git_hub、紹介したメソッドが定義されているページ
Git_Hub/kaminari
https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-core/lib/kaminari/models/page_scope_methods.rb
- 投稿日:2019-11-26T22:45:10+09:00
libvipsを使ってRails6でActiveStorageする【Windows,Ubuntu環境】
この記事でつくるもの
RailsのActiveStorageを使って簡単なサムネイル表示するCRUDを作成します。
手順
- Ubuntuにlibvipsの最新ソースを持ってきて、ビルドとインストール
- UbuntuからWindow環境をマウントしてrails new
- ActiveSrorageに必要なRailsコマンドと設定
- 必要なgemのインストール
- Ubuntuからrails s
Ubuntuにlibvipsの最新ソースを持ってきて、ビルドとインストール
RailsのActiveStorageはlibvipsまたはImageMagickが必要です。
この記事では実行速度の速いlibvipsを使用します。まずは必要なパッケージをインストールします。
Ubuntu$ sudo apt-get install git build-essential libxml2-dev libfftw3-dev \ libmagickwand-dev libopenexr-dev liborc-0.4-0 \ gobject-introspection libgsf-1-dev \ libglib2.0-dev liborc-0.4-dev
gitソースから取得したソースをビルドするのに必要なパッケージUbuntu$ sudo apt-get install automake libtool swig gtk-doc-tools
libvips取得し、ビルドとインストールUbuntu$ git clone https://github.com/libvips/libvips.git $ cd libvips libvips$ ./autogen.sh libvips$ make libvips$ sudo make install
PATHの設定Ubuntu$ export VIPSHOME=/usr/local $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$VIPSHOME/lib $ export PATH=$PATH:$VIPSHOME/bin $ export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$VIPSHOME/lib/pkgconfig $ export MANPATH=$MANPATH:$VIPSHOME/manUbuntuからWindow環境をマウントしてrails new
UbuntuではWindow環境をマウントして参照できます。
Windowsからの方がソース編集しやすいので、マウントしてrails newします。Ubuntu$ cd /mnt/c/tmp_rails /mnt/c/tmp_rails$ rails new sample /mnt/c/tmp_rails$ cd sampleActiveSrorageに必要なRailsコマンドと設定
ActiveSrorageを使うためのマイグレーションファイルを作成
Ubuntu/mnt/c/tmp_rails/sample$ rails active_storage:install
Userモデルをscaffoldで作成Ubuntu/mnt/c/tmp_rails/sample$ rails g user name image:attachment
Userモデルにはimage:attachmentによりhas_one_attachedが追加されるapp/models/user.rbclass User < ApplicationRecord has_one_attached :image end
モデルをマイグレーションするUbuntu/mnt/c/tmp_rails/sample$ rails db:migrate
showページでサムネイル表示するようにするapp/views/users/show.html.erb<p> <strong>Image:</strong> <%= image_tag @user.image.variant(resize_to_limit: [100, 100]) %> </p>
libvipsを使うように指定app/config/application.rbmodule Sample class Application < Rails::Application config.load_defaults 6.0 config.active_storage.variant_processor = :vips #追記 end end必要なgemのインストール
image_processingはコメントアウトを外し、ruby-vipsは追記します
Gemfilegem 'image_processing', '~> 1.2' gem 'ruby-vips'Ubuntuからrails s
Ubuntu/mnt/c/tmp_rails/sample$ rails sこれで http://127.0.0.1:3000/users をブラウザから開けばデモのように動きます。
参考
- 投稿日:2019-11-26T21:40:19+09:00
RubyMineをさらに便利にするプラグインたち:後編
はじめに
Ateam cyma Adevent Calendar 2019 の 3日目です。
本日の担当は昨日に引き続き、エイチームのEC事業本部でWebアプリケーションエンジニアをしている@hibiheionです。この記事はRubyMineをより便利にするプラグインたち:前編の続きです。
後編では、あると便利なプラグインを紹介します。紹介内容について
前編と同じ内容ですが、紹介方法について改めて記載しておきます。
- 各プラグインの基本機能のみ説明しています。詳細は各プラグインのWebページをご確認ください。
- 特定の言語やサービスを使えるようにするものは対象外としています。
- 個人的な評価を三段階でつけています。
- 優:ないと困る
- 良:使用頻度が多め
- 可:使用頻度が少なめ
- ショートカットはキーマップ次第で変わるのでメニューからの操作で説明しています。なお、メニューから操作するとショートカットキーを教えてくれるプラグインもあります。
- プラグインの情報は記事を公開した2019年12月時点のものです。
プラグインの紹介
一覧
全部で20個あります。
順序は評価と名称の順です。
後編では「AceJump」から「SQL Formatter」の11個を説明します。
残りのプラグインは前編で説明しています。
名称 概要 評価 Grep Console tail -fで出力したログをGrep検索で絞り込める 優 highlightbracketpair カーソルがある箇所を囲んでいるカッコをハイライト表示する 優 IdeaVim Vimと同じような操作で使用できるようにする 優 Key Promoter X ショートカットキーが存在する操作をマウス等から実行した際に操作内容をショートカットキーとともに記録する 優 Quick File Preview ファイル一覧で選択したファイルのプレビューを表示する 優 Railways Railsのルーティングの一覧を表示する 優 Rainbow Brackets 対になるカッコを色分けして表示する 優 Scroll From Source Projectツールウィンドウ(ファイル一覧)の開いているファイルの場所に移動する 優 Tabdir タブ一覧で同じ名前のファイルにはディレクトリ名を表示する 優 AceJump ページ内の指定した文字にカーソルを移動する 良 Git Scope ファイルの一覧からGitの差分を確認できる 良 GitToolBox Git連携の機能を強化する 良 MultiHighlight 複数の検索結果を異なった色でハイライト表示する 良 String Manipulation 色々な方法で文字列を操作する 良 BrowseWordAtCaret カーソルがあたっている単語をファイル内検索する 可 JSON Viewer JSONを見やすい形に整形する 可 Open in splitted tab 参照先を分割したウィンドウで表示する 可 Pipe Table Formatter パイプ(|)を使ったテーブルを揃える 可 Realigner 指定した文字で複数行の連結、複数行への分割、文字列の囲い込みができる 可 SQL Formatter SQLを見やすい形に整形する 可 AceJump
https://plugins.jetbrains.com/plugin/7086-acejump/
機能
- ページ内の指定した文字にカーソルを移動する
- 例えば、ページ内の「A」という文字がある場所に少ないタイプ数で移動できる
- 効率よくカーソルを移動できる
- 使用例
「d」をタイプすると下記のように「d」の位置にナビゲーションが表示され、この状態で「j」をタイプすると「def」にカーソルを移動する使い方
説明のために機能の検索から実行していますが、ショートカットキーを割り当てて使うのが現実的です。
- 指定した文字に移動
- shiftキーを2回押して表示される検索ウィンドウで「acejump」を検索する
- 検索結果から「Activate AceJump mode」を選択する
- 移動したい文字(1文字)をキーボードからタイプする
- ページ内のタイプした文字の左にナビゲーションが出るので、移動したい先のアルファベットをキーボードからタイプする
- 指定した行に移動
- shiftキーを2回押して表示される検索ウィンドウで「acejump」を検索する
- 検索結果から「Display Line Markers」を選択する
- ページ内の各行にナビゲーションが出るので、移動したい先のアルファベットをキーボードからタイプする
補足
- 機能を限定したAceJump-Liteというプラグインもある
- 個人的にはAceJump-Liteのほうがナビゲーションが見やすいのでこちらを使っている
Git Scope
https://plugins.jetbrains.com/plugin/10083-git-scope/
機能
- ファイルの一覧からGitの差分を確認できる
- 内容はトップメニューの「VCS→Git→Commit file…」で出てくる内容と同じ
- Git Scopeはサイドメニューに出せるため確認しやすい
使い方
- ツールウィンドウの表示
- サイドメニューから「Git Scope」のツールウィンドウを開く
- サイドメニューに出ていない場合、トップメニューの「View→Tool Windows→Git Scope」から表示できる
- ツールウィンドウの操作
- ツールウィンドウには差分のあるファイルの一覧が表示される
- ファイル名をクリックすると差分を確認できる
GitToolBox
https://plugins.jetbrains.com/plugin/7499-gittoolbox/
機能
- Git連携の機能を拡張する
- 次のような機能が増える
- ステータスバーに出す情報を増やす
- ステータスの表示
- 今いる行の最終更新者の表示(git blameの情報)
- blame(更新情報)が見れるようになる
- ブランチにタグをつけられる
- Gitのリモートリポジトリを定期的に自動でフェッチし、変更があれば通知する
使い方
- プラグインの設定の変更
- 設定画面で「Other Settings→GitToolBox Global」を開く
- 自動フェッチ以外の表示項目などの設定
- 設定画面で「Other Settings→GitToolBox Project」を開く
- プロジェクト別の自動フェッチの設定
- git blameの確認
- 右クリックメニューの「Git→show inline blame」にチェックを入れると、カーソルが当たっている行のblameをつねに表示する(意外と邪魔にならない)
- 右クリックメニューの「Git→show blame details」でカーソルのある行の詳細なblameをポップアップで表示する
- ポップアップからコミット履歴の確認やリビジョンのコピーなどができる
- ブランチへのタグの追加
- 右クリックメニューの「Git→repository→Push Tags on Branch」を選択する
MultiHighlight
https://plugins.jetbrains.com/plugin/9511-multihighlight/
機能
使い方
説明のために機能の検索から実行していますが、ショートカットキーを割り当てて使うのが現実的です。
- ハイライト表示の切り替え
- 対象の文字列を選択する
- shiftキーを2回押して表示される検索ウィンドウで「multihighlight」を検索する
- 検索結果から「MultiHighlight: toggle highlight」を選択する
- ハイライト表示の全クリア
- shiftキーを2回押して表示される検索ウィンドウで「multihighlight」を検索する
- 検索結果から「MultiHighlight: clear highlights in current editor」を選択する
String Manipulation
https://plugins.jetbrains.com/plugin/2162-string-manipulation/
機能
- 色々な方法で文字列を操作する
- 操作内容は盛りだくさん
- フォーマット(JSON等)ごとに文字列をエスケープする
- エンコードする(URLエンコード等)
- スネークケース・キャメルケースといった記述方式を変換する
- 数値に対して値の加減算などをの計算を行う
- 複数の行を並び替える
- 条件を指定して不要な行を削除する
- 余分な半角スペースを取り除く(トリム)
- 複数の行の左端や右端を揃える
使い方
- 文字列の操作方法
- 操作対象の文字列を選択する
- 右クリックメニューの「String Manupilation」を選択する
- プラグインの設定の変更
- 設定画面で「Other Settings→String Manipulation」を開く
BrowseWordAtCaret
https://plugins.jetbrains.com/plugin/201-browsewordatcaret/
機能
- カーソルがあたっている単語をファイル内検索する
- 通常のファイル内検索でwordsにチェックを入れて検索したときの検索方法を使う
- IdeaVimを使っていると、「*」の検索で同じことができる
- 通常のファイル内検索より少ない手数で使用できる
使い方
説明のために機能の検索から実行していますが、ショートカットキーを割り当てて使うのが現実的です。
- 下方向に検索
- 検索したい単語にカーソルを合わせる
- shiftキーを2回押して表示される検索ウィンドウで「browse to」を検索する
- 検索結果から「browse to next word」を選択する
- 上方向に検索
- 検索したい単語にカーソルを合わせる
- shiftキーを2回押して表示される検索ウィンドウで「browse to」を検索する
- 検索結果から「browse to previous word」を選択する
JSON Viewer
https://plugins.jetbrains.com/plugin/9679-json-viewer/
機能
- JSONを見やすい形に整形する
- レスポンスの解析などで使う
使い方
- ツールウィンドウの表示
- サイドメニューから「JSON Viewer」のツールウィンドウを開く
- サイドメニューに出ていない場合、トップメニューの「View→Tool Windows→JSON Viewer」から表示できる
- JSONの整形
- JSONをコピーし、ツールウィンドウにペーストする
- ツールウィンドウの「Format」ボタンをクリックする
Open in splitted tab
https://plugins.jetbrains.com/plugin/7407-open-in-splitted-tab/
機能
- 参照先を分割したウィンドウで表示する
使い方
- ジャンプしたいメソッド等にカーソルを合わせる
- 右クリックメニューの「Go To→Open in split tab」を選択する
Pipe Table Formatter
https://plugins.jetbrains.com/plugin/7550-pipe-table-formatter/
機能
- パイプ(|)を使ったテーブルを揃える
- RubyではCucumberやTurnipのテストフィーチャ(Gherkin)のテーブルで使う
- ただ、日本語などのマルチバイト文字が含まれていると揃って見えない
- 操作例
使い方
- カーソルが当たっているテーブルを揃える
- 対象のテーブルにカーソルを合わせる
- 右クリックメニューの「Pipe Table→Format」を選択する
- ファイル内のすべてのテーブルを揃える
- 対象ファイルを開く
- 右クリックメニューの「Pipe Table→Format All」を選択する
- テーブルに列を追加する
- 列を追加したい位置の後ろにカーソルを移動する
- 右クリックメニューの「Pipe Table→Add Column Before」を選択する
- テーブルを選択する
- 対象のテーブルにカーソルを合わせる
- 右クリックメニューの「Pipe Table→Select Table」を選択する
Realigner
https://plugins.jetbrains.com/plugin/7082-realigner/
機能
- 指定した文字で複数行の連結、複数行への分割、文字列の囲い込みができる
- 例えば、ハッシュの項目が増えたときに「,」で複数行に分けられる
- 分割や結合に使う文字は決められる
使い方
- 文字列の分割
- 分割する文字列を選択する
- トップメニューの「Edit→Split into Lines」を選択する
- 分割に使う文字などを選択する
- 文字列の結合
- 結合する文字列を選択する
- トップメニューの「Edit→Join Lines With Glue」を選択する
- 結合に使う文字などを選択する
- 文字列の囲い込み
- 囲む文字列を選択する
- トップメニューの「Edit→Wrap」を選択する
- 囲む文字などを選択する
- 開始文字と終了文字で別の文字を指定できる
SQL Formatter
https://plugins.jetbrains.com/plugin/10858-sql-formatter/
機能
- SQLを見やすい形に整形する
- ログに出ているSQLをコピペしてSQLを確認できる
使い方
- ツールウィンドウの表示
- サイドメニューから「SQL Formatter」のツールウィンドウを開く
- サイドメニューに出ていない場合、トップメニューの「View→Tool Windows→SQL Formatter」から表示できる
- SQLの整形
- SQLをコピーし、ツールウィンドウにペーストする
- ツールウィンドウの「Format」ボタンをクリックする
まとめ
RubyMineのプラグインは以下のサイトで検索できます。
https://plugins.jetbrains.com/search?products=rubyメジャーなプラグインで出てきていないものもありますが、それは私の使い方に合わなかったためです。
例えば、前編で紹介したIdeaVimはVimmer以外には不要なものですよね。
時間があるときにでも探してみれば、自分にあったプラグインがきっと見つかると思います。最後にプラグインの開発者の皆様に対して感謝を述べて締めたいと思います。
プラグインを作ってくださってありがとうございます!次回予告
Ateam cyma Adevent Calendar 2019 の 3日目の記事は以上です。
2回にわたりお付き合いいただきありがとうございました。4日目はエンジニアの@bayasistさんが担当します。
Railsのフォームオブジェクトに関する記事です。さいごに
株式会社エイチームでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。
エンジニアで興味を持った方はcymaのQiita Jobsをご覧ください。
そのほかの職種は、エイチームグループ採用サイトをご覧ください。
- 投稿日:2019-11-26T21:39:25+09:00
RubyMineをさらに便利にするプラグインたち:前編
はじめに
Ateam cyma Adevent Calendar 2019 の 2日目です。
本日の担当はエイチームのEC事業本部でWebアプリケーションエンジニアをしている@hibiheionです。
業務では主に自転車ECサイトcymaのバックエンドの機能をRailsで書いています。本題
みなさんはエディタは何を使ってますか?
私は業務ではRailsを触っているのですが、エディタはRubyMineを使っています。
RubyMineは有料なだけあってRailsでの開発をとても効率よくできるようになっています。
RubyMineは他のエディタと同じようにプラグインで機能を拡張することも可能です。
ただ、RubyMineは他のエディタと比べると日本語でプラグインの情報が見つかりにくい気がします。そんなわけで、RubyMineのプラグインの情報をまとめれば役に立つのではないかと思ったので、自分が使ってみて良かったと感じたプラグインのざっくりとした使い方を紹介します。
この記事でRubyMineを使った開発がより便利になれば幸いです。なお、紹介するプラグインの多くはPHPStorm等の他のJetBrains製品でも利用できます。
紹介内容について
- 各プラグインの基本機能のみ説明しています。詳細は各プラグインのWebページをご確認ください。
- 特定の言語やサービスを使えるようにするものは対象外としています。
- 個人的な評価を三段階でつけています。
- 優:ないと困る
- 良:使用頻度が多め
- 可:使用頻度が少なめ
- ショートカットはキーマップ次第で変わるのでメニューからの操作で説明しています。なお、メニューから操作するとショートカットキーを教えてくれるプラグインもあります。
- プラグインの情報は記事を公開した2019年12月時点のものです。
プラグインのインストール方法
プラグインの紹介の前に念のためプラグインのインストール方法を紹介しておきます。
- 設定画面を開く
- Macではトップメニューの「RubyMine→Preferrences」を選択する
- Windowsではトップメニューの「File→Settings」を選択する
- 「Plugins」を開く
- 「Marketplace」から検索してインストールする
プラグインの紹介
一覧
全部で20個あります。
順序は評価と名称の順です。
記事が長くなったため、前後編の2回に分割しました。
今回は個人的には手放せない「Grep Console」から「Tabdir」の9個を紹介します。
名称 概要 評価 Grep Console tail -fで出力したログをGrep検索で絞り込める 優 HighlightBracketPair カーソルがある箇所を囲んでいるカッコをハイライト表示する 優 IdeaVim Vimと同じような操作で使用できるようにする 優 Key Promoter X ショートカットキーが存在する操作をマウス等から実行した際に操作内容をショートカットキーとともに記録する 優 Quick File Preview ファイル一覧で選択したファイルのプレビューを表示する 優 Railways Railsのルーティングの一覧を表示する 優 Rainbow Brackets 対になるカッコを色分けして表示する 優 Scroll From Source ファイル一覧で開いているファイルの場所に移動する 優 Tabdir タブ一覧で同じ名前のファイルにはディレクトリ名を表示する 優 AceJump ページ内の指定した文字にカーソルを移動する 良 Git Scope ファイルの一覧からGitの差分を確認できる 良 GitToolBox Git連携の機能を強化する 良 MultiHighlight 複数の検索結果をハイライト表示する 良 String Manipulation 色々な方法で文字列を操作する 良 BrowseWordAtCaret カーソルがあたっている単語をファイル内検索する 可 JSON Viewer JSONを見やすい形に整形する 可 Open in splitted tab 参照先を分割したウィンドウで表示する 可 Pipe Table Formatter パイプ(|)を使ったテーブルを揃える 可 Realigner 指定した文字で複数行の連結、複数行への分割、文字列の囲い込みができる 可 SQL Formatter SQLを見やすい形に整形する 可 GrepConsole
https://plugins.jetbrains.com/plugin/7125-grep-console/
機能
- tail -fで出力したログをツールウィンドウに表示する
- tail -fで出力するログをGrep検索で絞り込める
- 設定に基づいてログをハイライト表示する
- Rails用、SQL用など用途に応じた設定が可能
- 例えば、Rails用ではFATALを赤くする、SQL用ではINSERTを赤くする、といったことができる
- 設定内容は慣れないとわかりにくい
- 設定に基づいてファイルをハイライト表示する
- 基本的にログファイルを対象に使う
- 確認したいログを見つけやすくなる
使い方
- tailログに関する機能
- tailログの開始
- トップメニューの「Tools→Tail Fail in Console」もしくは「Tools→Tail Current File in Console」を選択する
- エディタで開いているファイルのログを出力するときに後者を使う
- tailログの絞り込み
- ログ出力のツールウィンドウで右クリックする
- 右クリックメニューの「Grep」を選択する
- Grep用の表示に切り替わるので、「Expression」に条件を入力する
- この状態でログが増えると、条件に該当するログだけが表示される
- 「RELOAD」ボタンをクリックすると、出力済みのログから条件に該当するものだけを絞り込む
- tailログのハイライト表示
- ログ出力のツールウィンドウで右クリックする
- 右クリックメニューの「Show Grep Console Statistics in Console」を選択する
- 設定用のウィンドウが表示されるので設定を行い、「OK」をクリックする
- ファイルに関する機能
- ファイルのハイライト表示
- 対象のファイルを開いている状態で、トップメニューの「Tools→Highlight Editor According to Grep Console Settings」を選択する
- 設定用のウィンドウが表示されるので設定を行い、「OK」をクリックする
- ファイルのハイライト表示の解除
- ハイライト表示した状態で、トップメニューの「Tools→Clear Grep Highlight in Editor」を選択する
HighlightBracketPair
https://plugins.jetbrains.com/plugin/10465-highlightbracketpair/
機能
使い方
- プラグインを有効にするだけ
IdeaVim
https://plugins.jetbrains.com/plugin/164-ideavim/
機能
- Vimと同じような操作で使用できるようにする
- 私はVimからRubyMineに乗り換えたのですが、さほど違和感なく使用できています
- RubyMineの機能も同時に使用できる
- IdeaVimの状態がノーマルモードでも挿入モードでも動作する
- IdeaVimがCtrlキーを使うため、RubyMineのキーマップの調整が必要
- Macはまだしも、Windowsだと影響が大きい
使い方
- ON・OFFの切り替え
- トップメニューの「Tools→Vim Emulator」でON・OFFを切り替えられる
- プラグインの設定の変更
- 本家Vimの「.vimrc」と同じように、「.ideavimrc」というファイルで設定を変更できる
- RubyMineと重複しているキーマップの確認
- 設定画面の「Editor→Vim Emulation」からRubyMineと重複しているキーマップを確認できる
- Vim Emulatorが有効な状態で確認する必要がある
Key Promoter X
https://plugins.jetbrains.com/plugin/9792-key-promoter-x/
機能
- ショートカットキーが存在する操作をマウス等から実行した際に操作内容をショートカットキーとともに記録する
- ショートカットキーの存在の気づくことができるため、ショートカットキーを覚えやすくなる
使い方
- 操作の記録
- プラグインを有効にするだけ
- 操作履歴の確認
- サイドメニューから「Key Promoter X」のツールウィンドウを開く
- サイドメニューに出ていない場合、トップメニューの「View→Tool Windows→Key Promoter X」から表示できる
Quick File Preview
https://plugins.jetbrains.com/plugin/12778-quick-file-preview/
機能
- Projectツールウィンドウ(ファイル一覧)で選択したファイルのプレビューを表示する
- プレビューといっても普通にファイルを開いたときとの違いはほとんどなく、プレビューの状態でも内容を変更できる
- 普通にファイルを開いたときと違い、Projectツールウィンドウで他のファイルに移動したときにプレビューは自動的に閉じられる
- 複数のファイルを辿っていくときに便利
使い方
- 他の設定の影響を受けるため、以下の事前準備が必要
- Projectツールウィンドウの設定を開く
- 「AutoScroll to Source」を無効にする
- プレビューの表示
- Projectツールウィンドウで対象ファイルをクリックする
- ダブルクリックしたときに正式にファイルを開く
- プラグインの設定によって変わるため、これは初期設定の場合の動作
- プラグインの設定の変更
- 設定画面で「Editor→General→Quick File Preview」を開く
Railways
https://plugins.jetbrains.com/plugin/7110-railways/
機能
- Railsのルーティングの一覧を表示する
- ルーティングを検索できる
- 選択したルーティングのアクションに移動できる
- ルーティングが把握しやすくなる
使い方
- サイドメニューから「Routes」のツールウィンドウを開く
- サイドメニューに出ていない場合、トップメニューの「View→Tool Windows→Routes」から表示できる
Rainbow Brackets
https://plugins.jetbrains.com/plugin/10080-rainbow-brackets/
機能
- 対になるカッコを色分けして表示する
- Rubyでは
()
、[]
、{}
が対象- HTMLではペアになるタグの
<>
を同じ色で表示する- ソースコードが読みやすくなる
- 表示例
使い方
- カッコの色分け
- プラグインを有効にするだけ
- プラグインの設定の変更
- 設定画面で「Other Settings→Rainbow Brackets」を開く
Scroll From Source
https://plugins.jetbrains.com/plugin/7606-scroll-from-source/
機能
- Projectツールウィンドウ(ファイル一覧)で開いているファイルの場所に移動する
- 他の方法でも開いているファイルに移動できるが、そちらの方法よりショートカットが割り当てられる・任意のタイミングで実行できるという利点がある
- ナビゲーションバー(パンくずリスト)から「Jump To Source」したときと違い、ショートカットキーが割り当てられる
- Projectツールウィンドウの設定で「Autoscroll from Source」を有効にした場合はファイルを開いたときに自動で移動するが、このプラグインでは必要なときにだけ移動できる
使い方
- ファイルを開いている状態で右クリックする
- 右クリックメニューの「Scroll From Source」を選択する
Tabdir
https://plugins.jetbrains.com/plugin/5045-tabdir/
機能
- タブ一覧で同じ名前のものが存在するファイルにはディレクトリ名を表示する
- RubyMineの標準機能だと同じ名前のファイルがタブ一覧にないとディレクトリ名が出ないが、このプラグインでは同じ名前のファイルがタブ一覧になくても良い
- Railsではどうしても同じ名前のファイルが増えるので、このプラグインがあればファイルを見分けやすくなる
- 表示例
使い方
- 他の設定の影響を受けるため、以下の事前準備が必要
- 設定画面で「Editor→General→Editor Tabs」を開く
- 「Show directory for non-unique file names」のチェックを外す
- ディレクトリ名の表示
- プラグインを有効にするだけ
- プラグインの設定の変更
- 設定画面で「Other Settings→Tabdir」を開く
次回予告
Ateam cyma Adevent Calendar 2019 の 2日目の記事は以上です。
3日目は引き続き私@hibiheionが担当します。
内容はもちろん「RubyMineをより便利にするプラグインたち:後編」です。
今回紹介しきれなかった残りの11個のプラグインを紹介します。さいごに
株式会社エイチームでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。
エンジニアで興味を持った方はcymaのQiita Jobsをご覧ください。
そのほかの職種は、エイチームグループ採用サイトをご覧ください。
- 投稿日:2019-11-26T21:39:16+09:00
Ruby記事まとめ
はじめに
記事数が多くなってきたので、ジャンル別に記事のリンクをまとめます。
本記事はRubyです。リンク
Rubyの基本箇所についての復習①(puts)
Rubyの基本箇所についての復習②(#{ })
Rubyの基本箇所についての復習③(gets.chomp)
Rubyの基本箇所についての復習④(ハッシュオブジェクト)
Rubyの基本箇所についての復習⑤(配列)
- 投稿日:2019-11-26T21:39:16+09:00
※Ruby記事まとめ
はじめに
記事数が多くなってきたので、ジャンル別に記事のリンクをまとめます。
本記事はRubyです。リンク
Rubyの基本箇所についての復習①(puts)
Rubyの基本箇所についての復習②(#{ })
Rubyの基本箇所についての復習③(gets.chomp)
Rubyの基本箇所についての復習④(ハッシュオブジェクト)
Rubyの基本箇所についての復習⑤(配列)
- 投稿日:2019-11-26T21:10:12+09:00
RSpecのlet(let!)とは
letとは
- RSpecのメソッドのひとつ
- letは呼ばれた時に初めてデータを読み込むという遅延読み込みをするメソッド。letはboforeブロックの外部で呼ばれるため、セットアップのコードを減らすことが可能。
- before ブロックの場合では各dscribeやcontextの前に毎回呼び出される。これはテストに予期しない影響を及ぼす恐れがある。また、不要なデータ作成をしてテスト実行を遅くする原因になることもある。
beforeブロックとの違い
- letは遅延読み込み
- letはインスタンス変数に格納しない
let!
- let!は遅延読み込みされないlet
- すべてのテストが余分なデータを持つ可能性がある
letとlet!,beforeブロックの使い分け
- 決まった定義はないがテストはDRYを意識するあまり読みにくくなってしまったら本末転倒。
- よって、読みやすいと思った方を使用していく。
- 投稿日:2019-11-26T20:42:53+09:00
APIキー・環境変数を設定したのにうまく反映されない時には.....
概要
メルカリのクローンサイトを作成する際にインフラ、デプロイ関係の担当をした者が沼にハマった部分を共有し、同じような状態から抜け出すための一助になればと思ったことが投稿するきっかけになります。
今回は、API関連のキーや環境変数を設定したのに本番環境では上手く挙動しない際に対処した方法をいくつか紹介します!
基本的なことから盲点な部分まで、同じような状況で困っている方の一助に少しでもなれたら幸いです。環境
- Rails v5.2.3
- Ruby v2.5.1
- Unicorn
- capistrano
- nginx
- mysql
- AWS, EC2(本番環境)
①まずはスペルミス、スペース(空白)の確認
最初から言われなくても分かるよっていう内容ですいません。
しかし、スペルミスは皆さん気をつけていることだとは思いますが、意外と足を引っ張るのはスペース(空白)の確認です。(例) AWS_SECRET_ACCESS_KEY = '************************' => スペースあり AWS_SECRET_ACCESS_KEY='************************' => スペースなし上記の微妙な違いで、内容は正確だがキーや環境変数が反映されない状態に陥ってしまいます。
まずは初心に戻った気持ちでスペース(空白)のチェックをしてみるのも良いかもしれません。②設定を反映させるためには...
スペルミスやスペースもなく設定が完了。以上終わり。 ではないのです。
設定した内容を反映させるためにはAWS(EC2)であれば、一度EC2上からログアウトし再度ログインすることによって設定が反映されます。
ただし、 .bash_profile などに設定をし直ぐに反映をさせたい場合は以下のコマンドを打つことによって反映されます。source ~/.bash_profile設定ファイル名は任意のもので大丈夫です。
また、設定ファイルの違いって案外知らないと思うので(自分は笑)、気になる方は下記にまとめたので見てみてください!
読み込み順 設定ファイル 概要 1 /etc/profile ログインする全ユーザー共通設定 2 ~/.bash_profile ※下記に概要記載 3 ~/.bashrc bash起動時に読み込ませたい設定 4 /etc/bashrc システム全体の関数等の設定 ※ ~/.bash_profileに関しては、~/.bash_login, ~/.profileと順に、上のファイルが無ければ下のファイルが読み込まれる流れになります。
③capistrano(自動デプロイ)導入後に設定が反映されない時
今回の投稿で一番伝えたかった内容になります。なぜかと言いますと...自身が沼にハマったからです。笑
スペルミス、余計なスペースも無い、EC2からログアウト・再ログインして設定の反映もバッチリ。さあ、後はcapistranoを動かして挙動を見に行きますか.....? んん? あれ、反映されていない。(白目)
この状況で沼にハマり、かれこれ2~3時間。 やっと見つけた救世主のコマンドを紹介します。(※今回はcapistranoを使用しています。)bundle exec cap -t production unicorn:stop => unicornの停止。これを打たないとコードが反映されていない状況に。 bundle exec cap production deploy =>再度capistranoで自動デプロイの実行。これで反映。この2つのコマンドを実行することによって、本番環境でAPIを利用したり、しっかりと環境変数も用いることができていました。
終わりに
インフラ関連に対する苦手意識を持っていましたが、何事も根拠があって、それを理解することによって物事の流れを掴むことができ、やりがいに繋がっていくと感じます。僕もまだまだこれからですが、また沼ったことを共有できればと考えています。長々と見て頂きありがとうございました。
参照
- 投稿日:2019-11-26T18:57:42+09:00
rails 自動更新
ajaxを用いて自動更新機能を実装する。
今回は前回、作成した非同期通信のコードに書き加える形で自動更新を実装する。完成品
環境
ruby:2.5.1
rails:2.5.3
DB:mysql(Sequel Pro)
ブラウザ:Google
OS:Mac(10.14.6)Github
https://github.com/tana1818/auto_update
作成する前に
なぜ必要か
日々使ってるLINEを考えてみましょう。
LINEのように、相手側が入力すると自分側が、LINEを開いたままでも、
リアルタイムで更新されます。あれも自動更新機能が実装されています。
自動更新がないままLINEのチャット機能を使用するとなると
表示されている画面をいちいちリロードしないと画面が更新されません。
これを一人ならまだしも、グループでチャットをするとなると面倒です。自動更新の大きなメリットはリロードしなくてもリアルタイムでデータが更新されるという点です。
通信の流れ
実装に移る前に通信の流れを把握しておきましょう。
①ブラウザからindexアクションのリクエストが来たのでサーバーはレスポンスを返す
②ブラウザはサーバーからのレスポンスを解析
③解析して必要な全てのアセット (JavaScriptファイル、スタイルシート、画像) をサーバーから取得する
④JavaScriptを読み込む(最新のデータがあったら取得、なかったら取得しない)
⑤json形式で返ってきた値をcontrollerで判別
⑥最後にviewとjbuilderの情報を返す同じ画面に居続けたら③から⑥を繰り返し自動更新が行われる
作成手順
ここでは自動更新機能を実装するだけの人用で書いていくが
一応データベースの情報はいじってないので自分の非同期通信を実装した方がいれば
「コード編集(ビュー)」の手順からファイルに追記していけば同じデータベースで自動更新機能が実装できる。結論、データベースの名前が一律"ajax"になる。
もし「非同期通信」と「自動更新」のファイルを比較化をしたいって人は
データベースの名前を決めるファイル(database.yml)を編集してrails db:createしてください
Githubからコードをダウンロード
下記のURLからコードをダウンロード
https://github.com/tana1818/ajax
ダウンロードが完了したらファイル名を変更(なんでも良い)
任意のディレクトリにファイルうを配置したらファイル名変更
自分は自動更新なので’auto_update’って名前にしました。
gemのインストール、DBの作成、マイグレーション実行、サーバー起動
auto_updatebundle install #gemのインストール rake db:create #DB作成 rake db:migrate #マイグレーションファイルを実行する rails s #サーバーの起動コード編集(ビュー)
①タイトルバーの名前変更
views/fruits/application.html.haml!!! %html %head %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ //下記記述変更 %title Auto_update = csrf_meta_tags = csp_meta_tag = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %body = yield
②indexにdata-id付与views/fruits/index.html.haml.container .row %h1 Listing fruit - if flash[:notice] %p= flash[:notice] .row %table.table.table-bordered.table-sortable %thead %tr %th Name %th %th %th %tbody.fruit_item - @fruits.each do |fruit| //下記追加('item'クラスにデータのid情報追加) %tr.item{"data-id": "#{fruit.id}"} %td = fruit.name %td = link_to 'Show', fruit %td = link_to 'Edit', edit_fruit_path(fruit) %td = link_to 'Destroy', fruit, data: {confirm: 'Are you sure?'}, method: :delete .row %h1 New fruit = render 'form'
jsファイル編集
assets/javascripts/fruits.js$(function(){ //非同期通信 function buildHTML(fruit){ //通信が成功するとdoneメソッドの引数にデータが入るようになっているため、これを利用してHTMLを組み立てる //5行目にdata-id = ${fruit.id}を追加 var html = `<tr class = "item ui-sortable-handle" data-id = ${fruit.id}> <td> ${fruit.name} </td> <td> <a href = /fruits/${fruit.id}>Show</a> </td> <td> <a href = /fruits/${fruit.id}/edit>Edit</a> </td> <td> <a data-confirm = "Are you sure?", rel = "nofollow", href = /fruits/${fruit.id}, data-method = "DELETE">Destroy</a> </td> </tr>` return html; } $('#new_fruit').on('submit', function(e){ //'#new_fruit'の'submit'が押された時に発火 e.preventDefault(); //これを書いてるせいで'submit'を押した際に要素にdisabledが付与される→最後の.alwasが書いてある行を足すことでそのdisabled要素を削除してる var formData = new FormData(this); //FormDataオブジェクトの引数はthisとなってる。イベントで設定したfunction内でthisを利用した場合はイベントが発生したDOM要素を指す。今回であればnew_commentというIDがついたフォームの情報を取得している $.ajax({ url: "/fruits/", //ここはアクションのURLなのでrails routesで確認 type: "POST", //ここはアクション名 data: formData, dataType: 'json', processData: false, contentType: false }) .done(function(data){ //非同期通信の結果として返ってくるデータは、done(function(data) { 処理 })の関数の引数で受け取る var html = buildHTML(data); $('.fruit_item').append(html) //htmlに追加 // $('.b').val('') //なんかエラー出るなーと思ったらvarと書き間違えていた(ここでは'submit'を押した後テキストボックスに空の要素を付与してる) $('.new_fruit')[0].reset() //初期値があれば初期値にリセットされる }) .fail(function(){ //エラーが置きた際 alert('投稿できませんでした') }); // .always(function(){ //この記述を書いてないと連続で投稿できない // $(".c").removeAttr("disabled") // }) return false; //要素の効果を無効化する、ちょっとわかんないけどとりま親要素のクリックをなかったことにするっぽいw //上記のコメント化記述は'disabled'を消しにいってる //こっちの方が記述量が少なくて良い }) //自動更新 if (location.pathname.match()){ //もし現在のURLパスがindexアクションだったら(http://localhost:3000/fruitsもしくはhttp://localhost:3000) setInterval(update, 5000);//5000ミリ秒ごとにupdateという関数を実行する } function update(){ if($('.item')[0]){ //もし'.item'というクラスがあったら var fruit_id = $('.item:last').data('id'); //一番最後にある'.item'クラスの'id'というデータ属性を取得し、'fruit_id'という変数に代入 $.ajax({ url: location.href, //urlは現在のページを指定 type: 'GET', //アクション名指定(データを表示させる) data: { id: fruit_id }, //rails に引き渡すデータ(これがparams[:id]になる) dataType: 'json' }) .done(function(data){ if (data.length){ //もしdataに値があったら $.each(data, function(i, data){ //'data'を'data'に代入してeachで回す var html = buildHTML(data); $('.fruit_item').append(html); }) } }) .fail(function(){ //そんなにこの記述はいらない気がするけど、異常系のエラー(途中で通信が中断されたり)が起きた時用 alert('自動更新に失敗しました') }); } else { clearInterval(); //値がなかったらsetIntervalを止める(この記述はなくても動く、別画面に遷移した際は、ブラウザを閉じた時同様、遷移した時点で遷移前のJavaScript実行は勝手に打ち切られる模様) } } });
コントローラー編集(indexのみ)
controllers/fruites.controller.rbdef index @fruits = Fruit.all @fruit = Fruit.new respond_to do |format| #記述追加 format.html format.json { @new_fruit = Fruit.where('id > ?', params[:id]) } #jsのdataの情報がparams[:id]に入る end endindex.json.jbuilderのファイル編集
views/fruits/index.json.jbuilderjson.array! @new_fruit.each do |fruit| json.name fruit.name json.id fruit.id # 配列かつjson形式で@new_fruitを返す end
以上で実装は終了
再度サーバーを立ち上げ直してauto_updaterails sうまく実装してできていれば上記のGIFのように自動で最新のデータが更新される
うまく実装できました?
実装できて自分の理解ができたら無駄なコメントは削除して読みやすいコードにしましょう。
(なんだか自分のコードにも_form.html.hamlに無駄なコメントアウトがあったりしたので、、)今回でqiita2記事目なんですが、めちゃしんどいっすw
書いてみるとやってる事全然簡単に見えるのに、これあげるのにどれだけかかったか、、、
qiita人民の凄味を改めて実感しました。
ちょいまだまだ補足が書けていないのですが、一旦あげようと思います。
指摘事項あれば教えて頂けると嬉しいです。
それでははてなブログもやっているので是非!
https://tanagram18.hatenablog.com/
- 投稿日:2019-11-26T18:23:33+09:00
RailsでAPIを作ってJson形式のレスポンスを返すまで
はじめに
まとめておくと後で楽だよねってことで
環境
Ruby 2.6.5p114
Rails 6.0.1環境自体はDockerコンテナに押し込めているので、まずはDockerの設定周りからやっていきます。
Docker関連
docker-compose.yml
こちらの記事を参考にしつつ作成しています。
ENTRYPOINTは自分が理解しきれてないので指定していません。
なくても動きます。後、重要なのはmysqlのvolumesとして
/etc/mysql/conf.d
がいったん必須です。
mysql8からデフォルトの認証方式がパスワードになっていないので、それをパスワードにするために永続化して設定ファイルを入れてあげないと困ります。
認証方式変わったということは、それをデフォルトとして他を変えるのが本筋ですが、公開するサービスではないので、いったんパスワードにしています。version: '3' services: db: image: mysql:8 environment: MYSQL_ROOT_PASSWORD: password ports: - '13306:3306' volumes: - ./mysql:/var/lib/mysql - ./mysql-confd:/etc/mysql/conf.d rails: build: context: . dockerfile: ruby/Dockerfile command: bash -c "bundle exec rails s -p 3000 -b '0.0.0.0'" environment: - "DATABASE_HOST=db" - "DATABASE_PORT=13306" - "DATABASE_USER=root" - "DATABASE_PASSWORD=password" volumes: - .:/app ports: - "13000:3000"Dockerfile
こちらはRails6からwebpackerを入れる必要があるため、その設定は入れています。
今回はAPIを作るようのアプリケーションにするため、この設定はまるごといらないのですが、削除しなくても困らないので残しています。
APIモードで作らない場合はdocker-compose.ymlのcommandでインストールしてあげるかDockerfile内でインストールしてあげればOKです。あと重要なのは最後に
RUN bundle install
を入れておくぐらいでしょうか。
Gemを書き換えた際にはイメージを書き換えないと駄目っぽいのですが(本当か?)、これやっておかないと起動時にGemがないと言われて怒られます。
1個目の方はRails関連のGemを入れるようなので、別にやっておかないとアプリケーションの方のGemの読み直してくれません。※
ここも色々参考にさせていただいた記事があるのですが、見つけ次第追記します。
どの記事だっけなぁ・・・FROM ruby:2.6.5 ENV TZ='Asia/Tokyo' RUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime ENV LANG C.UTF-8 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y nodejs yarn RUN gem install bundler WORKDIR /tmp ADD ruby/Gemfile Gemfile ADD ruby/Gemfile.lock Gemfile.lock RUN bundle install ENV APP_HOME /app RUN mkdir -p $APP_HOME WORKDIR $APP_HOME ADD . $APP_HOME RUN bundle installGemfile
バージョンは適宜読み替えてください。
Gemfile.lockは参考にある通り空ファイルです。Gemfileはrailsアプリケーションを生成した際に上書きされると困るので、rootフォルダとは別の場所に置いてあります。
source 'https://rubygems.org' gem 'rails', '~> 6.0.1'railsアプリケーションの生成
ファイルの準備が出来たらアプリケーションを生成します。
docker-compose run rails rails new . --api --force --no-deps --database=mysqlconfig/database.yml
立ち上げる前にデータベースの設定を書き換えます。
passwordとhostの書き換えだけです。docker-composeでアプリケーションを作成するとネットワークをいい感じで作ってくれるため、hostをサービス名で設定しておけばいいので楽ですね。
docker-composeで設定したportsもこのネットワーク外部から繋いだ時のものでネットワーク内部からは普通のportで接続となります。default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password host: dbdatabase作成
rakeコマンド使うなり自力で作るなりしてDB作っておいてください。
docker起動
ビルドして起動します。
中々に時間がかかるので、間違えてやり直しをするのが結構辛かったです。docker-compose build docker-compose up -d起動確認
localhost:13000
につないで画面出ればオッケーです。
駄目ならエラー見つつ頑張ってください。routes.rbの設定
起動したので、routesを書いてURLを生成します。
適当でも良いですが、せっかくなので少し真面目に書きます。
resourcesをnewsにしているのは作ろうとしているアプリケーションのURLだからです。routes.rbRails.application.routes.draw do namespace :api do namespace :v1 do resources :news end end endrouteの確認
railsの操作をする際は色んなことをやっていくことになるので、内部で操作していった方が楽だと思います。外からでも操作できるので、どっちでも良いです。
入ってやる方は以下のコマンドで入れます。docker-compose exec rails bash確認したらこんな感じで出ると思います。
rails初期から存在するrouteと共に設定したrouteが見れます。これで上のrouteが正しく設定されたことを確認します。
root@69c223a51ae2:/app# rails routes Prefix Verb URI Pattern Controller#Action api_v1_news_index GET /api/v1/news(.:format) api/v1/news#index POST /api/v1/news(.:format) api/v1/news#create api_v1_news GET /api/v1/news/:id(.:format) api/v1/news#show PATCH /api/v1/news/:id(.:format) api/v1/news#update PUT /api/v1/news/:id(.:format) api/v1/news#update DELETE /api/v1/news/:id(.:format) api/v1/news#destroy rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create rails_relay_inbound_emails POST /rails/action_mailbox/relay/inbound_emails(.:format) action_mailbox/ingresses/relay/inbound_emails#create rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create rails_conductor_inbound_emails GET /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#index POST /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#create rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#show PATCH /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update PUT /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update DELETE /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#destroy rails_conductor_inbound_email_reroute POST /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format) rails/conductor/action_mailbox/reroutes#create rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#createcontrollerの作成
これもコマンドで作成します。
Controllerの名前はroutesで確認したControllerと同じにする必要があるので、その点だけ注意ください。rails generate controller api/v1/news作ったら中身です。
news_controller.rbclass Api::V1::NewsController < ApplicationController def index render json: { status: 'SUCCESS'} end endとりあえずのレスポンスを返却するためだけの中身です。
途中でキャッシュ関連で変更が反映されないという問題に直面しましたが、その際はこちらを参考にして解決しました。
Rails6でも同様の方法で解決できるようです。終わりに
Rails使うならここまではぱぱっと出来るようになりたいですね。
一通りやってみて環境構築周りの注意点とか分かったので、やっぱり手を動かして作るって大事です。
- 投稿日:2019-11-26T16:35:59+09:00
Ruby学習ログ:その1【do...end = {...} , etc.】
Rubyを学び始めて2ヶ月ほどの初学者です。
アウトプットも兼ねて学習したことを投稿していこうと思います。
早速いきましょう。「do...end」は、「{...}」に書き換えられる
hoge = [1, 2, 3] hoge.each do |hogey| puts "オラは#{hogey}歳だ!" end puts "========================================" hoge = [1, 2, 3] hoge.each {|hogey|puts "オラは#{hogey}歳だ!"}実行結果オラは1歳だ! オラは2歳だ! オラは3歳だ! ======================================== オラは1歳だ! オラは2歳だ! オラは3歳だ!同じ結果になりました
いつでも使えるか?
ところでデメリットはないのか?と思って調べてみたらどうやらあるっぽい
→ブロックをdo…endで書くか{…}で書くかにより挙動が変わる例上の記事の中で「戻り値」に関して理解が曖昧だったので
→【Ruby】メソッドの戻り値ってなんだ
この中の演習をやってみましたdef shipping_free?(price) return price >= 5000 end if shipping_free?(10000) puts "5000円以上のお買い上げなので送料はいただきません" else puts "追加で送料をいただきます" end実行結果5000円以上のお買い上げなので送料はいただきません「戻り値」の解説ならこちらの方がわかりやすいかも
→関数の「戻り値」って何?C言語とRubyを比較して理解する!:Rubyの関数の戻り値
*戻り値(返り値)は関数が返す「値」のこと (何か関数に値を入れたとき、関数から出てくる値)所感
わからないワードをググって調べると、また新たにわからないワードが出てきてプログラミングは奥深いなと感じた。基礎へ立ち返ることも必要であるし、内容の質は置いておいて、頭を整理する意味でも定期的に文章としてアウトプットすることは大切だと思った。
- 投稿日:2019-11-26T15:48:10+09:00
Codr0 : TwiterでコードをシェアできるWebアプリを作ろうと思った
たぶん、長い記事
きっかけ(こんな呟きを見かけた
ソースコードをツイートするときに
— えるは個人えんじにゃー(喪中) (@ellnore_pad_267) November 2, 2019<br>source code<br>
ってやってマークダウンみたいに引用文にして欲しい。
ここはもうURLとかハッシュタグとかも全部エスケープして欲しい。作成の過程で得られたもの。
- Active Record Storage
- Twitter Login方法と仕組み、そのたTwitterあれこれ
- JSの基礎
- AWS S3の使い方
- XSS対策
作成要件
- マークダウン投稿、シンタックスハイライト表示
- gem: redcarpet, rouge
- 投稿から画像生成
- クラウドストレージに保存
- AWS S3を使用
作成の流れ:予定
- rails new codr, git init, heroku create、Active Storage
- AWS S3あれこれ
- twitter登録、ログイン機能作成
開発環境
- vm : Linux Ubuntu (virtualbox + vagrant)
- Ruby 2.5.1p57
- Rails 5.2.3
- Postgresql
実作業: アプリ作成、諸準備
rails new codr -d postgresql # DB設定等は割愛Gem
今回は公開にまで至る予定なので、railsやdeviseの日本語化等も。が、想定ユーザはエンジニアだしと思い、最終的には英語オンリーのサイトになった。
Gemfilegem 'mini_racer' # uncomment gem 'rails-i18n' # japanize # authetication gem 'devise' # login gem 'omniauth' # SNS login gem 'omniauth-twitter' # twitter login gem 'devise-i18n' # japanize devise gem 'devise-i18n-views' gem 'redcarpet' # for markdown gem 'rouge' # for syntax highlight gem 'meta-tags' gem 'aws-sdk-s3' # for aws s3gitignore => rails.credentials.yml
当初は.
gitignore
とgem 'dotenv'等を使っていた。が、作成途中でRails5.2からのrails.credentials.ymlを知り、利用した。rails.credentials.ymlは暗号化されており、なお、復号化には
/config/master.key`を利用。irb# editor setting EDITOR="vi" bin/rails credentials:edit # edit credentials.yml rails credentials:edit # show credential.yml rails credentials.yml:show # herokuにmaster.keyを環境変数として指定 heroku config:set ENV_VAR="環境変数" --app "アプリ名" # 追加した変数を使用するには Rails.application.credentials.dig(:twitter, :API_Key)rails gあれこれ
# devise # install devise rails g devise:install rails g devise User name:String # Add Admin column to User rails g migration AddAdminToUsers # add setting at /db/migrate/20191103141531_add_admin_to_users.rb add_column :users, :admin, :boolean, default: false # add views and controllers to modify devise rails g devise:controllers users rails g devise:views users # japanize # add at /config/application.rb config.i18n.default_locale = :ja => create /config/locale/devise.view.ja.yml# scaffold post rails g scaffold Post user:references name:string content:text date:datetimeActive Record Associations関連付け
/app/model/user.rbhas_many :posts/app/model/post.rbbelongs_to :user投稿関連
マークダウン投稿
基本:
Redcarpet::Markdown.new(renderer, extensions = {}).render(@post.content)
オプションやXSS対策等を追加したく、helperメソッドを作成した。app/helpers/posts_helper.rbModule PostsHelper require 'rouge/plugins/redcarpet' class RougeRedcarpetRenderer < Redcarpet::Render::HTML include Rouge::Plugins::Redcarpet def header(text, level) # #や##等がh2、h3となるようにした。 level += 1 "<h#{level}>#{text}</h#{level}>" end end def markdown(text) render_options = { filter_html: true, # do not allow any user-inputted HTML in the output. hard_wrap: true, } extensions = { autolink: true, # <>で囲まれていない時は、リンクとして認識しない fenced_code_blocks: true, # ```\n ```内をコード部分と見做す lax_spacing: true, no_intra_emphasis: true, strikethrough: true, superscript: true, tables: false, # テーブルを認識しない highlight: true, disable_indented_code_blocks: true, space_after_headers: false # #の後にスペースが無くても、h1等とする。 } renderer = RougeRedcarpetRenderer.new(render_options) Redcarpet::Markdown.new(renderer, extensions).render(text).html_safe end endhtml_safe => sanitize
html_safeではXSS対策としては駄目と知った。名前詐欺である。
sanitizeヘルパーを使用した。ホワイトリスト方式。要参照app/views/posts/index.html.erb# sanitize(html, options = {}) <div id="capture" class="content"> <%= sanitize(markdown(@post.content), tags: %w(div img h1 h2 h3 h4 h5 strong em a p pre code ), attributes: %w(class href)) %> </div>投稿内容のデータ化、AWSへの画像保存
最初はTwitterAPIを利用して、投稿から作成、DBに直接保存した画像でTwitter投稿しようとした。だが、Herokuでは画像が保持されない事、TwitterAPIの変更などいろいろ面倒なことが発生したので、最終的には画像をAWS S3に保存し、og:imageに添付する形を取った。
- Webアプリ内で通常投稿
- showページ表示(同時にhtml2canvasでBase64としてデータ取得、hidden_fieldに収納
- Tweetボタン押す(Postされ、postモデル内でbase64をデコード
- Active Storageを通して、AWS S3に保存
- 保存画像urlをog:imageに添付
Active Storage
Rail5.2からの機能で、今まで常用されてたと言うcarrievaveやpaperclip等を使わずに、クラウドストレージ等へのアップロードが容易になる。今回はAWS S3を使った話。
irb# set up rails active_storage:install # 今回は画像が紐づくPostテーブルが既にあるので、不要 # rails g resource comment content:text rails db:migrateapp/models/post.rbclass Post < ApplicationRecord # 今回は1つの投稿につき、1枚の画像なので。複数なら => has_many_attached :prtscs has_one_attached :prtsc endapp/config/enviroments/# ファイル保存先変更 # development.rb config.active_storage.service = :local # production.rb config.active_storage.service = :amazon
rails credentials:edit
でAWSアクセスキーとシークレットキーを追加。config/credentials.yml.encaws: access_key_id: secret_access_key:config/storage.ymltest: service: Disk root: <%= Rails.root.join("tmp/storage") %> local: service: Disk root: <%= Rails.root.join("storage") %> amazon: service: S3 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> region: ap-northeast-1 bucket: codr0Gemfile# gemが必要 gem 'mini_magick' # 今回は不要だったので、入れず。 gem 'aws-sdk-s3', require: falsehtml2canvas
- Tweetボタン押下時に、画像をPostするためのフォーム、hidden_fieldを用意
html2canvas.js
をapp/assets/javascripts
ディレクトリ配下に保存。- html上に置くscriptコードを改修
app/views/posts/show.html.erb<%= form_with(model: @post, local: true) do |form| %> <%= form.hidden_field :id, value: @post.id %> <%= form.hidden_field :prtsc, value: "" %> # idはpost_prtscになる。 <%= form.submit "Post", class:"btn btn-outline-dark", id:"tweet", value:"tweet" %> <% end %>showページ描画後に、Redcarpetで出力された
<div id="capture"></div>
内にある<pre><code>
を選択し、html2canvasの基本機能でBase64データ化し、最後に<input type="hidden" id="post_prtsc">
のvalueにセットするようにした。app/views/layouts/application.html.erb<script type="text/javascript"> html2canvas(document.querySelector("#capture"),{scale:1, width:600}).then(canvas => { var base64 = canvas.toDataURL('image/jpeg', 1.0); document.getElementById('post_prtsc').setAttribute('value', base64); }); </script>Base64デコード
app/models/post.rbattr_accessor :img def parse_base64(img) if img.present? # data:image/jpeg;base64,/9j/4AAQSkZJRgABA・・・から/9j/4AA以降を選択取得 content = img.split(',')[1] # 今回は、ユーザによる画像アップロード投稿ではなく、拡張子が決まっている filename = Time.zone.now.to_s + '.jpg' decoded_data = Base64.decode64(content) # String.IO.newにより、アプリ内に一時ファイルを作成しなくて済む prtsc.attach(io: StringIO.new(decoded_data), filename: filename) end endあとはposts_controllerで、paramsから受け取ったBase64データを上の
parse_base64(img)
で変換し、保存すれば完了。AWS S3
AWS上での登録、設定、バケット作成等は割愛。
Tweet button
公式で生成されるTweetボタンのURLを利用し、押下時にwindow.openでTweet投稿ページを開くようにした。rubyonrailsで用意した変数をjsに渡す
gem 'gon'
も考えたが、見送った。app/views/layouts/application.html.erb<script> var base = 'https://twitter.com/intent/tweet?url='; var pageUrl = 'https://codr0.herokuapp.com/posts/' + document.getElementById('post_id').value; var option = '&button_hashtag=Codr0&ref_src=twsrc%5Etfw'; var href = base + pageUrl + option; var twit = document.getElementById('tweet'); twit.addEventListener('click', function() { window.open( href ); }); </script>Twitterログイン
TwitterDeveloperAccountが必要。割愛。
なお、omniauthは脆弱性が見つかっており、githubの方でもアラートが来るのだが、パッチが無いのだが。クックパッドの人が対処してくれたので、感謝したい。
app/models/user.rb# 参考ページと同じ基礎的な所は割愛する。 class User < ApplicationRecord def self.from_omniauth(auth) find_or_create_by!(provider: auth['provider'], uid: auth['uid']) do |user| # 一部割愛 user.username = auth['info']['nickname'] # SNS登録時は、ダミーメールを登録 user.email = User.dummy_email(auth) end end # SNS登録(providerが存在する)時は、パスワード要求をしない def password_required? super && provider.blank? end def self.new_with_session(params, session) if session['devise.user_attributes'] new(session['devise.user_attributes']) do |user| user.attributes = params end else super end end private def self.dummy_email(auth) "#{auth.uid}-#{auth.provider}@example.com" end endTwitterのニックネームが取得できるようになったので、元からあるUserのnameテーブルは削除した。
デザイン
- 投稿日:2019-11-26T15:40:13+09:00
【Ruby初心者】シンボルの使い方でつまづいたこと
はじめに
元々PHPをやっていたのですが、Rubyに触ってみて一週間経ちました。
参考書を2冊読んだのですが、一番わからないポイントがシンボルについてでした。
今回はシンボルについての記事です。:apple?
参考書を60ページくらい読んだら出てきました。
:apple.class #=> Symbol 'apple'.class #=> String初めてみる記法です。
Ruby使いは左側にコロンのついてるものをシンボルと呼ぶらしいです。主な特徴
同じシンボルであれば全く同じオブジェクトである
str1 = "string" str2 = "string" p str1.equal?(str2) #=> false p str1.object_id #=> 70332245439420 p str2.object_id #=> 70332245439400 sym1 = :symbol sym2 = :symbol p sym1.equal?(sym2) #=> true p sym1.object_id #=> 901148 p sym2.object_id #=> 901148シンボルは1つのオブジェクトを参照しているものなので、右側の値が同じならシンボルは同じものになる。
→ String型に比べて、メモリを消費しない参考記事:Rubyの文字列とシンボルの違いをキッチリ説明できる人になりたい
https://qiita.com/Kta-M/items/53a13ef60e14fcb41193シンボルの使い方
連想配列のキーとして使われる
#文字列をキーにする連想配列 {'japan' => 'yen', 'us' => 'dollar', 'india' => 'rupee'} #これのキーをシンボルにすると次のようになる {:japan => 'yen', :us =>'dollar', :india => 'rupee'}配列のキーをシンボルにすることによって、データの抽出が高速になる
(理由:Rubyのプログラムはシンボルのデータを整数として持っているので、データの処理が速くなる)シンボルと同名のメソッドを呼び出す使い方
class User attr_reader :first_name, :last_name, :age def initialize(first_name, last_name, age) @first_name = first_name @last_name = last_name @age = age end endシンボルが出てきました。
attr_reader :first_name, :last_name, :age
attr_readerは書き換えはできないけど、インスタンスにおいて指定したプロパティを読み込めますという設定にするものみたいです。
でもなんでattr_readerで指定してるのがシンボルなのかわからなくなりました。・・・
どうやらこれは、同名のメソッドを呼び出ししているみたいです。
:first_name → first_nameのメソッドattr_reader :first_nameを設定すると、
class User def initialize(first_name) @first_name = first_name end def first_name @first_name end endとした時と、同じことになるみたいです。(attr_readerを設定すると同名のゲッターメソッドを作成する)
今回はattr_readerで設定をしているので、first_nameのゲッターを呼び出しているが、
attr_accessorで設定をすると、ゲッターとセッターで呼び出すみたいです。これまでとは違ったシンボルの使い方ですね
似たような使い方で
['ruby', 'java', 'perl'].map(&:upcase) #を次のように変更(条件付き) ['ruby', 'java', 'perl'].map{|s| s.upcase}がありました。これもシンボルと同名のメソッドを呼び出しているみたいですね。
参考記事: Rubyのmap(&:name)というのはどういう意味?
https://qa.atmarkit.co.jp/q/35まとめ
2つのシンボルの使い方があることを理解しました。
1、連想配列のキーにつかう
2、シンボルと同名のメソッドを呼び出す以上
- 投稿日:2019-11-26T14:56:34+09:00
ABC083B - Some Sums
問題
https://atcoder.jp/contests/abs/tasks/abc083_b
1回目
回答
N,A,B = gets.chomp.split(" ").map(&:to_i) res = 0 for num in 1..N do numList = num.to_s.chars.map(&:to_i) sum = 0 for n in numList do sum += n end if sum >= A && sum <= B res += num end end puts res結果
2回目
回答
N,A,B = gets.chomp.split(" ").map(&:to_i) res = 0 for num in 1..N do s = 0 n = num while n > 0 do s += n % 10 n /= 10 end if s >= A && s <= B res += num end end puts res結果
- 投稿日:2019-11-26T11:41:40+09:00
Windowsにてp "日本語"で"日本語"を表示させる方法
Ruby on WindowsのFAQシリーズ
初学者からよく聞く
nihongo.rbp "日本語" p ["日本語"] p "日本語": :"日本語"が
"\u65E5\u672C\u8A9E" ["\u65E5\u672C\u8A9E"] {:"\u65E5\u672C\u8A9E"=>:"\u65E5\u672C\u8A9E"}となる問題です。
ちなみに脳筋回答
コンソールに日本語表示させる必要なんて無いんじゃない?
(割と真面目に、Rubyのp
による日本語表示をwindowsでやる必要は無いと思ってます)真面目回答
出力が真の意味で文字化けする危険性があるが
スクリプトの頭にEncoding.default_internal = __ENCODING__付けとけ
p "日本語"とは?
引数を人間に読みやすい形に整形して改行と順番に標準出力 $stdout に出力します。主にデバッグに使用します。
引数の inspect メソッドの返り値と改行を順番に出力します。つまり以下のコードと同じです。
print arg[0].inspect, "\n", arg[1].inspect, "\n", ...
つまり、
p("日本語")
はputs "日本語".inspect
となります。p "日本語".encoding # => #<Encoding:UTF-8> p "日本語".inspect # => "\"\\u65E5\\u672C\\u8A9E\"" p "日本語".inspect.encoding # => #<Encoding:Windows-31J>なので、
"日本語".inspect
の時点で\u
表記への変換が行われている事
および、その理由が文字コードを変換しつつ元の情報を損なわないための措置という事がわかります。
"日本語".encoding
はるりま: 多言語化にリテラルのエンコーディング
文字列リテラル、正規表現リテラルそしてシンボルリテラルから生成されるオブジェクトのエンコーディングは スクリプトエンコーディングになります。現在のスクリプトエンコーディングは __ENCODING__ により取得することができます。
とある通り、スクリプトエンコーディング(
__ENCODING__
)によって取得できます。
また、Ruby2.0以降のスクリプトエンコーディングの既定はUTF-8ですでは、
"日本語".inspect
は?というと
るびマ Ruby M17N の設計と実装Encoding.default_internal が設定されている場合は、全ての入力された String のエンコーディングは Encoding.default_internal の返すエンコーディングと等しいと仮定することが可能になります。この場合、ライブラリが返す文字列も Encoding.default_internal になっているべきです。
ライブラリが返す文字列は、
Encoding.default_internal
になっているべき という事なのでEncoding.default_internal
によって指定できる事がわかります。結論
Encoding.default_internal = __ENCODING__を行うことによって、
String#inspect
が返す文字列を文字列リテラルと同じエンコーディングにできる。問題点
Windowsではlocaleが
cp932
なので
文字列内に絵文字などのcp932に変換できない文字を含むと文字化けします。
この問題のためにdefault_internal
がnil
の場合はdefault_external
を代替エンコーディングとして使っていると推測できます。
- 投稿日:2019-11-26T00:34:01+09:00
Railsを使ってチャットアプリを作る
はじめに
Railsを使って簡単なチャットアプリを作ったので、そのときの手順をまとめました。
環境
$ ruby -v ruby 2.5.1p57 (2018-03-29 version 63029) [x86_64-darwin19] $ bin/rails -v Rails 5.2.3参考
チャットアプリを作る際、以下を参考にさせて頂きました。
この記事で掲載しているソースコードの大半は下記の2つのページの内容が基になっています。Action Cableでリアルタイムチャットアプリの作成方法 (Rails 5.1.4にて)(その1) herokuで動かす!
Rails 5 + ActionCableで作る!シンプルなチャットアプリ(DHH氏のデモ動画より)ソースコード
チャットアプリのソースコードは以下で確認できます。
Neufo/rails-sample-app/ChatApp手順
1. アプリケーションを作る
まずはチャット機能を実装するアプリケーションを作ります。
$ rails new ChatApp2. コントローラーを作る
ChatAppディレクトリに移動して以下のコマンドを実行します。
$ bin/rails generate controller rooms showこれで
ChatApp/app/controllers/
にrooms_controller.rb
が作られます。3. モデルを作る
ユーザーが入力したメッセージを保存するモデルを作ります。
$ bin/rails generate model message content:text $ bin/rails db:migrate4. メッセージの一覧を表示する
RoomsControllerのshowメソッドでメッセージの一覧を取得します。
ChatApp/app/controllers/rooms_controller.rbclass RoomsController < ApplicationController def show @messages = Message.all end end取得したメッセージを表示するためにViewを編集します。1
ChatApp/app/views/rooms/show.html.erb<h1>Chat room</h1> <div id="messages"> <%= render @messages %> </div>messagesの各要素をページに埋め込むために部分テンプレートを作ります。
(ChatApp/app/views/ に messagesフォルダを作り、その中にファイルを保存します)ChatApp/app/views/messages/_message.html.erb<div class="message"> <p><%= message.content %></p> </div>5. チャンネルを作る
クライアント/サーバ間の通信処理のひな形を作ります。
$ bin/rails generate channel room speakコマンドを実行するとクライアント用、サーバ用のファイルが作られます。
- ChatApp/app/assets/javascripts/channels/room.coffee (クライアント)
- ChatApp/app/channels/room_channel.rb (サーバ)
サーバを起動して動作を確認してみます。
$ bin/rails sブラウザで
/rooms/show
を開き、コンソールでApp.room.speak()
と入力します。trueが返ってくれば成功です。
6. jQueryを有効にする
クライアントがメッセージを送受信するときにjQueryを使うので準備しておきます。
ChatApp/Gemfilegem 'jquery-rails'上記をGemfileに追記したら
bundle update
を実行します。jQueryのライブラリを読み込むようapplication.jsに追記します。
ChatApp/app/assets/javascripts/application.js(省略) //= require jquery //= require jquery_ujs //= require_tree .7. メッセージの送受信処理を作る
以下の機能を作ります。
- クライアント側
- サーバからメッセージを受信する (received)
- ユーザが入力したメッセージをサーバに送信する (speak)
ChatApp/app/views/rooms/show.html.erb<form> <label>Say something:</label><br> <!-- room_speaker からサーバへメッセージを送信する --> <input type="text" data-behavior="room_speaker"> </form>ChatApp/app/assets/javascripts/channels/room.coffee$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) -> # Enterキーが押されたらサーバへメッセージを送信する if event.keyCode is 13 # return = send App.room.speak event.target.value event.target.value = '' event.preventDefault() App.room = App.cable.subscriptions.create "RoomChannel", connected: -> # Called when the subscription is ready for use on the server disconnected: -> # Called when the subscription has been terminated by the server received: (data) -> # メッセージ一覧の末尾に受信したメッセージを追加する $('#messages').append data['message'] speak: (message) -> # サーバのspeakメソッドを呼び出す @perform 'speak', message: message
- サーバ側
- room_channelを購読する (subscribed)
- クライアントから受信したメッセージを保存する (speak)
- room_channelに参加しているクライアントにメッセージをブロードキャストする (perform)
ChatApp/app/channels/room_channel.rbclass RoomChannel < ApplicationCable::Channel def subscribed # room_channelからデータを受信する stream_from "room_channel" end def unsubscribed # Any cleanup needed when channel is unsubscribed end def speak(data) # データベースにクライアントから受信したメッセージを保存する Message.create! content: data['message'] end endメッセージをブロードキャストするジョブを定義します。
$ rails generate job MessageBroadcastChatApp/app/jobs/message_broadcast_job.rbclass MessageBroadcastJob < ApplicationJob queue_as :default def perform(message) # room_channelにメッセージをブロードキャストする ActionCable.server.broadcast 'room_channel', message: render_message(message) end private def render_message(message) ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message }) end endデータベースにメッセージを保存したら、ジョブを登録します。
ChatApp/app/models/message.rbclass Message < ApplicationRecord # データベースにメッセージが保存されたらジョブを登録する after_create_commit { MessageBroadcastJob.perform_later self } end登録されたジョブは自動的に実行されます。
MessageBroadcastJobのperformメソッドが呼び出され、すべてのクライアントにメッセージが送信されます。
クライアントはreceivedでメッセージを受け取り、メッセージ一覧の末尾に受信したメッセージを追加します。
これでチャットアプリの作成は終了です。
<%= render @messages %>
は<%= render partial: "messages/message", collection: @messages %>
を省略形です。参考:コレクションをレンダリングする ↩