- 投稿日:2020-01-20T23:01:37+09:00
devise ユーザーのプロフィール画面作成と編集(デフォルトをカスタマイズ)
はじめに
deviseを導入の仕方を学び、デフォルトのままだとユーザーフレンドではないと思い、deviseのカスタマイズをしようと思います。
・sign up(アカウント登録)時にユーザーの名前も一緒に登録!
・users/showで「名前」「メールアドレス」「プロフィール」の表示!
・users/editで簡単編集!上記の3つについて、rails初心者目線で書いていきたいと思います。
完成イメージ
Qiita初めての記事で緊張しますが、分かりやすく書いていきます!
環境
ruby 2.5.6
rails 5.2.3
devise 4.7.1前提
今回、deviseは導入済みでその後どうやってカスタマイズしていくか進めます。
deviseの導入がまだの方用に参考URL貼っておきます。
devise導入方法URL
(1)公式ドキュメント
(2)[Rails] deviseの使い方(rails4版)
※(2)だと「1.deviseの導入」まで進めてください。MVC(model/ view/ controller )設定
modelの生成
ターミナル.$ rails g devise userテキストエディターのapp/models見てみると一番下にuser.rbがあります。
これでuser modelで出来ました。viewの生成
ターミナル.$ rails g devise:viewsテキストエディターのapp/views/devise見てみるとこんな感じになります。
controllerの生成
ターミナル.$ rails g devise:controllers usersテキストエディターのapp/controllers/users見てみるとこんな感じになります。
確認できたら、deviseのcontroller生成されました!各種ファイルの設定
この章の目的:
(1)sign upでメールアドレスとパスワードの他に名前もを登録
(2)ユーザーのプロフィール画像の作成
そのために各種の設定していきます。userテーブルにカラムを追加しよう
ターミナル.$ rails g migration add_name_profile_to_usersテキストエディターでdb/migrateの中を確認すると
その時作った「日時add_name_profile_users」と表示されたファイルが出来ます。例えばこんな感じ「20200120053617_add_name_profile_to_users.rb」です。
〇〇_add_name_profile_to_users.rbclass AddNameProfileUsers < ActiveRecord::Migration[5.2] def change add_column :users, :name, :string #追記 add_column :users, :profile, :text #追記 end endターミナル.$ rails db:migrate解説(なぜ、 userテーブルに「name」「profile」カラムを?)
※なぜ、 userテーブルに「name」「profile」カラムを追加したか解説します。
結論、deviseの初期状態でのカラムに「name」「profile」カラムがないからです。userテーブルが持っているカラムをどこで確認するのか?
①db/schema.rbで確認
これはカラム追加した後ですが、本来は「name」「profile」カラムがない状態です。②rails cで確認
ターミナルでrails c
をして
User.column_names
をすると確認できます。モデル
app/models/user.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable validates :name, presence: true #追記 validates :profile, length: { maximum: 200 } #追記 endnameに空欄は許しませんよ!
profileは200文字に抑えてくさだいね!って制限をかけます。コントローラー
app/controllers/application_controller.rbclass ApplicationController < ActionController::Base protect_from_forgery with: :exception # ログイン済ユーザーのみにアクセスを許可する before_action :authenticate_user! # deviseコントローラーにストロングパラメータを追加する before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters # サインアップ時にnameのストロングパラメータを追加 devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) # アカウント編集の時にnameとprofileのストロングパラメータを追加 devise_parameter_sanitizer.permit(:account_update, keys: [:name, :profile]) end end後はviewで「sign up時にユーザーの名前も一緒に登録」「users/showで「名前」「メールアドレス」「プロフィール」の表示」を表示していきます。
ユーザー情報を記入
app/views/devise/registrations/new.html.erb<h2>Sign up</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> <!--サインアップ時に名前を入力できるようにフォームを追加--> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <div class="field"> <%= f.label :password %> <% if @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimum)</em> <% end %><br /> <%= f.password_field :password, autocomplete: "new-password" %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "new-password" %> </div> <div class="actions"> <%= f.submit "Sign up" %> </div> <% end %> <%= render "devise/shared/links" %>ユーザー情報を編集
app/views/devise/registrations/edit.html.erb<h2>Edit <%= resource_name.to_s.humanize %></h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= devise_error_messages! %> <!--アカウント編集時に名前を入力できるようにフォームを追加--> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <!--アカウント編集時にプロフィールを入力できるようにフォームを追加--> <div class="field"> <%= f.label :profile %><br /> <%= f.text_area :profile, autofocus: true %> </div> <div class="actions"> <%= f.submit "Update" %> </div> <% end %> <%= link_to "Back", :back %>プロフィール画面の作成
ここでの目的
・プロフィール画面の作成手順
①コントローラーとviewファイルの生成と設定
②ルーティング設定
の手順で進めていきます。コントローラーとviewファイルの生成
ターミナル.$ rails g controller Users showdeviseとまた別にコントローラーとshow.html.erbを作成します。
app/controllers/users_controller.rbclass UsersController < ApplicationController def show @user = current_user end endcurrent_userは現在ログインしているユーザーのことを指します。
app/views/users/show.html.erb<h1>about me</h1> <h3>ユーザー名</h3> <%= @user.name %> <h3>メールアドレス</h3> <%= @user.email %> <h3>プロフィール</h3> <%= @user.profile%>ルーティング設定
route.rbdevise_for :users, controllers: { registrations: 'users/registrations' } get "users/show" => "users#show"パスワードを入力せずにユーザー情報を編集
編集画面で入力した情報をsubmitすると
Current password can't be blank
が出てくると思います。これはパスワードを入れて情報を更新してください。ってことなので、
パスワードを入れずにユーザー情報を編集できるようにします。registrations_controller.rbの追記
registrations_controller.rbclass RegistrationsController < Devise::RegistrationsController protected def update_resource(resource, params) resource.update_without_password(params) end endユーザー情報を編集する際にパスワード無しで編集可能になりました!
参考URL
[Devise] パスワードを入力せずにユーザー情報を編集する
devise導入からユーザ-のプロフィール画面を作成するまで
- 投稿日:2020-01-20T22:30:18+09:00
Dockerを使ってRuby2.7&Rails6を構築
はじめに
2020/01/20時点での最新安定版のrails開発環境の構築の記事がなかったので。
【Imagemagick対応】Dockerを利用して、Rails環境を作成
基本上記の記事のままです。
ただ、上記の記事はRuby2.6&Rails5.2.2で、そのままバージョンを書き換えるだけではうまく動作しなかったので、動作できるようにしたインストール資材を記載しておきます。
参考までに上記記事からの変更点も記載しておきます。コンテナ立ち上げ以降は参考記事の手順のまま実施できます。
インストール資材
Dockerfile
DockerfileFROM ruby:2.7 ENV RUNTIME_PACKAGES="linux-headers libxml2-dev libxslt-dev make gcc libc-dev nodejs tzdata postgresql-dev postgresql" \ DEV_PACKAGES="build-base curl-dev" \ HOME="/myapp" WORKDIR $HOME # Counter Measure to Error:"Autoprefixer doesn’t support Node v4.8.2. Update it" RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \ && apt-get install -y nodejs # yarnパッケージ管理ツールインストール RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ 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 && \ apt-get update && apt-get install -y yarn RUN apt-get update && \ apt-get install -y default-mysql-client \ postgresql-client \ sqlite3 \ --no-install-recommends && \ rm -rf /var/lib/apt/lists/* ADD Gemfile $HOME/Gemfile ADD Gemfile.lock $HOME/Gemfile.lock RUN bundle install ADD ./ $HOME COPY ./ $HOME CMD ["rails", "server", "-b", "0.0.0.0"]変更点①:ruby2.6⇒2.7
変更点②:mysql-client⇒default-mysql-client
mysql-client
はインストールできなくなっていたので、代わりにdefault-mysql-client
をインストールします。
mysql-client
がインストールできない理由は以下の記事が参考になります。Circle CI で mysql-client が apt-get install できなくなってCI環境が壊れた話
変更点③:yarnを追加でインストール
エラーメッセージは控えていませんでしたが、参考記事のままインストールしようとしたら
yarn
がないと怒られました。そのため、# yarnパッケージ管理ツールインストール
のところでインストールしています。docker-compose.yml
docker-compose.ymlversion: '3' services: db: container_name: db image: postgres:latest environment: POSTGRES_USER: root POSTGRES_PASSWORD: password POSTGRES_INITDB_ARGS: "--encoding=UTF-8" web: container_name: app build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/myapp ports: - "3000:3000" depends_on: - db変更点①:passwordありユーザーの設定
セキュリティ的にpasswordありユーザーがほしかったので、
environment
のところでrootユーザにpasswordを付与しています。Gemfile
Gemfilesource 'https://rubygems.org' gem 'rails', '~> 6.0.2', '>= 6.0.2.1'Imagemagickは使用しないのでGemfileから削除しています。
Gemfile.lock
Gemfile.lockこちらは空のまま。
- 投稿日:2020-01-20T21:43:57+09:00
【Ruby】3文字の時だけ「遊☆戯☆王」みたいに出力されるアルゴリズム
はじめに
最近、遊戯王のゲーム実況をみながら寝落ちするのが日課です。
ある日、Rubyいじってたら、な ぜ か 「遊☆戯☆王」みたいに文字の間に☆入れたいなーって思ったんです。
こんなことをアルゴリズムの記事とか言って書いている自分のアルゴリズム、コレガワカラナイ。問題のソースコード
keywords = ["遊戯","海馬","木馬","羽蛾","城之内","本田","杏","エクゾディア"] keywords.each do |keyword| sleep 0.2 # 1行だすごとに一旦休憩する(なんとなく) if keyword.length == 3 #3文字であればの判定をしている k = keyword.chars #1文字ずつ分割する puts k.join("☆") #文字を☆でつなぐ(オサレ) else puts keyword #3文字出ない場合はそのまま表示 end end出力するとこんな感じになります。
遊戯 海馬 木馬 羽蛾 城☆之☆内 本田 杏 エクゾディア
3文字の城之内くんだけが☆入っていますね。
他の文字列でもやってみました
151匹を出力してみました。
名前が3文字のポケモンだけ☆が入っていますね。
ポ☆ッ☆ポ、ピ☆ッ☆ピはパワーワードに見えますね。たくさんの文字列精査して☆入れたいときには、CSVに入れるのが便利です。
CSVから文字列を持ってくるときのソース
require "csv" #csvを使用する場合はこのコードを書く keywords = [] # csvから情報を持ってきて配列に入れる CSV.foreach("keywords.csv") do |keyword| keywords << keyword[0] end keywords.each do |keyword| sleep 0.2 if keyword.length == 3 k = keyword.chars #1文字ずつ分割する puts k.join("☆") #文字を☆でつなぐ(オサレ) else puts keyword end end※コメントにてスッキリした書き方教えていただきました!
keywords.csv
は同階層に置き、そのCSVのA列を上から順に読み込んで、3文字チェックをかけています。
このkeyword[0]
の部分でどの列から持ってくるかを判定しています。
/ A B [0] [1] 1 フシギダネ くさ・どく 2 フシギソウ くさ・どく 3 フシギバナ くさ・どく 4 ヒトカゲ ほのお 5 リザード ほのお 6 リザードン ほのお・ひこう 上記のような感じで、
[0]
はA列、[1]
はB列から情報を持ってきます。
タイプを文字数判定したかったらkeyword[1]
として引っ張ります。参考記事
文字数をうまく判定できないときには
CSVファイルに、見えないけれど存在する、封印されし文字列が入っている可能性があります。
そのため、下記の方法でCSVファイルから封印されし文字列を墓地へ送りましょう。参考記事
- 投稿日:2020-01-20T21:36:23+09:00
初級者向け アルゴリズムを解説(1)
初級者向け アルゴリズムを解説
これからアルゴリズムについて解説をしていきますが、こちらの解説はプログラミング初級者(「最近プログラミングを始めました!!」向けになります。また数回のシリーズに分けてプログラミングで使用されるアルゴリズムを解説していきます。なお、次回からRubyのコードが出てきますが、簡単なメソッドを使用してコーディングする予定です。
※このシリーズは「アルゴリズムを、はじめよう(著:伊藤静香)」を参考にしております。アルゴリズムとは
はじめにアルゴリズムについて、簡単に説明します。
そらく、アルゴリズムと聞くと、プログラミング・難しい..と考える方もいるかと思いますが、アルゴリズムを端的に言えば「手順」です。但し、ただの手順ではなく、「問題や課題を解決するための手順を表現した考え方やアイディア」です。
実は我々の身の回りにも様々なアルゴリズムが使用されています。例えば、「お菓子のレシピ」、これにもアルゴリズムが使用されており、必要な材料とその量、また調理法がレシピに記載されています。そのレシピ通りに作れば誰でもお菓子が作れるようになっているわけです。つまり、レシピは「ある料理を作りたい」という課題を解決するための手順で有り、これこそがアルゴリズムです。アルゴリズムの基本形
アルゴリズムの手順(構造)には3つの基本形があります。
1. 順次構造
2. 選択構造
3. 反復構造1. 順次構造
順次構造とは、実行する処理を最初から順番に行っていくことです。
例えば、友達の家に飲み物を買って行くシーンを思い浮かべてみましょう。
「1.自宅を出る」「2.お店に行く」「3.飲み物を買う」「4.お店を出る」「5.友達の家に行く」の順番に物事が進むはずです。この1〜5までの処理を順番に行うことが順次構造となります。2. 選択構造
選択構造とは、その名の通り、処理を選んで実行する手順です。
例えば、先ほど友達に飲み物を買う際に、その友達が炭酸が好きで、特にコーラが大好きだとしましょう。おそらく、あなたはお店に入って、まずコーラがあれば、コーラを買い、なければ、その他の炭酸飲料を買うと思います。
この、ある条件下で(今であればコーラがあるかないか)、その答えがYESならその条件判断の処理を行い(今であればコーラを買う)、もしその答えがNOであればその他の処理を行う(今であればその他の炭酸飲料を買う)、これが選択構造となります。3. 反復構造
反復構造とは、同じ処理を繰り返す手順になります。
例えば、先ほど友達に飲み物を買うための予算を500円としてお店に行った際、たまたまそのお店が閉店セールをやっていたことで、予算が300円余ったとしましょう。そこで、あなたは予算がなくなるまで、1つ100円のお菓子を買うことにしました。このときの処理を細かくと次のようになります。
・予算が300円 (お菓子が買える)
・お菓子を1つ買う。(100円支出する)
・予算が200円になる。(まだ買える)
・お菓子を1つ買う。(100円支出する)
・予算が100円になる。(まだ買える)
・お菓子を1つ買う。(100円支出する)
・予算が0円になる。(もう買えない)
・お店を出る。
この、予算を確認して、予算がなくなるまでお菓子を買う(同じ処理を繰り返す)ことが反復構造になります。次回予告
今回はここで終了です。
次回は本格的にプログラミングで使用される基本的なアルゴリズムについて説明していきたいと思います。
- 投稿日:2020-01-20T21:23:01+09:00
秀丸からRubyを呼び出して、選択範囲を加工する
秀丸からRubyを呼び出して、選択範囲を加工する
利用環境
秀丸エディタ 32bit Ver8.89 (2020/1/20時点最新)
RubyEval for 秀丸マクロ Ver.0.02概要
筆者は、25年らいの秀丸エディタのユーザーだ。
今は、フリーでも同等機能のエディタがあるらしいし、unix畑の人はViライクをお使いであろうか
さて、上でRubyEval for 秀丸マクロ Ver.0.02は、2005年に公開されたマクロで15年前のものであるが
いまだ現役バリバリに愛用させていただいている。
秀丸エディタ上で選択した文字列を、Rubyのソースコードロジックで変換して返す
いろいろ応用範囲が広い。
是非、秀丸ユーザーには使ってみて欲しい
いくつかサンプルを紹介する。ソースコード例
重複行を削除して排除、ユニーク行をゲットする 秀丸連携
uniqueGet.rb#------------------------------------------------------------------------------- # 重複行を削除して排除、ユニーク行をゲットする 秀丸連携 # uniqueGet.rb #------------------------------------------------------------------------------- unless $HIDEMARU_MACRO #非秀丸テスト時 ヒアドキュメントを利用 from_hide = <<-ENDOFSRC テスト-----------------M テスト-----------------M aaa bbb bbb aaa ccc ENDOFSRC else from_hide = $RUBY_EVAL_INSTR end #秀丸から受け取った文字列を、改行ごとに、Arrayへ格納 ary = Array.new ary = from_hide.split(/\n/) #重複管理Array uniqueAry = Array.new #リターン out_hide = "" #重複しない行のみ、out_hideへ格納 (改行も) ary.each{ |str| unless uniqueAry.index(str) out_hide = out_hide + str + "\n" #重複管理へ格納 uniqueAry.push(str) end } unless $HIDEMARU_MACRO #非秀丸テスト時 print確認 print out_hide else #最後に評価した文字を返す out_hide end上記とは逆、重複行を取得 秀丸連携
duplicationGet.rb#------------------------------------------------------------------------------- # 重複行取得 秀丸連携 # duplicationGet.rb #------------------------------------------------------------------------------- unless $HIDEMARU_MACRO #非秀丸テスト時 ヒアドキュメントを利用 from_hide = <<-ENDOFSRC テスト-----------------M テスト-----------------M aaa bbb bbb aaa ccc ENDOFSRC else from_hide = $RUBY_EVAL_INSTR end #秀丸から受け取った文字列を、改行ごとに、Arrayへ格納 ary = Array.new ary = from_hide.split(/\n/) #重複管理Array uniqueAry = Array.new #リターン out_hide = "" #重複する行のみ、out_hideへ格納 (改行も) ary.each{ |str| unless uniqueAry.index(str) #重複管理へ格納 uniqueAry.push(str) else out_hide = out_hide + str + "\n" end } unless $HIDEMARU_MACRO #非秀丸テスト時 print確認 print out_hide else #最後に評価した文字を返す out_hide endロジックで、代入 入れ替え 秀丸連携
a = b → b = a / c = d → d = ccsSubstitutionChange.rb#------------------------------------------------------------------------------- # 代入 入れ替え 秀丸連携 # csSubstitutionChange.rb #------------------------------------------------------------------------------- unless $HIDEMARU_MACRO #非秀丸テスト時 ヒアドキュメントを利用 from_hide = <<-ENDOFSRC a = b; c = d; ENDOFSRC else from_hide = $RUBY_EVAL_INSTR end #秀丸から受け取った文字列を、改行ごとに、Arrayへ格納 ary = Array.new ary = from_hide.split(/\n/) #重複管理Array uniqueAry = Array.new #リターン out_hide = "" #重複しない行のみ、out_hideへ格納 (改行も) ary.each{ |str| #代入前をマッチさせる。 str =~ /(\t*)(.*) = (.*);/ tab = $1 lefth = $2 right = $3 out_hide = out_hide + tab + right + " = " + lefth +";\n" } unless $HIDEMARU_MACRO #非秀丸テスト時 print確認 print out_hide else #最後に評価した文字を返す out_hide end秀丸のマクロでも同等のことは、可能であろうけど
やはり一般的なスクリプト言語で実装していた方が、いろいろ応用範囲は広い。
今は、Pythonでも同等のことができるマクロもあるようなので、また調べて紹介したい。
- 投稿日:2020-01-20T21:07:07+09:00
Erubi とは何か
Qiita には Ruby on Rails の記事が 2 万本以上あるのに,Rails で使われているライブラリー Erubi の記事ほぼゼロ。
Erubi とは何なのか。まず eRuby について
Rails を少しでもかじったことがあれば,eRuby というテンプレート言語を知っていると思う。いや「eRuby」という名称に馴染みがなくても,拡張子が
.erb
のアレと言えば分かるだろう。テキストに Ruby のコードを埋め込むテンプレート言語の名称だ。読み方はおそらく「イー・ルビー」で,「e」は embedded(埋め込まれた)の頭文字。
データに基づいて HTML を生成するのによく使われるが,対象となるテキストは HTML に限らない。テキストであれば何でもいい。
実際,Rails では,例えば config/database.yml なんかは eRuby で記述されている。拡張子は単に
.yml
だが,pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>なんて行があるのを見たことがあると思う。環境変数
RAILS_MAX_THREADS
の値を取得し(もし定義されてなければ5
を採用し),それをpool
の値にしているわけだ。
こんなふうに YAML テキストを生成するために eRuby テンプレートシステムが使われている。ところで,この言語名について,「それ,『eRuby』じゃなくて『ERB』じゃないの?」と思った人も少なくないだろう。名称は本質ではないが,あとで触れよう。
Erubi とは
さて,一般にテンプレートを処理するプログラム部品を「テンプレートエンジン」などと言うが,Erubi は eRuby 用テンプレートエンジンの一つだ。
eRuby テンプレートを処理するエンジンには,ERB,Erubis,Erubi などがある。歴史的にはこの順に登場した1。これらは少し仕様が違うが,本記事ではそのことに触れない。
ERB は Ruby の標準添付ライブラリー erb で提供されているテンプレートエンジンだ。
テンプレート言語の名前として「eRuby」の代わりに「ERB」もしくは「erb」を使っているのをよく見かける。本来は特定のテンプレートエンジンのクラス名,ライブラリー名のはずだが,識者も使っているし,間違いとまでは言えないのかなと思っている。
それはさておき,標準添付ライブラリーに eRuby テンプレートエンジンが用意されているのに,新たに Erubis が作られたのは,高速化と高機能化が主な動機だろうと思う。
実際,Erubis は速いし,高機能だ。Erubis は Rails に採用された。Rails 5.0 までは Erubis(を改造したもの)が Rails の eRuby テンプレートエンジンだった。
Erubi はこの Erubis のフォーク(派生)である。さらなる高速化を図るとともに,機能をそぎ落として簡素化した。動機の一つには,Erubis の開発が止まって久しい,ということもあるようだ2。
そして,Rails ではバージョン 5.1 から Erubi(の改造したもの)を採用するようになった。
Erubi を知らなくて済む理由
ふつうの Rails プログラマーは Erubi について知る必要があまり無い。eRuby の仕様(正確に言えば Rails における eRuby の仕様)だけ分かっていればよい。
それは,テンプレートエンジンを動かすところを Rails がすべてやってくれるからだ3。
ではなぜこんな記事を書いているかというと,Rails のビュー以外にも eRuby の使い道があるからだ。とにかくどんなテキストでもデータに基づいて生成できるんだからね。
Erubi を使ってみる
Rails などとは無関係に Erubi の機能を使ってみよう。
その際,Tilt という gem を援用すると楽ができるのでそうしよう。まず最初に,テンプレートが外部ファイルになっている場合の Erubi の使い方を述べ,次にテンプレートが String オブジェクトとして用意されている場合の書き方に触れる。
テンプレートに対しては,ローカル変数
lv
,インスタンス変数@iv
,メソッドm
を渡してやることにする。Erubi の使い方
gem として erubi と tilt を使うので,Gemfile を以下のようにしておく。
Gemfilesource "https://rubygems.org" gem "tilt" gem "erubi"テンプレートは外部ファイル sample.txt.erb
sample.txt.erbiv: <%= @iv %> lv: <%= lv %> m: <%= m 3 %>に置く。ファイル名は必ずしも拡張子を
.txt.erb
のように二重に付ける必要はなく,単にsample.erb
で構わない4。このテンプレートに,インスタンス変数,ローカル変数,メソッドを渡してレンダリングさせ,表示させてみよう。以下のコードになる。
require "bundler" Bundler.require t = Tilt::ErubiTemplate.new("sample.txt.erb") scope = Object.new scope.instance_variable_set "@iv", 1 def scope.m(x) "<#{x}>" end puts t.render(scope, lv: 2)これを実行すると
iv: 1 lv: 2 m: <3>と表示される。
コードを読み解いていこう。
まずはTilt::ErubiTemplate.new("a.txt.erb")の部分。
今の場合,Erubi のテンプレートを扱うので,Tilt::ErubiTemplate というクラスを使う。
new
にファイルのパスを渡してやると,ファイルを読み込んでインスタンスを作ってくれる。次に,いきなり最終行だが,
t.render(scope, lv: 2)を見てみよう。
render
は文字通りレンダリングのためのメソッド。レンダリング結果の文字列が返る。テンプレートに与えるデータを引数として渡してやる。
第一引数は置いておいて,第二引数lv: 2
に注目しよう。これはハッシュである(キーワード引数ではない)。
第二引数のハッシュは,ローカル変数の名前と値の組を指定するものだ。
今の場合,テンプレート中で,値2
を持つlv
というローカル変数が定義されることになる5。
ローカル変数は好きなだけ指定できる。第一引数には任意のオブジェクトを渡すことができる。このオブジェクトはテンプレート中で
self
となる。
render
の第一引数に渡すオブジェクトを「スコープ」と呼ぶらしい(なので変数名もscope
としておいた)。テンプレート中の Ruby コードは,このスコープオブジェクトのコンテキストで評価される。
だから,スコープオブジェクトのメソッドを呼び出すことができるし,スコープオブジェクトのインスタンス変数を読み書きすることができる。上のコード例では,Object クラスのインスタンスを生成して
scope
に代入し,scope.instance_variable_set "@iv", 1のようにしてインスタンス変数を定義したが,もちろん他の手段でインスタンス変数を定義してもよい。
また,メソッドについては,上のコード例では
def scope.m(x) "<#{x}>" endのようにして特異メソッドを定義しておいたが,他の手段でメソッドを定義してもよい。
例えばメソッド群を定義したモジュールを用意しておき,スコープオブジェクトにextend
するのでもよい。
つまり,module Helper # 云々 end scope.extend Helperといった具合である。
HTML エスケープのメソッドなど,テンプレート中でよく使うヘルパーメソッドを定義しておくといい。スコープが不要なら
render
メソッドの第一変数にはnil
でも渡しておけばよいだろう。ファイルでなく String の場合
eRuby テキストがファイルではなく既に String オブジェクトとして存在している場合はどう書くか。
たとえばerb_text = <<EOT iv: <%= @iv %> lv: <%= lv %> m: <%= m 3 %> EOTのように与えられている場合。
Tilt::ErubiTemplate.new に,引数ではなくブロックで与えればよい。つまり,Tilt::ErubiTemplate.new{ erb_text }のように。ただこれだけのこと。
「ファイルパスなら引数で与え,テキストならブロックで与える」というのは Nokogiri の使い方に似ている。Erubi::Engine を改造して埋め込み記号を変更
さいごに,改造に関する話題を。
こんなことがあった。
rails new
が雛形ファイル群を生成するのと同じように,自作の Ruby 製コマンドでファイル群を生成したかった。
生成するファイルは,毎回完全に同じなわけではなく,いくつかのパラメーターによって内容が違っていた。
こういう場合に生成ファイルのテンプレートを eRuby 形式で用意するのは良いやり方だろう。ただ,ちょっとややこしいのが,生成するファイル群の中に eRuby ファイルがあったこと。
これの何が問題かというと,「eRuby テンプレートを生成する eRuby テンプレート」になるので,Ruby コード埋め込みがややこしくなるのだ。eRuby には,
<%% %>
が<% %>
となり,<%%= %>
が<%= %>
となるルールがあるので,「テンプレートのテンプレート」も記述できる。
とは言え,<% %>
と<%% %>
がごちゃまぜになったテンプレートは見づらい。
よしっ,テンプレートのテンプレートでは<% %>
や<%= %>
の代わりに[[ ]]
や[[= ]]
を使うことにしよう!
(えっ?)そんなことが,できるんである。
regexp オプションを与える
Erubi のテンプレートエンジンである Erubi::Engine を生成する際,
regexp
というオプションを与えることができる。
これは,Erubi がテンプレートテキストから Ruby コード部分を抜き出す正規表現だ。
デフォルトでは/<%(={1,2}|-|\#|%)?(.*?)([-=])?%>([ \t]*\r?\n)?/m
となっている。
これを/\[\[(={1,2}|-|\#|%)?(.*?)([-=])?\]\]([ \t]*\r?\n)?/m
に変えてやればいい。
Tilt を使う場合,Erubis::Engine.new をしなくてよいぶん,一工夫が必要になる。
まず,regexp
オプションの値を変えた Erubi::Engine のサブクラスを作り,Tilt::ErubiTemplate.new
のオプションで,エンジンクラスとして指定するのだ。
以下のようなコードになる。require "bundler" Bundler.require class MyErubiEngine < Erubi::Engine def initialize(input, properties={}) properties[:regexp] = /\[\[(={1,2}|-|\#|%)?(.*?)([-=])?\]\]([ \t]*\r?\n)?/m super(input, properties) end end t = Tilt::ErubiTemplate.new(engine_class: MyErubiEngine){ <<~EOT } iv: [[= @iv ]] lv: [[= lv ]] m: [[= m 3]] [[% 3/0 ]] EOT scope = Object.new scope.instance_variable_set "@iv", 1 def scope.m(x) "<#{x}>" end puts t.render(scope, lv: 2)実行すると
iv: 1 lv: 2 m: <3> <% 3/0 %>のように表示される。
コメントの形式で書いた[[% 3/0 ]]
が<% 3/0 %>
になるのは興味深い。
ほかにもいくつかあるが,さしあたりこの三つの名前を知っていればいいと思う。 ↩
とはいえ,私の印象では Erubis の完成度は高い。最新版が古いからといって,それだけでダメとは言えないんじゃないか。 ↩
フレームワークというものの存在意義の一つはそういうところにある。 ↩
.erb
以外の拡張子でも構わない。ただ,そうするとテキストエディターでのコードハイライトが eRuby 用にならないし,(本記事では扱わないが)Tilt にテンプレートエンジンを自動的に選ばせる使い方もできない。 ↩よく「テンプレートにローカル変数を渡す」という言い方をするが,正確に言えば渡しているのではなく,定義させているのだ。しかし,誤解がないかぎり「渡す」と表現してもよいと思う。 ↩
- 投稿日:2020-01-20T20:18:50+09:00
[Ruby] Hash で Value を昇順にする時 Key も昇順にする
- 投稿日:2020-01-20T20:12:09+09:00
【第10章】Railsチュートリアル 5.1(第4版) ユーザーの更新・表示・削除
はじめに
個人的な理解・備忘録を目的としてます。
筆者自身は動画版Railsチュートリアルで進めているため、アプリ作成中コード・ブランチ名などの若干の違いがありますので参考程度に流し見して頂けたら嬉しいです。
理解不足のため、何かありましたらコメント等ご指摘してくださると幸いです(^_^;)10.0 目標
未実装だったedit、update、index、destroyアクションを加え、RESTアクションを完成させる。
その他 個人的進行
単数形と複数形
モデル(概念的)→単
それ以外→複数(ほぼ全部)10.1 ユーザーを更新する
10.1.1 編集フォーム
編集フォームのモックアップ
(公式より参考)まずはフィーチャーブランチを作成。
$ git checkout -b updating-users最初はeditアクションを実装する。
app/controllers/users_controller.rb# GET /users/:id/edit def edit @user = User.find(params[:id]) #=> app/views/users/edit.html.erb end endapp/views/users/edit.html.erb
<% provide(:title, "Edit user") %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <!--入力formの送信先を指定--> <%= form_for(@user) do |f| %> <!--エラーメッセージ--> <%= render 'shared/error_messages' %> <!--入力formを作成--> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Save changes", class: "btn btn-primary" %> <% end %> <!--編集完了ボタンとユーザー画像を表示--> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="http://gravatar.com/emails" target="_blank">change</a> </div> </div> </div>
ユーザーのeditビュー画面の表示と、saveを押すとupdateアクションに移行しているか(エラー画面)を確認する。Webブラウザは通常GETリクエストとPOSTの2つのリクエストのみのため、PATCHリクエストを送信できないので、RailsはPOSTリクエストと隠しinputフィールドを利用してPATCHリクエストを「偽造」している。
edit.heml.erb
とnew.html.erb
はform_for(@user)...と構造は同じだが、
editにはDBに入っている値、newはDBにない新しいインスタンスが入り、これをRailsのActive Recordにあるnew_record?メソッドが判断する。Ruby on Rails チュートリアル 第10章 ユーザー更新 beforeフィルター フレンドリーフォワーディング adminまで
最後に、サイト内移動用のヘッダーSettingsにユーザー一覧表示用のリンクを追加する。
app/views/layouts/_header.html.erb<li><%= link_to "Users", users_path %></li> <li><%= link_to "Settings", edit_user_path(current_user) %></li>10.1.2 編集の失敗
ユーザー登録に失敗したときと似た方法で、編集に失敗した場合について扱う。updateアクションを追加して失敗時の処理表示を実装する。
app/controllers/users_controller.rb# PATCH /users/:id def update @user = User.find(params[:id]) if @user.update_attributes(user_params) # Success else # Failure #=> @user.errors.full_messages() render 'edit' end endパスワードなしで更新すると、エラーメッセージが出る。
10.1.3 編集失敗時のテスト
統合テストを生成
$ rails generate integration_test users_editテスト内容を記載する。流れは下記の通り。
- まず編集ページにアクセス
- editビュー(テンプレート)が描画されるかどうかをチェック
- その後、無効な情報を送信
- editビューが再描画されるかどうかをチェック
この特徴として、
PATCHリクエストを送るために
patchメソッドを使っている
というものがある。patchメソッドはとは、getやpost、deleteメソッドと同じように、HTTPリクエストを送信するためのメソッド。test/integration/users_edit_test.rbrequire 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "unsuccessful edit" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user), params: { user: { name: "", email: "foo@invalid", password: "foo", password_confirmation: "bar" } } assert_template 'users/edit' end endテストが通過すればok!
10.1.4 TDDで編集を成功させる
TDD
TDDとはテスト駆動開発(Test-Driven Development: TDD)の名称で、プログラム実装前にテストコードを書き(テストファーストと呼ばれる)、動作する必要最低限な実装をとりあえず行った後にコードを改善していく手法である。
基本スタイルは
1. (RED:失敗する)テストコードを書く
2. テストに通る(GREEN:成功する)最低限のコードを書く
3. コードの重複を除去する(リファクタリング)
を繰り返すもので、アジャイル開発等でよく用いられる。
(※本記事では(公式)
の理解を目的とするため、REDは一部省略してリファクタリングに移る場合もあります)この節では編集フォームが動作するようにする。
今回はアプリケーション用のコードを実装する前に統合テストとして受け入れテスト (Acceptance Tests)を行う。
受け入れテストとは、ある機能の実装が完了し、受け入れ可能な状態になったかどうかを決める(成功したらどうなるか?の)テスト
とされている。
先ほどのテストをベースとして、
- 今回はユーザー情報を更新する有効な情報を送信する
- 次に、flashメッセージが空でないかどうか
- プロフィールページにリダイレクトされるかどうか
- DBのユーザー情報をインスタンスに上書きする(リロード)
- データベース内のユーザー情報が正しく変更されたかどうか
test/integration/users_edit_test.rbtest "successful edit" do get edit_user_path(@user) assert_template 'users/edit' name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email endもちろん成功部を実装していないためテストしてもRED。
updateアクションif文に成功パターンとして、flashと@userでリダイレクト動作を追加する。
app/controllers/users_controller.rbdef update @user = User.find(params[:id]) if @user.update_attributes(user_params) # Success flash[:success] = "Profile updated" redirect_to @user else先ほどのテストでパスワードが空で渡しているためバリデーションで弾かれるが、例外処理として
allow_nil: true
というオプションをvalidatesに追加してテストを通過 & 更新flashの表示を確認。app/models/user.rbvalidates :password, presence: true, length: { minimum: 6 }, allow_nil: true10.2 認可
editアクションとupdateアクションの動作導入はできたが、今のままでは誰でも (ログインしていないユーザーでも) ユーザー情報を編集できてしまうので、ユーザーにログインを要求し、かつ自分以外のユーザー情報を勝手に変更できないように制御する。こういったセキュリティ上の制御機構を
セキュリティモデル
と呼ぶ。
(公式より参考)
(実際のところ、本人でもセッションが切れてしまった場合も含む)
この節では、ログインしていないユーザーが保護された(自分の権限のない)ページにアクセスしようとしたらログインを促すよう対処する。認証と認可
日本語だと似たような印象になるが、
認証(英:Authentication, AuthN)
→ 何者であるかを特定すること。
ex.「〇〇ですか?」と尋ねられる、職務質問で身分証の提示を求められるなど
Railsでは、*** authenticateメソッド***認可(英:Authorization, AuthZ)
→ 行動やリソースの使用を許可すること。
ex.「△△の資格がありますね。あのカウンターへどうぞ」と権限を認められる。
Railsでは、beforeメソッド`<参考>
認証と認可10.2.1 ユーザーにログインを要求する
beforeフィルター
beforeフィルターとは、before_actionメソッドを使って何らかの処理が実行される直前に特定のメソッドを実行する仕組みのこと。
今回はユーザーにログインを要求するためコントローラーに追加する。
before_actionの後にメソッド名をシンボルでlogged_in_userメソッドを定義、その後に:onlyオプション (ハッシュ) で渡されたeditアクション、updateアクションを入れることで、「only以下のアクション(edit、updateアクション)が実行される前に、最初に定義したメソッド(logged_in_user)を実行してね」という内容になる。
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:edit, :update] 省略 # beforeアクション # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? flash[:danger] = "Please log in." redirect_to login_url end end endこの段階ではテストしててもRED。原因としては、editアクションやupdateアクションでログインを要求するようになったため、ログインしていないユーザーだとこれらのテストが失敗するようになったため。対処としては、editアクションやupdateアクションをテストする前にログインしておくよう、
log_in_asヘルパー
を実装する。test/integration/users_edit_test.rbtest "unsuccessful edit" do log_in_as(@user) #=> Michaelとしてログイン 省略 test "successful edit" do log_in_as(@user) #=> MichaelとしてログインテストはGREEN。
しかし、実はまだbeforeフィルターの実装はまだ終わっていない。セキュリティモデルに関する実装を取り外してもテストが通ってしまうか、beforeフィルターをコメントアウトしてテスト確認。app/controllers/users_controller.rbclass UsersController < ApplicationController # before_action :logged_in_user, only: [:edit, :update]0 failures, 0 errors, 0 skips通過してしまった。
beforeフィルターは基本的にアクションごとに適用していくので、Usersコントローラのテストもアクションごとに書いていく。手順としては、
1.routes
からedit、updateの正しい種類のHTTPリクエスト確認
2. そのリクエストを使ってeditアクションとupdateアクションをそれぞれ実行
3. flashにメッセージが代入されたかどうか
4. ログイン画面にリダイレクトされたかどうか$ rails routes Prefix Verb URI Pattern Controller#Action sessions_new GET /sessions/new(.:format) sessions#new root GET / static_pages#home static_pages_home GET /static_pages/home(.:format) static_pages#home help GET /help(.:format) static_pages#help about GET /about(.:format) static_pages#about contact GET /contact(.:format) static_pages#contact signup GET /signup(.:format) users#new POST /signup(.:format) users#create login GET /login(.:format) sessions#new POST /login(.:format) sessions#create logout DELETE /logout(.:format) sessions#destroy users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update PUT /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroyeditとupdateアクションの保護に対するテスト追加。
beforeフィルターが入っているかの確認(ユーザー:Michael追加)。具体的には、
1. ログインしてない状況でgetリクエスト→ユーザーの編集ページに
2. flashが出て
3. ログインにリダイレクトされるかもう一つはpatchリクエスト(ブラウザ以外からもある)にもneforeの確認を行うもの。
test/controllers/users_controller_test.rbrequire 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "should redirect edit when not logged in" do get edit_user_path(@user) assert_not flash.empty? assert_redirected_to login_url end test "should redirect update when not logged in" do patch user_path(@user), params: { user: { name: @user.name, email: @user.email } } assert_not flash.empty? assert_redirected_to login_url end endテストして
2 failures, 0 errors, 0 skipsエラーでなく失敗したのok(コメントアウト解除)
10.2.2 正しいユーザーを要求する
ユーザーが自分の情報だけを編集できるようにしたい。まずはユーザーの情報が互いに編集できないことを確認するために、ユーザー用のfixtureファイル(YAML)に2人目のユーザー(Archer)を追加する。
test/fixtures/users.ymlarcher: name: Sterling Archer email: duchess@example.gov password_digest: <%= User.digest('password') %>次に、
log_in_asメソッド
を使ってeditアクションとupdateアクションをテスト。このとき、既にログイン済みのユーザーを対象として(①ArcherさんでログインしてMichaelさん入ろうとする、②Archerさんでログインしてpatchを送ろうとする)、ログインページではなくルートURLにリダイレクトしている点に注意。test/controllers/users_controller_test.rbdef setup @user = users(:michael) @other_user = users(:archer) #=> 他ユーザー追加 endテストではエラーになるので、beforeアクションに書き足す。
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:edit, :update] before_action :correct_user, only: [:edit, :update] #順番に注意! 上から順番に「ログインしたユーザー」且つ正しいユーザー # beforeアクション # ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? flash[:danger] = "Please log in." redirect_to login_url end end # 正しいユーザーかどうか確認 def correct_user # GET /users/:id/edit # PATCH /users/:id @user = User.find(params[:id]) redirect_to(root_url) unless @user == current_user endテストは通過。
リファクタリングとして、一般的な慣習に倣ってcurrent_user?という論理値を返すメソッドを実装。correct_userの中で使えるようにしたいので、Sessionsヘルパーの中にこのメソッドを追加する。
(旧)unless @user == current_user (新)unless current_user?(@user)app/controllers/users_controller.rbredirect_to(root_url) unless current_user?(@user) #=> @user == current_userapp/helpers/sessions_helper.rb# 渡されたユーザーがログイン済みユーザーであればtrueを返す def current_user?(user) user == current_user endテスト通過。
10.2.3 フレンドリーフォワーディング
フレンドリーフォワーディング
フレンドリーフォワーディングとは、ユーザーがログインした後、ログイン直前に閲覧していたページヘとリダイレクトさせる(あると便利な)機能のこと。
フレンドリーフォワーディングのテストは、ログイン手前でログインページへ(ユーザさんにログインしてもらう)
ログインした後に編集ページへアクセスするという順序を逆にするもの。test/integration/users_edit_test.rbtest "successful edit with friendly forwarding" do get edit_user_path(@user) log_in_as(@user) assert_redirected_to edit_user_url(@user) name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), params: { user: { name: name, email: email, password: "", password_confirmation: "" } } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end実装してないのでテストして失敗(failure)。
ユーザーを希望のページに転送するには、リクエスト時点のページをどこかに保存しておき、その場所にリダイレクトさせる必要があり、
store_location
とredirect_back_or
の2つのメソッドを使って対応する。app/helpers/sessions_helper.rb# 記憶したURL (もしくはデフォルト値) にリダイレクト def redirect_back_or(default) redirect_to(session[:forwarding_url] || default) session.delete(:forwarding_url) end # アクセスしようとしたURLを覚えておく def store_location session[:forwarding_url] = request.original_url if request.get? end
redirect_back_orメソッド
転送先のURLを保存する場所は(今回は一時的なものなので)DBでなくsession
を使い、もともとユーザーが行きたかった場所を保存しておいてURLがある場合はリダイレクトし、ない場合(sessionが切れたり分からなくなったら)デフォルト値にユーザーのページを表示する。終わったらsessionを消す。
デフォルトのURLは、sessionコントローラのcreateアクションに追加し、サインイン成功後にリダイレクトします
store_locationメソッド
リクエストが送られたURLをsession変数のforwarding_urlキーに格納。ただし、GETリクエストが送られたときのみ(後置if)。ログインユーザー用beforeフィルターにstore_locationメソッドを追加する。
app/controllers/users_controller.rb# ログイン済みユーザーかどうか確認 def logged_in_user unless logged_in? store_location #=> アクセスしようとしたURLを覚えておく flash[:danger] = "Please log in." redirect_to login_url end endapp/controllers/sessions_controller.rbdef create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_back_or user #=> フレンドリーフォワーディングを備えるこれでテストは通過する。
Settings
の確認もok.10.3 すべてのユーザーを表示する
この節ではすべての(大量の)ユーザーをページごとに一覧表示、
かつsignupしたユーザーのみが閲覧できるindexアクションを実装する。
それに伴い、①DBにサンプルデータを追加する方法、②将来ユーザー数が膨大になってもindexページを問題なく表示できるようにするためのユーザー出力のページネーション (pagination=ページ分割) の方法、を学ぶ。モックアップ
(公式より参考)10.3.1 ユーザーの一覧ページ
indexページを不正なアクセスから守るために、まずはindexアクションが正しくリダイレクトするか検証するテスト。
test/controllers/users_controller_test.rb#=> 習慣として、indexに関するテストは一番上に書く test "should redirect index when not logged in" do get users_path #=> user(s)_pathでindexのurl(/users)へgetリクエスト assert_redirected_to login_url endbeforeフィルターに何もないため失敗するので、beforeフィルターの
logged_in_user
にindexアクション
を追加して、このアクションを保護する。すべてのユーザーを表示するために、User.allを使ってデータベース上の全ユーザーを取得し、ビューで使えるインスタンス変数@usersに代入。app/controllers/users_controller.rbbefore_action :logged_in_user, only: [:index, :edit, :update] #=> 「:index」追加 def index @users = User.all endユーザーのindexビュー(app/views/users/index.html.erb)を新規に作成。
userはハッシュを受け取らないので、引数に2つ(gravatar_for userとsize: 50)を与えるとエラーが起こる。<% provide(:title, 'All users') %> <h1>All users</h1> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul>app/helpers/users_helper.rbmodule UsersHelper # 引数で与えられたユーザーのGravatar画像を返す def gravatar_for(user, options = { size: 80 }) #=> デフォでsize80追加 gravatar_id = Digest::MD5::hexdigest(user.email.downcase) size = options[:size] #=>変数size ,下記で「?s=#{size}」追加 gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end endSCSSの追記
app/assets/stylesheets/custom.scss/* Users index */ .users { list-style: none; margin: 0; li { overflow: auto; padding: 10px 0; border-bottom: 1px solid $gray-lighter; } }ビュー画面ができたので、ヘッダー(app/views/layouts/_header.html.erb)にユーザー一覧ページへのリンクを更新する。
<% if logged_in? %> <li><%= link_to "Users", users_path %></li>テストして通過。
10.3.2 サンプルのユーザー
indexページに複数のユーザーを表示させてみる。
まずはGemfile
にFaker gem
を追加する。Gemfilegem 'bcrypt', '3.1.12' gem 'faker', '1.7.3' #=> 追加データベース上にサンプルユーザーを生成するRailsタスク(サンプルユーザーを生成するRubyスクリプト)を追加。
Railsではdb/seeds.rb
というファイルを標準とする。
中身としては、
1. まずユーザー(Example User)を作る
2. Fakerの「.name」メソッドからそれっぽいユーザーを99人増やすdb/seeds.rbUser.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar") 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password) endそしてbundle install。だが、筆者の場合失敗。
エラー対応:GemfileにFaker gemを追加できない
bundle install
しようとするとエラー。
サーバを止めてもダメ。環境
Rails v5.1.6
faker v1.7.3$ bundle install The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies... Bundler could not find compatible versions for gem "i18n": In snapshot (Gemfile.lock): i18n (= 1.7.0) In Gemfile: rails (= 5.1.6) was resolved to 5.1.6, which depends on activesupport (= 5.1.6) was resolved to 5.1.6, which depends on i18n (>= 0.7, < 2) faker (= 1.7.3) was resolved to 1.7.3, which depends on i18n (~> 0.5) Running `bundle update` will rebuild your snapshot from scratch, using only the gems in your Gemfile, which may resolve the conflict.対応策
fakerのバージョンを指定しないGemfile#旧 gem 'faker', '1.7.3' gem 'faker' #=> バージョン指定なし再度bundle install実行。
$ bundle install The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies... Using rake 13.0.1 Using concurrent-ruby 1.1.5 Using i18n 1.7.0 Using minitest 5.10.3 Using thread_safe 0.3.6 Using tzinfo 1.2.5 Using activesupport 5.1.6 Using builder 3.2.3 Using erubi 1.9.0 Using mini_portile2 2.4.0 Using nokogiri 1.10.5 Using rails-dom-testing 2.0.3 Using crass 1.0.5 Using loofah 2.3.1 Using rails-html-sanitizer 1.3.0 Using actionview 5.1.6 Using rack 2.0.7 Using rack-test 1.1.0 Using actionpack 5.1.6 Using nio4r 2.5.2 Using websocket-extensions 0.1.4 Using websocket-driver 0.6.5 Using actioncable 5.1.6 Using globalid 0.4.2 Using activejob 5.1.6 Using mini_mime 1.0.2 Using mail 2.7.1 Using actionmailer 5.1.6 Using activemodel 5.1.6 Using arel 8.0.0 Using activerecord 5.1.6 Using ansi 1.5.0 Using execjs 2.7.0 Using autoprefixer-rails 9.7.2 Using bcrypt 3.1.12 Using bindex 0.8.1 Using rb-fsevent 0.10.3 Using ffi 1.11.2 Using rb-inotify 0.10.0 Using sass-listen 4.0.0 Using sass 3.7.4 Using bootstrap-sass 3.3.7 Using bundler 1.17.3 Using byebug 9.0.6 Using coderay 1.1.2 Using coffee-script-source 1.12.2 Using coffee-script 2.4.1 Using method_source 0.9.2 Using thor 0.20.3 Using railties 5.1.6 Using coffee-rails 4.2.2 Fetching faker 2.10.1 Installing faker 2.10.1 Using formatador 0.2.5 Using ruby_dep 1.5.0 Using listen 3.1.5 Using lumberjack 1.0.13 Using nenv 0.3.0 Using shellany 0.0.1 Using notiffany 0.1.3 Using pry 0.12.2 Using guard 2.13.0 Using guard-compat 1.2.1 Using guard-minitest 2.4.4 Using multi_json 1.14.1 Using jbuilder 2.7.0 Using jquery-rails 4.3.1 Using ruby-progressbar 1.10.1 Using minitest-reporters 1.1.14 Using puma 3.9.1 Using sprockets 3.7.2 Using sprockets-rails 3.2.1 Using rails 5.1.6 Using rails-controller-testing 1.0.2 Using tilt 2.0.10 Using sass-rails 5.0.6 Using spring 2.0.2 Using spring-watcher-listen 2.0.1 Using sqlite3 1.3.13 Using turbolinks-source 5.2.0 Using turbolinks 5.0.1 Using uglifier 3.2.0 Using web-console 3.5.1 Bundle complete! 24 Gemfile dependencies, 82 gems now installed. Gems in the group production were not installed. Use `bundle info [gemname]` to see where a bundled gem is installed.無事終了
(とてもありがたかった)ご参考先
Railsチュートリアルでfakerがインストールできない場合の対処法本編へ戻ります
DBリセット(これまでの登録ユーザー初期化)、Railsタスクを実行 (db:seed) 。$ rails db:migrate:reset $ rails db:seedサンプルですが、たくさんのユーザーさん登場。
10.3.3 ページネーション
ユーザーが増えたのはいいが、今度は逆に1つのページに大量のユーザーが表示されて(仮に1万人とかになったときに)重くなってしまう。
そこで解決するのが、ページネーション (pagination) **というもの。
ページネーションとは、検索などに使われてるような「1つのページに一度に〇〇個だけ表示する」**というもの。
今回は1つのページに一度に30人だけ表示するのに、シンプルとされるwill_paginateメソッド
を使う。そのためには、Gemfileにwill_paginate gem とbootstrap-will_paginate gemを両方含め、Bootstrapのページネーションスタイルを使ってwill_paginateを構成する。Gemfilegem 'faker' gem 'will_paginate', '3.1.6' gem 'bootstrap-will_paginate', '1.0.0'$ bundle install新たにpaginateメソッドを追加したため、念のためここでサーバーの再起動を行っておく。
indexページ(app/views/users/index.html.erb)でpaginationを使う<% provide(:title, 'All users') %> <h1>All users</h1> <%= will_paginate %> <ul class="users"> <% @users.each do |user| %> <li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li> <% end %> </ul> <%= will_paginate %>
will_paginateメソッド
は、usersビューのコードの中から@usersオブジェクトを自動的に見つけ出し、それから他のページにアクセスするためのページネーションリンクを作成してくれる。ただし、現在の@users変数にはUser.allの結果が含まれているが 、will_paginateではpaginateメソッドを使った結果が必要となる。必要となるデータの例は次のとおり
paginateでは、キーが:pageで値がページ番号のハッシュを引数に1を渡すと1~30までのユーザーまで出る
ちなみにpageがnilの場合、 paginateは単に最初のページを返す。$ rails console > User.paginate(page: 1) User Load (1.0ms) SELECT "users".* FROM "users" LIMIT ? OFFSET ? [["LIMIT", 11], ["OFFSET", 0]] (0.1ms) SELECT COUNT(*) FROM "users" => #<ActiveRecord::Relation [#<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-01-19 06:42:48", updated_at: "2020-01-19 06:42:48", password_digest: "$2a$10$xDXvcjV4nyrflH.nVpxu2uWGCeBYR5quXeo1ERVKIUE...", remember_digest: nil>, #<User id: 2,... 省略paginateを使うことで、このアプリでユーザーのページネーションを行えるようになる。
具体的には、indexアクション内のallをpaginateメソッドに置き換えて、indexアクションでUsersをページネートするapp/controllers/users_controller.rbdef index #旧 @users = User.all @users = User.paginate(page: params[:page]) end現在の位置(ページネーションの番号)と下のデータが一致。
10.3.4 ユーザー一覧のテスト
ユーザーの一覧ページが動くようになったので、ページネーションに対するテストを行う。
今回のテストでは、
1. ログイン
2. indexページにアクセス
3. 最初のページにユーザーがいることを確認
4. ページネーションのリンクがあることを確認
の順でテストを行う。まずはfixtureにさらに30人のユーザーを追加する。
今後必要になるので、2人の名前付きユーザーも一緒に追加。test/fixtures/users.ymlmichael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %> archer: name: Sterling Archer email: duchess@example.gov password_digest: <%= User.digest('password') %> lana: name: Lana Kane email: hands@example.gov password_digest: <%= User.digest('password') %> malory: name: Malory Archer email: boss@example.gov password_digest: <%= User.digest('password') %> <% 30.times do |n| %> user_<%= n %>: name: <%= "User #{n}" %> email: <%= "user-#{n}@example.com" %> password_digest: <%= User.digest('password') %> <% end %>統合テストを生成。
$ rails generate integration_test users_index Running via Spring preloader in process 12447 invoke test_unit create test/integration/users_index_test.rb test/integration/users_index_test.rbページネーションを含めたUsersIndexのテスト内容を記述。
具体的には、
1. Michael(何かのユーザー)でログイン
2. ユーザーのindexページへ移動(テンプレート)
3. ページネーションクラスがあるか
4. ユーザーの名前(変数user)をクリックするとそのprofileページに行くかtest/integration/users_index_test.rbrequire 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "index including pagination" do log_in_as(@user) get users_path assert_template 'users/index' assert_select 'div.pagination' User.paginate(page: 1).each do |user| assert_select 'a[href=?]', user_path(user), text: user.name end end endテストは通過。
10.3.5 パーシャルのリファクタリング
いくつかリファクタリングを行う。
リファクタリングの第一歩は、ユーザーのliをrender呼び出しに置き換える。(app/views/users/index.html.erb)
renderをパーシャル (ファイル名の文字列) に対してではなく、Userクラスのuser変数に対して実行している。これは、renderにモデルのインスタンスオブジェクトを渡したときのデフォルトの挙動。この場合、Railsは自動的に_user.html.erbという名前のパーシャルを探しにいくので、各ユーザーを表示するパーシャルを作成する。<ul class="users"> <% @users.each do |user| %> <%= render user %> <!-- => app/views/リソース名/_モデル名.html.erb--> <!-- => app/views/users/_user.html.erb--> <% end %> </ul>各ユーザーを表示するパーシャル
app/views/users/_user.html.erb<li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> </li>今度はrenderを@users変数にして、最終的に下記に。
<ul class="users"> <%= render @users %> </ul>Railsは@usersをUserオブジェクトのリストであると推測する。さらに、ユーザーのコレクションを与えて呼び出すと、Railsは自動的にユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力するので、each文がなくなりコードは短くなった。
一応テストして通過。
10.4 ユーザーを削除する
destroy
の実装。この節では、ユーザーを削除するためのリンクを追加する。もちろん、ユーザーを削除(delete)できるのは管理権限を持ったユーザーのみ。モックアップは以下の形式。(公式より参考)
ただしその前に、削除を実行できる権限を持つ管理 (admin) ユーザーのクラスを作成する。
10.4.1 管理ユーザー
特権を持つ管理ユーザーを識別するために、論理値をとるadmin属性をUserモデルに追加する。
こうすると自動的にadmin?メソッド (論理値booleanを返す) も使えるようになるため、これを使って管理ユーザーの状態をテストする。
変更後のデータモデルは以下(公式より参考)まずはマイグレーションを実行してadmin属性を追加(属性の型をbooleanに指定)
$ rails generate migration add_admin_to_users admin:boolean Running via Spring preloader in process 6078 invoke active_record create db/migrate/20200120090448_add_admin_to_users.rbマイグレーションを実行するとadminカラムがusersテーブルに追加される。デフォルトでは管理者になれないことを示す+nilが入るケースを防ぐため、
default: false引数
を与える。db/migrate/[timestamp]_add_admin_to_users.rbclass AddAdminToUsers < ActiveRecord::Migration[5.1] def change add_column :users, :admin, :boolean, default: false end endマイグレーションを実行。
$ rails db:migrateコンソール(sandbox)で動作を確認すると、期待どおりadmin属性が追加されて論理値をとり、さらに疑問符の付いたadmin?メソッドも利用できるようになっている。
$ rails console --sandbox > user = User.first > user.admin? => false > user.toggle!(:admin) => true > user.admin? => trueここではtoggle!メソッドを使って admin属性の状態をfalseからtrueに反転している。
toggle!メソッドの「!」は破壊的メソッドで、「書き換えたらもう元には戻らない」ことを示している。演習用として、最初のユーザーだけをデフォルトで管理者にするよう(admin→true)、サンプルデータを更新しておく。
db/seeds.rbUser.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", admin: true) 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password) endデータベースをリセットして、サンプルデータを再度生成。
$ rails db:migrate:reset $ rails db:seed10.4.2 destroyアクション
まず、destroyアクションへのリンクを追加する。ユーザーindexページの各ユーザーに削除用のリンクを追加+管理ユーザーへのアクセスを制限が目標。
ユーザー削除用リンクの実装 (管理者にのみ表示される)
(app/views/users/user.html.erb)
※admin権限を持っていても、自分自身は消せないように && !currentuser? で確認を取っている。<li> <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %> <% if current_user.admin? && !current_user?(user) %> | <%= link_to "delete", user, method: :delete, data: { confirm: "You sure?" } %> <% end %> </li>実際にユーザExample Userでログインしてみると、アクションまで(エラー画面で)確認できる。
実際に動作するdestroyアクションを追加する。このアクションでは、該当するユーザーを見つけてActive Recordのdestroyメソッドを使って削除し、最後にユーザーのindexページにリダイレクトさせる。ユーザーを削除するためにはログインしていなくてはならないため、destroyアクションもlogged_in_userフィルター(before_action)に追加している。
ただしこれでは、コマンドラインでDELETEリクエストを直接発行するという方法でサイトの全ユーザーを削除される可能性があるため、destroyアクションにもadmin_userフィルターを入れてアクセス制御を実装する。
app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update, :destroy] before_action :correct_user, only: [:edit, :update] before_action :admin_user, only: [:destroy] 省略 # DELETE /users/:id def destroy User.find(params[:id]).destroy flash[:success] = "User deleted" redirect_to users_url end private # 管理者かどうか確認 def admin_user redirect_to(root_url) unless current_user.admin? end end10.4.3 ユーザー削除のテスト
fixtureファイルの一番上(Michael)を管理者にする。
test/fixtures/users.ymlmichael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %> admin: true管理者権限の制御をアクションレベルでテストする。
「ユーザーがログインしてないときにDELETEリクエスト送ったらだめ」
「ログインしていたとしても、adminじゃなかったらやはりだめ」
という内容。test/controllers/users_controller_test.rbtest "should redirect destroy when not logged in" do assert_no_difference 'User.count' do delete user_path(@user) end assert_redirected_to login_url end test "should redirect destroy when logged in as a non-admin" do log_in_as(@other_user) assert_no_difference 'User.count' do delete user_path(@user) end assert_redirected_to root_url endテストは通過。
最後に、削除リンクとユーザー削除に対する統合テストとして「ユーザーを削除したらユーザーの総数が1つ消えてるよ?」というテストを付け加える(先のテストを大幅に改造)。
上のテストは、
1. サンプルとしてMichaelさん(admin)、Archerさん(non_admin)のユーザーデータを持ってくる
2. ログイン(ユーザーパスが見えるはず)
3. ページネーション見える
4. ユーザーがadminかどうかチェック(adminならdeleteが見えるはず)
5. 選択すればArcherさん(non_admin)は消えるはず下のテストは,
1. non_adminとしてログイン
2. deleteリンクは見えないはずなのでcountは0か?test/integration/users_index_test.rbrequire 'test_helper' class UsersIndexTest < ActionDispatch::IntegrationTest def setup @admin = users(:michael) @non_admin = users(:archer) end test "index as admin including pagination and delete links" do log_in_as(@admin) get users_path assert_template 'users/index' assert_select 'div.pagination' first_page_of_users = User.paginate(page: 1) first_page_of_users.each do |user| assert_select 'a[href=?]', user_path(user), text: user.name unless user == @admin assert_select 'a[href=?]', user_path(user), text: 'delete' end end assert_difference 'User.count', -1 do delete user_path(@non_admin) end end test "index as non-admin" do log_in_as(@non_admin) get users_path assert_select 'a', text: 'delete', count: 0 end endテストは通過。
最後にherokuへデプロイ。
$ git add -A $ git commit -m "Finish ch10" $ git checkout master $ git merge updating-users $ git push heroku master本番環境として
・ DBリセットは危険なので本来あまりやらない
・ 本番環境にrun rails db:seed
で擬似データを送る。これもあまりやらない
・ リモートのリンクのfetchをクリック$ heroku pg:reset DATABASE ▸ WARNING: Destructive action ▸ To proceed, type sample-app or re-run this command with --confirm sample-app > sample-app $ heroku run rails db:migrate $ heroku run rails db:seed $ git remote -v ※リンク確認本番環境でログインしてユーザー削除の確認ができたので終了!
- 投稿日:2020-01-20T15:23:04+09:00
【Rails】Pryを使ったメソッドのデバッグ方法
Rails案件で「pry」を使って実装したメソッドをデバッグする機会があったので、使い方の備忘録
pryとは
REPLで対話的にプログラムを実行したり、ActiveRecordを利用したDB操作や、変数の中身を調べることができるコマンドラインツール
※ Laravelで言うところの「tinker」とほぼ同じような使い方ができる、超便利なデバッグツールサンプル
サンプルとして、以下のメソッドを例に解説
app/lib/utils/test_sample.rbmodule Utils module TestSample class << self def sample_method(param) data = [1, 2, 3, 4, 5] data.each do |r| if r == param return true end end return false end end end endメソッドのデバッグ方法
bundle exec rails c
で、コンソールを立ち上げる$ bundle exec rails c Loading development environment (Rails 6.0.0) [1] pry(main)>ソースコードの「ブレークポイントを貼りたい箇所」に
binding.pry
を差し込むapp/lib/utils/test_sample.rbmodule Utils module TestSample class << self def sample_method(param) data = [1, 2, 3, 4, 5] data.each do |r| + binding.pry if r == param return true end end return false end end end end※ 変更を加えた際の反映は、pryのコンソールに
reload!
を入力してリロードすることコンソールから、デバッグしたいメソッド指定して実行する
[1] pry(main)> reload! Reloading... => true [2] pry(main)> Utils::TestSample.sample_method 3 From: /Users/nakano_shingo/Documents/minnshu-gms/app/lib/utils/test_sample.rb @ line 8 Utils::TestSample.sample_method: 4: def sample_method(param) 5: data = [1, 2, 3, 4, 5] 6: 7: data.each do |r| => 8: binding.pry 9: if r == param 10: return true 11: end 12: end 13: 14: return false 15: end※ 引数を指定したい場合は、上記のようにハイフンの後に引数を渡す
その時点の変数の値を確認したい場合は、コンソールに変数名を入力する
[1] pry(Utils::TestSample)> param => 3 [2] pry(Utils::TestSample)> r => 1pryのコマンドは、以下のようにコンソールから
help
を入力すると使い方を教えてくれるpry> help <コマンド>
よく使うコマンド
コマンド 説明 next 次の行を実行 step 次の行かメソッド内に入る continue プログラムの実行をcontinueしてpryを終了 finish 現在のフレームが終わるまで実行 exit/quit 実行中のステップを終了し、次のステップ(binding.pry)まで処理を実行 !!! pryのコンソールを抜ける ※ 参考: Pryコマンドまとめ - Qiita
「next」や「step」などのコマンドが使えない場合は、こちら
を参考にpry-nav
のgemを追加で導入すればOK
スクリプト言語でも、デバッグ時に手軽にステップ実行ができるのはとても便利です
- 投稿日:2020-01-20T15:09:19+09:00
Redis のキューに定期的に複数プロセスから重複なしで値を追加
resque のキューに定期的にタイムスタンプを追加して、cron 的に使いたかったのだが、キューに追加する処理を 1 つのプロセスでやるのは可用性が低いので、(複数のサーバに分散した) 複数のプロセスから重複なしで値を追加したかった。
下記の方法でうまくいきそう。
- 最後に値を追加したときのタイムスタンプを保持しておき、追加前にチェックする
- 1 だけだとほぼ同時に実行される場合に重複する可能性があるので Redis のトランザクション機構を利用する
下記は 1 秒ごとにタイムスタンプをキューに追加する例。
# cron.rb require "redis" require "json" QUEUE_NAME = "per_sec" # キューの名前 TIMESTAMP_NAME = "per_sec_timestamp" # 最後にキューに値を追加したときのタイムスタンプ redis = Redis.new loop do t = Time.now.to_i redis.watch(TIMESTAMP_NAME) # TIMESTAMP_NAME の値が exec までに変わっていれば multi - exec 間のコマンドを失敗させる last = redis.get(TIMESTAMP_NAME).to_i if last < t redis.multi redis.rpush(QUEUE_NAME, t) redis.set(TIMESTAMP_NAME, t) redis.exec ? puts("set #{t}") : puts("transaction fail") end sleep 0.1 end上記のスクリプトを 2 プロセス立ち上げて、
while true; do redis-cli lpop per_sec; sleep 0.1s; done
でキューの内容を pop しつづけると下記のようになり、重複なしでキューに追加できていることがわかる。
- 投稿日:2020-01-20T14:02:04+09:00
深くネストしたルーティングにはshallowを使う
目的
- Qiitaの記事に大まかな流れをテンプレート化しておき、作業効率を上げる。
- 今後、railsを学ぶ方に向けての参考に役立てる。
前提条件
- 実行環境
- Ruby 2.5.1
- Rails 5.2.4.1
- MySQL 5.7.7
(変更前)_config/routes.rbRails.application.routes.draw do resources :projects do resources :queries, only: %i[new create] do resources :issue_categories end end end? shallowを適用したコード
(変更後)_config/routes.rbRails.application.routes.draw do resources :projects do resources :queries, only: %i[new create], shallow: true do resources :issue_categories end end end ------------------ # ↓ 上と同じ Rails.application.routes.draw do resources :projects do shallow do resources :queries, only: %i[new create], shallow: true do resources :issue_categories end end end end?※赤文字部分が省略される
Helper HTTP Verb Path Controller#Action 1 変更前 project_query_issue_categories_path GET /projects/:project_id/queries/:query_id/issue_categories(.:format) issue_categories#index 変更後 query_issue_categories_path /queries/:query_id/issue_categories(.:format) 2 変更前 project_query_issue_categories_path POST /projects/:project_id/queries/:query_id/issue_categories(.:format) issue_categories#create 変更後 query_issue_categories_path /queries/:query_id/issue_categories(.:format) 3 変更前 new_project_query_issue_category_path GET /projects/:project_id/queries/:query_id/issue_categories/new(.:format) issue_categories#new 変更後 new_query_issue_category_path /queries/:query_id/issue_categories/new(.:format) 4 変更前 edit_project_query_issue_category_path GET /projects/:project_id/queries/:query_id/issue_categories/:id/edit(.:format) issue_categories#edit 変更後 edit_issue_category_path /issue_categories/:id/edit(.:format) 5 変更前 project_query_issue_category_path GET /projects/:project_id/queries/:query_id/issue_categories/:id(.:format) issue_categories#show 変更後 issue_category_path /issue_categories/:id(.:format) 6 変更前 project_query_issue_category_path PATCH /projects/:project_id/queries/:query_id/issue_categories/:id(.:format) issue_categories#update 変更後 issue_category_path /issue_categories/:id(.:format) 7 変更前 project_query_issue_category_path PUT /projects/:project_id/queries/:query_id/issue_categories/:id(.:format) issue_categories#update 変更後 issue_category_path /issue_categories/:id(.:format) 8 変更前 project_query_issue_category_path DELETE /projects/:project_id/queries/:query_id/issue_categories/:id(.:format) issue_categories#destroy 変更後 issue_category_path /issue_categories/:id(.:format) 関連URL
- 投稿日:2020-01-20T13:04:55+09:00
Rubyのencodeとforce_encodingの違い
はじめに
Rubyのencodeメソッドとforce_encodingメソッドの違いをご存じでしょうか?
まずはRubyのリファレンスマニュアルを見てみましょう。encode
self を指定したエンコーディングに変換した文字列を作成して返します。(以下、省略)
force_encoding
文字列の持つエンコーディング情報を指定された encoding に変えます。
このとき実際のエンコーディングは変換されず、検査もされません。
encode
はその名の通り文字列を変換してくれて、force_encoding
はエンコーディング情報(?)を変えるけど、文字列自体は変換されないと。エンコーディング情報が何かは分かりませんが、とりあえず動かしてみましょう。動作確認
検証用に「テスト」という文字列を
iso-2022-jp
という文字符号化方式に変換したものを用意します。メールでUTF-8を期待していたところに別の文字符号化方式の文字列が送られてきたイメージです。puts "テスト".encode('iso-2022-jp')出力結果
$B%F%9%H(B以降の内容を試す場合は下記のリンク先からエンコードされた文字列をコピーしてご使用ください。
https://paiza.io/projects/jCRDa1-PFB06JTcOZTGJ-gこの文字列を
UTF-8
の元の文字列に戻してみましょう。まずは
encode
を試してみます。puts "$B%F%9%H(B".encode('utf-8')出力結果
$B%F%9%H(Bあれ、何も変わらない・・・。
次に
force_encoding
puts "$B%F%9%H(B".force_encoding('utf-8')出力結果
$B%F%9%H(Bこちらも変わらず・・・。
この文字列を元に戻すにはどうすれば良いのでしょうか?
以下に、前提となる知識と変換方法をご紹介します。エンコーディング情報
force_encoding
の説明の中にもありましたが、文字列はエンコーディング情報を持ちます。
文字列のエンコーディング情報を確認するにはencodingメソッドで確認することができます。puts "テスト".encoding出力結果
UTF-8
encoding
の返り値として、Encodingオブジェクトを返します。Encodingクラスは文字エンコーディング(文字符号化方式)のクラスです。encode
とforce_encoding
の実行後に確認してみると違いが分かりそうですね。文字列とバイト
たとえば「あ」という文字列はバイトで表すと3つの数字が返ってきます。
puts "#{"あ".bytes}"出力結果
[227, 129, 130]それぞれの数字を16進数に変換すると
[e3 81 82]
となり、この数字はUTF-8
の「あ」と一致します。(参考)
このように文字はそれぞれバイト列が決まっているので、バイト列の数値が変化していれば文字が変わったと判断できそうですね。対象文字列の確認
さて、これらを踏まえた上で改めて先程の文字列を見てみましょう。
str = "$B%F%9%H(B" puts "文字列:#{str}" puts "バイト列:#{str.bytes}" puts "エンコーディング情報:#{str.encoding}"出力結果
文字列:$B%F%9%H(B バイト列:[36, 66, 37, 70, 37, 57, 37, 72, 40, 66] エンコーディング情報:UTF-8エンコーディング情報を見ると
UTF-8
となっているので、encode
でUTF-8
に変換しようとしても何も変わりません。また、force_encoding
を実行しても、元々のエンコーディング情報がUTF-8
のためこちらも何も変わりません。これによって最初の動作確認で何も変化しなかった理由が分かりました。では、どうすれば変換できるのでしょうか?変換方法
以下の2ステップで変換します。
1.force_encoding
でエンコーディング情報をiso-2022-jp
に変換する
2.encode
で文字列をUTF-8
に変換するstr = "$B%F%9%H(B" puts "オリジナル" puts str puts str.encoding puts "#{str.bytes}" puts "force_encoding" force_encode_str = str.force_encoding('iso-2022-jp') puts force_encode_str puts force_encode_str.encoding puts "#{force_encode_str.bytes}" puts "encode" encode_str = force_encode_str.encode('utf-8') puts encode_str puts encode_str.encoding puts "#{encode_str.bytes}"実行結果
オリジナル $B%F%9%H(B UTF-8 [27, 36, 66, 37, 70, 37, 57, 37, 72, 27, 40, 66] force_encoding $B%F%9%H(B ISO-2022-JP [27, 36, 66, 37, 70, 37, 57, 37, 72, 27, 40, 66] encode テスト UTF-8 [227, 131, 134, 227, 130, 185, 227, 131, 136]
force_encoding
で文字列に期待するコーディング情報に変換し、その後、encode
で期待する文字符号化方式(今回であればUTF-8
)で文字列を変換しました。文字列はもちろんのこと、バイト列も変換されているのが確認できますね。まとめ
encode
- 文字列を指定したエンコーディングに変換
- 文字列がもつエンコーディング情報についても、指定したエンコーディングに変換
- エンコーディング情報と指定したエンコーディングが同一の場合、変化しない
force_encoding
- 文字列自体は変化しない
- 文字列がもつエンコーディング情報を指定したエンコーディングに変換
- encodeの前処理として使いそう
参考
encoding - What is the difference between #encode and #force_encoding in ruby? - Stack Overflow
- 投稿日:2020-01-20T12:28:14+09:00
[Ruby]メールアドレスのドメイン数を調べる
メールアドレスのドメインとそのドメインの総数を配列として多い順で表示する方法です。
コードemails = [] User.all.each{|u| emails << /@/.match(u.email).post_match} emails.group_by(&:itself).map{|key, value| [key, value.count]}.to_h.sort{|(k1, v1), (k2, v2)| v2 <=> v1 }結果emails => [["gmail.com", 1234], ["yahoo.com", 1024], ["vodafone-jp.com", 761], ["hotmail.com", 373],,,,,,]
- 投稿日:2020-01-20T12:03:29+09:00
【Rails×Ajax】いいね機能の実装で上手く出来ないあなたへの2つの注意喚起 #学習者向け
目的
はじめまして。
今回、Railsでいいね機能をQiita等の記事通りに行っても上手く行かない!という方へ向けた、ちょっとした実装の際のチェック項目を列挙させていただきます。前提条件
対象となる読者
- 「いいね機能」の実装において、railsの同期処理では問題なく処理されるが、Ajax通信が上手く行かない
開発環境
- ruby 2.5.1
- Rails 5.2.4.1
- mysql Ver 14.14
実装済み機能
- 同期処理で「いいね機能」が正しく処理されていること
筆者が参考にした記事
- 「いいね機能」同期処理編 【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】?
- 「いいね機能」非同期処理編 【Rails×Ajax】いいね機能ハンズオン #学習者向け#Ajax
Ajax処理のいいね機能の実装方法のチェック項目
ずばり、先に結論をここで提示させていただきます
1. 部分テンプレートの呼び出しが正しく相対パスで指定されているか
2. インスタンス変数の指定が
favorites_controller.rb
で指定されているかの2点です。では、詳しく見ていきましょう。
1. 部分テンプレートの呼び出しが正しく相対パスで指定されているか
よく陥りがちなミスの一つですね。実際にどのように間違えて実装しどの様なエラー文が出たのでしょうか?
view/items/show.html.haml.btn-bar .btn-box = render partial: "favorite_ajax", locals: { item: @item } -# view/items/_favorite_ajax.html.hamlでいいねボタンを部分テンプレートを作成したview/items/_favorite_ajax.html.haml- if user_signed_in? -# ユーザーがログインしているか判断 - if item.favorited_by?(current_user) -# ログイン中のユーザーがいいねしているかしていないかを判断 = link_to item_favorites_path(item.id), method: :delete, class: "favorite red", remote: true do -# リクエストをjs形式で送信 = icon('fas', 'heart') いいね! = item.favorites.count - else = link_to item_favorites_path(item.id), method: :post, class: "favorite", remote: true do -# リクエストをjs形式で送信 = icon('far', 'heart') いいね! = item.favorites.count - else = link_to new_user_session_path, class: "favorite", remote: false do -# リクエストをjs形式で送信 = icon('far', 'heart') いいね! = item.favorites.countview/favorites/destroy.js.haml(失敗例)$('.btn-box').html("#{escape_javascript(render partial: "favorite_ajax", locals: { item: @item })}"); -# この記述ではview/favorites/_favorite_ajax.html.hamlを呼び出していることとなる。従って、対応するファイルが無いことからTemplate::Error(Missing partial)が発生view/favorites/destroy.js.haml(失敗例)$('.btn-box').html("#{escape_javascript(render partial: "favorite_ajax", locals: { item: @item })}"); -# この記述ではview/favorites/_favorite_ajax.html.hamlを呼び出していることとなる。従って、対応するファイルが無いことからTemplate::Error(Missing partial)が発生
items_controller.rb
のshowアクションのビューでいいね機能の実装をしています。また、いいね機能のDBへの保存・削除はfavorites_controller.rb
のcreateアクション・destroyアクションで実装をしています。今回、いいねボタンを押した際にビューが切り替わる部分を
view/items/_favorite_ajax.html.haml
で切り出し部分テンプレートを作成しました。ajaxではview/favorites/destroy.js.haml
view/favorites/destroy.js.haml
をそれぞれ用意し、view/items/_favorite_ajax.html.haml
を呼び出したかったのですが、相対パスの指定が誤っていました。以下のように修正するとTemplate::Error(Missing partial)
は解消されます。view/favorites/destroy.js.haml$('.btn-box').html("#{escape_javascript(render partial: "items/favorite_ajax", locals: { item: @item })}"); -# partial: にitems/ を追加view/favorites/destroy.js.haml$('.btn-box').html("#{escape_javascript(render partial: "items/favorite_ajax", locals: { item: @item })}"); -# partial: にitems/ を追加2. インスタンス変数の指定がfavorites_controller.rbで指定されているか
こちらはまず、どんなエラー文が出たか確認して見ましょう
renderの中身のitem.favorited_by?
に対してundefined method `favorited_by?' for nil:NilClassとエラーが出ています。ここでいうitemとは
items_controller.rb
のshowアクションで定義されているインスタンス変数@itemをrenderの中身ではitemとして記述している、という意味です。favorited_by?
については、item.rb
で事前に定義した「ログイン中のユーザーがいいねしているかしていないかを判断」するメソッドです。models/item.rbclass Item < ApplicationRecord # (中略) def favorited_by?(user) favorites.where(user_id: user.id).exists? end endこのことから、
render内ではitem.favorited_by?
が定義されていない
→ render内ではitem
そのものが定義されていない
→view/favorites/destroy.js.haml
では、@itemが定義されていない
→favorites_controller.rb
では、@itemが定義されていない!!ということが判明しました。確認してみると確かに
favorites_controller.rb
では、@itemが定義されていなかったので、以下のように記述を加えたところ、正しくAjax処理が実行されました。
(items_controller.rb
でも同様のset_itemメソッドを定義済みです)favorites_controller.rbclass FavoritesController < ApplicationController before_action :authenticate_user! # 追記========================================================================== before_action :set_item # ============================================================================== def create favorite = current_user.favorites.build(item_id: params[:item_id]) if favorite.save else flash.now[:alert] = favorite.errors.full_messages end end def destroy favorite = Favorite.find_by(item_id: params[:item_id], user_id: current_user.id) if favorite.destroy else flash.now[:alert] = '削除できませんでした。' end end private # 追記========================================================================== def set_item @item = Item.find(params[:item_id]) end # ============================================================================== endまとめ
いかがだったでしょうか。
いいね機能のAjaxは、実装の手順そのものはすごくシンプルです。しかし、いいね機能専用のビューを用意していなかったり、部分テンプレートの保存場所の違いによって記述内容が異なるケースがあります。当たり前のことではあるのですが、記事通り実装してみて上手く出来なかった時、解決の一助となれば幸いです。※私自身初めてのQiitaの投稿です!
ご指摘等ございましたらコメントにてお待ちしております。
- 投稿日:2020-01-20T08:38:25+09:00
【Rails】 DataTables 実装方法
はじめに
RailsアプリケーションでDataTablesを使っている記事を見かけなかったので、まとめることにしました。
やり方がわかっていないと細かい仕様を変更するのに時間がかかってしまいますが、やり方さえ覚えてしまえばとても使いやすい強力なツールです。
Bootstrap や jQuery UI を使うことによって、時間をかけずに多機能&良いレイアウトを提供してくれるため、爆速で開発できるRailsととても相性がいいと個人的には思っています。DataTables
DataTables は、HTMLのテーブルに、ページ切り替え、ページ当たりの件数設定、ソート、フィルタなどの機能を簡単に追加できるjQuery プラグインのライブラリ。
使い方
設定
GemFile# ページネーションにはkaminariを使用するため gem 'kaminari' gem 'jquery-datatables-rails'$ bundle install$ rails g jquery:datatables:install $ rails g jquery:datatables:install bootstrap3app/assets/javascripts/application.js//= require dataTables/jquery.dataTables //= require dataTables/bootstrap/3/jquery.dataTables.bootstrapapp/assets/stylesheets/application.css*= require dataTables/jquery.dataTables *= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
- Bootstrap
※ version3系をダウンロードする。(GitHubにBootstrap4の記載がないため念のため。。)
4系でも使えると知っている方がいればコメントください。。# 下記のようにファイルを配置する app/assets/javascripts/bootstrap.min.js app/assets/stylesheets/bootstrap.min.cssアプリケーション実装
前提条件
Userモデルを scaffold で作成していること。
全体像
厳密には異なるところもありますが、ざっくりと全体像を示すとこんな感じになります。
① id が users の要素を持った table を作成(table#users)し、CoffeeScript にて読み込み
② ajax で CoffeeScript から Rails 側へ、データの算出を要求する
③ コントローラーにて ajax を受け取り、 UserDatatable モデルへデータの算出を要求する
④ 要求を満たすデータを返す
⑤ ④のデータを json ファイルにして返す
⑥ CoffeeScript にて実装したテーブルを HTML にて表示するビュー
slim だと本当にシンプルに書くことができます。
app/views/users/index.html.slim// 検索項目 button#search_btn type="button" |詳細検索 button#search_exec_btn type="button" |検索 button#search_clear_btn type="button" |クリア table tr td = label :user, :id td = text_field_tag :id, '', id: "search_id" td = label :user, :username td = text_field_tag :username, '', id: "search_username" td = label :user, :name td = text_field_tag :name, '', id: "search_name" // DataTables のテーブル表示 table#incidentsルーティング
jsonファイルを dataTables 側に渡すときに使用するルーティング。
のちほど、Javascriptにて使用する。config/routes.rbresources :users do collection do post 'ajax_data' end endコントローラー
UserDatatableのインスタンスをjsonファイルとして返す。
app/controllers/users_controller.rbdef ajax_data respond_to do |format| format.html format.json {render json: UsersDatatable.new(params) } end endUserDatatableはパラメーターを受け取って、SQL を実行し JSON 形式に変換するクラス。
テーブルに表示させたい項目や検索結果などをこちらのファイルにて割り出している。app/datatables/users_datatable.rbclass UsersDatatable attr_accessor :params def initialize(params) @params = params end # jQuery DataTables へ渡すためのハッシュを作る # 補足:コントローラーの render json: で指定したオブジェクトに対して as_json が呼び出される def as_json(options = {}) { recordsTotal: User.count, # 取得件数 recordsFiltered: users.total_count, # フィルター前の全件数 data: users, # 表データ } end def users @users ||= fetch_users end # 検索条件や件数を指定してデータを取得 def fetch_users User.where(search_sql).page(page).per(per) end # カラム情報を配列にする def columns return [] if params["columns"].blank? params["columns"].to_unsafe_h.map{|_,v| v["data"]} end # 検索ワードが指定されたとき def search_sql search_sql = [] for column, search_params in params["columns"] do search_sql.push("#{search_params["data"]} like '%#{search_params["search"]["value"]}%'") if search_params["search"]["value"].present? end search_sql.join(" and ") end # ソート順 def order_sql return "" if params["order"]["0"].blank? order_data = params["order"]["0"] order_column = columns[order_data["column"].to_i] # "id desc" のようにSQLの一部を作る "#{order_column} #{order_data["dir"]}" end # kaminari 向け、ページ数 def page params["start"].to_i / per + 1 end # kaminari 向け、1ページで取得する件数 def per params["length"].to_i > 0 ? params["length"].to_i : 10 end endJavascript
app/assets/javascripts/users.coffee$ -> # DataTables オブジェクト作成(ここではhtmlのid=users) user_table = new DataTables($('#users')) # ajax にて、 json ファイルを読み込み。作成したルーティングを引数に入れる。 user_table.setAjax("/users/ajax_data") # user_table へカラムを追加する user_table.setColumns([ { data: 'id', title: 'ユーザID', width: '5%' }, { data: 'username', title: 'ユーザ名', width: '25%' }, { data: 'name', title: '名前', width: '30%' }, { data: 'created_at', title: '登録日時', width: '20%' }, { data: 'updated_at', title: '更新日時', width: '20%' }, ]) user_table.setOption(false, true, true) # 表示順番を0番目のカラムを昇順で表示(ここではidを昇順で表示) user_table.setOrders([[0,'asc']]) # #users へ user_table を描写する。 user_table.drawTable() # 各行をクリックすると詳細画面へ遷移するように設定 $('#users tbody').on 'click', 'tr', -> data = $('#users').dataTable().fnGetData(this); document.location = "/users/#{data.id}" # 詳細検索ボタンを押すと、検索ボタンおよびカラムが表示されるように設定 $('#search_btn').on 'click', -> $('.search-body').slideToggle(200) $('#search_exec_btn').slideToggle(200) $('#search_clear_btn').slideToggle(200) # 検索ボタンを押すと、表示されているテーブルのカラムが検索条件に合致したものだけを表示するように設定。 $('#search_exec_btn').on 'click', -> # dataTable の API を利用する。 user_table_api = $('#users').dataTable().api() # 各検索カラムの値を読み込み search_id = $('#search_id').val() search_username = $('#search_username').val() search_name = $('#search_name').val() # 検索結果をテーブルに描画する user_table_api.columns(0).search(search_id).draw() user_table_api.columns(1).search(search_username).draw() user_table_api.columns(2).search(search_name).draw() $('#search_clear_btn').on 'click', -> # 検索欄 入力内容 初期化 tmp_array = ["id", "username", "name"] tmp_array.forEach (key) -> $("#search_#{key}").val('').trigger('change')まとめ
少し複雑な実装でしたので、Keynoteで作った全体像を用いての説明でした。
いかがでしたでしょうか。少しでも理解の助けになれればなと思います。また、DataTables は高機能なライブラリですので、他にもできることを紹介していきたいと思います。
参考
- DataTables(公式ページ)
- GitHub(rweng/jquery-datatables-rails)
- Rails | Ajax で動的な検索・ソート機能付きページネーションを実装する(jQuery DataTablesプラグイン連動)
- DataTablesの使い方
- DataTablesでページが切り替わっても、ソートやページャや検索結果を保持しておくようにしたい。
- javascript でテキストをクリップボードにコピーする2つの方法
- 投稿日:2020-01-20T05:37:32+09:00
HanamiのアプリケーションをREST APIとして実装する
Hanamiは複数のアプリケーションをモノリシックに構築することができます。
複数のアプリケーションを実装するにあたり、REST APIとして実装するアプリケーションを作成する機会がありましたが、3年以上の前の Build a Web API with Hanami ぐらいしか情報がなく苦労したので、備忘録として残しておきます。
API化するHanamiのアプリケーションの準備
今回は公式のチュートリアルが終わった段階の
web
アプリケーションに対してPOSTメソッド利用できるAPI化を施そうと思います。APIに不要なView関連ディリクトリの削除
assets, views, template の各ディリクトリは不要なので削除しちゃってください。
web - ├── assets - │ ├── images - │ ├── javascripts - │ └── stylesheets ├── config ├── controllers - ├── templates - └── viewsこのままでは、Hanamiのサーバが起動するときに読み込みエラーになるので、
apps/web/application.rb
の各クラスの読み込み箇所を修正します。
※assetsの設定は起動時に読み込まれないので、削除してもしなくても大丈夫です。apps/web/application.rb# Relative load paths where this application will recursively load the # code. # # When you add new directories, remember to add them here. # - load_paths << [ - 'controllers', - 'views' #削除 + load_paths << ['controllers']apps/web/application.rb# The relative path to templates # - templates 'templates' + # templates 'templates'また、今回はメーラーも使用しないのでこのタイミングで削除します。
bookshelf/config/environment.rb- mailer do - root 'lib/bookshelf/mailers' - - # See https://guides.hanamirb.org/mailers/delivery - delivery :test - end + # mailer do + # root 'lib/bookshelf/mailers' + # + # # See https://guides.hanamirb.org/mailers/delivery + # delivery :test + # endテストの作成
HanamiはTDDを推奨の設計手法としてしているのでテストを先に作成しましょう。
今回はPOSTのAPIを実装するのでspec/web/controllers/books/create_spec.rb
を作成してテストを書いていきます。spec/web/controllers/books/create_spec.rbRSpec.describe Web::Controllers::Books::Create, type: :action do include Rack::Test::Methods let(:app) { Hanami.app } describe "create books" do let(:request_body) do { bools: { title: 'hogehoge', author: 'foo' } } end let(:do_request) { post '/api/books', params: request_body, header: { 'Content-Type' => 'application/json' } } it 'is http status 201' do do_request expect(last_response.status).to eq 201 expect(last_response.body).to eq request_body end it 'is expect response body' do do_request expect(last_response.body).to eq request_body end it 'is created created books' do expect { do_request }.to change { BookRepository.new.first.nil? }.from(true).to(false) end end endアクションのテストとの違いは直接エンドポイントを叩いているところです。
エンドポイントを叩くためには下記のモジュールをincludeしてHanami.app
をapp
に格納する必要がありますinclude Rack::Test::Methods let(:app) { Hanami.app }ルーティング
今回は
/api/books
というエンドポイントを作成します。
まずconfig/environment.rb
において、下記のように設定することでホスト名の次に来るパスにapi
を自動で追加できます。mount Web::Application, at: '/api'そして、Hanami には rails と同じような RESTful Resource(s) な記法で エンドポイントをルーティングできるのでapi以下のルーティングを下記のように設定します
web/config/route.rbresources 'books', only: [:create]これでルーティングの設定は完了です。
今回は
:create
なのでhttpメソッドは POST になりますが、:update
の場合、httpメソッドは PATCH になります。PUT は存在しないので注意してください。
Hanami側から指定してくれているので PUT か PATCh で惑わずに済んで助かります。Action(Controller)
エラー周りに改善の余地がありますが、バリデーションと保存処理はアクションから独立させています。
最終的にself.body
に入れた値がレスポンスボディとして返されるので、作成したリソースからJSON化したものをここに格納すればJSONのレスポンスボディとして返されます。self.status
も同様です。apps/web/controllers/books/create.rbmodule Web module Controllers module Books include Api::Action accept :json def call(params) validate_result = Form::BooksValidator.new(params).validate raise StandardError unless validate_result.success? BooksInteractor.new.create(attribute) self.body = response_body self.status = 201 rescue => exception self.status = 400 @error = exception end def response_body JSON.dump(BooksRepository.new.last.to_hash) end end end endHanamiのバリデーションは コンポーネント指向な
dry-validation
を模して実装されています。
今回やっていることは rails で言うところの StrongParamater の処理と同じですが、ガッツリとバリデーションも記述できるので、アクション側からそのあたりの責務を一通り引き取ることができます。lib/bookshelf/validators/form/books_validator.rbrequire 'hanami/validations' module Form class BooksValidator include Hanami::Validations validations do required(:books).schema do required(:title) { filled? } required(:author) { filled? } end end end end保存処理をIteratorにまとめたものです。こちらも保存処理関連の責務をアクションから剥がすことができます。
lib/bookshelf/interactors/books_interactor/create.rbrequire 'hanami/interactor' module BooksInteractor class Create include Hanami::Interactor def initialize(params) @attributes = { title: params[:books][:title], author: params[:books][:author] } end def call BooksRepository.new.create(@attributes) end end endこれらの実装により、
Web
アプリケーションをPOSTメソッドを持ったAPIにすることができます。終わりに
Hanami を触っていて思ったことは Entity と Repository の元となっている ROM(Ruby Object Mapper) の理解や Itarator の使い所をわかっていないと、どうしても rails 感が抜けずに劣化 rails になりがちな点です。まだプロダクトレベルのサンプルも少なく手探りなところもありますが、自分の中で消化できたらそのあたりの記事も書けたらと思います。
- 投稿日:2020-01-20T01:32:38+09:00
【これからプログラミング&クラウドを始める人向け】AWS Cloud9 を利用して Ruby の開発環境を作ってみる③ - Ruby のバージョン管理
はじめに
この記事は無料で AWS Cloud9 を利用してプログラム学習を開始する方法いついて入門者向けに記載した内容になっています。
AWS アカウントの作り方は → こちら
AWS Cloud9 の開始方法は → こちら今回行うこと
複数の案件や学習教材を利用した場合、それぞれ Ruby のバージョンが異なる場合があります。
今回は複数の Ruby のバージョンをインストールして状況に応じてバージョン変更する方法を Cloud9 上で実践してみます。Ruby のバージョン確認
- 右下の□ボタンを押すとコマンドコンソールが全画面表示になります。
- AWS Cloud9 上で現在インストールされている Ruby のバージョンを確認してみます。
ruby -vバージョン管理ソフトウェアの導入
- 今回は「 rvm (Ruby Version Manager) 」を利用する手順になります。
- 先程と同様にまずは Cloud 9 上の rvm のバージョンを確認します。
rvm -v
- 上記のコマンドを入力すると rvm のバージョンが表示されます。
- 次に rvm のヘルプ表示を行います。
rvm help※ rvm とだけ入力してもヘルプは表示されます。
- 入力するとコマンドとその説明が表示されます。
- 次にこの環境に表示されている ruby のバージョンを表示してみます。
rvm list
- 現在この環境で利用されているバージョンは 2.6.3 であると表示されています。
- 次にインストール可能なバージョンを確認します。
rvm list known
- 実行可能な Ruby のバージョンがずらっと出てきます
このように実際に環境を構築する際にはヘルプを開き、バージョンを確認しながら進めることになります。
Cloud9上に Ruby 別バージョンのインストール
鍵のインストール
- RVMの公式サイトから以下画像の枠部分のコマンドをコピーします。
- コマンドをターミナル上で実行します。root 権限での実行が必要なため sudo 権限で実行します。
sudo gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
- 実行後の画面はこのようになっています。
バージョンを指定してインストールする
- 今回は敢えて 2.3.2 をインストールしてみます。
- 数分ほど完了までに時間がかかります。
rvm install 2.3.2
- 完了後の画面はこんな感じです。
- ここで改めてインストールされているバージョンを確認します。
rvm list
- 以下のキャプチャのように言語の使用状況は言語表示の手前の記号で判断ができます。
- => 現在使用中のバージョン
- =* 現在使用中かつデフォルト設定バージョン
- * デフォルト設定されているバージョン
- 現在はインストールした 2.3.2 が使用中の設定になっています。
使用するバージョンの変更
- 以下のコマンドで使用するバージョンを変更することができます。
rvm use 2.6.2(対象バージョン)
- 実行結果と確認結果は以下のようになっています。
- ## デフォルト設定のバージョンの変更
- デフォルトのバージョンは --default を追加したコマンドで変更ができます。
rvm --default use 2.3.2(対象バージョン)インストールした特定バージョンの削除
- 以下のコマンドで削除することができます。
rvmsudo rvm remove 2.3.2
- 実行結果
まとめ
上記のようにバージョン管理のソフトウェアを活用して、複数バージョンの管理を行うことが可能です。行う内容自体は他の言語でも同様のことを進めていくことになります。次回は Git 環境を AWS のサービスを利用して用意していく記事を書こうと思います。お楽しみに!
関連記事