- 投稿日:2019-02-18T23:59:34+09:00
9日目。RubyをHerokuで・・・動きません!
今日はProgateでつくったRubyをHerokuで動かすにリトライです。
Heroku公式のRubyの使い方をみて、そのとおりにやってみたのですが、エラー連発で四苦八苦しております。$ mkdir hello-world $ cd hello-world/ $ bundle init 出力されたgemfileを編集 $ bundle install --path vender/bundle $ subl config.ru 中身はこれ。`run proc { [ 200,{},["Hello World!"]}` $ git init $ git add config.ru Gemfile Gemfile.lock $ heroku create $ git push heroku masterここでエラー。原因は・・・
-HerokuのRubyは2.4.5以上。
-ローカルのRubyは2.3.7p456。アップデートしてるのに、なぜか出来ない。ということで、次回はローカルのRubyのUpdateにチャレンジです。
簡単そうにみえて大変だー!以下、雑多な作業ログです。
Ruby アプリをクラウドにデプロイ・運用・スケール | Heroku
https://jp.heroku.com/ruby①bundle init でRubyプロジェクトを初期化
$ mkdir rubydays $ cd rubydays $ bundle init -bash: bundle: command not foundこりゃなんだ?
rbenvとbundlerというRubyのプラグインらしい。
なぜここで必要かが分からないけどいれてみる。bundlerのインストールの参考にさせていただきました!
https://qiita.com/Alex_mht_code/items/d2db2eba17830e36a5f1$ brew install rbenv ruby-build Updating Homebrew... <中略> $ rbenv --version rbenv 1.1.1 $ gem install bundler Fetching: bundler-2.0.1.gem (100%) ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory. $ sudo gem install bundler Password: <中略> $ rbenv rehash ## $ bundler -v Bundler version 2.0.1 $ bundler init Writing new Gemfile to /Documents/GitHub/rubydays/Gemfileよーしできたぞ!
②RubyのバージョンとRackの依存関係を特定
$ subl gemfile -bash: subl: command not foundこりゃなんだ2
Rubyのエディタらしい。いれておこう。
sublについて、こちらを参考にさせていただきました。
https://qiita.com/ashdik/items/aeb50f67e43b910204cbそしてSubline Textをインストールしようって書いてる。インストールしてみました。
https://www.sublimetext.com/シンボリックリンクをはる。これ、単なるエディタでしたってこと???
sudo ln -s /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl /usr/local/bin/subl $ subl gemfile ジェムファイルが開いた! Rubyとrackを指定してっと。 # frozen_string_literal: true source "https://rubygems.org" ruby "2.2.3" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } # gem "rails" gem "rack" $ bundle install --path vender/bundle The Gemfile specifies no dependencies Resolving dependencies... Bundle complete! 0 Gemfile dependencies, 1 gem now installed. Bundled gems are installed into `./vender/bundle`③「Hello World」でリクエストに応答
$subl config.ru run proc { [ 200,{},["Hello World!"]} $ git init Reinitialized existing Git repository in /Documents/GitHub/hello-world/.git/これでいいのかな???
Githubを見に行ったけどファイルはなし。うーん。$ git add config.ru Gemfile Gemfile.lockGemfile.lockってなんだろう。いま手元にはないし。
調べてみたら、なんだか大事そうなファイルです。
もういちど bundle install$ bundle install --path vender/bundle Your Ruby version is 2.3.7, but your Gemfile specified 2.2.3Gemfileのバージョンが違うって?だめもとで書き換えてみる。
$ bundle install --path vender/bundle Fetching gem metadata from https://rubygems.org/.............. Resolving dependencies... Using bundler 2.0.1 Fetching rack 2.0.6 Installing rack 2.0.6 Bundle complete! 1 Gemfile dependency, 2 gems now installed. Bundled gems are installed into `./vender/bundle` $ $ ls Gemfile Gemfile.lock config.ru venderGemfile.lockができた!
$ git add config.ru Gemfile Gemfile.lock $ git commit -m “init” [master (root-commit) 3a043e6] “init” 3 files changed, 24 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 config.ruこれでいいのかな?
④heroku createを使ってアプリをプロビジョン
$ heroku create Creating app... done, ⬢ pure-garden-42094 https://pure-garden-42094.herokuapp.com/ | https://git.heroku.com/pure-garden-42094.git $ git push heroku master Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 4 threads Compressing objects: 100% (4/4), done. Writing objects: 100% (5/5), 586 bytes | 293.00 KiB/s, done. Total 5 (delta 0), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Ruby app detected remote: remote: ! remote: ! You must use Bundler 2 or greater with this lockfile. remote: ! <中略> To https://git.heroku.com/pure-garden-42094.git ! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 'https://git.heroku.com/pure-garden-42094.git' $エラーメッセージで検索。2019年からのバージョンエラーって!
こちらを参考にさせていただきました。
https://qiita.com/yoshijbbsk1121/items/87250501b32c6433943eやってみる。
$ rm Gemfile.lock $ gem install bundler -v 1.17.3 $ bundler _1.17.3_ install $ git push heroku master Enumerating objects: 5, done. <中略> remote: ! remote: ! An error occurred while installing ruby-2.3.7 remote: ! remote: ! This version of Ruby is not available on Heroku-18. The minimum supported version remote: ! of Ruby on the Heroku-18 stack can found at: remote: ! remote: ! https://devcenter.heroku.com/articles/ruby-support#supported-runtimes remote: !だめかい!
同じくHeroku-18でひっかかっていた記事。
Ruby2.4.5以降が必要らしい。
https://qiita.com/newburu/items/bb31ef2b429b5557b45a$ rbenv install 2.4.5バージョンアップしてgit push
$ git push heroku master Enumerating objects: 5, done. <中略> remote: ! An error occurred while installing ruby-2.3.7そういえば、Gemfileに2.3.7って書いたっけ。2.4.5に修正してみよう。・・・しかし同じくエラー。
もう一回。
$ rbenv install 2.4.5 <中略> $ rbenv local 2.4.5 $ ruby --version ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18] $うーん、なんでだ!?
どうやったらローカルのRubyをアップデートできるんだろう。
続きはまたこんど。(所要時間 3時間半)
- 投稿日:2019-02-18T22:41:06+09:00
Rails Herokuにpushで詰まった話
git push heroku masterで詰まった
今回は,Herokuにアップロードしようとした所で詰まったので記録しておきます。
$ git push heroku master ・ ・ ・ remote: ! Push rejected, failed to compile Ruby app. remote: remote: ! Push failed remote: Verifying deploy... remote: remote: ! Push rejected to tranquil-wave-48446. remote: To https://git.heroku.com/tranquil-wave-48446.git ! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to・・・以下略remote: -----> Ruby app detected remote: remote: ! remote: ! You must use Bundler 2 or greater with this lockfile. remote: !実行した所複数のエラーが発生し、何が原因化を模索・・・
pgの指定がおかしいのか、gemfile.lock、bundlerのバージョンが問題なのか調べても中々解決策が見つからない、わからない
bundler バージョンが問題?
自分の環境とHeroku上の環境でbundlerのバージョンが異なることによるエラーが原因(?)の様でした。
https://github.com/bundler/bundler/issues/6784
以上の記事を参考にさせていただきました。
$ heroku buildpacks:set https://github.com/bundler/heroku-buildpack-bundler2を実行の後、再びHerokuへPushした所無事にアップロードができました!!!
感想
問題解決後にエラーを再検索してみると、同じ様なエラーになった人の記事がチラホラとありました。
もっと早い段階で気がつくことはできなかったのだろうか?
調べ方に改善の余地があるのかもしれない
- 投稿日:2019-02-18T22:38:19+09:00
Rubyのメソッド定義の入れ子
動作確認
Ruby 2.5
記事を書いたきっかけ
予想が外れたので.
はじめに
標準出力に何が表示されるだろうか
def f def g p self p "g" end p self p "f" end p f.g結果
出力結果はあえて後回しにして,先に解説から.
実行順に追ってみる.流れ
1行目.
def f
ブロックで,トップレベルにメソッドf
を定義している.
ブロックの中は実行されない.よって,def g
の定義も行われない.
ブロックをスルーして9行目へ.9行目.
p f.g
.
まず.メソッドf
を呼ぶ.その次に,f
の返り値をret
とすると,ret.g
を呼ぶ.
f
が呼び出されるので2行目へ.2行目.
def g
によって,トップレベルにメソッドg
が定義される.
6行目へ.6行目.
トップレベルでの self を inspect するとmain
になるらしい.7行目.
"f"
を出力.
メソッドf
の末尾.最後の評価値が"f"
なので,メソッドf
は"f"
を返す.
9行目に戻る.9行目.
f
の返り値が"f"
だったので,"f".g
を呼ぶ.
g
という名前のメソッドはString
には定義されていない…
が,先程トップレベルにg
という名前のメソッドを定義したので,先程のメソッドg
が呼び出される.
3行目へ.3行目.
"f"
に対してg
を呼び出したので,self は"f"
.4行目.
"g"
を出力.
メソッドg
の末尾.最後の評価値が"g"
なので,メソッドg
は"g"
を返す.
9行目に戻る.9行目.
g
の返り値が"g"
だったので,p "g"
する.EOF.
出力結果
main "f" "f" "g" "g"
- 投稿日:2019-02-18T22:18:11+09:00
【Rails】Active Storage環境下でGoogle Cloud Visionのセーフサーチを実装
個人開発のWebアプリ「まちかどルート」v5.42への実装メモです。
まえがき
Rails 5.2のファイルアップローダーであるActive Storage環境下だとGoogle Cloud Visionのセーフサーチがうまく動かなかったので下記のように工夫しました。
具体的には画像ファイルを /public にいったん配置して、そのパスを指定して画像分析にかけ、終わったら /public から削除するという流れです。
posts_controller.rb# 画像の選択の有無を判断 if post_params[:image] != nil # 画像を uploaded_file に格納 uploaded_file = post_params[:image] # /public へのパスを指定 output_path = Rails.root.join('public', uploaded_file.original_filename) # ファイルを開いて image_file に格納 image_file = File.open(output_path) # Google Cloud VisionのAPIを使う image_annotator = Google::Cloud::Vision::ImageAnnotator.new response = image_annotator.safe_search_detection image: image_file response.responses.each do |res| safe_search = res.safe_search_annotation # if文では公式ドキュメントと違って .to_s を付ける必要がありました if safe_search.adult.to_s == "VERY_LIKELY" || safe_search.adult.to_s == "LIKELY" flash[:error] = "不適切な画像と判断されました powered by Google Cloud Vision" redirect_to root_path return elsif safe_search.violence.to_s == 'VERY_LIKELY' || safe_search.violence.to_s == 'LIKELY' flash[:error] = "不適切な画像と判断されました powered by Google Cloud Vision" redirect_to root_path return elsif safe_search.medical.to_s == 'VERY_LIKELY' || safe_search.medical.to_s == 'LIKELY' flash[:error] = "不適切な画像と判断されました powered by Google Cloud Vision" redirect_to root_path return end end # 最後に /public の画像ファイルを削除します File.delete(output_path) end上記のコードを適所に書けばGoogle Cloud Visionのセーフサーチが機能します。そこで
VERY_LIKELY
またはLIKELY
と判断されればroot_path
にリダイレクトされるようになります。あとがき
Active Storage環境下だと公式ドキュメントどおりにやってもうまくいきませんでした。また、事前に
gem 'google-cloud-vision', '~> 0.32.2'
をbundle install
したあとに「protobufが見つかりません」という内容のエラーが出ました。そこでGemfile
にgem 'google-protobuf', '~> 3.7.0.rc.2'
を追記のうえgem update google-protobuf
を実行することで回避できました。
- 投稿日:2019-02-18T21:08:46+09:00
Sidekiqのデフォルト再試行スケジュール表(目安)
It will perform 25 retries over approximately 21 days.
Sidekiqの再試行が デフォルト設定の場合にどのくらいの間隔で行われるか が知りたかったのだけれども、公式 を読んでも
約21日で25回行う
くらいしか書かれていなかったので表にしてみた。追記(2019-02-19)
と思ったら、公式 に書いてあった。プルダウン見てなかった。。。
リトライの計算式
公式を参照。
(retry_count ** 4) + 15 + (rand(30) * (retry_count + 1))結果
前提
- あくまで目安
- 最短、平均、最長の各値は、最初のジョブが失敗してから実行されるまでの期間を表す
- ジョブ自体の処理時間等は加味していない(仮に処理が10秒かかるものだとすれば、2回目が実行されるタイミングは最短でも41秒後)
再試行回数
は、何回目の再試行かを表す最短
は、上記計算式のrand(30)
が毎回0
を返した場合としている平均
は、上記計算式のrand(30)
が毎回15
を返した場合としている最長
は、上記計算式のrand(30)
が毎回30
を返した場合としている表
再試行回数 最短 平均 最長 1回目 0日00時間00分15秒後 0日00時間00分30秒後 0日00時間00分45秒後 2回目 0日00時間00分31秒後 0日00時間01分16秒後 0日00時間02分01秒後 3回目 0日00時間01分02秒後 0日00時間02分32秒後 0日00時間04分02秒後 4回目 0日00時間02分38秒後 0日00時間05分08秒後 0日00時間07分38秒後 5回目 0日00時間07分09秒後 0日00時間10分54秒後 0日00時間14分39秒後 6回目 0日00時間17分49秒後 0日00時間23分04秒後 0日00時間28分19秒後 7回目 0日00時間39分40秒後 0日00時間46分40秒後 0日00時間53分40秒後 8回目 0日01時間19分56秒後 0日01時間28分56秒後 0日01時間37分56秒後 9回目 0日02時間28分27秒後 0日02時間39分42秒後 0日02時間50分57秒後 10回目 0日04時間18分03秒後 0日04時間31分48秒後 0日04時間45分33秒後 11回目 0日07時間04分58秒後 0日07時間21分28秒後 0日07時間37分58秒後 12回目 0日11時間09分14秒後 0日11時間28分44秒後 0日11時間48分14秒後 13回目 0日16時間55分05秒後 0日17時間17分50秒後 0日17時間40分35秒後 14回目 1日00時間51分21秒後 1日01時間17分36秒後 1日01時間43分51秒後 15回目 1日11時間31分52秒後 1日12時間01分52秒後 1日12時間31分52秒後 16回目 2日01時間35分52秒後 2日02時間09分52秒後 2日02時間43分52秒後 17回目 2日19時間48分23秒後 2日20時間26分38秒後 2日21時間04分53秒後 18回目 3日19時間00分39秒後 3日19時間43分24秒後 3日20時間26分09秒後 19回目 5日00時間10分30秒後 5日00時間58分00秒後 5日01時間45分30秒後 20回目 6日12時間22分46秒後 6日13時間15分16秒後 6日14時間07分46秒後 21回目 8日08時間49分41秒後 8日09時間47分26秒後 8日10時間45分11秒後 22回目 10日14時間51分17秒後 10日15時間54分32秒後 10日16時間57分47秒後 23回目 13日07時間55分48秒後 13日09時間04分48秒後 13日10時間13分48秒後 24回目 16日13時間40分04秒後 16日14時間55分04秒後 16日16時間10分04秒後 25回目 20日09時間49分55秒後 20日11時間11分10秒後 20日12時間32分25秒後 表を作った時に書いたプログラム
秒から日に変換する処理は、秒を時間表示へ変換するを使用させていただいた。
require "time" def retry_seconds(retry_count, additional_value) (retry_count ** 4) + 15 + (additional_value * (retry_count + 1)) end def print_retry_schedule(retries, execution_time) sec_list = (0..retries).reduce([0, 0, 0]) do |sum, retry_count| sum[0] += (retry_seconds(retry_count, 0) + execution_time) sum[1] += (retry_seconds(retry_count, 15) + execution_time) sum[2] += (retry_seconds(retry_count, 30) + execution_time) sum end str = "| #{retries + 1}回目 |" sec_list.each do |sec| day, sec_r = sec.divmod(86400) str += (Time.parse("1/1") + sec_r).strftime(" #{day}日%H時間%M分%S秒後 |") end p str end (0...25).each { |n| print_retry_schedule(n, 0) }
- 投稿日:2019-02-18T18:49:29+09:00
プログラミング学習記録2〜railsチュートリアルにかかる時間〜
今日やったこと
- railsチュートリアルはどれくらいの時間をかけるとクリアできるのか?を調べた
- ProgateのRuby1 リセット復習
- ProgateのRuby2 リセット復習
- ProgateのRuby3 リセット復習&そのまま復習
railsチュートリアルにかかる時間【40〜130時間】
Progateのrailsが終わったらrailsのチュートリアルをやろうと思っています。
そこで「他の人は大体どれくらいの時間をかけてやっているのだろう?」と思い、気になったので調べてみました。
結論から言うと当たり前ですが「人による」みたいですね。
早い人だと40時間で終わってたりもしますが、130時間くらいはかかるといった意見もありました。
私はそんなに飲み込みが早い方でもないですし、復習に時間をかけがちなタイプなので最低でも130時間以上はかかると思って、railsチュートリアルに臨みたいと思います。
Progate Ruby1,2,3
今日Progate Rubyの復習をしてみた結果、Rubyの1,2,3はもうリセット復習はしなくていいなと思いました。
復習するにしても、そのまま復習(コードを打たず、見るだけの復習)しかしないことにします。
私は9割理解してても、残りの1割ちょっとでも理解できないところがあるとできるまで復習してしまうタイプです。
ただ、プログラミングは基礎学習に完璧を求めてもスキルは向上しないみたいなので、名残惜しいですが割り切って前に進みます。
100DaysOfCode Day2感想
100DaysOfCodeが1日坊主にならずに済んでよかったです。
Qiitaに投稿することを考えると、ちょっとカッコつけたくなるというか、ちゃんとした記録を載せなきゃダメだなと思えるので、学習過程を記録していくことは今のところ自分にとってプラスだと思いました。
このまま100Days完走できるように頑張ります。
おわり
- 投稿日:2019-02-18T18:27:44+09:00
メソッド
メソッドの定義
Rubyのメソッドは「def」を使って定義する。
def メソッド名(引数1、引数2) #必要な処理 endexp,
#2つの数字を加算するメソッド def add(a, b) puts a + b end add(1, 2) =>3 #と出力される。メソッド名の付け方
メソッドの名前の付け方も変数と基本的に一緒。
#スネークケースでかく def hello_world puts 'hello, world' end #キャメルケースは使わない def helloworld puts 'hello,world' end #_(アンダースコア)でメソッド名をかく(あんま使わないみたい) def _hello_world puts 'hello, world' end #メソッド名に数字を入れる def hello_world_2 puts 'hello, world' end #数字で始まるメソッド名は使わない def 2_hello_world puts 'hello, world' end #メソッド名はひらがなにしない def あいうえお puts 'かきくけこ' endメソッドの戻り値
Rubyは最後の式が戻り値になるのが特徴。
なのでreturnはいらない。def greeting(country) if country == 'japan' puts 'こんにちは' else puts 'hello' end end greeting('japan') =>こんにちは #と出力される。参考URL
Programming from 30 - わからないまま進んだ、戻り値とnil。
・・・やっぱり戻り値は難しい。。。
何かいい解釈の仕方はないかなー、そして多くの人が戻り値苦労してるのか笑
ちょっと安心( ^ω^ )以上
- 投稿日:2019-02-18T18:01:04+09:00
【Rails】自作WebアプリからMastodonのプロフィール画像を変更
個人開発のWebアプリ「まちかどルート」v5.41への実装メモです。
プログラミングに入門して8ヶ月。
今回も SNS「Mastodon」のAPI に挑戦してみました。まえがき
・まちかどルートではMastodonのアカウントでログイン認証するようにしています。
・今回の実装をするまでは、まちかどルートのプロフィール画像を変えるためにわざわざMastodonで変更し、まちかどルートで再ログインする必要がありました。
・画像アップローダーとしてRails 5.2の新機能 Active Storage を使い、ファイルをAmazon S3に保管しているのですが
gem 'mastodon-api'
(v2.0)を通してアップロードするとき、その環境のせいでとても苦労しました。model
user.rbhas_one_attached :imageActive Storageを使うのでマニュアルどおりの作法でmodelにこう書きます。バリデーションについては今回、割愛します。
view
edit.html.erb<%= form_with model: @user, multipart: true, local: true do |f| %> <%= f.file_field :image %> <%= button_tag :type => "submit" do %>変更を保存<% end %>プロフィール画像のファイルを選択するためのフォームです。
controller
users_controller.rbdef update # 画像の選択の有無を確認 if params[:image] != nil # 画像を uploaded_file に格納 uploaded_file = params[:image] # 画像を /public にいったん配置するため output_path にパスを設定 output_path = Rails.root.join('public', uploaded_file.original_filename) # Mastodonには2MB制限があるので画像を縮小 ## MiniMagickを使います。まずは画像を入力 img = MiniMagick::Image.read(uploaded_file) ## 縮小します img.resize "300x300" ## 縮小したら /public に書き出します img.write output_path # MastodonのAPIを通してアップロードします ## 配列を用意します。Mastodon指定のパラメーターは avatar です user_array = { "avatar": output_path } ## APIを叩くためのクライアントを生成します domain = '[対象のMastodonインスタンスのドメイン]' access_token = '[ユーザーのアクセストークン]' client = Mastodon::REST::Client.new(base_url: "https://#{domain}", bearer_token: access_token) ## MastodonのAPIを叩きます client.update_credentials(user_array) # 以上でMastodon側のプロフィール画像が変更されます # 続いて、まちかどルートにも同じ画像を反映させます ## MastodonのAPIを叩いて変更後のavatarを取得します @user.avatar = client.verify_credentials.avatar ## ユーザー情報を保存します @user.save # 最後に、不要となった/publicの画像を削除します File.delete(output_path) end flash[:notice] = "アイコンを変更しました" redirect_to @user endcontrollerが一番苦労しました。
解説はコメントアウトにある通りです。あとがき
Active Storageはとても簡単に導入できるアップローダーなのですが
user_array = { "avatar": @user.image }と書ければ、わざわざ/publicに画像を配置する手間がなくて楽なのに
url_for(@user.image)
とやっても「そんな画像はありません」というエラーが返ってきてしまうんです。というわけで、いろいろ試行錯誤して上記のようになりました。とくに/publicにいったん配置する方法がわかったので、これから他の画像系APIを使うのに役立ちそうです。今後も学んでいきたいと思います。
- 投稿日:2019-02-18T17:14:59+09:00
Shrineのimage_dataを含むモデルのFactoryの作り方
(編集しようとしたら誤って削除してしまったので再投稿しました)
railsの画像アップロードライブラリshrineを用いた場合の、FactoryBotのFactoryの書き方について悩んだポイントがあったので書いておきます
前提
登場するModel
- Area
column type memo name string require
- Shop
column type memo area bigint require logo bigint require name string require
- Logo
column type memo area bigint require name string require image_data text require Area has_many Shop
Area has_many Logo
Shop belongs_to Logo各Shopはエリアに登録されているロゴを使用できるイメージです。
悩んだこと
logoのファクトリーのimage_data カラムをどう書くべきなのか悩みました。
spec/factories/logos.rbFactoryBot.define do factory :logo do area sequence(:name) { |i| "logo#{i}" } image { #この部分!! } end endもともとは↓のように書いていたのですが、これだとlogoのテストデータが作成されるたびにファイルへアクセスするためRSpec全体の実行時間に大きな影響を与えていました。
image { File.open("#{Rails.root}/spec/fixtures/img/example.png") }RSpec実行時間
Factory追加前→ 27s
追加後 →1min 3sshopが作られるたびにFactoryBotによってlogoも自動で作られるのだから遅くなるのは当たり前ですね
試したこと
適当な文字列をつっこむ
image_data { 'sample' }結果
imageを実際に使うテストにてJSON::ParserError:765: unexpected token at 'sample'
それはそう。。
fixture_file_upload使ってみる
この記事ではfixture_file_upload使ったらFile.openより相当早い結果になっているので期待大!
image { fixture_file_upload("#{Rails.root}/spec/fixtures/img/example.png", 'img/png') }結果
テストはすべてpass!
ただしFinished in 1 minute 9.24 seconds (files took 15.67 seconds to load)
File.openのときと変わらないくらい時間かかってる。。
使い方がおかしい?最終的に
ファイルアクセスではなく架空のcacheを作成してみる
Shrineのuploaded_fileメソッドを用いてキャッシュの状態でファイルが上がっていることにする作戦
factory :logo do area sequence(:name) { |i| "logo#{i}" } image_data { Shrine.uploaded_file( 'id' => SecureRandom.hex(8), 'storage' => 'cache', 'metadata' => { 'mime_type' => 'image/jpeg', 'size' => 1.megabyte }).to_json } end結果
Finished in 31.04 seconds
いいじゃん!!
補足
最終型において
image
ではなくimage_data
を使っている理由について書きます。はじめはFile.openと同じように下記のように書いていたんですが、
image { Shrine.uploaded_file( 'id' => SecureRandom.hex(8), 'storage' => 'cache', 'metadata' => { 'mime_type' => 'image/jpeg', 'size' => 1.megabyte }).to_json}結果
imageを実際に使うテストにてエラーが起きるNo such file or directory @ rb_sysopen - /usr/src/app/public/uploads/cache/675ce0d849e8a5f8
ファイルをあげようとしてるんだけど、そんなファイルはないよと言われているように見えます。
shrineを用いた実装において、
image_data → 添付ファイルの全情報がjson形式で保存されるためのDBカラム image → 添付ファイルをハンドリングするためのvirtualなattributeということは
image_data → upload済みのファイル情報を入れる。 image → uploadしたいファイル情報を入れる。最終形では、
Shrine.uploaded_file
でファイルが上がっていることにしていたのでimage_dataに入れるのが正解ということでした
- 投稿日:2019-02-18T15:58:59+09:00
真偽値と条件分岐
Rubyの真偽値
Rubyの真偽値は以下のルールがある。
・false or nil であれば偽。
・それ以外は真。
※nilも偽として扱われるので気をつける真として扱われるもの
#trueそのもの true #全ての数値 1 0 -1 とか #全ての文字列 'true' 'false' とか論理演算子(&&と||)
&&や||のような論理演算子を使うと、複数の条件を1つにまとめることができる。
&&(かつ)
「かつ」は「条件1も条件2も真であれば真、それ以外は偽」となる条件1 && 条件2
a1 = true a2 = true a3 = false a1 && a2 => true a1 && a3 => false||(または)
「または」は「条件1か条件2のどちらかが真だと真になり、両方偽であれば偽」となる。条件1 || 条件2
a1 = true a2 = false a3 = false a1 || a2 => true a1 || a3 => false組み合わせ
&& と || は組み合わせて使うこともできる。
&& は ||より優先順位が高いため()を使った方がわかりやすい。「条件1かつ条件2が真、または条件3かつ条件4が真なら真」
・条件1 && 条件2 || 条件3 && 条件4
・(条件1 && 条件2) || (条件3 && 条件4)a1 = true a2 = true a3 = false a4 = false a1 && a2 || a3 && a4 => true (a1 && a2) || (a3 && a4) => trueif式
Rubyのif式は下記のように書く。
if 条件A
#条件Aが真だった場合の処理
elsif 条件B
#条件Bが真だった場合の処理
elsif 条件C
#条件Cが真だった場合の処理
else
#それ以外の条件の処理
end※elseif ではなく elsifと書く!
#値が10より大きいかどうかで処理が変わるif式 n = 11 if n > 10 puts '10より大きい' else puts '10以下' end =>10より大きい と出力される。 #国名で処理が変わるif式 country = 'italy' if country == 'japan' puts 'こんにちは' elsif country == 'u.s' puts 'Hello' elsif country == 'italy' puts 'ciao' else puts '???' end => ciao と出力される。・if修飾子(後置if)
Rubyのif式は修飾子として文の後ろに置くこともできる。そもそも修飾子って何よってなったので一応調べました。。。
「名詞または動詞の意味を修飾する内容語」
・・・修飾語みたいな感じかな?多分。#普通の毎月1日はポイント5倍にするif式 point = 7 day = 1 if day == 1 point *= 5 end #if修飾子を使った毎月1日はポイント5倍にするif式 point = 7 day = 1 point *= 5 if day == 1 point => 35 と出力される。参考URL
勉強に使った参考URL。
- 投稿日:2019-02-18T14:36:08+09:00
[Rails]複合主キーを持つテーブルのレコードを更新する(*非常用)
環境
- Ruby 2.5.1
- Rails 5.2.1
- Mysql2 0.5.1
前提
そもそも、Rails において複合主キーの Model を扱うことはサポート外になります。複合主キーを使うと下記のような警告も出力されます。
WARNING: Active Record does not support composite primary key. Composite primary key is ignored.また、
find
,update
,save
,delete
メソッドの使用が不可能になります。これらのメソッドを呼び出すと下記のようなエラーを出力します。Model
- MultiPkeyModel という複合主キーの Model があったとします
- pkey_1, pkey_2 カラムが主キーとします
find
MultiPkeyModel.find(1) # 数値は適当 # ActiveRecord::UnknownPrimaryKey: # Unknown primary key for table multi_pkey_models in model MultiPkeyModel.そもそも id カラムを持たないので find の引数に何も指定しようがないですが、引数有無問わず上記のエラーが発生することを確認しました。
update, save, delete
record = MultiPkeyModel.find_by(pkey_1: somevalue1, pkey_2: somevalue2) # update record.update(foo: 'bar') # ActiveRecord::StatementInvalid: Mysql2::Error: # Unknown column 'multi_pkey_models.' in 'where clause': # UPDATE `multi_pkey_models` SET `foo` = 'bar' WHERE `multi_pkey_models`.`` IS NULL # save record.foo = 'bar' record.save # => update と同様のエラーのため省略 # delete record.delete # ActiveRecord::StatementInvalid: Mysql2::Error: # Unknown column 'multi_pkey_models.' in 'where clause': # DELETE FROM `multi_pkey_models` WHERE `multi_pkey_models`.`` IS NULLいづれの場合も、 SQL 文の WHERE 句が
WHERE `multi_pkey_models`.`` IS NULL
というおかしな状態になっているため失敗していると思われます。 Model クラスが id カラムを持たないため、本来 id カラムが指定されうる箇所が空になってしまっています。本題
では、どうするべきか。そもそも複合主キーによるテーブルを使った運用自体をよしとするかどうかという話もありますが、本項ではそこまで壮大な話は扱わず、 複合主キーテーブルを使わざるをえない状況 という前提で、苦肉の策を共有してみたいと思います。
find の代わり
これは
find_by
ですみます。レコードの更新を伴わない場合はすぐには困ることもなさそうです。MultiPkeyModel.find_by(pkey_1: somevalue1, pkey_2: somevalue2)update, delete の代わり
身も蓋もないですが、SQL文を直接発行したほうが手っ取り早いと思います。
下記は update を行う場合のコードの記述例です。sql = <<-SQL UPDATE `multi_pkey_models` SET `foo` = :foo WHERE `pkey_1` = :pkey_1 AND `pkey_2` = :pkey_2 SQL ActiveRecord::Base.connection.execute(ActiveRecord::Base.send( :sanitize_sql_array, [ sql, foo: 'bar', pkey_1: someValue1, pkey_2: someValue2 ] ))どうしても Active Record を使いたい場合は
update_all
という手もあります。MultiPkeyModel.where(pkey_1: someValue1, pkey_2: someValue2).update_all(foo: 'bar')ただ、update_all は本来は複数のレコードをまとめて更新するためのメソッドです。 pkey_1, pkey_2 の組み合わせを持つレコードは必ず1つであることがテーブルの制約上保証されてはいますが、そういった背景を知らない人がコードを読んだ際に、コード上は複数のレコードを更新しにいっているように見えるので、あまり読み手に優しくないと思います。
ただ、「なんでここSQL文直接書いてるんだろう?」と思う人もいるかもしれないのでSQL文生書きにしていることの理由などをコメントで補足しておくといいとおもいます。あれ、update_all 呼んでコメント書くのも一緒か とも思いましたが、そこはエンジニア各位のご判断にお任せしたいとおもいます。
まとめ
複合主キーを持つテーブルを扱う際には、各種 CRUD メソッドが想定どおり機能しないケースがあるのでご注意ください。
ちなみに
gem でなんとかしている例もけっこうあるようです。実際問題、筆者も含め複合主キーってけっこう使いますよね。。。
- composite-primary-keys/composite_primary_keys: Composite Primary Keys support for Active Record
- 「composite_primary_keys」の検索結果 - Qiita
識者からのご指摘、ご感想ありましたらぜひコメントいただけるとうれしいです。
- 投稿日:2019-02-18T14:21:10+09:00
(Ruby)セッターにはselfを付けてレシーバを明示的にしよう。
はじめに
・なんでRubyのセッターにはselfを付けないといけないの? って人のための記事。
(自分が1時間ハマったため)
・Ruby初心者向けです。前置き
attr_accessor :nameは
# これがゲッター def name @name end # これがセッター def name=(argument) @name = argument endの略です。
(ちなみにattr_accessorは「属性へのアクセス(アトリビュートアクセサー)」という意味)本題:ユーザークラスを用意する。
userclass.rbclass User attr_accessor :name def set_default_name self.name = "デフォルト山田" if name.nil? end endこのクラスには、@nameがnilだったとき、@nameにデフォルトネームを代入してくれるというメソッド(set_default_name)が定義されています。
[1] pry(main)>user = User.new [2] pry(main)>user.name => nil [3] pry(main)>user.set_default_name => "デフォルト山田" [4] pry(main)>user.name => "デフォルト山田"
しかし、このメソッド、どこか違和感ありませんか?def set_default_name self.name = "デフォルト山田" if name.nil? endなんで左側の「self.name=」はname=メソッドのレシーバを明示的にして(selfがレシーバであることを明記して)、右側の「name.nil?」のレシーバは暗黙的なんでしょうか?(self.name.nil?となっていない)
「納得行かない、セッター側である「self.name=」のselfをとりあえず消してみよう!」と、実際に消してみると、インスタンス変数の値を書き換えることができなくなります。(エラーは出ないが、セッターとして@nameの値を書き換えられない。)
だめだ…わからん…。
結論
def set_default_name name = "デフォルト山田" if name.nil? endこれみたくself.name=にしてない定義だとname=メソッドを呼び出せずに、nameというローカル変数に"デフォルト山田"を代入するという挙動になるからでした。
これがわからずselfを書いていない状態で、「なんでname=メソッド呼び出せないんや…」と一生詰まってたのでこの記事を書きました。同じ壁にぶつかる方はいないかもしれませんが、駆け出しの方は二の舞を踏まないようご注意ください。
…ていうかname=メソッドって何?
こちらの記事がわかりやすかったのでどうぞ。
【Ruby】「ゲッター」と「セッター」を理解する
https://qiita.com/k-penguin-sato/items/5b75be386be4c55e3abf
- 投稿日:2019-02-18T11:56:40+09:00
form_with は remote (Ajax) が既定値なのに scaffold で生成されるコードは local: true オプションが付いていてどうにかしたい
やりたいこと
- Rails の scaffold をジェネレートすると
form_with
にlocal: true
のオプションが付いています。local
オプションのデフォルトはfalse
なわけで、 Ajax にしていこうという流れの中で scaffold のコードが Ajax じゃないのに違和感を覚えました。- というわけで、 scaffold で生成されたコードに対し、現実的なコード改修量で Ajax リクエストによる CRUD を実現してみます。
前提
- webpacker を使っている
- rails-ujs を使っている
- turbolinks を使っている
$ yarn add rails-ujs turbolinks
app/javascript/packs/application.jsimport Rails from 'rails-ujs' Rails.start() import Turbolinks from 'turbolinks' Turbolinks.start()
- 下記のようなユーザモデルを scaffold で作成した直後の状態を想定
name
およびage
という属性を持つ。前者はstring
で後者はinteger
name
およびage
ともに入力必須 (バリデーションエラー時の挙動確認で必要なので)$ ./bin/rails g scaffold User name age:integer
app/models/user.rbclass User < ApplicationRecord + validates :name, :age, presence: true end
- Rails のバージョンは 5.2.2
$ rails -v 5.2.2実施
ざっくり言うと以下の流れに。
- save 成功時、リダイレクトが Ajax の場合でも動くようにする
- save 失敗時、 form 要素だけを html で返し、その結果により既存 form を入れ替える
_form
パーシャルファイル、local: true
を外すこのポストの内容の前提なので。
app/views/users/_form.html.erb-<%= form_with(model: user, local: true) do |form| %> +<%= form_with(model: user) do |form| %> <% if user.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>
_form
パーシャルファイルが<form>
タグだけを描画する、という前提を変えない通常、 scaffold ジェネレータを実施すると
_form
パーシャルファイルは<form>
タグに該当する html を描画するわけですが、それ以外の要素を_form
パーシャルファイル内で描画しないようにします。
たとえば<form>
タグの直下に<a>
タグでリンクを置きたいからといって_form
パーシャルファイルの中にそれを記述してはいけません。同じことを実現するには_form
パーシャルファイルを呼び出す側に記述すればことが済むはずです。例えば以下のようにするのではなく。。。
app/views/users/edit.html.erb<%= render 'form', user: @user %>app/views/users/_form.html.erb<%= form_with(model: user) do |form| %> ...省略 <% end %> <%= link_to 'Other Site', 'http://example.com' %>
_form
パーシャルでは<form>
だけ描画します。app/views/users/edit.html.erb<%= render 'form', user: @user %> <%= link_to 'Other Site', 'http://example.com' %>app/views/users/_form.html.erb<%= form_with(model: user) do |form| %> ...省略 <% end %>gem turbolinks を入れる
単に turbolinks を動かすだけなら JS の npm 管理下にある turbolinks を入れるだけで良いけど、 gem の turbolinks は別の理由で必要です。
gem turbolinks は Rails コントローラのredirect_to
の挙動をさしかえ、非Ajax の場合のリダイレクトと同じような動きをするようにします。
参考: https://github.com/turbolinks/turbolinks-classic/blob/master/lib/turbolinks/redirection.rbcreate/update アクションのバリデーションエラー時に html 全体でなく
_form
パーシャルの内容だけ返す
- render で
_form
パーシャルだけを描画して返すようにします。このときにlocals
を指定するのを忘れずに- 更に、ステータスコードは 200 でなく 422 Unprocessable Entity などのクライアントエラーを返すようにします
app/controllers/users_controller.rbdef create @user = User.new(user_params) if @user.save redirect_to @user, notice: 'User was successfully created.' else - render :new + if request.xhr? + render partial: 'form', status: :unprocessable_entity, locals: { user: @user } + else + render :new + end end endapp/controllers/users_controller.rbdef update if @user.update(user_params) redirect_to @user, notice: 'User was successfully updated.' else - render :edit + if request.xhr? + render partial: 'form', status: :unprocessable_entity, locals: { user: @user } + else + render :edit + end end endフォームで
ajax:error
イベントを拾ったときに自身の内容を書き換えるapp/javascript/packs/application.jsimport Rails from 'rails-ujs' Rails.start() import Turbolinks from 'turbolinks' Turbolinks.start() // ここから document.addEventListener('turbolinks:load', (event) => { const forms = document.querySelectorAll('form[data-remote="true"]') // remote フォームについて forms.forEach((form) => { form.addEventListener('ajax:error', (event) => { // 先のコントローラの処理で 200 を返しているとここは発火しないので注意 const detail = event.detail const xhr = detail[2] const contentType = xhr.getResponseHeader('content-type') if (contentType === 'text/html; charset=utf-8') { // html が返ってきている場合 const target = event.currentTarget const tmp = document.createElement('div') tmp.innerHTML = xhr.responseText const element = tmp.firstElementChild target.innerHTML = element.innerHTML // <form> タグの innerHTML の中身を入れ替える } // TODO: form タグ自体の属性についても厳密に丸々入れ替えるべきかもしれないが、ここではそこまでしていません }) }) })ここまで実施すると、 scaffold コードをベースにした Ajax CRUD が実現できているはずです。
良し悪し
- Pros.
_form
パーシャルに<form>
を書くというルールを前提とした場合に非常にシンプル- Javascript の記述がほとんど要らない
rake app:templates:copy
でコピーした controller と view のテンプレートファイルに少し手を入れる程度で、他にほとんど気にすることがない- Cons.
- この実装は、バリデーションエラー時に
<form>
タグ以外の場所を更新できない
- 実施する場合、たとえば
<form ... data-remote-placeholder="#form-wrapper">
のような属性があれば form でなくdocument.querySelector(form.dataset.remotePlaceholder)
が示す参照先をベースに書き換えする。みたいな拡張は可能。- あるいは SJR でやるとか
まとめ
- scaffold はまだ Ajax 化されていない (今後されるの?)
- scaffold のコードをベースに Ajax 化してみた
- gem turbolinks を使うと Ajax/非Ajax を意識せずに
redirect_to
を使える- save 失敗時に form の html 要素を返し、それにより自身の form の中身を差し替える(と決めておくと簡単)
補足
https://github.com/hamajyotan/scaffold_ajaxify
この記事での内容を実施したリポジトリの共有です。
- 投稿日:2019-02-18T10:57:48+09:00
数値
数値について
正の整数 → 10とか
浮動小数点数 → 1.5とか負の整数 → -3とか
負の浮動小数点数 → -4.75とか※数値には(アンダースコア)を含めることができる。
は無視されるので数の区切りとして使うこと便利。1_000_000_000 =>1000000000 と出力される。四則演算
数値を使って足し算・引き算・掛け算・割り算もできる。
演算子の優先度は数学と同じで「*」と「/」が「+」と「-」よりたかい。10 + 20 => 30 100 - 25 => 75 12 * 5 => 60 20 /5 => 4正と負の逆転
変数の手前に-をつけると、数値の正と負を逆転できる。
n = 1 puts -n => -1 と出力される。整数同士の割り算
整数同士の割り算は整数になってしまう。(小数点以下は切り捨てられる)
1 / 2 => 0 と出力される。 #0.5ではない浮動小数点数を出力したいときは、どっちかの数値に小数点の.0をつける。
1.0 / 2 => 0.5 1 / 2.0 => 0.5 と出力される。※変数に整数が入っているときは「to_f」メソッドで整数から浮動小数点数に変更できる。
n = 1 n.to_f => 1.0 n.to_f / 2 => 0.5 と出力される。変数に格納された数値の増減
Rubyの変数の値を増減させる演算子は「+=」と「-=」、「=」、「/=」、「*=」。
#nの値を1増やす n = 1 n += 1 => 2 と出力される。 #nの値を1減らす n = 3 n -= 1 => 2 と出力される。 #nの値に3をかける n = 2 n *= 3 => 6 と出力される。 #nの値を2でわる n = 6 n /= 2 => 3 と出力される。 #nの値を2乗する n **= 2 => 9 と出力される。参考URL
勉強するときに参考にしたURL。
Ruby入門 - 演算子
Let'sプログラミング - 自己代入
- 投稿日:2019-02-18T05:05:55+09:00
rails gem carrierwave imageがnull
rails
画像を投稿機能を付ける段階でネットの情報を見ながらgem で carrierwaveを使う方もいると思います。
エラーがないはずなのにデータベースのimageの値がnull になるケースがある方もいるかもしれませんのでここに書いておきます。
form_tag, のなかに → multipart: :true
を書き忘れているかもしれませんので確認をしてみては、
低レベルの記事ではありますが参考になればいいと思います。
なぜ必要かと言うと
multipartオプションがないと例えば
cat1.jpeg という画像の場合、
cattt1.jpeg というファイル名だけをstringとして受け取ってしまい、画像情報を受け取れないのです。
ファイルを取り込むときは:multipart => true
をform_tagの第二引数に指定すると
StringIO(stringを拡張したもの)でクエリーがやってきて画像が取り込めるようになるそうです。
以上です
- 投稿日:2019-02-18T02:58:59+09:00
Shrineのimage_dataを含むモデルのFactoryの作り方
railsの画像アップロードライブラリshrineを用いた場合の、FactoryBotのFactoryの書き方について悩んだポイントがあったので書いておきます
前提
登場するModel
- Area
column type memo name string require
- Shop
column type memo area bigint require logo bigint require name string require
- Logo
column type memo area bigint require name string require image_data text require Area has_many Shop
Area has_many Logo
Shop belongs_to Logo各Shopはエリアに登録されているロゴを使用できるイメージです。
悩んだこと
logoのファクトリーのimage_data カラムをどう書くべきなのか悩みました。
spec/factories/logos.rbFactoryBot.define do factory :logo do area sequence(:name) { |i| "logo#{i}" } image { #この部分!! } end endもともとは↓のように書いていたのですが、これだとlogoのテストデータが作成されるたびにファイルへアクセスするためRSpec全体の実行時間に大きな影響を与えていました。
image { File.open("#{Rails.root}/spec/fixtures/img/example.png") }RSpec実行時間
Factory追加前→ 27s
追加後 →1min 3sshopが作られるたびにFactoryBotによってlogoも自動で作られるのだから遅くなるのは当たり前ですね
試したこと
適当な文字列をつっこむ
image_data { 'sample' }結果
imageを実際に使うテストにてJSON::ParserError:765: unexpected token at 'sample'
それはそう。。
fixture_file_upload使ってみる
この記事ではfixture_file_upload使ったらFile.openより相当早い結果になっているので期待大!
image { fixture_file_upload("#{Rails.root}/spec/fixtures/img/example.png", 'img/png') }結果
テストはすべてpass!
ただしFinished in 1 minute 9.24 seconds (files took 15.67 seconds to load)
File.openのときと変わらないくらい時間かかってる。。
使い方がおかしい?最終的に
ファイルアクセスではなく架空のcacheを作成してみる
Shrineのuploaded_fileメソッドを用いてキャッシュの状態でファイルが上がっていることにする作戦
factory :logo do transient do cache_image Shrine.uploaded_file( 'id' => SecureRandom.hex(8), 'storage' => 'cache', 'metadata' => { 'mime_type' => 'image/jpeg', 'size' => 1.megabyte }).to_json end area sequence(:name) { |i| "logo#{i}" } image { cache_image } image_data { cache_image } end結果
Finished in 31.04 seconds
いいじゃん!!
補足
この場合、image/image_dataともに明示的にcache_imageを入れてあげないとうまくいきませんでした。
はじめは下記のように書いていたんですが、
image { Shrine.uploaded_file( 'id' => SecureRandom.hex(8), 'storage' => 'cache', 'metadata' => { 'mime_type' => 'image/jpeg', 'size' => 1.megabyte }).to_json}結果
imageを実際に使うテストにてNo such file or directory @ rb_sysopen - /usr/src/app/public/uploads/cache/675ce0d849e8a5f8
ちゃんとimageが上がっていない?とってこれていない?
shrineの実装を見ないとわからなそうなので今回はここまでで
- 投稿日:2019-02-18T01:33:40+09:00
駆け出しrailsエンジニアがはじめの一ヶ月でくらったレビューをさらす
はじめに
早いもので、現職について1ヶ月が過ぎました。
初めてrailsを業務で扱っている自分ですが、バリバリの先輩エンジニアにメタメタのギッタギタにレビューをしていただいています。
とてもありがたい。。備忘録の意味も込めて整理しておこうと思います。
指摘事項の雰囲気ごとにまとめました。
- 開発の常識だよ系
- 知っておこう系
- かっこよく書こう系
設計だったり実装方針だったりのレビュー事項は一般化しづらいので含めていません。
そんなこと言われなくてもわかるでしょという恥ずかしい内容も多々ありますが、駆け出しなんだし恥ずかしがらずに晒していきます。いってみよう。
開発常識系
rubyやrailsに限らず、開発者としてやっておこう/意識しようという内容のもの
ファイル末尾に空行を入れよう
POSIXという偉い規格が定めているテキストファイルの定義に反するから
テキストファイルとは「1 つ以上の行」行は「0 個以上の改行以外の文字と末尾の改行」
不要なファイルはコミットしない
例えば、rails gで自動生成されたものでも不要なファイルは消そう
modelsのspecとかは絶対作るわけではないのでrails gはいろいろ作ってくれがちなのでmigrationファイルを作成するときくらいしか使わないかもとのこと。
不要なトランザクションは貼らない
当たり前なのだけれど
example_update.rbdef update @hoge = Hoge.find(params[:id]) if Hoge.transaction_with {update_with_huga} redirect_to .. else ... end end private def update_with_huga #呼び出し元でトランザクション貼ってるからこっちでは貼る必要なし end※transaction_withはswitch_pointのメソッド
カラムの追加位置を意識しよう
add_column時はbeforeオプションを使って適切な位置に追加する
知っておこう系
「知らないとやばいよ」〜「知っておくと便利だよ」まで含めて
外部キー貼るとき
外部キーを表すときは、
t.bigint
ではなくt.references
で設定する
勝手にindexを貼ってくれる.first / .last
.first
だと、 全てのデータをSELECT文で取得して最初のカラムを返す
レコードの数が増えていくであろうモデルに対しては使わないように気をつけるclients = Client.first #SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1Active Record クエリインターフェイスはどんなSQLが発行されるのかを確認した上で使うこと
関連付けの使用
不要なSQLを発行させていないか気をつける
post.user.id #postに紐づくuserを取得するselect文が発行される post.user_id #sqlは発行されないsanitize_sql_like使おう
そもそもsanitizeってなんだよと最初は思いました。
ものすごく簡単に言うとユーザーからの不正なSQL実行を防ぐための入力値のエスケープ処理というイメージであっているはず#一般的なlike検索 -> 文字列にsql入れられたら実行されて困っちゃう User.where('name LIKE ?', `%#{args[:name]}%`) #sanitize_sql_like -> 文字列にエスケープかかるから安心 User.where('name LIKE ?', "%#{sanitize_sql_like(args[:name])}%")validationはいろいろ準備されてるよ
例:入力値として整数だけを受け付けたいカラムが存在するとき
数字のみ受け付けたい# もともとの自分の実装 VALID_NUMBER_INPUT_REGEX = /\A[0-9]+\z/.freeze validates :hoge, presence: true, format: { with: VALID_NUMBER_INPUT_REGEX } # レビュー後 validates :hoge, numericality: { only_integer: true }numericality以外にもたくさんある
validationに限らずRailsGuideは一通り目を通すべきと痛感しましたかっこよく書こう系
こうするとrubyぽいよ、railsぽいよ、逆にそう書くとかっこわるいよという指摘
()いらない
java出身だと最初は違和感が抜けないです
@post = current_user.post.build() #()は不要だよ @post = current_user.post.build不要な返り値いらない
場合によりけりですが、例えばなにかのbefore_actionで認証して失敗したらリダイレクトさせるときなんかは返り値不要
return true if @account&.authenticated? #このtrueいらない redirect_to hoge_path冗長なifはださい
これはrubyだからというわけでもない気がしますが。
シンプルな分岐はifをつかわなくても書ける場合が大半booleanを返すメソッドif account.admin? true else account.shop.id == record.id end #こう書ける account.admin? || account.shop_id == record.idscopeを使おう
ItemはShopに所属している前提
もともとの自分の実装#コントローラ @items = Item.search(current_user.shop_id, params[:search_word]).page(params[:page]).per(USER_PER_PAGE) #Userモデル searchメソッド def self.search(shop_id, name) Item.where(shop: shop_id).where('name LIKE ?', "%#{name}%") endレビュー後#コントローラ @items = Item.where(shop: current_user.shop).keyword_by(params[:search_word]).page(... # keyword_byは、model側にscopeを作る #Userモデル scope scope :keyword_by, ->(search_word) do if search_word.present? where('name LIKE ?', "%#{name}%") end endscopeを用いたほうが
search
の中に処理を内包するより、Controllerでどのようなフィルタリングをするかがわかりやすくなります。関連があるときは明示的に使おう
#もともとの自分の実装 Item.where(shop: current_user.shop).find(params[:id]) #レビュー後 current_user.shop.items.find(params[:id])このように書くことで、
current_user
に紐づく何かを処理しないといけないということを明確にすることが多いpartial collection
なんらかの配列があってそれぞれになにかを表示したいというケース
haml-# もともとの自分の実装 - hoge_array.each do |hoge| = hoge.name = link_to .. ... -# レビュー後 = render partial: 'hoge', collection: hoge_array, as: 'hoge' -# この上で下のような_hoge.html.hamlを準備する = hoge.name = link_to .. ...最後に
本当は「要件と照らし合わせたときの実装方針」とか「責務の分担に関する考え方」みたいな自分が感動した部分に関して紹介したかったのですが言語化が難しかったです。。
この部分に熟練のエンジニア方の凄さがつまっているはずなので、またの機会にアウトプットできればと思います。
駆け出しエンジニアのみなさま
おれはこんなレビューされて痺れたぜという話があれば教えてください。
- 投稿日:2019-02-18T00:24:10+09:00
文字列の作成
文字列
Rubyでは文字列を作成する方法がいくつかあるが、一般的なのは
「シングルクォート(’)」と「ダブルクォート(”)」で囲む方法。'これは文字列です' "これは文字列です"シングルクォートとダブルクォートでは共同が異なることがある。
例えば、文字列中に改行文字(/n)を入れる場合は、ダブルクォート(”)で囲まなくてはいけない。ダブルクォート(")
puts "おはようございます\nこんにちは" =>おはようございます =>こんにちは #と出力される。他にもダブルクォートを使うと式展開が使える。
式展開を使うときは変数や式を#{}の中に書く。name = sato puts "Hello, #{name}!" =>Hello, sato! #と出力される。 i = 10 puts "#{i}は16進数にすると#{i.to_s(16)です}" =>10は16進数にするとaです #と出力される。シングルクォート(')
puts 'おはようございます\nこんにちは' =>おはようございます\nこんにちは #と出力される。シングルクォートの場合は式展開はされない。
「#{}」を使ってもただの文字列とみなされる。name = sato puts 'Hello, #{name}!' =>Hello, #{name}! #と出力される。バックスラッシュ(/)
ダブルクォートを使う文字列で、改行文字や式展開の機能を打ち消したいときは、
手前にバックスラッシュをつける。puts "おはようございます\\nこんにちは" =>おはようございます\nこんにちは #と出力される。 name = sato puts "Hello,\#{name}!" =>Hello, #{name}! #と出力される。