- 投稿日: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:55:32+09:00
Rails +MySQLのプロジェクトをherokuへデプロイ
RailsのデフォルトDBはSQLiteですが、
MySQLにしたものとして先に進めます
MySQL導入方法
https://qiita.com/shigeshige/items/02ce019e02bcfff14b9b
今回はheorkuへ会員登録したものとして先に進めますproduction環境
実際にHerokuでデプロイした後もMySQLで動くようにします
デフォルトでfalseとなっている以下の箇所をtrueに変更
Railsは本番環境での動的な画像の表示がデフォルトでオフになっています。
config/environments/production.rb# 以下の箇所を変更 config.assets.compile = true config.assets.initialize_on_precompile=falseheroku だと、 sqlite3 がビルドできないので、開発環境でだけ使うよ、っていう宣言が必要
(heroku では、 production の gem だけビルドされる)↓を削除し
Gemfilegem 'sqlite3'代わりに↓を挿入する
Gemfilegem 'sqlite3', group: [:development, :test] gem 'mysql2' gem 'rails_12factor', group: :production$ bundle install コミットしておきますHeroku作成
$ heroku create $ git push heroku mastercleardbアドオンを追加
後述するアドオンの追加のところで、Herokuにクレジットカード情報を登録しておく必要がある(お金はかかりません)ので、登録しておいてください。
$ heroku addons:create cleardb:igniteheroku-postgresqlアドオンを削除
$ heroku addons:destroy heroku-postgresql -a <アプリ名>DATABASE_URLを編集
$ heroku config | grep CLEARDB_DATABASE_URL CLEARDB_DATABASE_URL:mysql2://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true環境変数を設定
それぞれを設定する
heroku config:add DB_NAME='<データベース名>' heroku config:add DB_USERNAME='<ユーザー名>' heroku config:add DB_PASSWORD='<パスワード>' heroku config:add DB_HOSTNAME='<ホスト名>' heroku config:add DB_PORT='3306' 上記のURLをMySQL2に切り替えます。コピーペーストしつつmysql2に書き換えてください heroku config:add DATABASE_URL='mysql2://<ユーザー名>:<パスワード>@<ホスト名>/<データベース名>?reconnect=true'heroku config で確認できますdatabase.ymlに設定
database.ymlproduction: <<: *default database: <%= ENV['DB_NAME'] %> username: <%= ENV['DB_USERNAME'] %> password: <%= ENV['DB_PASSWORD'] %>Herokuにデプロイ
$ git push heroku master $ heroku run rake db:create $ heroku run rake db:migrate テストデータがある場合 $ heroku run rake db:seed $ heroku openでデプロイできました!!!
色々とうまくいかない場合もありましたがなんとかできました
参考になれば幸いです
- 投稿日:2019-11-26T23:50:56+09:00
Rails + TypeScript + Swagger 環境をDockerで爆速で構築する
なにこの記事
単純にdcoker-composeを使って、フロントとWEB APIを分けて、ついでにAPIをSwaggerで管理するテンプレートの紹介と、テンプレート作るまで手順を書いたザックリとした記事です。
たぶん爆速でできる、はず。テンプレート
こちらのリポジトリからクローンしてください。
./qs init
で環境整うので、あとはhttp://localhost:8080
にアクセスすると、こんな感じで簡単なWEBアプリが作られてます。Swaggerを利用する際は、
./qs up doc
した後で、http://localhost
にアクセスするとAPIドキュメントを利用できます。テンプレート作成までの手順
ここからはどうやって上記のテンプレート作ったかの手順を説明していきます。
構成
docker-composeのサービス構成としては下記のようにしました。
webがフロントエンドでTypeScriptにて処理を行います。
apiがWEB APIでRuby on Railsで作られてます。
フロントも含めて全てRailsで記述できますが、今回はフロントとAPIのプラットフォームが別という想定で構築しました。
APIドキュメントはSwaggerがイメージを配布してたのでサービスを分けました。docker-compose.ymlversion: '3' services: db: image: postgres ports: - "5432:5432" volumes: - data:/var/lib/postgresql/data:cached api: build: ./api command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - ./api:/app:cached - bundle:/usr/local/bundle:cached environment: HOME: /app RAILS_ENV: development ports: - "3000:3000" tty: true links: - db web: build: ./web command: npm start volumes: - ./web:/app:cached environment: NODE_ENV: development ports: - "8080:8080" tty: true links: - api doc: image: swaggerapi/swagger-ui volumes: - ./api.yml:/usr/share/nginx/html/api.yml environment: API_URL: api.yml ports: - "80:8080" volumes: bundle: driver: local data: driver: localディレクトリ構成は以下のようにしてます。各フォルダ配下にDockerfileを配置して、プロジェクトフォルダ直下のdocker-composeから管理しています。
projects # プロジェクトフォルダ - api # Railsのソースを配置 - Dockerfile - Gemfile - web # TypeScriptのソースを配置 - Dockerfile - package.json api.yml # Swagger定義 docker-compose.ymlそれでは各サービスの構築手順を解説していきます。
API
まずはAPIを立ち上げましょう。とは言っても簡単です。
コマンド打っただけで簡単に構築できました。やっぱりRailsって便利ですね。アプリの立ち上げ
DockerfileとGemfileを配置して、下記のコマンドを実行していきます。
http://localhost:3000
にアクセスしてRailsのウェルカムページが表示されれば完了です。$ docker-compose build api $ docker-compose run --rm api bundle install $ docker-compose run --rm api bundle exec rails new . -f -d=postgresql --api $ docker-compose run --rm api bundle update # database.ymlを下記の内容で手動で書き換える $ docker-compose run --rm api bundle exec rails db:create $ docker-compose up apidatabase.ymldefault: &default adapter: postgresql encoding: utf8 min_messages: WARNING host: db port: 5432 username: postgres password: postgres pool: 5 timeout: 5000 stats_execution_limit: 10 development: <<: *default database: development test: <<: *default database: test production: <<: *default database: productionDockerfile
# use ruby version 2.6.5 FROM ruby:2.6.5 # using japanese on rails console ENV LANG C.UTF-8 # remove warn ENV DEBCONF_NOWARNINGS yes ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE yes ENV XDG_CACHE_HOME /tmp EXPOSE 3000 # install package to docker container RUN apt-get update -qq && apt-get install -y \ build-essential \ libpq-dev \ vim \ less # install yarn RUN apt-get install apt-transport-https RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update && apt-get install -y yarn # install nodejs RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - RUN apt-get install -y nodejs # setting work directory RUN mkdir /app WORKDIR /app # setting environment value ENV HOME /app # executing bundle install COPY Gemfile /app/GemfileGemfile
source 'https://rubygems.org' gem 'rails', '~> 5.2.3'エンドポイントの追加
文字列フィールドを1つもつModelのAPIを追加します。下記のコマンド打てば、API一式とテストを作ってくれます。素晴らしいいい。
Rspecを使うのでGemfileにgemを追加しておいてください。$ docker-compose run --rm api bundle exec rails g scaffold Content body:string $ docker-compose run --rm api bundle install $ docker-compose run --rm api bundle exec rails g rspec:install $ docker-compose run --rm api bundle exec g rspec:integration Contentこれでほぼ完成ですが、なぜかapiフラグをつけるとストロングパラメーターを使わないので、下記のようにコントローラーを修正します。
contents_controllers.rbdef content_params params.require(:content).permit(:body) endあとは、適宜取得したいようにController、Modelの修正と、テストの追加はしておいてください。
面倒なんでここでは割愛します。リポジトリのコミットログ見てください。rack_corsの導入
このままだとフロントからアクセスできないので、rack_corsを導入します。
Gemfileにrack-cors
を追加して、docker-compose run --rm api bundle install
してください。
そして、下記の修正をconfig/application.rb
に追記すれば完成です。config/application.rbconfig.middleware.insert_before 0, Rack::Cors do allow do origins 'localhost:3000', 'localhost:8080' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options] end end疎通確認
これだけでAPIの構築が完了しました。最後に疎通確認を行います。
RailsコンソールからContentモデルに適当にデータを登録して、ブラウザからhttp://localhost:3000/contents
を叩いて見ましょう。[ { "id": 1, "body": "hoge", "created_at": "2019-11-22T15:54:26.543Z", "updated_at": "2019-11-22T15:54:26.543Z" }, { "id": 2, "body": "foo", "created_at": "2019-11-22T15:54:30.464Z", "updated_at": "2019-11-22T15:54:30.464Z" } ]無事に取得できました。これでAPIの作成は終了です。
フロント
実際にユーザーがアクセスして使うフロントアプリケーションを作成します。
React、VueなどJSフレームワークを使ってもいいのですが、今回は超小さいアプリなのでTypeScriptを使ってサクッと作ろうかと思います。アプリの立ち上げ
apiと同じようにDockerfileとpackage.jsonを配置して、下記のコマンドを実行していきます。
$ docker-compose build web $ docker-compose run --rm web npm installurlとかは適宜変えてください。パッケージは今回使うものを記載してあります。
package.json{ "name": "web", "version": "1.0.0", "description": "web app", "main": "index.js", "scripts": { "start": "http-server -o", "tsc": "tsc", "build": "webpack" }, "repository": { "type": "git", "url": "git+https://github.com/belion-freee/rails_web_api.git" }, "author": "belion-freee", "license": "ISC", "bugs": { "url": "https://github.com/belion-freee/rails_web_api/issues" }, "homepage": "https://github.com/belion-freee/rails_web_api#readme", "devDependencies": { "axios": "^0.19.0", "http-server": "^0.11.1", "ts-loader": "^6.2.1", "typescript": "^3.7.2", "webpack": "^4.41.2", "webpack-cli": "^3.3.10" } }処理の記述
TypeScriptは初めてでしたので、下記の記事を参考にして作成しました。
https://qiita.com/EBIHARA_kenji/items/31b7c1c62426bdabd263
API通信のために axios
Webサーバーとして立ち上げるために http-server
をそれぞれ使ってます。
tsconfig.json
がTypeScriptの設定を記述するファイルです。自分は下記のように設定しました。もっといろんな設定ができるので公式リファレンスを読んでみてください。tsconfig.json{ "compilerOptions": { "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "outDir": "./build", /* Redirect output structure to the directory. */ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ "strict": true, /* Enable all strict type-checking options. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "include": [ "src/**/*.ts" ], "exclude": [ "node_modules", "**/*.spec.ts" ] }ソースコード
ここからは実際のソースコードです。下記のように記述しました。
index.html
を用意して、DOMに要素を動的にjsで展開して行く構成です。index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="./style.css"> <title>TS-Web</title> </head> <body> <h1>This is the template of Web APP</h1> <h2>created by TypeScript</h2> <h3>Those are contents from Web API</h3> <div id="contents-form"> <form> <input type="text" value="" id="content-body"> <button type="button" class="add-content">Add</button> </form> </div> <div id="contents"></div> <script src="./build/index.js"></script> </body> </html>
src
ディレクトリ配下にindex.html
を操作するjsファイルを配置します。
index.ts
: indexを操作するためのjs。api.ts
: Rails-APIと通信するための機能を備えたクラス。https://github.com/belion-freee/rails_web_api/tree/master/web/src
ビルド
ここまで作成したら次にビルドする必要があります。ビルドにはwebpackを仕様します。すでにインストール済みなので、コマンドを実行してビルドしましょう。
$ docker-compose run --rm web npm run build
build
ディレクトリにソースが作成されていれば成功です。
ここまででフロントの実装は終了です。APIドキュメント
次にAPIドキュメントを作成します。とは言ってもすごい簡単です。
プロジェクト配下に
api.yml
を配置します。サンプルは公式から拝借しました。
https://github.com/swagger-api/swagger-samples/blob/master/java/inflector-dropwizard/src/main/swagger/swagger.yaml内容はこんな感じです。
https://github.com/belion-freee/rails_web_api/blob/master/api.yml記述に関しては、API自体がシンプルなのもありますが、ドキュメント読まなくてもなんとなく書けるくらい分かり易かったです。
記述が終わったら、
docker-compose up doc
してhttp://localhost
にアクセスすると読めるので、記述が正しいか確認してください。
これで全ての工程が終了です。まとめ
いかがだったでしょうか。
API関連はすごい簡単に導入できましたが、TypeScriptはちょっと時間がかかりました。
TypeScriptに関してはもっとちゃんと書きたいなと思います。だいぶやっつけ感ある。それでも、ものすごい早く複数プラットフォームのミニマムなWEBアプリを開発できたので、Dockerさんに感謝です。
流石にミニマムすぎるので、暇なときにAPI認証のエンドポイントを実装したいと思います。
- 投稿日: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: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-26T21:48:20+09:00
Ruby on rails記事まとめ
はじめに
記事数が多くなってきたので、ジャンル別に記事のリンクをまとめます。
本記事はRuby on railsです。リンク
Ruby on railsの基本箇所についての復習①(フォルダを作る)
Ruby on railsの基本箇所の復習②(rake db:create)rake routesによるエラー解決(devise)
部分テンプレートでのMissing Templateエラー(ファイル名)
デプロイしても更新されない時の解決方法
sassが読み込めないエラー
- 投稿日:2019-11-26T21:48:20+09:00
※Ruby on rails記事まとめ
はじめに
記事数が多くなってきたので、ジャンル別に記事のリンクをまとめます。
本記事はRuby on railsです。リンク
Ruby on railsの基本箇所についての復習①(フォルダを作る)
Ruby on railsの基本箇所についての復習②(rake db:create)rake routesによるエラー解決(devise)
部分テンプレートでのMissing Templateエラー(ファイル名)
デプロイしても更新されない時の解決方法
sassが読み込めないエラー
- 投稿日: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:09+09:00
【Rails】Guardによるテスト自動化の設定【Rails Tutorial 3章まとめ】
GemfileにGuardを導入
group :test do gem 'rails-controller-testing', '1.0.2' gem 'minitest', '5.10.3' gem 'minitest-reporters', '1.1.14' gem 'guard', '2.13.0' gem 'guard-minitest', '2.4.4' endテストグループ内に導入する。
minitestも一緒に入れとくと良さげ。Guardの初期化とGuardfileの編集
$ bundle exec guard init初期化するとルートライブラリにGuardfileが生成される。
Guardfile.rb# Guardのマッチング規則を定義 guard :minitest, spring: "bin/rails test", all_on_start: false do watch(%r{^test/(.*)/?(.*)_test\.rb$}) watch('test/test_helper.rb') { 'test' } watch('config/routes.rb') { integration_tests } watch(%r{^app/models/(.*?)\.rb$}) do |matches| "test/models/#{matches[1]}_test.rb" end watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches| resource_tests(matches[1]) end watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches| ["test/controllers/#{matches[1]}_controller_test.rb"] + integration_tests(matches[1]) end watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches| integration_tests(matches[1]) end watch('app/views/layouts/application.html.erb') do 'test/integration/site_layout_test.rb' end watch('app/helpers/sessions_helper.rb') do integration_tests << 'test/helpers/sessions_helper_test.rb' end watch('app/controllers/sessions_controller.rb') do ['test/controllers/sessions_controller_test.rb', 'test/integration/users_login_test.rb'] end watch('app/controllers/account_activations_controller.rb') do 'test/integration/users_signup_test.rb' end watch(%r{app/views/users/*}) do resource_tests('users') + ['test/integration/microposts_interface_test.rb'] end end # 与えられたリソースに対応する統合テストを返す def integration_tests(resource = :all) if resource == :all Dir["test/integration/*"] else Dir["test/integration/#{resource}_*.rb"] end end # 与えられたリソースに対応するコントローラのテストを返す def controller_test(resource) "test/controllers/#{resource}_controller_test.rb" end # 与えられたリソースに対応するすべてのテストを返す def resource_tests(resource) integration_tests(resource) << controller_test(resource) endspringサーバーを使用するようにマッチング規則(?)を書き換える。
gitignoreの編集
# Ignore Spring files. /spring/*.pidgitとspringが競合するらしい(意味はわかってない)ので、gitignoreに上の二行を記述。
Guardを起動
bundle exec guardコンソールを新規に開いてGuardを起動。
Returnキーで手動実行できる。
Ctrl+Dで終了する。デスクトップに結果を通知
こちらの記事を参考にさせていただきました。
「Mac OSX El CapitanでGuardからテスト結果の通知を受け取ってデスクトップに表示する」
http://b0npu.hatenablog.com/entry/2016/04/17/155457$brew search terminal-notifier $brew install terminal-notifierHomebrewでterminal-notifierをインストール。
Gemfile.rbgroup :development do gem 'terminal-notifier-guard', '~> 1.6.1' enddeveropmentグループにterminal-notifier-guardを導入して、bundle install。
Guardを起動して適当にファイルを変更すると...ちゃんとデスクトップ通知が来た?
springのkill
テストが重くなってきたら、プロセスを確認してspringをkillする。
$ ps aux | grep spring ec2-user 12241 0.3 0.5 589960 178416 ? Ssl Sep20 1:46 spring app | sample_app | started 7 hours ago $ kill -15 12241 $ pkill -15 -f spring最初の数字(プロセスid、pid)で指定する。
spring関係を一括でkillするにはpkillを使う。
- 投稿日:2019-11-26T21:28:16+09:00
Ruby on railsの基本箇所の復習②(rake db:create)
はじめに
引き続きrailsでアプリを作成する手順を開設します。
実行
アプリのフォルダを作ったら、アプリのディレクトリに移動して
rails s
というコマンドを入力するとアプリが起動します。
…と言いたいところですが、この時点ではアプリに対応するデータベースが存在していないので、エラーが発生してしまいます。
これを解決するには、データベースを作ります。
データベースの作成は次のコマンドをターミナルで実行すれば完了します。rake db:create
そして次のようにコマンドを実行すればアプリが実行します。
rails s
(前略) * Environment: development * Listening on tcp://localhost:3000 Use Ctrl-C to stop
という風に表示されたら成功です。
- 投稿日:2019-11-26T21:28:16+09:00
Ruby on railsの基本箇所についての復習②(rake db:create)
はじめに
引き続きrailsでアプリを作成する手順を開設します。
実行
アプリのフォルダを作ったら、アプリのディレクトリに移動して
rails s
というコマンドを入力するとアプリが起動します。
…と言いたいところですが、この時点ではアプリに対応するデータベースが存在していないので、エラーが発生してしまいます。
これを解決するには、データベースを作ります。
データベースの作成は次のコマンドをターミナルで実行すれば完了します。rake db:create
そして次のようにコマンドを実行すればアプリが実行します。
rails s
(前略) * Environment: development * Listening on tcp://localhost:3000 Use Ctrl-C to stop
という風に表示されたら成功です。
- 投稿日: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-26T20:32:14+09:00
【Rails】provideとyieldを使ったページタイトルの設定【Rails Tutorial 3章まとめ】
各ページのビューにprovideを記述
app/views/static_pages/home.html.erb<% provide(:title, "Home") %> <h1>Sample App</h1>appilication.htmlのtitleにyiledを記述
app/views/layouts/application.html.erb<!DOCTYPE html> <html> <head> <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> . . . </head> <body> <%= yield %> </body> </html>ページタイトルに対するテスト
test/controllers/static_pages_controller_test.rbdef setup @base_title = "Ruby on Rails Tutorial Sample App" end test "should get home" do get static_pages_home_url assert_response :success assert_select "title", "Home | #{@base_title}" endassert_selectを使って、titlesセレクタがページタイトルと一致することを確認する。
ページタイトルの重複部分はsetup内の変数@base_titleに入れておく。
- 投稿日:2019-11-26T20:32:14+09:00
【Rails】provideとyieldを使ったページタイトルの設定【Rails Tutorial 3・4章まとめ】
各ページのビューにprovideを記述
app/views/static_pages/home.html.erb<% provide(:title, "Home") %> <h1>Sample App</h1>appilication.htmlのtitleにyieldを記述
app/views/layouts/application.html.erb<head> <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> </head>ページタイトルに対するテスト
test/controllers/static_pages_controller_test.rbdef setup @base_title = "Ruby on Rails Tutorial Sample App" end test "should get home" do get static_pages_home_url assert_response :success assert_select "title", "Home | #{@base_title}" endassert_selectを使って、titlesセレクタがページタイトルと一致することを確認する。
ページタイトルの重複部分はsetup内の変数@base_titleに入れておく。~ここから4章~
full_titleヘルパーを作成
rootであるhomeビューでは、アプリケーション名のみをタイトルに表示したい。
そのため、homeビューではprovideは不要である。
しかし、この場合タイトルが「 | Ruby on Rails Tutorial Sample App」となり、余計な|が入る。
これを修正するためにページタイトルを返すfull_titleヘルパーを作成する。app/helpers/application_helper.rbmodule ApplicationHelper # ページごとの完全なタイトルを返す。 def full_title(page_title = "") base_title = "Ruby on Rails Tutorial Sample App" if page_title.empty? base_title else page_title + " | " + base_title end end endapp/views/layouts/application.html.erb<html> <head> <title><%= full_title(yield(:title)) %></title> </head>変数page_titleの初期値を空にしておき、provideからくる引数yield(:title)が与えられなければ、base_titleのみを表示する。
与えられれば、|を追加して、完全なタイトルを表示する。
(ちなみに、empty?メソッドの末尾の?は、論理値を返すメソッドであることを意味する)homeビューからはprovideを消しておく。
app/views/static_pages/home.html.erb<h1>Sample App</h1>「home」という文字はもうタイトルには無いので、テストを修正しておく。
test/controllers/static_pages_controller_test.rbtest "should get home" do get static_pages_home_url assert_response :success assert_select "title", @base_title end
- 投稿日:2019-11-26T20:32:14+09:00
【Rails】provideとyieldを使ったページタイトルの設定【Rails Tutorial 3・4・5章まとめ】
各ページのビューにprovideを記述
app/views/static_pages/home.html.erb<% provide(:title, "Home") %> <h1>Sample App</h1>appilication.htmlのtitleにyieldを記述
app/views/layouts/application.html.erb<head> <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> </head>ページタイトルに対するテスト
test/controllers/static_pages_controller_test.rbdef setup @base_title = "Ruby on Rails Tutorial Sample App" end test "should get home" do get static_pages_home_url assert_response :success assert_select "title", "Home | #{@base_title}" endassert_selectを使って、titlesセレクタがページタイトルと一致することを確認する。
ページタイトルの重複部分はsetup内の変数@base_titleに入れておく。~ここから4章~
full_titleヘルパーを作成
rootであるhomeビューでは、アプリケーション名のみをタイトルに表示したい。
そのため、homeビューではprovideは不要である。
しかし、この場合タイトルが「 | Ruby on Rails Tutorial Sample App」となり、余計な|が入る。
これを修正するためにページタイトルを返すfull_titleヘルパーを作成する。app/helpers/application_helper.rbmodule ApplicationHelper # ページごとの完全なタイトルを返す。 def full_title(page_title = "") base_title = "Ruby on Rails Tutorial Sample App" if page_title.empty? base_title else page_title + " | " + base_title end end endapp/views/layouts/application.html.erb<html> <head> <title><%= full_title(yield(:title)) %></title> </head>変数page_titleの初期値を空にしておき、provideからくる引数yield(:title)が与えられなければ、base_titleのみを表示する。
与えられれば、|を追加して、完全なタイトルを表示する。
(ちなみに、empty?メソッドの末尾の?は、論理値を返すメソッドであることを意味する)homeビューからはprovideを消しておく。
app/views/static_pages/home.html.erb<h1>Sample App</h1>「home」という文字はもうタイトルには無いので、テストを修正しておく。
test/controllers/static_pages_controller_test.rbtest "should get home" do get static_pages_home_url assert_response :success assert_select "title", @base_title end〜ここから5章〜
full_titleヘルパーのテスト
タイトルに誤字などがないか、正しく機能しているかどうかをテストする。
applicationヘルパーにあるfull_titleヘルパーはそのままではテストで使えないので、includeを使ってテストで使えるようにする。
test/test_helper.rbclass ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. fixtures :all # Add more helper methods to be used by all tests here... include ApplicationHelper endtest/helpers/application_helper_test.rbrequire 'test_helper' class ApplicationHelperTest < ActionView::TestCase test "full title helper" do assert_equal full_title, "Ruby on Rails Tutorial Sample App" assert_equal full_title("Help"), "Help | Ruby on Rails Tutorial Sample App" end endasseet_equalは第一引数と第二引数が等しいかどうかを確認するために使用する。
ところで、test/helpers/application_helper_test.rbは自動で生成されないようである。
- 投稿日:2019-11-26T20:05:31+09:00
【Rails】generateとdbの元に戻し方【Rails Tutorial 3章まとめ】
generate
$ rails generate controller StaticPages home help $ rails destroy controller StaticPages home help $ rails generate model User name:string email:string $ rails destroy model Userコントローラーの場合、コントローラー名のみでなくアクション名も指定する。
モデルの場合は不要。db
$ rails db:migrate $ rails db:rollback $ rails db:migrate VERSION=0rollbackで一つ前の状態に戻す。
VERSION=0で最初の状態に戻す。キャメルケースでコントローラーを生成
$ rails generate controller StaticPages home help create app/controllers/static_pages_controller.rbコントローラー名をキャメルケースで指定すると、自動でスネークケース(アンダーバーで繋がれた名前)になる。
- 投稿日: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:42:00+09:00
Rails Deviseでユーザー編集を現在のパスワードを入力しないで更新する
個人の備忘録
#背景
railsでユーザー登録のためにdeviseを使った簡単なプリを作成中に編集画面で
現在のパスワードを入れないで更新できないかと考えたため。環境
rails 5.2.3
ruby 2.6.3まずdeviseを用いた際のルーティングを確認
config/routes.rbdevise_for :users, controllers: { registrations: 'users/registrations', sessions: 'users/sessions' }特に問題なさそう
registrations_controller.rbに下記を記載
registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController before_action :authenticate_user! protected *この部分を記載 def update_resource(resource, params) resource.update_without_current_password(params) end (省略) end受けとったリソースを、update_without_current_passwordメソッド(Userモデルに後で実装します)で
パスワード抜きで更新できるようにし、引数にparamsを指定してリソースの中身を更新しています。User モデルに update_without_current_password を実装
一個前でregistration_controllerに自力で定義したメソッドを使えるようにモデルに定義していく
models/user.rbdef update_without_current_password(params, *options) params.delete(:current_password) *params.delete(:current_password) で current_password のパラメータを削除。 if params[:password].blank? && params[:password_confirmation].blank? *パスワード変更のためのパスワード入力フィールドとその確認フィールドの両者とも空の場合のみ、パスワードなしで更新できるようにするためです。 params.delete(:password) params.delete(:password_confirmation) end result = update_attributes(params, *options) clean_up_passwords result endあとはビューのフォームを消すだけ
パスワードのフォームを削除して終了
参考にしたサイト
Devise でユーザーがパスワードなしでアカウント情報を変更するのを許可
Deviseでパスワードの入力無しでユーザー情報の更新をする方法
- 投稿日:2019-11-26T18:33:52+09:00
「 LoadError: dlopen(〜): Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib」と表示された場合の解決法
railsで新しいアプリのDBを作成するべく
rake db:create
をしたところ、以下のエラーメッセージが表示。LoadError: dlopen(/Users/(ユーザー名)/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2/lib/mysql2/mysql2.bundle, 9): Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib Referenced from: /usr/local/opt/mysql@5.6/lib/libmysqlclient.18.dylib Reason: image not found - /Users/(ユーザー名)/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/mysql2-0.5.2/lib/mysql2/mysql2.bundle /Users/(ユーザー名)/projects/(アプリ名)/config/application.rb:7:in `<top (required)>' /Users/(ユーザー名)/projects/(アプリ名)/Rakefile:4:in `require_relative' /Users/(ユーザー名)/projects/(アプリ名)/Rakefile:4:in `<top (required)>' /Users/(ユーザー名)/.rbenv/versions/2.5.1/bin/bundle:23:in `load' /Users/(ユーザー名)/.rbenv/versions/2.5.1/bin/bundle:23:in `<main>' (See full trace by running task with --trace)
rake routes
などのコマンドでも同様のエラーが出るため、ニッチもサッチも行かず困った事態に。
bundle doctor
を行いエラーが出たgemを再インストールしたり、openssl
を再インストールしてもダメ。色々と探した結果、最終的にはこちらの記事の対処法で上手く行きました。
https://github.com/kelaberetiv/TagUI/issues/86brew update && brew upgradeでHomebrewとインストールしたパッケージ(Formula)のアップデートを実施。
改めて
rake db:create
をしたところDB作成に成功しました。他のコマンドもちゃんと実行できます。直前にHomebrewで色々と入れたので、それが問題だったのかもしれません(node.jsが怪しい)。
同様のエラーが出た場合はご参考までにどうぞ。
参考にさせていただいた記事:brewのupdateとupgradeの違い
https://qiita.com/okhrn/items/aa71b066a525456550c0
(こちらの記事によればbrew upgrade
だけで大丈夫かもしれません。)
- 投稿日: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-26T17:47:07+09:00
MaterializeのParallaxとかsidenavをRailsで使うときの注意点
はじめに
RailsでMaterializeのParallaxとかsidenavとかとか使おうと
意気揚々とgem入れてサンプルコードをコピペして動かなかった人へ。
私も同じことをしたので、これ読んで速攻解決して次に進みましょう!
一応、jQueryを使います。Railsで使うには初期化が必要
javascriptファイル作って中身をコピペしましょう。
私はわかりやすく「materialize-jquery.js」としました。materialize-jquery.js$(document).on('turbolinks:load', function () { // 各項目を初期化 $('.sidenav').sidenav(); $('.parallax').parallax(); $('.dropdown-trigger').dropdown(); $('.modal').modal(); }); // sidenavだけは、進む/戻るで動かなくなるので都度destroyが必要 $(document).on("turbolinks:before-cache", function () { $('.sidenav').sidenav('destroy'); $('.dropdown-trigger').dropdown('destroy'); $('.modal').modal('destroy'); });これで問題なく動きます。
ついでに
こんな風にすると、sidenavが右から開きます。
$('.sidenav').sidenav({ edge: "right" });Materializeのページではオプションの使い方の例すら書いておらず
非常に不親切自走力が試されます。
他のオプションも同様なので、色々試してみましょう!それでも動かない人
もしかしたら、jqueryとかを読み込む順番が間違っているかもしれないので
下記を参考に並び替えてみてください。app/assets/javascripts/application.js//= require rails-ujs //= require jquery //= require materialize //= require activestorage //= require turbolinks //= require_tree .
- 投稿日:2019-11-26T17:02:16+09:00
Rails link_toでアンカーを設定する
背景
スタッフの詳細ページにそのスタッフが作成したメニューの一覧を表示したいが、ただリンクさせるだけだと
スクロールをしなくてはならないためあまりつかいやすいとは思わなかったため*個人の備忘録のために記載
環境
Rails 5.2.3
ruby 2.6.3まずリンク先の飛びたい場所にidを指定
<div class="mi-contents2" id="staff_menu">link_toにanchorを指定する
<%= link_to 'テキスト', リンク先のpath(anchor: 'リンク先のid', ~~~ %>下記が実際の設定した例
<li class="usi-menu"><%= link_to 'このスタッフのメニューへ', staff_path(staff.id, anchor: 'staff_menu'), class: 'btn btn-primary' %></li>上記のような記述をすると
<a href="/staffs/1#staff_menu">のようなリンクが生成される
- 投稿日: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:34:38+09:00
[RSpec] Ajaxのテストの書き方
はじめに
いいね機能やフォロー機能をajaxで作る時、Request specではどう書くかわからず
困ったので、メモしておきます。結論
結論から言うとこのような形で書くと、ajaxで書いた機能をテストできます。
今回はいいね機能のテストを書いております。
いいね機能についてはこちらの記事を参考に作りました。
[Rails]いいね機能の非同期での実装!!!(前提としてlike_pathのrouteには
username
とpost_id
が表示されるようにしているため、FactoryBotでuserとそのpostを作り、そのpostにいいねできることをテストしております。)it "いいねしていない場合、いいねができること" do expect do post like_path(username: user.user_name, post_id: 1), xhr: true end.to change(Like, :count).by(1) endこの
xhr: true
を書かないと下のエラーが出てしまいます。ActionController::UnknownFormat: LikesController#like is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: []
xhr: true
を書くだけでテストがうまく通るのですが、調べてもすぐには分からず時間がかかってしまいました。
以上です!
- 投稿日:2019-11-26T15:20:58+09:00
ActiveAdmin 力技集
はじめに
皆さんは、ActiveAdmin使ってますか?
ActiveAdmin、サッと管理画面作るには素晴らしい反面、
ちょっとでも基本的な実装から外れると、途端につまる、、
そんな経験をされた方も多いかと思います。そして往々として、業務では基本的な実装から外れるものですよね。
そんな格闘の中で得た、「ActiveAdminでちょっと凝ったことをするのに使える(かもしれない)方法」を、
「ActiveAdmin 力技集」 としてシェアしようと思います。なお、この方法が正しいというわけではなく、こう乗り切ったよ、くらいの感覚で見ていただけるとありがたいです。
もっといい方法があれば、ぜひコメントをお願いします!環境
Ruby 2.5.5
Rails 5.2.2.1
activeadmin 1.3.1ActiveAdmin 力技集
Model
顧客がいて、接点があって、請求と明細がある
app/models/customer.rbclass Customer < ApplicationRecord has_many :contacts has_many :invoices endapp/models/contact.rbclass Contact < ApplicationRecord belongs_to :customer endapp/models/invoice.rbclass Invoice < ApplicationRecord belongs_to :customer has_many :items, class_name: 'InvoiceItem' endapp/models/invoice_item.rbclass InvoiceItem < ApplicationRecord belongs_to :invoice end絞り込まれた一覧へのリンク
例えば顧客の詳細画面に、その顧客との接点一覧へのボタンを置きたい時。
app/admin/customers.rbbutton_to '接点履歴一覧へ', admin_customer_contacts_path, method: :get, params: { q: { customer_id_equals: customer.id } }検索条件が
params[:q]
に入るのがキモ。
filter
で検索を実装して実際に検索してみて、そのパラメータを再現すると手っ取り早い。なお、このパラメータの使用に際して、
filter
で実装されている必要はない。別のカラムでソート
app/admin/customers.rbActiveAdmin.register Customer do index do column '顧客ID', :id # sortable: にソートしたいカラム名を記述 column '顧客名', :name, sortable: :kana end endこのように漢字を表示しつつ読み仮名でのソートができる。
formでjsを使う
例えばformで顧客を選択したいがselectだと厳しいので、オートコンプリートを実装したい時などに。
app/admin/contacts.rbActiveAdmin.register Contact do # ... form partial: 'form' endapp/views/admin/contacts/_form.html.erb<script language="JavaScript"> // @contactが暗黙的に渡ってくる。 var contact = <%= raw @contact.to_json %> // ... </script>当然、この例では
@contact
の中身がブラウザで丸見えになるので、セキュリティ等問題がないユースケースか、検証が必要。関連先によるソート・絞り込み
例えば顧客との接点の一覧画面で、顧客によってソート・絞り込みをしたい時。
app/admin/contacts.rbActiveAdmin.register Contact do filter :date, label: '年月日' # 関連先の型までは見てくれないようなので、as: :string などの指定が必要 filter :customer_name, as: :string, label: '顧客名' index do column '年月日', :date column '顧客ID', :customer_id # sortableには発行されるSQLで使えるカラム名を渡す。今回はcustomersテーブルをjoinしているので、customers.kana # なお、関連名を渡すといい感じにリンク化してくれる column '顧客名', :customer, sortable: :'customers.kana' column '内容', :memo end controller do def scoped_collection # 関連先でソートを行う場合はjoinが必要 end_of_association_chain.joins(:customer) end end end
index
の表はscoped_collection
で発行されたSQLの結果を使っている、と考えるとイメージしやすい。デフォルト検索条件
必ず「ある年月の一覧」を表示したい場合はこのように。
app/admin/invoices.rbActiveAdmin.register Invoice do # ... controller do # 必ず年月による絞り込みがかかるよう、indexページ表示前にparamsの不足を補う before_action only: :index do # 最初に表示されるのは先月分。 prev_month = Time.current.prev_month if params[:q].present? params[:q].merge!(year_eq: prev_month.year, month_eq: prev_month.month) if params[:q][:year_eq].blank? || params[:q][:month_eq].blank? else params.merge!(q: { year_eq: prev_month.year, month_eq: prev_month.month }) end end end end他の検索条件を邪魔しないように注意が必要。
GROUP BYした結果の一覧を作る
例えば版管理を行う請求書の一覧などで、版違いは同じ行でまとめたい時。
app/admin/invoices.rbActiveAdmin.register Invoice do index do # ... column 'バージョン', class: 'minWidth100' do |latest_invoice| Invoice .where(customer_id: latest_invoice.customer) .where(year: latest_invoice.year) .where(month: latest_invoice.month) .each do |invoice| div { span link_to "第#{invoice.version}版", admin_invoice_path(invoice) } end nil # nilを返さないと、上の行の結果が出力されてしまう end end controller do # ... def scoped_collection # 一覧には顧客ごとの請求書を出したいのだが、返るレコード数分、表の行が作られてしまう。 # そのため、orderした上でgroup、顧客ごとに最新の請求書だけを取得 # 参考: https://qiita.com/ryo-ishii/items/36b878cf2d0bd8ef7e07 Invoice .group(:customer_id, :year, :month) .from( end_of_association_chain .select('invoices.*, customers.name, customers.kana, CONCAT(year, LPAD(month, 2, 0)) AS ym') .joins(:customer) .order(version: :desc), :invoices ) end end endまとめ
ActiveAdminの良いところでもあり辛いところは、暗黙的に定義されている変数が多いところだと思います。
それらをいかに把握して差し込んでいくかが、ActiveAdminで力技をする際に重要になります。それ以前に、力技を使わなくても済むような要件、データモデルの定義を心がけていきたいですね。
- 投稿日:2019-11-26T13:58:40+09:00
【Rails Guide】まとめ
6. 2番めのモデルを追加する
app/models/comment.rbclass Comment < ApplicationRecord belongs_to :article endreferencesキーワードは、モデルの特殊なデータ型を表す。
指定されたモデル名の後ろに_idを追加した名前を持つ新しいカラムをデーターベーステーブルに作成db/schema.rbclass CreateComments < ActiveRecord::Migration[6.0] def change create_table :comments do |t| t.string :commenter t.text :body t.references :article, null: false, foreign_key: true t.timestamps end end endt.references はarticle_idという名前のinteger型のカラムとインデックスとarticlesのidカラムを指す外部キー制約を設定する
dependent: :destroy
削除のオプション
- 投稿日:2019-11-26T12:46:51+09:00
Rails server(Rails s)が再起動できない! 問題内容と解決法について
Rails server(以下、Rails s)に関する、バグが発生しましたので、内容と解決法を以下に纏めます。
皆様の参考になれば、幸いです。問題内容
-内容:
Rails sを再起動しようとしたところ、既にサーバーがあり起動できないという問題
-背景/詳細:
binding.pry使用中、exit!・ctrl+c等をコマンドしてもなぜかループから抜けられない。
=>ターミナルを消してrails sを再起動して使用を試みる。
=>既にサーバーがあるとの文言があり、起動できないとのこと。以下のような状況でした。
terminalrails s => Rails 5.0.7.2 application starting in development on http://localhost:3000 => Run `rails server -h` for more startup options A server is already running.解決方法
結論、lsofコマンドを使用し、該当のPIDをキルして解消しました。
詳細を以下に記載していきます。lsofコマンドを使用して状況を確認
今回はlocalhost:3000使用していたので、$ lsof -i:3000と記載してチェックします。
以下のように「34139」が既に存在していることがわかります。
このタスクが切れてないのが問題の原因であることが、明白になりました。terminal$ lsof -i:3000 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME ruby 34139 ruby 34139該当のPID番号をキルして解消
それでは上記で確認できたタスクを削除します。
terminal$ kill 34139*念のため、再度状況を確認。何も動いていない状況であることが確認できました。
これで、再度「rails s」を使用することができます。terminal$ lsof -i:3000因みに。。lsofコマンドについて
今回使用したlsofコマンドについて、調べてみましたので、以下に記載しておきます。
詳細は参照ページを確認頂けますと幸いです。
オプション 意味 -a 複数のオプションを指定した際に、AND(かつ)の意味で機能させる -P ポート番号をサービス名に変換しない -c プロセス名を指定する -i ネットワークソケットファイルを指定する -n IPアドレスを表示する(名前解決しない) -p プロセスIDを指定する -u ユーザー名を指定する
項目 意味 COMMAND 実行されているコマンド名 PID プロセスID USER ユーザー名 FD ファイルディスクリプタ TYPE 種類 DEVICE デバイス SIZE/OFF ファイルサイズ NODE iノード番号(プロトコル) NAME ファイル 参照
Linux基本コマンドTips一覧
https://www.atmarkit.co.jp/ait/articles/1904/18/news033.html
- 投稿日:2019-11-26T11:02:11+09:00
【Rails環境構築】MySQL2が原因で「bundle install」失敗した時の対処法
はじめに
Railsの環境構築をローカルでしようとrails newからはじめたところ、
bundle install
ができずに引っかかったため備忘録として残しておきます。環境
OS : Mac OS Catalina 10.15.1
Ruby : 2.6.3p62
Rails : 6.0.1
Homebrew : 2.1.16
Bundle : 1.17.2エラー
$ bundle install 省略(gemのインストール) Installing mysql2 0.5.2 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. 省略 An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling.ざっくり解釈した限りだと、「mysql2 0.5.2のインストール時にnative extention(CやC++で書かれるMySQLの拡張のライブラリ)のビルドに失敗したので、bundleの前に
gem install mysql2 -v '0.5.2'
で確認してください」とのことですが、私の場合root権限でこのコマンドを打っても解決せず...解決法
結論から言うと、「ビルド時にOpenSSL公開鍵のPATH指定が必要」と「
bundle config
でPATHオプションをLDFLAGS、CPPFLAGSで別々に指定」で解決しました。1.公開鍵のPATH確認
brew info openssl 省略 For compilers to find openssl@1.1 you may need to set: export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" ← ここ使用 export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include" ← ここ使用 省略2.それぞれPATHを指定
$ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"それぞれ、「build.mysql2の現在のローカル値を置き換えています」と返答がきます。
3. bundle install (ここで終了)
$ bundle install私の場合はこれで解決し、「rails new」でも問題なく作成できました。
失敗or解決に直接繋がらなかったコマンド
1. PATHの両指定
bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include"2.mysqlの再起動
$ sudo mysql.server restartその他
公開鍵、LDFLAGS、CPPFLAGSなど知識やAppleの独自TLS背景などまだまだ勉強不足でした?
表記や解釈ミスなどあればご指摘頂けますと幸いです。参考
こちらの記事がなければ解決できませんでした(本当に助かりました)。
https://qiita.com/fukudakumi/items/463a39406ce713396403
https://qiita.com/akito19/items/e1dc54f907987e688cc0
http://woshidan.hatenadiary.jp/entry/2017/01/21/150948