- 投稿日:2020-03-28T23:50:35+09:00
【Ruby on Rails】Ajax通信で特定の要素を変更する
はじめに
RailsのAjax通信使って特定の要素を編集・更新する方法について記載します。
例で簡単なカテゴリー一覧画面の1つのカテゴリーの名前を更新する処理を使います。バージョン
- ruby 2.6.3
- rails 5.2.1
画面概要
この右のほうにある鉛筆マークの編集アイコンボタンを押すと、該当の行のカテゴリー名がフォームに変わります。
変更後のカテゴリー名にデータが更新され、表示を非同期で変更します。
例では、「交通」→「交通費」に変更します。
処理概要
要素にAjax通信するオプションを追加
link_toにremote: true
オプションをつけます。Ajax通信するためのルーティングを追加
コントローラやモデルでオブジェクトの取得または更新
追加したアクションのためのJavaScript用のviewファイルを用意し、画面の該当の要素を変更するためのJavaScriptを記載
実装前
実装前の主要なコード部分は以下になります。
app/controllers/categories_controller.rbdef index @categories = Category.where(user: current_user).order(:created_at) endapp/views/categories/index.html.erb<ul class="todo-list" id="own-categories"> <% @categories.each do |category| %> <li id="category-list-id-<%= category.id %>"> <%= render 'a_category_list', category: category %> </li> <% end %> </ul>app/views/categories/_a_category_list.html.erb<span class="handle ui-sortable-handle"> <i class="fa fa-ellipsis-v"></i> <i class="fa fa-ellipsis-v"></i> </span> <span class="text"><%= category.name %></span> <%= category.common_mark %> <div class="tools"> <%= link_to edit_category_path(category), class: "text-redpepper space-left" do %> <i class="fa fa-lg fa-edit"></i> <% end %> <%= category.common_btn %> </div>カテゴリー名を編集するフォームに変更するAjax処理の実装
鉛筆マークの編集アイコンボタンを押下して、カテゴリー名の要素をフォーム要素に変更する処理を記載していきます。
※注意:説明しないclass属性やid属性などがありますが、Ajax通信処理とは関係ありません。この処理には
gem rails-ujs
が必要なので追加してbundle install
します。Gemfilegem 'rails-ujs'1. Ajax通信するオプションを追加
編集アイコンボタンにremote: trueを仕込む。
app/views/categories/_a_category_list.html.erb<%= link_to edit_category_path(category), remote: true, class: "text-redpepper space-left" do %> <i class="fa fa-lg fa-edit"></i> <% end %>2. Ajax通信するためのルーティングを追加
Ajax通信するときでも、ルーティングのresourcesメソッドが柔軟に対応してくれます。
ここでは、editアクションを追加します。config/routes.rbresources :categories, only: [:index, :edit]3. コントローラでオブジェクトの取得
app/controllers/categories_controller.rbdef edit @category = Category.find(params[:id]) end4. 追加したアクションのためのJavaScript用のviewファイルを用意し、画面の該当の要素を変更するためのJavaScriptを記載
editアクションなので、
app/views/categories/edit.js.erb
を作成します。app/views/categories/edit.js.erbid = "<%= @category.id %>"; target = document.querySelector(`#category-list-id-${id}`); html = "<%= j(render partial: 'form', locals: { category: @category }) %>"; target.innerHTML = html;app/views/categories/_form.html.erb<%= form_with(model: category) do |form| %> <div class="row"> <div class="col-xs-10"> <div class="input-group input-group-sm"> <%= form.text_field :name, value: category.name, required: true, class:"form-control", max: 15 %> <span class="input-group-btn"> <%= form.submit submit_btn_letters, class: "btn btn-brown" %> </span> </div> </div> <div class="col-xs-1"> <%= category.cancel_btn %> </div> </div> <% end %>これで、カテゴリー名を編集するためのフォームが表示できます。
カテゴリー名を更新するAjax処理の実装
編集フォームに変更できたので、次に、変更したい文字「交通費」に変更して、
更新ボタンを押下し、更新後のカテゴリー名を表示する処理を記載していきます。1. Ajax通信するオプションを追加
form_withに
remote: true
オプションを追加します。
(明示的に記載しなくてもデフォルトでremote: trueにはなっている。)app/views/categories/_form.html.erb<%= form_with(model: category, remote: true) do |form| %> <!-- 省略 --> <% end %>2. Ajax通信するためのルーティングを追加
updateアクションを追加
config/routes.rbresources :categories, only[:index, :edit, :update]3. コントローラでオブジェクトの更新
カテゴリー名を「交通費」で更新します。
app/controllers/categories_controller.rbdef update @category = Category.find(params[:id]) @category.update(category_params) end private def category_params params.require(:category).permit(:name, :is_common) end4. 追加したアクションのためのJavaScript用のviewファイルを用意し、画面の該当の要素を変更するためのJavaScriptを記載
編集フォームを更新したカテゴリー名に変更する処理を行います。
updateアクションなのでapp/views/categories/update.js.erb
を作成します。app/views/categories/update.js.erbid = "<%= @category.id %>"; target = document.querySelector(`#category-list-id-${id}`); html = "<%= j(render partial: 'categories/a_category_list', locals: { category: @category }) %>"; target.innerHTML = html;おわりに
JavaScriptでAjax処理を書いてもいいのですが、シンプルなAjax通信はRailsであれば簡単にできます。
ただ、〇〇.js.erb
の書き方は癖が強いので要注意です。
- 投稿日:2020-03-28T22:38:33+09:00
Qiitaの自分の全ての投稿をダウンロードするRubyスクリプト
Qiitaの自分の投稿をダウンロードするスクリプトを書くのが流行っているみたいだったので、僕も流行りに乗ってRubyで書いてみました。
限定共有記事も含めて自分の全ての投稿をダウンロードできます。
標準ライブラリのみを使用しているので、gemのインストール等は必要ありません。サンプルコード
QIITA_ACCESS_TOKEN
はread_qiita
権限がついてるアクセストークンに書き換えてください。
アクセストークンの発行はこちらから → アクセストークンの発行 - Qiita
OUT_DIR
は出力先ディレクトリパスにご自由に書き換えてください。download_qiita_items.rbrequire 'open-uri' require 'fileutils' require 'json' require 'date' # アクセストークン QIITA_ACCESS_TOKEN = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' # 出力先ディレクトリ OUT_DIR = File.expand_path('./qiita_items', __dir__) # ===================================================================== # 出力先ディレクトリを作成 FileUtils.mkdir_p(OUT_DIR) 1.upto(100) do |i| # 記事一覧を取得 response = URI.open( "https://qiita.com/api/v2/authenticated_user/items?page=#{i}&per_page=100", 'Authorization' => "Bearer #{QIITA_ACCESS_TOKEN}" ) items = JSON.parse(response.read) # 記事が見つからなかった場合は終了する break if items.empty? # 記事を書き出す items.each do |item| filename = "#{item['title']}.md" File.write(File.join(OUT_DIR, filename), item['body']) end end実行例
$ ruby download_qiita_items.rb$ ls qiita_items Docker Imageのタグ一覧を確認できるCLIツール`docker-tags`を作りました.md GitHub Markdown APIの使い方.md Gitコミット件数を確認するコマンド.md JavaScriptで元の配列を変更せずに並び替えした配列を作る.md Qiita APIを使って記事一覧を取得する.md React+material-uiでコピーボタン付きのテキストボックスを作る.md Slack APIを使用してメッセージを送信する.md TerraformでVPC・EC2インスタンスを構築してssh接続する.md 【AWS】Qiita 週間いいね数ランキング【自動更新】.md 【Docker】Qiita 週間いいね数ランキング【自動更新】.md 【Git】自分が今いるブランチを確認するコマンド.md 【Go】AWS SSMパラメータストアの値を取得する.md ... ...
- 投稿日:2020-03-28T21:17:18+09:00
VisualStudioCodeのRubyアウトラインが一部表示されないときは繰り返し処理でdoをつけているせいかもしれない
VSCodeのアウトラインは便利ですね、ショートカットでアウトラインにフォーカス当たるようにしたりしています。
def hoge end def fuga end def piyo endとすると。
◇ hoge
◇ fuga
◇ piyo
みたいに表示されます。ところがdef hoge end def fuga while cond1 do end while cond2 do end end def piyo endとすると、
◇ hoge
◇ fuga
になってしまうのです。piyo以下が表示されなくなってしまいます。しかし、繰り返し処理の後ろのdoは省略できますdef hoge end def fuga while cond1 end while cond2 end end def piyo endとすると、ちゃんと表示されるのです。
◇ hoge
◇ fuga
◇ fuga一つの関数の中に繰り返し処理が複数出てくるようなコードはよくないのかもれないけれど、ひとまず解決。
- 投稿日:2020-03-28T20:41:29+09:00
Rubyでナノ秒、PostgreSQLだとマイクロ秒
環境: circleci/ruby:2.4.2-stretch-node-browsers, circleci/postgres:10.12
とあるアプリケーションで、新しくCircleCIを設定してRspecを動かしてみたら、エラーが大量に出ました。
expected: 2020-03-25 12:36:20.477148202 +0900 got: 2020-03-25 12:36:20.477148000 +0900Rubyはナノ秒(小数点以下9桁)まで時刻を保持し、PostgreSQLはマイクロ秒(小数点以下6桁)までしか保持しないので、比較が失敗しまくっていると判明。ひえー、どうすんのこれ。助けて!
用語
参照: Orders_of_magnitude_(time)
名前 単位 例 ミリ秒 10-3 秒 0.123 マイクロ秒 10-6 秒 0.123456 ナノ秒 10-9 秒 0.123456789 以下、3桁ずつピコ 10-12、フェムト 10-15、アット 10-18、ゼプト 10-21、ヨクト 10-24と続きます。
Macでは
Mac(rbenvでインストール)では、RubyのTime#nsecはナノ秒(9桁)を表しますが、実際はマイクロ秒(6桁)で切り取られるので、Postgresとの違いに気づきませんでした。
t = Time.now t.to_f #=> 1585394326.856343 t.nsec #=> 856343000Ubuntuだとナノ秒までしっかり入ります。
t = Time.now t.to_f #=> 1585394640.8082483 t.nsec #=> 808248257対策
Time#<=> を上書きしてミリ秒で比較することでエラーを減らすことができました。<=> を上書きするだけで、== も != も < も対策できます。Time#<=> を上書きすると、ActiveSupport::TimeWithZone の比較にも使われます。
マイクロ秒で比較(floor(6))ではうまく行きませんでした。
spec/supports/time_patch_helper.rbif ENV['CIRCLECI'] class Time alias_method :old_cmp, :<=> def <=>(val) if val.try(:acts_like_time?) self.to_f.floor(3) <=> val.to_f.floor(3) else old_cmp(val) end end end endちなみにRubyには時刻の小数点以下を丸めるメソッド Time#round があり、Ruby 2.7 では切り落としを行う Time#floorが追加されています。
今後やりたいこと
- 上記の対策よりもうまい方法を考える。
- Rubyのソースコードをチェックしたり、Linuxでコンパイルしてみたりする(たぶんC言語の#defineに違いがある)。
- 投稿日:2020-03-28T18:33:00+09:00
応用 Lesson2 検索力についてのまとめ
応用カリキュラム Lesson2対応 リンク集
エンジニアにとって検索力がとても重要なことを学び、
テキストにあるワードを検索してみました。初投稿
まずQiitaの使い方からよく分かりませんので、その辺は徐々に。
とりあえず自身のアウトプットとして書きます。
どなたかの何かのお役に立てたら光栄です。では、ワードごとにリンクを貼っていきます。
あえて、ワードに対する答えは載せません。
あくまでリンク集です。(自分のためにも…)【リンク集 ~Link Page〜】
1. jQueryのメリット・デメリットは簡単に言うと?
<検索ワード>
jQuery メリット<参考サイト>
AtoOne 様
https://www.atoone.co.jp/column/10176/2. Queryは最近のトレンドの中でどのような立ち位置か?
<検索ワード>
jQuery トレンド 立ち位置<参考サイト>
Hideout der Eremit 様
https://dereremit.jp/archives/2603. deviseとは簡単に言うと?
<検索ワード>
deviceとは<参考サイト>
キツネの惑星 様
https://kitsune.blog/rails-devise4. deviseと同様な機能で他によく使われるものは何か?違いは何か?なぜdevise以外にそれが生まれたか?
これ、めちゃくちゃ苦戦しました。
もっと分かりやすい、的確なサイトがあると思います。<検索ワード>
rails 認証 gem トレンド …他多数?<参考サイト>
Tech Blog 様
https://www.for-engineer.life/entry/rails-sorcery/5. unicornとは簡単に言うと何か?
6. unicornと同様な機能で他によく使われるものは何か?違いは何か?なぜunicorn以外にそれが生まれたか?
<検索ワード>
unicornとは<参考サイト>
@takahiro1127 様
https://qiita.com/takahiro1127/items/fcb81753eaf381b4b33c〜気づき〜
検索力にはある程度自信を持っていましたが、
信憑性のある、確からしい情報を得るためには
テクニックがあることを知りました。同時に自分の無力さに愕然(笑)
今後も学習のノート代わり、アウトプットとして活用させていただきます。
- 投稿日:2020-03-28T18:29:22+09:00
Rails6でアプリ起動時にWebpacker configuration file not foundエラーが発生した時の対処方法
執筆背景
scaffoldを使って簡単なアプリを作ろうと以下のコマンドを打ち込むと、Webpackerがインストールされていないというエラーが発生したため、記述しておこうと思う。
$ rails new app_name $ cd app $ rails g scaffold post content:text $ rails s (中略) Webpacker configuration file not found /Users/ユーザ名/app_name/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /Users/ユーザ名/app_name/config/webpacker.yml (RuntimeError)ちなみに、このときのrubyのバージョンは2.7.0、railsのバージョンは、6.0.2.2です。
結論
このエラーは、webpackerがインストールされていない場合に起こるエラーみたいです(rails6からwebpackerが標準になったため)。
webpackerのインストールには、Node.jsとYarnのインストールが必要で、これらをインストールした上でwebpackerをインストールすると解決します。
作業内容
Gemfileを確認し、gem 'webpacker'の記述を確認。
Gemfilegem 'webpacker', '~> 4.0'bundle installしたのち、webpackerをインストールしようと試みるもNode.jsがインストールされていないと怒られる。
$ bundle install $ rails webpacker:install sh: node: command not found sh: nodejs: command not found Node.js not installed. Please download and install Node.js https://nodejs.org/en/download/Node.jsをインストールしたのち、再度webpackerのインストールを試みると、Yarnをインストールしろと怒られる。
$ brew install node (中略) ==> node Bash completion has been installed to: /usr/local/etc/bash_completion.d $ rails webpacker:install Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/Yarnをインストールしたのち、再度webpackerのインストールを試みたところインストールすることができた。
$ sudo npm install -g yarn added 1 package in 3.509s $ yarn -v 1.22.4 $ yarn install success Saved lockfile. ✨ Done in 3.49s. $ rails webpacker:install ✨ Done in 6.34s. Webpacker successfully installed ? ?アプリを再起動。
$ rails s Use Ctrl-C to stop Started GET "/posts/index" for ::1 at 2020-03-28 18:06:09 +0900
- 投稿日:2020-03-28T18:09:39+09:00
【Ruby】インスタンスメソッド・クラスメソッド・特異メソッドの違い
インスタンスメソッド、クラスメソッド、特異メソッドの違いについてまとめてみました!!
インスタンスメソッドとは?
- インスタンスから呼べるメソッドのこと
定義の仕方と解説
Human.instance_methods.grep(/say_hello/)でメソッドがあることがわかるのでインスタンスメソッドクラスメソッドとは?
- クラスから呼べるメソッドのこと
定義の仕方と解説1
定義の仕方と解説2
クラスメソッドのselfはクラスそのものです。
厳密にいうとselfが書かれた場所に参照するレシーバを返します。
selfの代わりにクラス名をそのまま使ってもクラスメソッドになります。Human.methods.grep(/say_hello/)でメソッドがあることがわかるのでクラスメソッド
Human.singleton_methods.grep(/say_hello/)でメソッドがあることがわかるので特異メソッド(シングルトンメソッド
上記の検証からクラスメソッドは特異メソッドであることがわかります。
特異メソッド(シングルトンメソッド)とは?
- 1つのオブジェクト固有のメソッド
- クラスメソッドは特異メソッド
特異メソッドがオブジェクト固有のメソッドとは?
- オブジェクト固有とはオブジェクトIDが同じということ
- インスタンスのオブジェクトIDは一つ一つ違う
コードで説明
- インスタンスのobject_idをみるとそれぞれ違う。クラス、モジュールは同じ
- classメソッドはオブジェクトのクラスが確認できる
- ancestorsメソッドは継承関係を確認できる
- object_idは各オブジェクトに対して一意な整数を確認できる
class Human
endp BasicObject.class
=> Class
p BasicObject.ancestors
=> [BasicObject]
p BasicObject.object_id
=> 580
p BasicObject.object_id
=> 580
BasicObjectはnewメソッドがなくインスタンス化できない
❌ p BasicObject.new.object_id
❌ p BasicObject.new.object_idp Kernel.class
=> Module
p Kernel.ancestors
=> [Kernel]
p Kernel.object_id
=> 600
p Kernel.object_id
=> 600
Kernelはモジュールのためnewメソッドがなくインスタンス化できない
❌ p Kernel.new.object_id
❌ p Kernel.new.object_idp Object.class
=> Class
p Object.ancestors
=> [Object, Kernel, BasicObject]
p Object.object_id
=> 60
p Object.object_id
=> 60
p Object.new.object_id
=> 620
p Object.new.object_id
=> 640
p Human.class
=> Class
p Human.ancestors
=> [Human, Object, Kernel, BasicObject]
p Human.object_id
=> 660
p Human.object_id
=> 660
p Human.new.object_id
=> 680
p Human.new.object_id
=> 700参考
- 投稿日:2020-03-28T16:22:21+09:00
【ruby,rails】環境変数から読み込んだ秘密鍵の文字列の改行コードがエスケープされていて秘密鍵としてパースできないときの対処法
概要
環境変数から読み込んだ秘密鍵の文字列の改行コードがエスケープされてしまうことによって、秘密鍵としてパースできないときの文字列処理を共有します。
解決策
たとえば下記のような秘密鍵があったとき、
-----BEGIN RSA PRIVATE KEY----- MIGrAgEAAiEAvnrd8LBnzAGxCW+i7KtVQSiTsssMtbwcs5styeKsn2kCAwEAAQIh AKBF8glb5Xqa0cQG0ygg4hIFdipmvEJhiCuhX93krDCBAhEA51bAM0gFPvxyk9Xe ioIOBQIRANLJEv4Xw7MwT7EEEARL5RUCEBa8bu1bUbCsDPK8nT+NoqUCEQCIzFCU MY4j7BW8N3vBnhPlAhBgs4tSfe6RbpertixmCygk -----END RSA PRIVATE KEY-----環境変数に設定するときは下記のように改行コードを使って、文字列を1行にして設定すると思います。
-----BEGIN RSA PRIVATE KEY-----\nMIGrAgEAAiEAvnrd8LBnzAGxCW+i7KtVQSiTsssMtbwcs5styeKsn2kCAwEAAQIh\nAKBF8glb5Xqa0cQG0ygg4hIFdipmvEJhiCuhX93krDCBAhEA51bAM0gFPvxyk9Xe\nioIOBQIRANLJEv4Xw7MwT7EEEARL5RUCEBa8bu1bUbCsDPK8nT+NoqUCEQCIzFCU\nMY4j7BW8N3vBnhPlAhBgs4tSfe6RbpertixmCygk\n-----END RSA PRIVATE KEY-----\nこの1行にした秘密鍵をアプリケーション内で読み込むと、下記のようにバックスラッシュが2つになってしまいます。
"-----BEGIN RSA PRIVATE KEY-----\\nMIGrAgEAAiEAvnrd8LBnzAGxCW+i7KtVQSiTsssMtbwcs5styeKsn2kCAwEAAQIh\\nAKBF8glb5Xqa0cQG0ygg4hIFdipmvEJhiCuhX93krDCBAhEA51bAM0gFPvxyk9Xe\\nioIOBQIRANLJEv4Xw7MwT7EEEARL5RUCEBa8bu1bUbCsDPK8nT+NoqUCEQCIzFCU\\nMY4j7BW8N3vBnhPlAhBgs4tSfe6RbpertixmCygk\\n-----END RSA PRIVATE KEY-----\\n"このまま秘密鍵オブジェクトを作成しようとすると、エラーになります。
OpenSSL::PKey::RSA.new(str) => OpenSSL::PKey::RSAError: Neither PUB key nor PRIV key: nested asn1 errorそこで、この2つのバックスラッシュを1つのバックスラッシュに置換することで、秘密鍵オブジェクを作成することができるようになります。
str.gsub(/\\n/, "\n") => "-----BEGIN RSA PRIVATE KEY-----\nMIGrAgEAAiEAvnrd8LBnzAGxCW+i7KtVQSiTsssMtbwcs5styeKsn2kCAwEAAQIh\nAKBF8glb5Xqa0cQG0ygg4hIFdipmvEJhiCuhX93krDCBAhEA51bAM0gFPvxyk9Xe\nioIOBQIRANLJEv4Xw7MwT7EEEARL5RUCEBa8bu1bUbCsDPK8nT+NoqUCEQCIzFCU\nMY4j7BW8N3vBnhPlAhBgs4tSfe6RbpertixmCygk\n-----END RSA PRIVATE KEY-----\n" OpenSSL::PKey::RSA.new(str.gsub(/\\n/, "\n")) => #<OpenSSL::PKey::RSA:0x00007f9d7dd734e0>もちろん、秘密鍵だけではなく、改行コードのバックスラッシュがエスケープされたすべての文字列に対しても同様の解決策が有効です。
- 投稿日:2020-03-28T16:15:10+09:00
ユーザー管理に必須!Gemの『devise』を使おう!
Railsにおいてユーザーを管理する際に必要となるのが『devise』と呼ばれるGemです。
deciseの設定を忘れてしまわないように備忘録として記述します。
deviseの機能
このGemを使用することによって、ユーザーを管理する機能を追加する際に簡単に管理を行うことができるようになります。
deviseの設定
1.Gemfileへの記述
# 省略 gem 'devise' # 最終行に追記2.ターミナルにてbundle install
$ bundle installbundle installでGemを更新・反映する。
3.deviseの設定ファイルを作成
$ rails g devise:install4.deviseの機能を持ったUserモデルの作成
注意したいのがここ!通常のrails g model 〇〇ではないという点です。
ここでは下記のように記述してUserモデルを作成します。
# deviseコマンドでモデルを作成 $ rails g devise userこれでdevise機能を持ったUserモデルが作成されました。
またroutes.rbには自動的に下記のコードが追加されます。
config.routes.rbRails.application.routes.draw do devise_for :users #以下略devise_for :usersの記述によりユーザーのログイン・ログアウト、新規登録で必要なルーティングが作成されます。
5.rails db:migrateを行う
ターミナル上でrails db:migrateを行います。
# 作成されたマイグレーションファイルを実行 $ rails db:migrate上記を行うことでデータベース上にusersテーブルが作成されます。
まとめ
deviseの設定をスムーズに行えるようになりました。ただしユーザーのログイン・ログアウト・新規登録に関するバックエンドの実装に関しては曖昧な部分もあるため、追記で記述しアウトプットの機会を持ちたいと考えています。
- 投稿日:2020-03-28T15:47:01+09:00
Rails6 + CentOS8にRMagickをサクッと導入する ( MiniMagicも)
railsで画像アップロード機能を作ったとき、
「クソッ、画像が回転してしまう!!!」
という場面にぶち当たる方、多くいらっしゃると思います。そこで軽くググって、
「RMagickを導入すればいいのか!!!」
↓
「gem "rmagick"」、bundle install
↓
なにかしらのエラーが発生。
「できねえじゃねえか!!!」というのを経験した方に読んでいただければといいと思います。
バージョン、前提条件
Ruby 2.6.5
Ruby on Rails 6.0.2.1
CentOS 8なお、画像アップロードにはActiveStorageは使わず、carrierwaveを使っています。(carrierwaveの説明はここではしません。)
余談ですが、carrierwaveではなくActiveStorageを使うメリットってなんですかね...
もしもActiveStorage絶対使うマンの方いらっしゃれば教えてください。。。CentOS8にImageMagickをダウンロード
1. 開発ツールを入れます。
$ sudo dnf groupinstall 'Development Tools'2. wgetでソースをインストール
$ wget https://www.imagemagick.org/download/ImageMagick.tar.gz
3. ソースの解凍
$ tar xvfz ImageMagick.tar.gz4.コンパイル
$ cd ImageMagick-7.0.10-2/ $ ./configure $ make $ make install5.パスの設定
$ vi .bash_profile $ export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig $ export LD_LIBRARY_PATH=/usr/local/lib $ source .bash_profileGemに追加
Gemfilegem "rmagick"$ bundle installこんだけです。
追記: rails g taskでエラーがでたため、MiniMagickに鞍替え
rails g taskで以下のエラーが出た。
/home/user/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap- 1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require': libMagickCore-7.Q16HDRI.so.7: cannot open shared object file: No such file or directory - /home/user/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/rmagick-4.1.0/lib/RMagick2.so (LoadError)...は?ちょっと分からなかったため、MiniMagickを使うことに。(スミマセン、もうちょっと調べてきます。。。)
Gemfile# gem "rmagick" gem "minimagick"また、uploader(ここは解説なし)も変更
# include CarrierWave::RMagick include CarrierWave::MiniMagick参考
https://www.rootlinks.net/2019/11/08/install-imagemagick-on-centos-8/
https://qiita.com/tajihiro/items/3f10c59f19fcd0a9e660
- 投稿日:2020-03-28T15:47:01+09:00
Rails6 + CentOS8にRMagickをサクッと導入する
railsで画像アップロード機能を作ったとき、
「クソッ、画像が回転してしまう!!!」
という場面にぶち当たる方、多くいらっしゃると思います。そこで軽くググって、
「RMagickを導入すればいいのか!!!」
↓
「gem "rmagick"」、bundle install
↓
なにかしらのエラーが発生。
「できねえじゃねえか!!!」というのを経験した方に読んでいただければといいと思います。
バージョン、前提条件
Ruby 2.6.5
Ruby on Rails 6.0.2.1
CentOS 8なお、画像アップロードにはActiveStorageは使わず、carrierwaveを使っています。(carrierwaveの説明はここではしません。)
余談ですが、carrierwaveではなくActiveStorageを使うメリットってなんですかね...
もしもActiveStorage絶対使うマンの方いらっしゃれば教えてください。。。CentOS8にImageMagickをダウンロード
1. 開発ツールを入れます。
$ sudo dnf groupinstall 'Development Tools'2. wgetでソースをインストール
$ wget https://www.imagemagick.org/download/ImageMagick.tar.gz
3. ソースの解凍
$ tar xvfz ImageMagick.tar.gz4.コンパイル
$ cd ImageMagick-7.0.10-2/ $ ./configure $ make $ make install5.パスの設定
$ vi .bash_profile $ export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig $ export LD_LIBRARY_PATH=/usr/local/lib $ source .bash_profileGemに追加
Gemfilegem "rmagick"$ bundle installこんだけです。
参考
https://www.rootlinks.net/2019/11/08/install-imagemagick-on-centos-8/
https://qiita.com/tajihiro/items/3f10c59f19fcd0a9e660
- 投稿日:2020-03-28T15:46:47+09:00
Railsチュートリアル〜6章〜ついにモデル作成へ突入
userモデルの作成
5章を足早に終わらせて続いて6章に入っていきます。
ここではユーザー登録ページを作成していく。
・ユーザー用のデータモデルの作成
・データを保存する手段の確保について学ぶ。Railsではデータモデルとして扱うデフォルトのデータ構造のことをモデルと呼ぶ。Railsでは、データを永続化するデフォルトの解決策としてデータベースを使ってデータを長期間保存する。データベースとやり取りをするデフォルトのライブラリはActive Recordという。
Active Recordはデータオブジェクトの作成・保存・検索のためのメソッドを持っている。データベースの移行
今回の目的は永続性のあるモデルを構築することである。
Railsでユーザーをモデリングする時は属性を明示的に識別する必要がなく、デフォルトでリレーショナルデータベースを使用する。リレーショナルデータベースはデータ行で構成されるテーブルからなり、各行はデータ属性のカラム(列)を持つ。nameとemailのカラムをもつusersテーブルを作成する。Userモデルを生成する $ rails generate model User name:string email:string invoke active_record create db/migrate/20160523010738_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.ymlgenerateコマンドの新しい結果の一つとして、マイグレーションと呼ばれるファイルが生成される。マイグレーションはデータベースの構造をインクリメンタルに変更する手段を提供する。(インクリメンタル=次第に増やす)要するにマイグレーションはデータベースの構造増やす手段を提供すること。
マイグレーション自体は、データベースに与える変更を定義したchangeメソッドの集まりである。changeメソッドはcreate_tableというRailsのメソッドを呼びユーザーを保存するためのテーブルをデータベースに作成する。
このメソッドはブロック変数を1つ持つブロックを受け取る。このブロックの中でnameとemailカラムをデータベースに作る。
モデル名は単数形(User)だがテーブル名は複数型(users)である。
ブロックの最後の行t.timestampsは特別なコマンドで、created_atとupdated_atという2つの「マジックカラム (Magic Columns)」を作成します。これらは、あるユーザーが作成または更新されたときに、その時刻を自動的に記録するタイムスタンプである。マイグレーションは、次のようにdb:migrateコマンドを使い実行することができる。これを「マイグレーションの適応」と呼ぶ。
$ rails db:migratedb:migrateが実行されると、db/development.sqlite3という名前のファイルが生成されます。これはSQLite5の実体である。development.sqlite3ファイルを開くためのDB Browser for SQLiteというツールを使うと、データベースの構造を見ることができる。
ほぼすべてのマイグレーションは、元に戻すことが可能
元に戻すことを「ロールバック (rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。$ rails db:rollbackmodelファイル
ユーザーオブジェクトの検索
User.find(1) User.findにユーザーのidを渡し,その結果Active Recordはそのidのユーザーを返します。 一般的なfindメソッド以外に、Active Recordには特定の属性でユーザーを検索する方法もあります。 User.find_by(email: "mhartl@example.com") >> User.first => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2016-05-23 19:05:58", updated_at: "2016-05-23 19:05:58"> firstはデータベースの最初のユーザーを返す。 >> User.all => #<ActiveRecord::Relation [#<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2016-05-23 19:05:58", updated_at: "2016-05-23 19:05:58">, #<User id: 2, name: "A Nother", email: "another@example.org", created_at: "2016-05-23 19:18:46", updated_at: "2016-05-23 19:18:46">]> User.allでデータベースのすべてのUserオブジェクトが返ってくることがわかります。また、返ってきたオブジェクトのクラスがActiveRecord::Relationとなっています。これは、各オブジェクトを配列として効率的にまとめてくれるクラスである。ユーザーオブジェクトの更新
基本的な更新の方法は2つである。
>> user # userオブジェクトが持つ情報のおさらい => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2016-05-23 19:05:58", updated_at: "2016-05-23 19:05:58"> >> user.email = "mhartl@example.net" => "mhartl@example.net" >> user.save => true変更をデータベースに保存するために最後にsaveを実行する必要があることを忘れない。
保存を行わずにreloadを実行すると、データベースの情報を元にオブジェクトを再読み込みするので失敗する。属性を更新するもう1つの方法は、update_attributesを使うケース
>> user.update_attributes(name: "The Dude", email: "dude@abides.org") => true >> user.name => "The Dude" >> user.email => "dude@abides.org"update_attributesメソッドは属性のハッシュを受け取り、成功時には更新と保存を続けて同時に行う。
特定の属性のみを更新したい場合は、次のようにupdate_attributeを使います。このupdate_attributeには、検証を回避するといった効果もある。>> user.update_attribute(:name, "El Duderino") => true >> user.name => "El Duderino"ユーザーの検証
UserモデルにNameとemailの属性を与えた。ここに制限を加えてデータを登録する際に適切な管理が行われるようにする。(具体的には名前を空にしない、emailはアドレスのフォーマットに従う。メールアドレスがデータベース内で重複しない。等)
Active Recordでは検証(バリデーション)という機能を通して、こういった制約を課すことができる。
よく使われるケース
存在性(presence)
長さ(length)
フォーマット(format)
一意性(uniqueness)
確認(confirmation)
の検証である。有効性の検証
有効なモデルのオブジェクトを作成しその属性のうちの一つを有効でない属性に意図的に変更する。→バリデーション機能で失敗するかどうかテストする。
デフォルトのUserテストrequire 'test_helper' class UserTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end有効なオブジェクトに対してテストを書くためにsetupという特殊なメソッドを使って有効なUserオブジェクト(@user)を作成する。setupメソッド内に書かれた処理は各テストが走る直前に実行される。@userはインスタンス変数だが、setupメソッド内で宣言しておけばすべてのテスト内でインスタンス変数が使用できる。
require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com") end test "should be valid" do assert @user.valid? end endassertメソッドを使ってテストする。
@user.valid?がtrueを返すと成功し、falseを返すと失敗する。$ rails test:models modelに関するテストはこのコマンドを使用する。存在性の検証
最も基本的なバリデーションである。
渡された属性が存在することを検証する。ここではユーザーがデータベースに保存される前にname emailフィールドの両方が存在することを保証する。require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com") end test "should be valid" do assert @user.valid? end test "name should be present" do @user.name = " " assert_not @user.valid? end test "email should be present" do @user.email = " " assert_not @user.valid? end endname属性の存在性に関するテストを追加します。具体的には@user変数のname属性に対して空白の文字列をセット
assert_notメソッドを使って Userオブジェクトが有効でなくなったことを確認する。name属性の存在性を検証する。
class User < ApplicationRecord validates :name, presence: true end長さの検証
ここではユーザーモデル上の名前の長さを50字を上限とし、51文字以上の名前
emailの文字を255字を上限とするように長さの制限をかける。
nameの長さの検証に用いるテストrequire 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com") end . . . test "name should not be too long" do @user.name = "a" * 51 assert_not @user.valid? end test "email should not be too long" do @user.email = "a" * 244 + "@example.com" assert_not @user.valid? end end長さの検証を追加する
class User < ApplicationRecord validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 } endフォーマットの検証
name属性の検証には、空文字でない、名前が51文字未満であるという最小限の制約しかいまのところない。
email属性の場合は、有効なメールアドレスかどうかを判定するために、もっと厳重な要求を満たさなければなりません。ここではメールアドレスにおなじみのパターンuser@example.comに合っているかどうかも確認する。(なお今回ここで使うテストや検証は、形式があからさまに無効なものを拒否するだけであり、すべての有効なメールアドレスを受け入れられるものではない点に注意してください。)>> %w[foo bar baz] => ["foo", "bar", "baz"] >> addresses = %w[USER@foo.COM THE_US-ER@foo.bar.org first.last@foo.jp] => ["USER@foo.COM", "THE_US-ER@foo.bar.org", "first.last@foo.jp"] >> addresses.each do |address| ?> puts address >> end USER@foo.COM THE_US-ER@foo.bar.org first.last@foo.jp``メールアドレスのバリデーションは扱いが難しく、エラーが発生しやすい部分なので、有効なメールアドレスと無効なメールアドレスをいくつか用意して、バリデーション内のエラーを検知していきます。具体的には、user@example,comのような無効なメールアドレスが弾かれることと、user@example.comのような有効なメールアドレスが通ることを確認しながら、バリデーションを実装していきます (ちなみに今の状態では、空でない文字列はすべてメールアドレスとして通ってしまいます) 。
require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com") end . . . test "email validation should accept valid addresses" do valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org first.last@foo.jp alice+bob@baz.cn] valid_addresses.each do |valid_address| @user.email = valid_address assert @user.valid?, "#{valid_address.inspect} should be valid" end end endここでは、assertメソッドの第2引数にエラーメッセージを追加していることに注目してください。これによって、どのメールアドレスでテストが失敗したのかを特定できるようになります。 assert @user.valid?, "#{valid_address.inspect} should be valid次に、user@example,com (ドットではなくカンマになっている) やuser_at_foo.org (アットマーク ‘@’ がない) といった無効なメールアドレスを使って 「無効性 (Invalidity)」についてテストしていく。
require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com") end . . . test "email validation should reject invalid addresses" do invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com] invalid_addresses.each do |invalid_address| @user.email = invalid_address assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" end end endメールフォーマットを正規表現で検証する。
class User < ApplicationRecord validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } end一意性を検証する
メールアドレスの一意性を強制するために (ユーザー名として使うために)、validatesメソッドの:uniqueオプションを使う。
今回のテストではRubyのオブジェクトを作るだけでなく、実際にメモリ上にレコードをデータベースに登録する必要がある。重複するメールアドレス拒否のテスト
require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com") end . . . test "email addresses should be unique" do duplicate_user = @user.dup duplicate_user.email = @user.email.upcase @user.save assert_not duplicate_user.valid? end end上のコードでは、@userと同じメールアドレスのユーザーは作成できないことを、@user.dupを使ってテストしています。dupは、同じ属性を持つデータを複製するためのメソッドです。@userを保存した後では、複製されたユーザーのメールアドレスが既にデータベース内に存在するため、ユーザの作成は無効になるはずである。
メールアドレスの一意性を検証する
emailのバリデーションにuniqueness: trueというオプションの追加class User < ApplicationRecord validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true endメールアドレスの大文字小文字を無視した一意性の検証
app/models/user.rb class User < ApplicationRecord validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } end trueをcase_sensitive: falseに置き換えデータベースのインデックス
データベースにカラムを作成するとき、そのカラムでレコードを検索する (find) 必要があるかどうかを考えることは重要です。例えば、リスト 6.2のマイグレーションによって作成された email属性について考えてみましょう。第7章ではユーザーをサンプルアプリにログインできるようにしますが、このとき、送信されたものと一致するメールアドレスのユーザーのレコードをデータベースの中から探しだす必要があります。
インデックスなどの機能を持たない素朴なデータモデルにおいてユーザーをメールアドレスで検索するには、データベースのひとりひとりのユーザーの行を端から順に読み出し、そのemail属性と渡されたメールアドレスを比較するという非効率的な方法しかない。emailカラムにインデックスを追加することで、この問題を解決することができます。(インデックスは本の索引機能のようなもの)
emailインデックスを追加すると、データモデリングの変更が必要になります。Railsではマイグレーションでインデックスを追加します。Userモデルを生成すると自動的に新しいマイグレーションが作成されたことを思い出してください。
今回の場合は、既に存在するモデルに構造を追加するので、次のようにmigrationジェネレーターを使ってマイグレーションを直接作成する必要があります。$ rails generate migration add_index_to_users_emailユーザー用のマイグレーションと異なり、メールアドレスの一意性のマイグレーションは未定義になっています。リスト 6.29のように定義を記述する必要があります16。
メールアドレスの一意性を強制するためのマイグレーション
db/migrate/[timestamp]_add_index_to_users_email.rb class AddIndexToUsersEmail < ActiveRecord::Migration[5.0] def change add_index :users, :email, unique: true end end上のコードでは、usersテーブルのemailカラムにインデックスを追加するためにadd_indexというRailsのメソッドを使っています。インデックス自体は一意性を強制しませんが、オプションでunique: trueを指定することで強制できるようになります。
最後に、データベースをマイグレートします。
$ rails db:migrateこの時点では、テストDB用のサンプルデータが含まれているfixtures内で一意性の制限が保たれていないため、テストは red になります。つまり、リスト 6.1でユーザー用のfixtureが自動的に生成されていましたが、ここのメールアドレスが一意になっていないことが原因です (リスト 6.30) (実はこのデータはいずれも有効ではありませんが、fixture内のサンプルデータはバリデーションを通っていなかったので今まで問題にはなっていなかっただけでした)。
リスト 6.30: Userのデフォルトfixture red test/fixtures/users.yml # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/ # FixtureSet.htmlこれで1つの問題が解決されましたが、メールアドレスの一意性を保証するためには、もう1つやらなければならないことがあります。それは、いくつかのデータベースのアダプタが、常に大文字小文字を区別するインデックス を使っているとは限らない問題への対処です。例えば、Foo@ExAMPle.Comとfoo@example.comが別々の文字列だと解釈してしまうデータベースがありますが、私達のアプリケーションではこれらの文字列は同一であると解釈されるべきです。この問題を避けるために、今回は「データベースに保存される直前にすべての文字列を小文字に変換する」という対策を採ります。例えば"Foo@ExAMPle.CoM"という文字列が渡されたら、保存する直前に"foo@example.com"に変換してしまいます。これを実装するためにActive Recordのコールバック (callback) メソッドを利用します。このメソッドは、ある特定の時点で呼び出されるメソッドです。今回の場合は、オブジェクトが保存される時点で処理を実行したいので、before_saveというコールバックを使います。これを使って、ユーザーをデータベースに保存する前にemail属性を強制的に小文字に変換します17。作成したコードをリスト 6.32に示します。(本チュートリアルで初めて紹介したテクニックですが、このテクニックについては11.1でもう一度取り上げます。そこではコールバックを定義するときにメソッドを参照するという慣習について説明します。)
email属性を小文字に変換してメールアドレスの一意性を保証する
app/models/user.rb class User < ApplicationRecord before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } endbefore_saveコールバックにブロックを渡してユーザーのメールアドレスを設定します。設定されるメールアドレスは、現在の値をStringクラスのdowncaseメソッドを使って小文字バージョンにしたもの。
セキュアなパスワードを追加する
セキュアパスワードという手法では、各ユーザーにパスワードとパスワードの確認を入力させ、それを (そのままではなく) ハッシュ化したものをデータベースに保存します。要するにハッシュ関数を使って、入力されたデータを元に戻せない (不可逆な) データにする処理を指す。
ユーザーの認証は、パスワードの送信、ハッシュ化、データベース内のハッシュ化された値との比較、という手順で進んでいきます。比較の結果が一致すれば、送信されたパスワードは正しいと認識され、そのユーザーは認証されます。。こうすることで、生のパスワードをデータベースに保存するという危険なことをしなくてもユーザー登録できる。
ハッシュ化されたパスワード
セキュアなパスワードの実装はclass User < ApplicationRecord . . . has_secure_password end上のようにモデルにこのメソッドを追加すると、次のような機能が使えるようになる。
・セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。
・2つのペアの仮想的な属性 (passwordとpassword_confirmation) が使えるようになる。存在性と値が一致するかどうかのバリデーションも追加される。
・authenticateメソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド) 。has_secure_password機能を使えるようにするには、モデル内にpassword_digestという属性を含める。
password_digestカラム用の適切なマイグレーションを生成。
マイグレーション名は自由に指定できる。add_password_digest_to_usersというマイグレーションファイルを生成するためには、次のコマンドを実行します。$ rails generate migration add_password_digest_to_users password_digest:string上のコマンドではpassword_digest:stringという引数を与えて、今回必要になる属性名と型情報を渡している。
password_digestカラムを追加するマイグレーション
db/migrate/[timestamp]_add_password_digest_to_users.rb class AddPasswordDigestToUsers < ActiveRecord::Migration[5.0] def change add_column :users, :password_digest, :string end endリスト 6.35では、add_columnメソッドを使ってusersテーブルpassword_digestカラムを追加しています。これを適用させるには、データベースでマイグレーションを実行します。
$ rails db:migrateまた、has_secure_passwordを使ってパスワードをハッシュ化するためには、最先端のハッシュ関数であるbcryptが必要になります。パスワードを適切にハッシュ化することで、たとえ攻撃者によってデータベースからパスワードが漏れてしまった場合でも、Webサイトにログインされないようにできます。サンプルアプリケーションでbcryptを使うために、bcrypt gemをGemfileに追加します。
リスト 6.36: bcryptをGemfileに追加する
source 'https://rubygems.org' gem 'rails', '5.1.6' gem 'bcrypt', '3.1.12' . . .最後にbundle installを実行
6.3.2 ユーザーがセキュアなパスワードを持っている
Userモデルにpassword_digest属性を追加し、Gemfileにbcryptを追加したことで、ようやくUserモデル内でhas_secure_passwordが使えるようになりましたUserモデルにhas_secure_passwordを追加する
app/models/user.rb class User < ApplicationRecord before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password end次にユーザーモデルにパスワードとパスワード確認を追加する。
def setup @user = User.new(name: "Example User", email: "user@example.com") endテストをパスさせるために、パスワードとパスワード確認の値を追加します。
リスト 6.39: パスワードとパスワード確認を追加する
test/models/user_test.rb require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . endリスト 6.41: パスワードの最小文字数をテストする red
test/models/user_test.rb require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . test "password should be present (nonblank)" do @user.password = @user.password_confirmation = " " * 6 assert_not @user.valid? end test "password should have a minimum length" do @user.password = @user.password_confirmation = "a" * 5 assert_not @user.valid? end endここで、次のような多重代入 (Multiple Assignment) を使っていることに注目してください。 @user.password = @user.password_confirmation = "a" * 5これはpasswordとpasswordconfirmationに対して同時に代入をしています
リスト 6.16ではmaximumを使ってユーザー名の最大文字数を制限していましたが、これと似たような形式のminimumというオプションを使って、最小文字数のバリデーションを実装することができます。
validates :password, length: { minimum: 6 }また、空のパスワードを入力させないために、存在性のバリデーションも一緒に追加します。結果として、Userモデルのコードはリスト 6.42のようになります。ちなみにhas_secure_passwordメソッドは存在性のバリデーションもしてくれるのですが、これは新しくレコードが追加されたときだけに適用される性質を持っています。したがって、例えばユーザーが ' ' (6文字分の空白スペース) といった文字列をパスワード欄に入力して更新しようとすると、バリデーションが適用されずに更新されてしまう問題が発生してしまうのです。
セキュアパスワードの完全な実装
app/models/user.rb class User < ApplicationRecord before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 } endこの時点では、テストは greenになるはずです。
ユーザーの作成と認証
以上でUserモデルの基本部分が完了しましたので、今度は7.1でユーザー情報表示ページを作成するときに備えて、データベースに新規ユーザーを1人作成しましょう。また、Userモデルにhas_secure_passwordを追加した効果についても (例えばauthenticateメソッドの効果なども) 見ていきましょう。ただしWebからのユーザー登録はまだできない (第7章で完成させます) ので、今回はRailsコンソールを使ってユーザーを手動で作成することにしましょう。6.1.3で説明したcreateを使いますが、後々実際のユーザーを作成する必要が出てくるので、今回はサンドボックス環境は使いません。したがって、今回作成したユーザーを保存すると、データベースに反映されます。それでは、まずrails consoleコマンドを実行してセッションを開始し、次に有効な名前・メールアドレス・パスワード・パスワード確認を渡してユーザーを作成してみましょう。
$ rails console >> User.create(name: "Michael Hartl", email: "mhartl@example.com", ?> password: "foobar", password_confirmation: "foobar") => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2016-05-23 20:36:46", updated_at: "2016-05-23 20:36:46", password_digest: "$2a$10$xxucoRlMp06RLJSfWpZ8hO8Dt9AZXlGRi3usP3njQg3...">うまくデータベースに保存されたかどうかを確認するために、開発環境用のデータベースをDB Browser for SQLiteで開き、usersテーブルの中身を見てみましょう (図 6.9)21。もしクラウドIDEを使っている場合は、データベースのファイルをダウンロードして開いてください (図 6.5)。このとき、先ほど定義したUserモデルの属性 (図 6.8) に対応したカラムがあることにも注目しておいてください。
images/figures/sqlite_user_row_with_password_4th_edition 図 6.9: SQLiteデータベースdb/development.sqlite3に登録されたユーザーの行 コンソールに戻ってpassword_digest属性を参照してみると、リスト 6.42のhas_secure_passwordの効果を確認できます。 >> user = User.find_by(email: "mhartl@example.com") >> user.password_digest => "$2a$10$xxucoRlMp06RLJSfWpZ8hO8Dt9AZXlGRi3usP3njQg3yOcVFzb6oK"これは、Userオブジェクトを作成したときに、"foobar"という文字列がハッシュ化された結果です。bcryptを使って生成されているので、この文字列から元々のパスワードを導出することは、コンピュータを使っても非現実的です
また6.3.1で説明したように、has_secure_passwordをUserモデルに追加したことで、そのオブジェクト内でauthenticateメソッドが使えるようになっています。このメソッドは、引数に渡された文字列 (パスワード) をハッシュ化した値と、データベース内にあるpassword_digestカラムの値を比較します。試しに、先ほど作成したuserオブジェクトに対して間違ったパスワードを与えてみましょう。
>> user.authenticate("not_the_right_password") false >> user.authenticate("foobaz") false間違ったパスワードを与えた結果、user.authenticateがfalseを返したことがわかります。次に、正しいパスワードを与えてみましょう。今度はauthenticateがそのユーザーオブジェクトを返すようになります。
>> user.authenticate("foobar") => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2016-05-23 20:36:46", updated_at: "2016-05-23 20:36:46", password_digest: "$2a$10$xxucoRlMp06RLJSfWpZ8hO8Dt9AZXlGRi3usP3njQg3...">第8章では、このauthenticateメソッドを使ってログインする方法を解説します。なお、authenticateがUserオブジェクトを返すことは重要ではなく、返ってきた値の論理値がtrueであることが重要です。4.2.3で紹介した、!!でそのオブジェクトが対応する論理値オブジェクトに変換できることを思い出してください。この性質を利用すると、user.authenticateがいい感じに仕事をしてくれるようになります。
>> !!user.authenticate("foobar") => true今日はここまで
- 投稿日:2020-03-28T15:36:57+09:00
[graphql-ruby] [ActionCableSubscriptions] subscriptionをtriggerする際に、送るオブジェクトが独自のクラスの場合
問題
Hash
、OpenStruct
、ActiveRecord
でない、独自のクラスを下のよう感じでtrigger
した場合AppSchema.subscriptions.trigger( "your_subscription_name", { }, { users: users, } )このようなエラーが出る
There was an exception - RuntimeError( Failed to implement User.id, tried: - `Types::UserType#id`, which did not exist - `String#id`, which did not exist - Looking up hash key `:id` or `"id"` on `#<User:0x000055a03c518290>`, but it wasn't a Hash To implement this field, define one of the methods above (and check for typos) )原因
渡したオブジェクトが
Hash
っぽくないって事でエラーになっている様子
しかしながら、全く同じオブジェクトをsubscriptionの最初の呼び出しの際に渡しても、それは正常に送ってくれる ...module Subscriptions class YourSubscriptionName < GraphQL::Schema::Subscription field :users, [Types::UserType], null: false def subscribe) users = ... # これはなぜか問題ない { users: users } end end end解決方法
issueに上がっていた内容
- 独自クラスにGlobalIDを埋め込む
- Subscriptionの宣言時に、独自のserializerを設定する
方法1
GlobalIDを使って下のようにメソッドを増やす
*データベース絡みじゃない特殊なケースはIDをjsonデータなどにして、find
の時に渡されるid
(自分が先に設定したjson)をパースするなどの工夫も出来る(らしい)# activerecordでない、独自のクラス class User include GlobalID::Identification # 最低でも id、==、self.findが必要 attr_reader :id def initialize(id) @id = id end def self.find(id) # どうにかこうにかデータを持ってくる User.new(id) end def ==(that) self.id == that.id end end方法2
subscriptionのクラスを宣言する際に
serializer
を設定する ... らしいclass AppSchema < GraphQL::Schema # 通常はこう # use GraphQL::Subscriptions::ActionCableSubscriptions use GraphQL::Subscriptions::ActionCableSubscriptions.new(serializer: YourSerialize)
- 投稿日:2020-03-28T14:55:12+09:00
【Rails】検索キーワードを空白で2つに分ける
はじめに
Itemモデルを検索する想定で中身は下記とする。
id name 1 test 2 sample 3 test sample やりたい事
検索フォームにて空白(スペース)を含むキーワードが入力された場合、
空白を境に文字を分けそれぞれのキーワードで検索を行い結果を返したい。items_controller.rb# 例えば下のキーワードで検索された場合 @keyword = "test sample" # そのまま検索しようとすると @items = Item.where("name LIKE ?", "%#{@keyword}%") # 結果は"test sample"という文字列を含んだid:3のItemのみが返ってくる @items.ids => [3]今回は「test sample」のキーワードで検索された際に、
Itemモデルのデータ全てを返せる事をゴールとする。1.検索キーワードを空白で分ける
まずはキーワードの内の空白を検知して分割する為に
split
メソッドを使う。splitの使い方"対象の文字列".split("区切りにする文字","分割する回数") # 分割する回数は省略可、省略した場合は区切りにする文字全ての箇所で分割する。 # またsplitは配列の形でデータを返すので注意実際に使用して書き換える。
items_controller.rb@keyword = "test sample" @keywords = @keyword.split(/[[:blank:]]+/)ここで区切りにする文字として指定した正規表現の内容は、
[[:blank:]]
→ 空白演算子、半角・全角両方の空白を対象にする為。
+
→ 空白が連続して入力された場合でも対応できるようにする為。2.分割したキーワード毎に検索を行う
items_controller.rb# 分割したキーワード毎に検索を行いインスタンスにセット @keywords.each do |keyword| @items = Item.where("name LIKE ?", "%#{keyword}%") end
split
は配列でデータを返すのでeach
メソッドで中身全てを取り出す。3.結果
items_controller.rb@keyword = "test sample" @keywords = @keyword.split(/[[:blank:]]+/) @keywords.each do |keyword| @items = Item.where("name LIKE ?", "%#{keyword}%") end @items = @items.order("id ASC") @items.ids => [1, 2, 3]4.最後に
検索の機能でまず実装しようと思ったのがこれだったので備忘録として。
他にも色々必要な機能があると思うので考えていきたいと思います。
- 投稿日:2020-03-28T13:36:35+09:00
RailsアプリにDockerとCircleCIを導入した際、DB周りでエラーになったときの対処法
こんにちは、ペーパーエンジニアのよしこです。
CircleCIで
git push
時に自動テスト(RSpec)しているRailsアプリに、Dockerを導入しました。その際にデータベース周りで躓いたエラーがあります。
同じエラーの報告が少なかったので、エラー解消した対処法を共有します。
エラー文
git push
をトリガーにCircleCIがテストを実行するのですが、その前にデータベースを構築します。
そこでエラーが出ていました。circleci/config.yml# DBをセットアップ - run: name: DBをセットアップ command: bin/rails db:schema:load --trace※全文は下記
#!/bin/bash -eo pipefail bin/rails db:schema:load --trace ** Invoke db:schema:load (first_time) ** Invoke environment (first_time) ** Execute environment ** Invoke db:load_config (first_time) ** Execute db:load_config ** Invoke db:check_protected_environments (first_time) ** Invoke environment ** Invoke db:load_config ** Execute db:check_protected_environments rails aborted! PG::ConnectionBad: could not translate host name "db" to address: Name or service not known /home/circleci/project/vendor/bundle/gems/pg-0.20.0/lib/pg.rb:56:in `initialize' . . 省略 . bin/rails:4:in `require' bin/rails:4:in `<main>' Tasks: TOP => db:schema:load => db:check_protected_environments Exited with code exit status 1 CircleCI received exit code 1環境
Ruby : 2.6.3
Rails : 5.1.6
postgres : 12.2
CircleCI : 2.1
Docker-compose version: '3'結論
database.yml
に、test環境でhost: localhost
を追加することで解決しました。config/database.ymldefault: &default adapter: postgresql encoding: unicode pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: postgres password: host: db timeout: 5000 development: <<: *default database: haito_notice_development test: <<: *default database: haito_notice_test host: localhost # <<<<<追加<<<<< production: <<: *default password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>Dockerのimage上では
db
としてDB構築していますので、develop環境やproduction環境ではhost: db
とする必要があります。しかし私の場合は、CircleCIのDocker imageをtest環境かつ
host: 127.0.0.1 (localhost)
で構築しているため、これに合わせてあげる必要があったのではと理解しています。CircleCI
基本、公式のドキュメントを元に記載していますが、
別のエラー対処のためにworkflowsやcommandsを現在は適用していないため参考程度に。circleci/config.ymlversion: 2.1 jobs: build: docker: - image: circleci/ruby:2.6.3-stretch-node environment: BUNDLE_JOBS: 3 BUNDLE_RETRY: 3 BUNDLE_PATH: vendor/bundle PGHOST: 127.0.0.1 PGUSER: postgres RAILS_ENV: test - image: circleci/postgres:12-alpine environment: POSTGRES_USER: postgres POSTGRES_DB: app_test steps: - checkout - restore_cache: name: 依存関係キャッシュを復元 keys: - v1-dependencies-{{ checksum "Gemfile.lock" }} - v1-dependencies- - run: name: Bundler を指定 command: bundle -v - run: name: バンドルをインストール command: bundle check || bundle install - save_cache: key: v1-dependencies-{{ checksum "Gemfile.lock" }} paths: - vendor/bundle - run: name: 静的コード解析を実行(RuboCop) command: bundle exec rubocop - run: name: DBの起動まで待機 command: dockerize -wait tcp://localhost:5432 -timeout 1m - run: name: DBをセットアップ command: bin/rails db:schema:load --trace # ここでエラーが起こっていました!!! - run: name: テストを実行(RSpec) command: | bundle exec rspec --profile 10 \ --format RspecJunitFormatter \ --out test_results/rspec.xml \ --format progress \ $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) - store_test_results: path: test_resultsDocker
念の為記載します。
こちらもDocker公式のドキュメントを参考に作っています。# Dockerfile FROM ruby:2.6.3 RUN apt-get update -qq && \ apt-get install -y nodejs \ postgresql-client RUN mkdir /app ENV APP_ROOT /app WORKDIR $APP_ROOT COPY ./Gemfile $APP_ROOT/Gemfile COPY ./Gemfile.lock $APP_ROOT/Gemfile.lock RUN bundle install COPY ./ $APP_ROOT COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"]entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /app/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"docker-compose.ymlversion: '3' services: db: image: postgres:12-alpine environment: POSTGRES_HOST_AUTH_METHOD: 'trust' volumes: - ./tmp/db:/var/lib/postgresql/data web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/app ports: - "3000:3000" depends_on: - dbご指摘やご不明な点などがございましたらお気軽にご連絡ください。
- 投稿日:2020-03-28T11:34:23+09:00
どこのページからでもリンクさせたい時
はじめに
この記事が、初めての投稿なので見にくかったらすみません。。
自分の備忘録も兼ねて、学んだことを書いていきます!トップページからしか、リンク出来ない!!
フリマアプリの作成中のことです。
ヘッダーに、ユーザー新規登録ボタンを置いていました。
トップページでは、そのボタンから登録ページへ遷移できます。
ですが、商品検索ページや商品詳細ページでは、ボタンを押してもエラーになってしまいました!!絶対パスを相対パスに変更
結論から言うと、絶対パスを相対パスに変えたら解決しました。
絶対パス↓
header.html.haml%li.listsRight__item.listsRight__item--new = link_to "新規登録", "users/sign_up"相対パス↓
header.html.haml%li.listsRight__item.listsRight__item--new = link_to "新規登録", "/users/sign_up""users" の前に、"/(スラッシュ)" を付けただけですね。
絶対パスになっていたため、トップページ(http://localhost:3000/) を基準にしてしか飛ぶ事ができませんでした。
それを相対パスにする事で、商品ページ(http://localhost:3000/items/) など、どのページからでも遷移できるようになりました!最後に
絶対パス/相対パスは、ただのターミナルでのディレクトリ指定方法だと考えていました。
ですが、実際の挙動にも影響を与えることを知り、基本が一番大切だなと改めて思いました。もし解釈が間違えていたら、コメントをお願いします!
- 投稿日:2020-03-28T09:43:29+09:00
Dockerを学習するための記事一覧
Dockerを学習するための記事をまとめてみました
エラーの対策も記述してます。
Running
bundle update
will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.
ERROR: Service 'web' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 6参考になる記事一覧
今回の動画ではインストール完了済みなので、この記事は飛ばしています。
DockerをMacにインストールする(更新: 2019/7/13)今回実施する記事は下記になります。
DockerでRailsの環境構築Mysqlの設定方法は下記が参考になります。
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)下記で $ docker-compose run web bundle installの方法が記載されてました。
DockerでRailsのプロジェクトを立ち上げるまで下記のエラーが出た場合
Running `bundle update` will rebuild your snapshot from scratch, using only the gems in your Gemfile, which may resolve the conflict. ERROR: Service 'web' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 6bundle updateを実施します
ターミナル$ docker-compose run web bundle update
- 投稿日:2020-03-28T00:43:34+09:00
[HowTo]マッチングアプリ的な友達機能を実装してみる。
現在、個人アプリを作成しているのですが、その中の機能として「友達機能」を実装することにしました。
スクールではカリキュラムになかった内容であったため、自身で色々と調べて実装したので、備忘録として、
以下のように記事にさせていただきます。
皆様の実装に少しでも役立てていただければ、幸いです。はじめに-機能に対する考え方
今回の「友達機能」実装にあたり、当初、「友達申請=>承認=>友達になる」というよくある流れでの実装を考えてました。
しかしながら、少し堅苦しい感じがしたので、もう少し気軽に友達申請を実現するために、最近流行っているマッチングアプリ的に「いいね」を友達申請の代わりに使用し、お互いに「いいね」されたら「友達」にするというやり方での実装を考えました。
以降、その考えをベースに実装をしております。実装イメージ
今回の機能の実装イメージは以下となります。
友達申請(いいね)の動作イメージ
友達申請(いいね)以下のようにハートマークをクリックすると完了します。
この動作をお互いに行うことで”友達”として認定します。お互いにいいねがされると友達として認識されます。
テーブル準備
まず、今回の友達機能を実装するにあたり"relationship"テーブルを準備いたします。
ターミナルrails g model relationshipマイグレーションファイルは以下のように作成します。
migration_fileclass CreateRelationships < ActiveRecord::Migration[5.2] def change create_table :relationships do |t| t.integer :follower_id t.integer :following_id t.timestamps end add_index :relationships, :follower_id add_index :relationships, :following_id add_index :relationships, [:follower_id, :following_id], unique: true end endマイグレーションファイルへの記述完了次第、ターミナルでrails db:migrateをしておきましょう。
ターミナル$ rails db:migrateモデル編集
テーブルが作成できましたので、続いてモデルを編集していきます。
今回は、友達申請(いいね)する人を"following”、友達申請(いいね)される人を"follower"とし、
友達となっているかどうかを"matchers"メソッドを作って判別します。User(一部)class User < ApplicationRecord has_many :following_relationships, foreign_key: "follower_id", class_name: "Relationship", dependent: :destroy has_many :followings, through: :following_relationships has_many :follower_relationships, foreign_key: "following_id", class_name: "Relationship", dependent: :destroy has_many :followers, through: :follower_relationships def following?(other_user) following_relationships.find_by(following_id: other_user.id) end def follow!(other_user) following_relationships.create!(following_id: other_user.id) end def unfollow!(other_user) following_relationships.find_by(following_id: other_user.id).destroy end #友達判定 def matchers followings & followers end endRelationshipモデルについては、follower/followingをUserに帰属させます。
relationshipclass Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :following, class_name: "User" validates :follower_id, presence: true validates :following_id, presence: true end以上でモデルに関しての編集は完了となります。
ビューの編集
今回の”友達申請(いいね)”に関してのビューを作成します。
(以下は”友達申請(いいね)”に関してのみの記述となりますので、必要に応じて肉付けしてください)今回のポイントは以下の通りです。
- if文で既に友達申請(いいね)をしているか否かを見極めます。
- 既に友達申請(いいね)をしている場合は、ハートを赤くしておき、ハートを押すと未申請状態(白色)にします。
- 逆に未申請の場合は、ハートを白くしておき、ハートを押すと申請済み状態(赤色)にします。
- これらの処理をform_withで情報を飛ばすことで実装してます。view(一部)- if current_user.following?(@user) = form_with model: @relationship,url: relationship_path, method: :delete, remote: true do |f| = button_tag type: 'submit', class: 'btn-liked',id: "unfollow_form" do %i.fas.fa-heart.fa-3x - else = form_with model: @relationship, remote: true do |f| %div= hidden_field_tag :following_id, @user.id = button_tag type: 'submit', class: 'btn-likes',id: "follow_form" do %i.far.fa-heart.fa-3xコントローラ
上記にてビューも完成したので、そちらに合わせてRelationshipコントローラを編集していきます。
友達申請(いいね)をしたときは、”create"メソッドを使用し、
友達申請(いいね)を取り消すときは、"destroy"メソッドを使用してます。
今回、基本的に申請時・申請取り消し時にページ遷移の必要はなかったので、Userページにredirectしてます。RelationshipsControllerclass RelationshipsController < ApplicationController def create current_user.following_relationships.create(create_params) redirect_to user_path(params[:following_id]) end def destroy @user=current_user @relationship = Relationship.where(following_id: params[:id],follower_id:@user.id) @relationship.destroy_all redirect_to user_path(params[:id]) end private def create_params params.permit(:following_id) end end以上で、上記イメージの友達機能が実装できます!
今回はいいねボタンを友達申請ボタンの代わりに使用しましたが、viewを変更するだけで実装できます!
色々と試してみていただけますと幸いです。参照
Railsでマッチング機能を作ってみる
https://qiita.com/Utr/items/da03bf4f23aba03da656Ruby on Rails ~フォロー(友達申請)機能の実装(コードメモ)
https://qiita.com/wtb114/items/dbba4364871aacf520cd以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。