- 投稿日:2020-12-15T22:53:45+09:00
Railsのルーティングをdrawを使ってまとめる
はじめに
gitlabのroutes.rbにて、
draw :apiの様に記載されているものを見つけ、とても便利だと思ったのでまとめます。draw
draw :apiの様に記載すると、config/routes/api.rbに記載したルーティングを読みに行ってくれます。例えば、namespaceごとにそれぞれ別のファイルにルーティングを記載することで、わかりやすくすることができます。
デフォルトで用意されているものではなく、gitlabのソースコードで独自に用意されていたものなので、少し下準備が必要になります。
下準備
config/initializers配下にdraw_routes.rbファイルを作成し、下記の内容を記載します。ほとんど、gitlabのソースコードと同じです。
module DrawRoute RoutesNotFound = Class.new(StandardError) def draw(routes_name) drawn_any = draw_route(routes_name) drawn_any || raise(RoutesNotFound, "Cannot find #{routes_name}") end def route_path(routes_name) Rails.root.join(routes_name) end def draw_route(routes_name) path = route_path("config/routes/#{routes_name}.rb") if File.exist?(path) instance_eval(File.read(path)) true else false end end end ActionDispatch::Routing::Mapper.prepend DrawRoute使用例
管理画面のroutesを切り出す
config/routes配下にadmin.rbを作成し、下記の様に記載
namespace :admin do resources :users endroutes.rbに
Rails.application.routes.draw do draw :admin endと記載することで、下記の様にroutesが作成されます。
admin_users GET /admin/users(.:format) admin/users#index POST /admin/users(.:format) admin/users#create new_admin_user GET /admin/users/new(.:format) admin/users#new edit_admin_user GET /admin/users/:id/edit(.:format) admin/users#edit admin_user GET /admin/users/:id(.:format) admin/users#show PATCH /admin/users/:id(.:format) admin/users#update PUT /admin/users/:id(.:format) admin/users#update DELETE /admin/users/:id(.:format) admin/users#destroy使用例2
大きくなるモデルのroutesを切り出す。
例えば、
users_controller.rbとusers/licences_controller.rbusers/activates_controller.rbがある場合に、user単位でフォルダにまとめます。
config/routes/user.rbresources :users do scope module: :users do resource :activates, only: %i[update destroy] resources :licences end endそして、routes.rbで読み込むことで
Rails.application.routes.draw do draw :user end下記の様なルーティングが作成されます。
user_activates PATCH /users/:user_id/activates(.:format) users/activates#update PUT /users/:user_id/activates(.:format) users/activates#update DELETE /users/:user_id/activates(.:format) users/activates#destroy licences GET /users/licences(.:format) users/licences#index POST /users/licences(.:format) users/licences#create new_licence GET /users/licences/new(.:format) users/licences#new edit_licence GET /users/licences/:id/edit(.:format) users/licences#edit licence GET /users/licences/:id(.:format) users/licences#show PATCH /users/licences/:id(.:format) users/licences#update PUT /users/licences/:id(.:format) users/licences#update DELETE /users/licences/:id(.:format) users/licences#destroy users GET /users(.:format) users#index終わりに
アプリが大きくなるにつれ、routes.rbがカオスになることがあると思うので、drawを使って良い感じにファイルを分けることで、すっきりしそうです。
- 投稿日:2020-12-15T22:36:17+09:00
他言語の視点で見るRuby
はじめに
Ruby その2 Advent Calendar 2020 15日目の記事です!!
今年の秋頃に、Ruby Association試験を受けました。
その時の試験勉強中に、「Rubyだとこう書くのか〜〜」とか「Rubyだとこの意味になるのか〜〜」みたいなことがただあったので、本当に軽く紹介しておきます!!
(全然知らないことが多いので、多めにみてください。。。)
勉強した時の教材はこちらから。気になったは人はやってみてください!!
https://gist.github.com/sean2121/945035ef2341f0c39bf40762cd8531e0begin ~ resucue
Rubyの例外処理と言いたら、これ。
beginブロック内で発生したエラーをresucueで拾い、エラーハンドリングをするものです。begin 1 / 0 rescue ZeroDivisionError => error p error end # => #<ZeroDivisionError: divided by 0>他の言語では、
try ~ catchが大半だと思います。try { nonExistentFunction(); } catch (error) { console.error(error); } // > ReferenceError: nonExistentFunction is not definedRubyでのtryとcatch
try
Rubyにも
tryはありますが、例外処理のメソッドではなく、オブジェクトに対してのメソッドです。
https://www.rubydoc.info/docs/rails/4.1.7/Object:tryRubyでの
tryは、&.とほぼ同意義です。(&.のことをnilガードと呼ぶことが多いです。)
「ほぼ」なので、少しだけ違いがあります。
&.は、オブジェクトがnilでない場合にメソッドを呼び出します。
tryは、オブジェクトのメソッドを呼び出すことができるときにメソッドを呼び出します。# &. 10&.to_s # => "10" 10&.hoge # => Error: undefined method `hoge' for 10:Fixnum (NoMethodError) nil&.to_s # nil # try 10.try(:to_s) # => "10" 10.try(:hoge) # => nil nil.try(:to_s) # => nilcatch
Rubyでの
catchは、ブロック内でthrowが走った時にその引数と一致していれば、抜けることができるものです。
throw ~ catchでワンセットですね。catch :error do puts "hoge" throw :error puts "fuga" end # => hogeこれはこれで、
begin ~ rescueと書き方が似てはいますが、だいぶ違います。
詳しくはこちら。
https://qiita.com/harvath/items/e716179538bc1da30f88例外処理よりかは、C言語の
gotoに近いものですね。*変数
C言語やGo言語を書いている人にとっては、
*変数はかなり見覚えがあると思います。みんな大好き、ポインタです!
tour_of_go.goimport "fmt" func main() { i, j := 42, 2701 p := &i fmt.Println(*p) *p = 21 fmt.Println(i) p = &j *p = *p / 37 fmt.Println(j) } /* 42 21 73 */ポインタは、値のメモリアドレスを指します。
簡単に表すとこんな感じです。(例:go)i := 42 p := &i fmt.Println(p) // 0xc000122020 fmt.Println(&p) // 0xc000124018 fmt.Println(*p) // 42 fmt.Println(i) // 42 fmt.Println(&i) // 0xc000122020 fmt.Println(*i) // エラーになる
変数 値 アドレス値 i i = 42 &i == p = 0xc000122020 p *p == i = 42 &p = 0xc000124018 ポインタについてはこちら。
https://qiita.com/yz2cm/items/01908cdfe56c304f2a14ただ、Rubyでの
*変数はポインタではありません!!(初めて知った時、めっちゃポインタだと勘違いしていた。。。)Rubyでは、変数を配列にするものです。つまり、
to_aメソッドと同じ意味のものです。
メソッドのキーワード引数につけると、可変長引数として利用できます。def foo (a, *b) p a, b end foo(1, 2, 3, 4) # => 1 # => [2, 3, 4]**変数
ちなみに、Rubyでは
**変数も書くことができます。これは、Hashを意味していることが多いです。def variadic_keyword(**hash_args) p hash_args end variadic_keyword(april:"spring", july:"summer") # => {:april=>"spring", :july=>"summer"}終わりに
本当にざっくり簡単に軽くまとめました!!
これ以外でもいろいろあると思うので、その都度更新していきます。
(試験の結果は聞かないでください。。。。)
- 投稿日:2020-12-15T22:32:43+09:00
【Rails】ハッシュタグ機能を実装
概要
ハッシュタグ機能を実装する方法をまとめます。
参照
https://glodia.jp/blog/3936/
https://qiita.com/Naoki1126/items/4ea584a4c149d3ad5472これら2つの記事の情報を組み合わせて実装しました。
ありがとうございます。完成イメージ
今回は、写真投稿アプリを題材に、写真のキャプションにハッシュタグを導入する方法を説明します。
写真詳細画面のキャプションにリンク付きハッシュタグが記載され、クリックするとそのハッシュタグのページが表示される。
開発環境
- macOS Catalina 10.15.7
- ruby 2.6.5
- Rails 6.0.3.4
実装の流れ
- 各種モデルとマイグレーションファイルを準備
- ハッシュタグ保存・更新アクションをモデルに追加
- ルーティングを設定
- ヘルパーメソッドを作成
- コントローラーにhashtagアクションを作成
- ビューの編集
今回のコード
コードは、必要な部分以外は省略して記載します。
1. 各種モデルとマイグレーションファイルを準備
hashtagモデルを作成
%rails g model hashtag中間テーブルを作成
%rails g model photo_hashtag_relationマイグレーションファイルを編集
テーブルのカラムを設定します。
create_hashtags.rbclass CreateHashtags < ActiveRecord::Migration[6.0] def change create_table :hashtags do |t| t.string :hashname t.timestamps end add_index :hashtags, :hashname, unique: true end endcreate_photo_hashtag_relations.rbclass CreatePhotoHashtagRelations < ActiveRecord::Migration[6.0] def change create_table :photo_hashtag_relations do |t| t.references :photo, index: true, foreign_key: true t.references :hashtag, index: true, foreign_key: true t.timestamps end end endモデルを編集
バリデーションとアソシエーションを設定します。
hashtag.rbclass Hashtag < ApplicationRecord validates :hashname, presence: true, length: { maximum:99} has_many :photo_hashtag_relations has_many :photos, through: :photo_hashtag_relations endphoto_hashtag_relation.rbclass PhotoHashtagRelation < ApplicationRecord belongs_to :photo belongs_to :hashtag with_options presence: true do validates :photo_id validates :hashtag_id end endマイグレート
%rails db:migrate2. ハッシュタグ保存・更新アクションをモデルに追加
photoモデルに以下のコードを追加します。
これで、写真投稿(create)、編集(update)時にハッシュタグがhashtagsテーブルに保存されます。photo.rb# 省略 has_many :photo_hashtag_relations has_many :hashtags, through: :photo_hashtag_relations . . # 省略 . . #DBへのコミット直前に実施する after_create do photo = Photo.find_by(id: self.id) hashtags = self.caption.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/) photo.hashtags = [] hashtags.uniq.map do |hashtag| #ハッシュタグは先頭の'#'を外した上で保存 tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#')) photo.hashtags << tag end end before_update do photo = Photo.find_by(id: self.id) photo.hashtags.clear hashtags = self.caption.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/) hashtags.uniq.map do |hashtag| tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#')) photo.hashtags << tag end end endルーティングを設定
photosコントローラーにhashtagアクションを定義し、getで各ハッシュタグのページを表示させます。
URLは末尾にハッシュタグ名が来るようにしました。
:nameに入る値はこの後ヘルパーメソッドで設定します。例えば、#カメというハッシュタグだとURLは
.../photo/hashtag/カメ
となります。route.rbget '/photo/hashtag/:name', to: "photos#hashtag"ヘルパーメソッドを作成
photos_helper.rbに以下のコードを記載します。
photos_helper.rbファイルがない場合は自分で作成します。helperについては、https://www.sejuku.net/blog/28563 などを参照
このヘルパーメソッド使用により、リンク付きのハッシュタグが入ったキャプションが作成されます。
コード内では、ハッシュタグ名が末尾に入ったURLが作成され、ハッシュタグクリック時のリンク先として設定されています。
ハッシュタグ名の前のURLには、先程route.rbに書いたURLを書き込みます。photos_helper.rbmodule PhotosHelper def render_with_hashtags(caption) caption.gsub(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/){|word| link_to word, "/photo/hashtag/#{word.delete("#")}"}.html_safe end endコントローラーにhashtagアクションを作成
ハッシュタグに紐付いた写真を@photosに代入し、hashtagビューで使用し表示させます。
photos_controller.rbdef hashtag @user = current_user @tag = Hashtag.find_by(hashname: params[:name]) @photos = @tag.photos endビューの編集
先程作成したヘルパーメソッド
render_with_hashtagsを記載することにより、リンク付きハッシュタグが記載されたキャプションを写真詳細画面に表示させます。show.html.erb# 省略 <%= render_with_hashtags(@photo.caption) %>ハッシュタグ画面では、hashtagアクションで設定した
@photosを取り込み、ハッシュタグに紐づく写真を1枚ずつ表示させます。hashtag.html.erb<h2>ハッシュタグ #<%= @tag.hashname %></h2> <ul> <% @photos.each do |photo| %> <li> <%= link_to photo_path(photo.id) do %> <%= image_tag photo.image.variant(gravity: :center, resize:"640x640^", crop:"640x640+0+0"), if photo.image.attached? %> <% end %> </li> <% end %> </ul>おわりに
以上が、今回行ったハッシュタグ機能実装の方法です。
意味が理解できていないコードが多々ありますが、追々理解していければと思います。初学者なので間違いがありましたら、ご指摘いただきたいですm(_ _)m
- 投稿日:2020-12-15T22:16:48+09:00
第11回、RubyでClass化
Class
本記事は大学院前期過程の講義[マルチスケールシミュレーション特論]に関する記事です
Ruby で Class を扱っていきます。Class化する前のhelloメソッド
def hello(name) puts "hello #{name}" end def gets_name name = ARGV[0] if name == nil puts "What\'s your name? " name = gets.chomp end return name end hello gets_nameこれに対して、Hello Classを定義して、RubyにおけるClassをみていきます
いきなりClass化
いきなりですが、Class化が完了したソースコードを拝見。
class Hello def initialize @name = gets_name puts_hello end def puts_hello puts "Hello #{@name}." end def gets_name name = ARGV[0] || 'world' return name.capitalize end end Hello.newなるほど。簡単やん。ここで注目すべきポイントを下に記述します。
- initialize
- これはいわゆるコンストラクタ。
- @name
- これはClassのメンバー変数となる。(アットマークをつける)
- capitalize
- 大文字にしてくれる。英単語そのままやけど。
- Hello.new
- これでハローclassの宣言が可能となる。
継承とかオーバーライド
これに関してもコードをみて学習。
# 継承 class Hello < String def hello "Hello #{self}." end end # オーバーライド class String def hello "Hello #{self}." end endなるほど。すごく単純。ここで改めて思うこと、Ruby ってめちゃめちゃ簡単やし単純やし学習コスト低いなー。
ちなみに呼び出しとか使用は以下の通りrequire 'colorize' # 継承呼び出し greeter = Hello.new(ARGV[0]) puts greeter.hello.green # オーバーライド呼び出し puts ARGV[0].hello.greenRuby簡単。Cで組込み開発してるけどしたくなくなる。以上。
- source ~/Library/Mobile Documents/com~apple~CloudDocs/KG/class/M1/multi_scale_sim/grad_members_20f/members/ryomichi56/./qiita/no11.org
- 投稿日:2020-12-15T22:00:14+09:00
ポートフォリオ開発まとめ
これまで、2週間かけて取り組んだポートフォリオの要点をまとめたので、アウトプットしたいと思います。
間違いがあれば、ご指摘いただければと思います。①要件定義
https://qiita.com/by-miwa30/items/93c4fc92b85e034b75cd②README
https://qiita.com/by-miwa30/items/d7e0395c8c4a13dcff6f③アプリの起動
https://qiita.com/by-miwa30/items/fbdaee77efaea6b6df11④ユーザー管理機能を実装
https://qiita.com/by-miwa30/items/4d868f447bfe7ce040f1⑤ストロングパラメーター
https://qiita.com/by-miwa30/items/fc761332dd0de9ef5bc2⑥新規登録
https://qiita.com/by-miwa30/items/65e175ee8c86e05c83ca⑦アプリ投稿
https://qiita.com/by-miwa30/items/dbdcccd494b33c1e713d⑧アプリの詳細ページ
https://qiita.com/by-miwa30/items/d01dd9a6ca68063affcd⑨コメント機能
https://qiita.com/by-miwa30/items/a97cebfb6f4617b7a275⑩ユーザー詳細ページ
https://qiita.com/by-miwa30/items/e78811610bfdeac8d735⑪AWSのアカウント作成
https://qiita.com/by-miwa30/items/ca356f6c021eb14fcfef⑫S3で保存先を用意する
https://qiita.com/by-miwa30/items/dc068e273d8e1d0c1764⑬EC2の初期設定
https://qiita.com/by-miwa30/items/09c972145fea0e919046⑭本番環境でデータベースを作成
https://qiita.com/by-miwa30/items/b1a73dbbde0f1bbcfa74⑮EC2のRailsを起動しよう(手動デプロイ)
https://qiita.com/by-miwa30/items/dd455dcb73d03e19012e⑯環境変数の設定
https://qiita.com/by-miwa30/items/924a653faef7a03eba36⑰本番環境Railsの起動エラー(手動)
https://qiita.com/by-miwa30/items/d7a37a9deccbe76c047e⑱Webサーバーの設定
https://qiita.com/by-miwa30/items/31251da3c26a8129aa60⑲デプロイを自動化
https://qiita.com/by-miwa30/items/d4b7b1fa3601bf1185ba⑳IPアドレスにアクセスエラー(自動デプロイ
https://qiita.com/by-miwa30/items/fbc620dfe6e71d4a417c
- 投稿日:2020-12-15T22:00:14+09:00
ポートフォリオまとめ
これまで、2週間かけて取り組んだポートフォリオの要点をまとめたので、アウトプットしたいと思います。
間違いがあれば、ご指摘いただければと思います。①要件定義
https://qiita.com/by-miwa30/items/93c4fc92b85e034b75cd②README
https://qiita.com/by-miwa30/items/d7e0395c8c4a13dcff6f③アプリの起動
https://qiita.com/by-miwa30/items/fbdaee77efaea6b6df11④ユーザー管理機能を実装
https://qiita.com/by-miwa30/items/4d868f447bfe7ce040f1⑤ストロングパラメーター
https://qiita.com/by-miwa30/items/fc761332dd0de9ef5bc2⑥新規登録
https://qiita.com/by-miwa30/items/65e175ee8c86e05c83ca⑦アプリ投稿
https://qiita.com/by-miwa30/items/dbdcccd494b33c1e713d⑧アプリの詳細ページ
https://qiita.com/by-miwa30/items/d01dd9a6ca68063affcd⑨コメント機能
https://qiita.com/by-miwa30/items/a97cebfb6f4617b7a275⑩ユーザー詳細ページ
https://qiita.com/by-miwa30/items/e78811610bfdeac8d735⑪AWSのアカウント作成
https://qiita.com/by-miwa30/items/ca356f6c021eb14fcfef⑫S3で保存先を用意する
https://qiita.com/by-miwa30/items/dc068e273d8e1d0c1764⑬EC2の初期設定
https://qiita.com/by-miwa30/items/09c972145fea0e919046⑭本番環境でデータベースを作成
https://qiita.com/by-miwa30/items/b1a73dbbde0f1bbcfa74⑮EC2のRailsを起動しよう(手動デプロイ)
https://qiita.com/by-miwa30/items/dd455dcb73d03e19012e⑯環境変数の設定
https://qiita.com/by-miwa30/items/924a653faef7a03eba36⑰本番環境Railsの起動エラー(手動)
https://qiita.com/by-miwa30/items/d7a37a9deccbe76c047e⑱Webサーバーの設定
https://qiita.com/by-miwa30/items/31251da3c26a8129aa60⑲デプロイを自動化
https://qiita.com/by-miwa30/items/d4b7b1fa3601bf1185ba⑳IPアドレスにアクセスエラー(自動デプロイ
https://qiita.com/by-miwa30/items/fbc620dfe6e71d4a417c
- 投稿日:2020-12-15T21:20:40+09:00
Railsのエラーメッセージの日本語化がうまくいかない
現象
エラーメッセージを日本語化するため、カラム名の日本語訳を記述したファイルがうまく機能しませんでした。
解決方法
記述のスペーシングを整えたらうまくいきました。
誤字脱字以外にもスペーシングで機能しないことがあることを新たに学ぶことができました。config/locales/models/ja.yml# 階層構造をスペーシングで表現しないとうまく機能しない ja: activerecord: models: event: イベント attributes: event: name: イベント名 place: 開催場所 content: イベント内容エラーメッセージを日本語化する手順
「rails-i18n」というgemをインストール
Gemfilegem 'rails-i18n'↓
bundle install
↓
config/application.rbの記述を編集config/application.rbconfig.i18n.default_locale = :jaこれで日本語化はできました。
しかし、カラム名はまだ英語表記のため、
(例)emailを入力してください
のようなエラーメッセージになってしまいます。カラム名を日本語に訳す記述をする
config/locales/models/に「ja.yml」というファイルを作成します。
作成したファイル内にカラム名に対応する日本語を記述します。config/locales/models/ja.yml# 階層構造をスペーシングで表現しないとうまく機能しない ja: activerecord: models: event: イベント attributes: event: name: イベント名 place: 開催場所 content: イベント内容参考文献
- 投稿日:2020-12-15T21:20:27+09:00
Rails6プロジェクト(API)をDockerで開発(少しイメージの軽量化)
本記事は東京学芸大学 櫨山研究室 Advent Calendar 2020の15日目の記事になります.
はじめに
本記事はRails6が出たのでその環境をDocker化し,また少しイメージを軽くする方法です(RDBはMysql8.0を想定).
起動を確認するところまでを記事にしています.
railsのプロジェクトはローカルで作成したもの使用しています(私はローカルで以下のように作成しています).console$ rails new api --api -d mysqlフロントとバックエンドを分けて開発を行いたかったので,Rails6プロジェクトをapiモードで作成しています.
また本記事ではフロントエンド側の記述はしていません.
普段はRailsを使用していないので,メモとして残します?♂️開発環境
- MacOs:
- Docker:
- docker-compose:
- ruby:2.6.3(rbenv で管理: 参考記事)
- Rails 6
- Mysql8.0
1. 準備
今回は既存のRails6プロジェクト(apiモード)をDockerしていきます.
1.1 ディレクトリ構成
今回はdocker-compose.ymlやDockerfileが階層的になっているため,
makeで実行していきます.1.2 ファイルの準備
Makefile
Makefile.PHONY: build build: docker-compose -f docker/docker-compose.yml build .PHONY: start start: docker-compose -f docker/docker-compose.yml up --remove-orphans .PHONY: start.background start.background: docker-compose -f docker/docker-compose.yml up -d --remove-orphans .PHONY: stop stop: docker-compose -f docker/docker-compose.yml down
buildでパッケージの取得startでアプリケーションを実行start.backgroundでバックグラウンドでアプリケーションを実行stopでアプリケーションを停止docker-compose.yml
docker/docker-compose.ymlversion: "3" services: api: container_name: api build: context: ../. dockerfile: ./docker/api/Dockerfile tty: true working_dir: /api volumes: - ../api:/api env_file: # dockerディレクトリ直下にapi.envファイルを作成し環境変数を設定する. - api.env ports: - "8000:3000" # フロントエンド側が3000番開放になるので変更 depends_on: - db command: bundle exec rails s -p 3000 -b '0.0.0.0' networks: - sample-rails-docker-network db: container_name: db build: context: ../. dockerfile: ./docker/mysql/Dockerfile volumes: - ./mysql/db:/docker-entrypoint-initdb.d env_file: # dockerディレクトリ直下にmysql.envファイルを作成し環境変数を設定する. - mysql.env networks: - sample-rails-docker-network networks: # 名前解決できるようにネットワークの設定 sample-rails-docker-network: driver: bridgeDockerfile:Mysql関連
docker/mysql/my.cnfFROM mysql:8.0 # mysq;8.0を使用する EXPOSE 3306 ADD docker/mysql/my.cnf /etc/mysql/conf.d/my.cnf CMD ["mysqld"]3306番ポートを開放し,設定ファイルを読み込みます.
docker/mysql/Dockerfile[mysqld] character-set-server=utf8 default_authentication_plugin=mysql_native_password [mysql] default-character-set=utf8 [client] default-character-set=utf8mysqlのvolumeは
docker/mysql/dbに設定しているので,必要な場合はそちらに配置してください.Dockerfile:Rails 6関連
docker/api/DockerfileFROM ruby:2.6.3-alpine as builder # ruby:2.6.3-alpineを使用する,bundle installするためにbuilderとして使用 RUN apk update && \ apk upgrade && \ apk --no-cache add mysql-client mysql-dev tzdata build-base && \ gem install bundler WORKDIR /tmp COPY api/Gemfile Gemfile COPY api/Gemfile.lock Gemfile.lock RUN bundle install FROM ruby:2.6.3-alpine ENV LANG ja_JP.UTF-8 RUN apk --update add mysql-client mysql-dev tzdata nodejs && \ gem install bundler ENV APP_HOME /api RUN mkdir -p $APP_HOME WORKDIR $APP_HOME COPY --from=builder /usr/local/bundle /usr/local/bundle COPY api $APP_HOME今回使用するimageは
ruby:2.6.3-alpineですが,それぞれの環境で変更しても大丈夫です.
マルチステージングにし,bundle installにだけ必要なもの(build-base)を省くようにする.(apk delで消しても問題ない.)
今回はmysqlが動く最低限の環境になっています.Rails関連
Gemfile
api/Gemfilesource 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.6.3' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.3', '>= 6.0.3.2' # database gem 'mysql2' # Use Puma as the app server gem 'puma', '~> 4.1' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.4.2', require: false # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible gem 'rack-cors' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end group :development do gem 'listen', '~> 3.2' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]Gemfileは,
mysql2,rack-corsを設定しています.
rack-corsはCORSの設定です.こちらもそれぞれの開発環境に合わせて変更してください(変更時にはDockerfileでapk add で加えるパッケージもも変更する必要があるので気をつけてください).
cors.rb
api/config/initializers/cors.rb# Be sure to restart your server when you modify this file. # Avoid CORS issues when API is called from the frontend app. # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. # Read more: https://github.com/cyu/rack-cors Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end endCORSの設定は全てを許可しているので,それぞれで変更しましょう.
ルーティング(routes.rb)
api/config/routes.rbRails.application.routes.draw do namespace 'api' do namespace 'v1' do resources :hoges end end endAPIなのでルーティングをVersion管理しやすくしておきます.
コントローラー(hoges_controller.rb)
api/app/controllers/api/v1/hoges_controller.rbmodule Api module V1 class HogesController < ApplicationController def index render json: { status: 'SUCCESS', message: 'Hello Rails!'} end end end end2. ビルドとアプリケーションの実行
2.1 rails new
今回は既存のプロジェクトを使用したので,省略します.(参考記事)
2.2 rake db:create
今回はDBの接続確認はしないので省略します.
参考までにapi直下で,以下のコマンドで環境を作成できます.console$ rails g model post title:string text:text $ rails g controller posts $ rails db:create $ rake db:migrate2-3. 依存関係の解決&imageの取得
console$ make build
apk add [パッケージ]とbundle installをしますので,少し時間がかかります.2-4. アプリケーションの実行
console$ make startRailsとmysqlが起動します.
起動の確認
いつもの画面が出てくれば問題なく起動できています.
api化の確認
console% curl http://localhost:8000/api/v1/hoges {"status":"SUCCESS","message":"Hello Rails!"}%問題なくできています!!
2.5 他のイメージと比較
サイズはプロジェクト事態が存在しての大きさになります.
イメージ サイズ ruby:2.6.3 1.1GB ruby:2.6.3-alpine 387MB ruby:2.6.3-alpine(ビルドベース削除) 264MB
bundle install時に必要なbuild-baseを削除するだけでかなり削減されています!おわりに
Rails6が出たので,そのAPIプロジェクトのDocker化,微量ながら軽量化をしました.
特に元イメージとalpineの差はとても大きいので,イメージサイズを減らすことができてよかったです.
これで好きなフロントエンドを使用してRailsのAPIを使用します.
- 投稿日:2020-12-15T21:18:32+09:00
【Ruby ~繰り返し処理~】勉強メモ4
Rubyの復習。
ほぼ自分の勉強メモです。
過度な期待はしないでください。繰り返し処理
- eachメソッド
eachメソッドを使うと、配列の要素を順番に取り出して処理を行うことが出来る。
使用方法は、「配列.each do |変数名|」と書き、「end」までの間に実行したい処理を書く。書き方配列.each do |変数名| # 処理 endeachメソッドは、配列の要素の数だけ繰り返し処理を行う。
「| |」で囲まれた変数に配列の要素が1つずつ入っていき、その上でeachメソッドの中の処理が実行される。
eachメソッド内の変数名は、好きな名前をつけられますが、配列の変数名の単数形にすることが慣習上多い。使用例colors = ["赤色", "青色", "黄色"] colors.each do |color| puts "色: #{color}" end出力結果色: 赤色 色: 青色 色: 黄色
- 投稿日:2020-12-15T21:00:34+09:00
Rubyのネイティブ拡張をRustで作成する
昨日、Mac環境でRubyのネイティブ拡張を作る記事を投稿したが、WindowsとLinuxでも動く設定が確認できた。
構成
基本は前回と同じ。
まず変わったのは
build.rsで、ライブラリ名もRubyのコンフィグからWin, Mac, Linux共通の値が取得できた。build.rsuse std::process::Command; fn rb_config(key: &str) -> String { let output = Command::new("ruby") .args(&["-e", &format!("print RbConfig::CONFIG['{}']", key)]) .output() .expect("failed run ruby"); return String::from_utf8(output.stdout).unwrap(); } fn main() { println!("cargo:rustc-link-search={}", rb_config("libdir")); println!("cargo:rustc-link-lib={}", rb_config("RUBY_SO_NAME")); }次が
Rakefileで、Windowsでも動かしやすいようにMakefileから変更した。
RustがサポートするWindows環境はmingwとmsvcがあるが、mingwを強制している。Rakefilerequire 'fileutils' require 'json' NAME = JSON.parse(`cargo metadata --format-version=1`).dig("packages", 0, "name") TARGET_SO = "#{NAME}.#{RbConfig::CONFIG["DLEXT"]}" desc "Delete artifacts" task :clean do sh "cargo clean" FileUtils.rm_f(TARGET_SO) end desc "Create native extension" task :build do env = {} case RUBY_PLATFORM when /mingw/ env = {"RUSTUP_TOOLCHAIN" => "stable-#{RbConfig::CONFIG["host_cpu"]}-pc-windows-gnu"} cargo_out = "target/release/#{NAME}.dll" when /darwin/ cargo_out = "target/release/lib#{NAME}.dylib" when /linux/ cargo_out = "target/release/lib#{NAME}.so" else raise "Platform #{RUBY_PLATFORM} is not supported" end sh env, "cargo build --release" cp cargo_out, TARGET_SO, preserve: true, verbose: true end desc "Run Ruby script" task :run => [:clean, :build] do ruby "run.rb" end他は変更なし
今後
gemとして公開するには gemspec 等が必要なので今後調べていくつもり。
- 投稿日:2020-12-15T20:53:14+09:00
【Ruby】メソッドの引数名を指定する。後ろにつくコロン(:)の意味
個人メモです。
メソッドの引数に後ろにコロンのつく変数が指定されている記述の意味について。
▼こういうの
def jadge(x:)def jadge(x:) p "exist x" if x.present? end引数の後ろにつくコロンの意味
引数の中で後ろにつくコロンは、変数名を指定している。(キーワード引数と呼ぶ)
通常、引数は渡された順に、第一引数、第二引数、、、に代入されるが、後ろにコロンをつければ、その変数名と合った引数の値を渡すことができる。
特に扱う変数が複数の場合は変数名で指定できるので便利。
コロンで変数名を指定#関数定義 def jadge(x:, y:) p "exist x" if x.present? p "exist y" if y.present? end #関数実行 jadge(y: false, x: true) => "exist x"
▼通常def jadge(x, y) p "exist x" if x.present? p "exist y" if y.present? end y = false x = true jadge(y, x) => "exist y"変数名ではなく、渡した順番で値が入る。
- 投稿日:2020-12-15T20:49:29+09:00
Herokuでデプロイをやり直す方法
<この記事について>
Herokuでデプロイした後に、本番環境に反映されていなくて再度デプロイをやり直す
ことで無事に解決できたので備忘録として投稿![環境]
・Ruby 2.6.5,
・Rails 6.0.0
・macOS<状況>
railsでアプリを制作時に、本番環境での動きを確認するために、Herokuでデプロイを実施。
一度目は問題なく動作したが、2回目で不具合発生。ビューについて、ローカル環境と同様の
実装となっていないため、試行錯誤するも解決できず。メンターに質問したところ、
新たに1つの操作をすることで解決できました。以下、全てターミナルで操作となります。
$ git commit --allow-empty -m "任意のコミットメッセージ"→通常、差分がないとcommit/pushできないが、差分がなくても強制実行してくれるコマンド
あとは、通常のHerokuへのデプロイと同じ流れです。
$ git push heroku master$ heroku run raills db:migrate本番環境で確認して問題なければ完了。
<終わりに>
ローカル環境で問題ないのに本番環境で同じように動作しないと焦りやすいですが、
このようなコマンドで解決できることを知っているだけで精神的にも安定すると思います!
- 投稿日:2020-12-15T20:06:24+09:00
【Ruby】present/blankメソッドと後置if。
個人メモです。
if文と
present?やblank?メソッドを使うことで、変数に値がセットされているかどうかで処理を分岐できる。rubyではifを後ろに書くことができる(後置ifという)。これを使うとシンプルな記述ができる。
present?とblank?
・
object.present?
objectに値が存在するとtrueを返す。・
object.blank?
objectに値がない、または空だとtrueを返す。
値なしになるのは、nil、empty("")の場合。値なしの場合irb(main):333:0> x => nil irb(main):333:0> x.present? => false irb(main):334:0> x.blank? => true値ありの場合irb(main):335:0> test => "テスト" irb(main):336:0> test.present? => true irb(main):337:0> test.blank? => false補足
blank?と似たメソッドでnil?やempty?もある。
検証するオブジェクトと出力の関係は以下。
メソッド nil empty ("") blank? true true nil? true false empty false true present? false false 値がemptyの場合irb(main):340:0> z = "" => "" irb(main):341:0> z.empty? => true irb(main):342:0> z.nil? => false
後置if
処理の後ろに条件式をかける。従来の記述の改行やendを省略できる。
・
処理 if 条件式後置ifdef jadge(x) "exist" if x.present? end通常のif文def jadge(x) if x.present? "exist" end end
変数の値有無による条件分岐
blankやpresentを使うことで条件分岐ができる。
後置ifを使えばスッキリ記述できる。def jadge(x) y = "No" if x.blank? y = "Exist" if x.present? p "回答は #{y} です" end
- 投稿日:2020-12-15T19:50:32+09:00
Railsチュートリアル(第6版) マイクロポストの投稿機能のバックグラウンド動作
概要
マイクロポストの投稿は以下の2stepで進む。
- ログインしてホーム画面を表示させる。ログイン状態のホーム画面にはマイクロポストの投稿フォームが設置されている。
- マイクロポストの内容を入力して送信ボタンを押すと、マイクロポストの内容がDBに保存される。
詳細
上記の各stepで実行されるバックグラウンドの動作は以下の通りである。
- ログイン時のバックグラウンド動作についてはこちらを参照。/static_pages/homeへのGETリクエストを送信するとStaticPagesコントローラのhomeアクションが実行される。このhomeアクションでは、ユーザーIDに紐付ける形で@micropostが新規作成される(マイクロポスト関連部分のみ抜粋)。homeアクションの最後で、対応したview(/static_pages/home.html.erb)が呼び出され、ホーム画面が表示される。このviewへはhomeアクションで作成された@micropostが渡されており、Railsはこの中身が空であることから、マイクロポストの新規作成であると判断している。
- ユーザーがマイクロポスト入力フォームに内容を入力して投稿ボタンを押すと、/micropostsへのPOSTリクエストが送信され、Micropostsコントローラのcreateアクションが実行される。このcreateアクション実行の直前には、現在ログイン中のユーザーであるかどうかが確認される。当のcreateアクションではparams[:micropost][:content]とparams[:micropost][:image]の値のみを許可して@micropostに格納し、DBへの保存を試みる。DBへの保存に成功すると、root_urlへのリダイレクトが行われ、ホーム画面が表示される。
- 投稿日:2020-12-15T18:52:38+09:00
【Ruby】if文の条件式で変数を代入する。
- 投稿日:2020-12-15T18:44:55+09:00
【Ruby】シンボルの変数と文字列の変数の違い。[:a]と['a']の違いについて。
個人メモです。
オブジェクトの値の取得方法で、
obj[:a]とobj['a']では対象となるデータの構造が異なる。オブジェクトがシンボルで作成されているか文字列かによる。
シンボルを使ったオブジェクト
obj = {a:1, b:2}の場合、プロパティ名はシンボルとなる。
データ取得時の指定もシンボルで行う。オブジェクトirb(main):165:0> obj = {a:1, b:2} => {:a=>1, :b=>2}データの取得##OK irb(main):166:0> obj[:a] => 1 ##エラー irb(main):167:0> obj['a'] => nilシンボル形式のオブジェクトのプロパティ名を
' 'で指定するとエラーになる。
文字列を使ったオブジェクト
obj2 = {"a" => 1, "b" => 2}のように文字列で作成したオブジェクトのデータ取得は文字列で指定する。##オブジェクト定義 irb(main):169:0> obj2 = {"a" => 1, "b" => 2} => {"a"=>1, "b"=>2} ##データ取得 irb(main):170:0> obj2['a'] => 1 irb(main):171:0> obj2["a"] => 1 ##エラー irb(main):172:0> obj2[:a] => nilシンボルを使うとエラーになる。
- 投稿日:2020-12-15T18:05:06+09:00
Everyday Rails でRSpecを学ぶ。サンプルサプリをbundle install するまで。
テストコードの重要性を認識して、everyday rails にてRSpecを学びはじめました。
bundle installできるまでに時間がかかったので
記事にします。まず、git hub からgit clone してきます。
そしてbundle install.
できない。nokogiriのエラー。
source ~/.bash_profileにて環境変数を変えることにより解決。gem ffiがインストールできない。
Gemflie.lockを削除後、bundle installで解決!
やった!
- 投稿日:2020-12-15T18:04:30+09:00
roman numerals
お題
アラビア数字 (arabic numerals) を受け取って, ローマ数字 (roman numerals) を返す method を書け.
(https://qiita.com/daddygongon/items/2d0a73a51ddab2d9da1b にある課題)今回書いた code
とりあえず解説より先に, 今回書いた code を貼っとく.
def to_roman (arabic_numerals) pair_arabic_roman = [[1,"I"],[4,"IV"],[5,"V"],[9,"IX"],[10,"X"],[40,"XL"],[50,"L"], [90,"XC"],[100,"C"],[400,"CD"],[500,"D"],[900,"CM"],[1000,"M"]] roman_numerals = "" pair_arabic_roman.reverse_each do |p_a, p_r| while arabic_numerals >= p_a do arabic_numerals = arabic_numerals - p_a roman_numerals = roman_numerals + p_r end end return roman_numerals end if $PROGRAM_NAME == __FILE__ arabic_numerals = ARGV[0].to_i print "#{arabic_numerals}\t : #{to_roman(arabic_numerals)}\n" end(Integer class に組み込んだ版はページ最後に貼ってる)
解説
まず, 作成する method 概要を整理する
- 関数名 : 何でも良い (ここでは to_roman としておく)
- 入力 : アラビア数字 (arabic numerals)
- 出力 : ローマ数字 (roman numerals)
では, さっそく作っていく
1 に対して I を出力
思うがままに書いたらこうなる.
def to_roman (arabic_numerals) if arabic_numerals==1 print "I\n" else print "hoge\n" end end if $PROGRAM_NAME == __FILE__ arabic_numerals = ARGV[0].to_i print "#{arabic_numerals}\n" to_roman(arabic_numerals) endもちろん実行通る.
ちなみに 1 以外の場合に"hoge"を出力してるのは何となく.2 に対して II, 4 に対して IV を出力
素直に書くとこうなった.
def to_roman (arabic_numerals) if arabic_numerals==1 print "I\n" elsif arabic_numerals==2 print "II\n" elsif arabic_numerals==4 print "IV\n" else print "hoge\n" end end if $PROGRAM_NAME == __FILE__ arabic_numerals = ARGV[0].to_i print "#{arabic_numerals}\n" to_roman(arabic_numerals) endもちろん実行通る.
5 に対して V を返す
elsif書くのめんどくさくなったので, ちょっとループで書いてみた.
あと, 関数内で出力するのではなく, 返り値を返すようにした.def to_roman (arabic_numerals) pair_arabic_roman = [[1,"I"], [2,"II"], [4,"IV"], [5,"V"]] pair_arabic_roman.each do |arabic, roman| if arabic_numerals==arabic return roman end end return "hoge" end if $PROGRAM_NAME == __FILE__ arabic_numerals = ARGV[0].to_i print "#{arabic_numerals}\n" print "#{to_roman(arabic_numerals)}\n" endもちろん実行通る.
1 ~ 10 に対して ローマ数字を返す
対応表
pair_arabic_romanにアラビア数字とローマ数字の対応全てを書くのはめんどくさすぎる.
なので, こうした.def to_roman (arabic_numerals) pair_arabic_roman = [[1,"I"],[2,"II"],[4,"IV"],[5,"V"],[6,"VI"],[9,"IX"],[10,"X"]] roman_numerals = "" pair_arabic_roman.reverse_each do |arabic, roman| # reverse each にした if arabic_numerals >= arabic arabic_numerals = arabic_numerals - arabic roman_numerals = roman_numerals + roman end end return roman_numerals end if $PROGRAM_NAME == __FILE__ arabic_numerals = ARGV[0].to_i print "#{arabic_numerals}\n" print "#{to_roman(arabic_numerals)}\n" end
roman_numeralsの初期値を""にして, どんどん文字列を連結していく作戦.
- 3 の場合 : 3 = 2 + 1 なので,
""は"II"になり, その後"III"になる.- 8 の場合 : 8 = 6 + 2 なので,
""は"VI"になり, その後"VIII"になる.1 ~ 20 に対して
ifのところをwhileに改良.def to_roman (arabic_numerals) pair_arabic_roman = [[1,"I"],[2,"II"],[4,"IV"],[5,"V"],[6,"VI"],[9,"IX"],[10,"X"]] roman_numerals = "" pair_arabic_roman.reverse_each do |p_a, p_r| while arabic_numerals >= p_a do arabic_numerals = arabic_numerals - p_a roman_numerals = roman_numerals + p_r end end return roman_numerals end if $PROGRAM_NAME == __FILE__ arabic_numerals = Range.new(1,20) arabic_numerals.each do |i| print "#{i}\t : #{to_roman(i)}\n" end endここまで来たらほぼ完成.
ちなみに
Rangeは python でもよく見るあれ.1 ~ 2000 に対して
対応表
pair_arabic_romanをある程度整理して, こうなった.def to_roman (arabic_numerals) pair_arabic_roman = [[1,"I"],[4,"IV"],[5,"V"],[9,"IX"],[10,"X"],[40,"XL"],[50,"L"], [90,"XC"],[100,"C"],[400,"CD"],[500,"D"],[900,"CM"],[1000,"M"]] roman_numerals = "" pair_arabic_roman.reverse_each do |p_a, p_r| while arabic_numerals >= p_a do arabic_numerals = arabic_numerals - p_a roman_numerals = roman_numerals + p_r end end return roman_numerals end if $PROGRAM_NAME == __FILE__ arabic_numerals = [51,97,99,439,483,499,500,732,961,999,1000,1999,2000] arabic_numerals.each do |i| print "#{i}\t : #{to_roman(i)}\n" end endもちろん実行通った. やったぜ.
こんな感じで, 冒頭に載せた code が出来上がった.発展課題
整数 Integer class を拡張して,
999.to_roman #=> CMXCIXと返すようにする.
さっき作った method を素直に Integer class に突っ込んだ.
class Integer def to_roman arabic_numerals = self pair_arabic_roman = [[1,"I"],[4,"IV"],[5,"V"],[9,"IX"],[10,"X"],[40,"XL"],[50,"L"], [90,"XC"],[100,"C"],[400,"CD"],[500,"D"],[900,"CM"],[1000,"M"]] roman_numerals = "" pair_arabic_roman.reverse_each do |p_a, p_r| while arabic_numerals >= p_a do arabic_numerals = arabic_numerals - p_a roman_numerals = roman_numerals + p_r end end return roman_numerals end end if $PROGRAM_NAME == __FILE__ a = ARGV[0].to_i print "#{a}\t : #{a.to_roman}\n" endもちろん期待通りの出力になってる.
参考資料
- source ~/Lecture/multiscale_simulation/grad_members_20f/members/gagagagazelle/docs/roman_numerals.org
- 投稿日:2020-12-15T18:00:32+09:00
【Ruby】演算子"::(コロンコロン)"【定数】
まえがき
Railsを使っていれば、誰でも以下の記述を見たことあると思います。
models/application_record.rbclass ApplicationRecord < ActiveRecord::Base self.abstract_class = true end
ApplicationRecordにActiveRecord::Baseを継承していますよね。なんにも変わったことありませんね。
・・・
・・
・
って、え、え、ちょっと待って下さい。
なんですか、::って!?!?!?
怖い!!!ということで、Ruby初学者である私が、
::について調べたことを健忘録として残します。結論
::は2つの特徴があります。1.
名前空間の絶対パスを指定するmodule Hoge class Fuga def self.fuga p "fugaです" end end end Hoge::Fuga.fuga #=> "fugaです" Fuga.fuga #エラー #Fugaクラスを作成 class Fuga def self.fuga p "fugaです" end end Hoge::Fuga.fuga #=> "fugaです" Fuga.fuga #=> "fugaです"2.
定数のスコープ演算子クラスまたはモジュールで定義された定数をスコープ外部から参照するためには
::を使います。class Hoge FUGA = "FUGA" #定数FUGA p FUGA #=> "FUGA" end #スコープ外から定数FUGAを出力する。 p Hoge::FUGA #=> "FUGA"また、Rubyではクラスやモジュールも定数として扱われます。
例えばHogeクラスを生成した時、Hogeクラスオブジェクトは、Hogeという名前の定数に代入されています。結局、ActiveRecord::Baseの
::は??ActiveRecord::Baseの
::はActiveRecordモジュール内のBaseクラスを指しています!!
ActiveRecordにネストされたBaseクラスなんですね〜。
簡単に書くと以下の通りです。active_record/base.rbmodule ActiveRecord #省略 class Base #省略 end #省略 endActiveRecord::Baseの正式な中身は以下の通りです。
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/base.rbActiveRecordの機能についてはこの方が深くまとめております。
Rails: ActiveRecord::Baseメソッドのまとめ
参考
Ruby公式リファレンス 変数と定数
https://docs.ruby-lang.org/ja/latest/doc/spec=2fvariables.html#constRailsガイド Active Record の基礎
https://railsguides.jp/active_record_basics.html
- 投稿日:2020-12-15T16:07:06+09:00
RSpecまとめ 3.モデルスペック
前回の続き。
モデルに対するテスト。モデルのバリデーションやクラスメソッド、インスタンスメソッドをテストしていきます。
モデルスペックの構造
モデルスペックには、以下の3つのテストを最低限入れます。
1.有効な属性で初期化された場合は、モデルの状態が有効(valid)になっていること
2.バリデーションを失敗させるデータであれば、モデルの状態が有効になっていないこと
3.クラスメソッドとインスタンスメソッドが期待通りに動作することモデルスペック作成
まずはUserモデルのテストです。
前回、スペックファイルの自動生成を設定しましたが、今回は既存のモデルに対するテストなので、手動でスペックファイルを生成します。$ rails g rspec:model userこのコマンドで、spec/models/user_spec.rbが生成されるので、このファイルにスペックを書いていきます。
spec/models/user_spec.rbrepuire 'rails_helper' #ヘルパーの読み込み RSpec.describe User, type: :model do #ブロック内にUserモデルのスペックをまとめている #name, email, passwordがあれば有効な状態であること it 'is valid with a name, email, and password' do #一つ一つのスペックはitで始まるブロック内に記述 user = User.new( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) expect(user).to be_valid #userが有効(valid)ならパスする end endRSpecでは、テストしたい値をexpect()に渡し、後ろに続くマッチャを呼び出すことでテストを行います。
今回の場合、userをbe_valid(有効か)というマッチャでテストしています。
また、(user)の後ろのtoは、テストする値がマッチャに適合すればsuccess、しなければfaildをテスト結果として返します。
toの反対の意味を持つto_notもよく使うので、覚えておくと良いでしょう。バリデーションのテスト
正しい値を与えたときにモデルが有効になっているかテストできたので、今度はバリデーションを失敗させるデータを与えたときにモデルが無効な状態かテストしていきます。
spec/models/user_spec.rb#nameがなければ無効であること it 'is invalid without a name' do user = User.new( name: nil email: "hoge@hoge.com" password: "hogehoge" ) user.valid? expect(user.errors[:name]).to include("can't be blank") end今回のスペックでは、userのnameをnilにした状態でvalid?メソッドを使ってuserの有効性を検証し、最後にuser.errors[:name]に"can't be blank"という内容のものが含まれていればパスするという内容です。
nameがないことがバリデーションに失敗した原因であるかを知りたいので、発生したエラーの内容をテストしています。
user.valid?で有効性を検証しないとエラーメッセージが出ないので、注意しましょう。この方法に沿って、他のバリデーションのスペックも書いていきましょう。
spec/models/user_spec.rb#emailが重複していれば無効であること it 'is invalid without a duplicate email address' do User.create( neme: "zeisho" email: "hoge@hoge.com" password: "hogehoge" ) user = User.new( name: "Skywalker" email: "hoge@hoge.com" password: "hogehoge" ) user.valid? expect(user.errors[:name]).to include("has already been taken") endcreateを使ってユーザーを保存し、その後で同じemailを持ったユーザーを生成するとemailの重複エラーが出るかという内容のスペックです。
この調子でUserモデルのスペックを完成させましょう。
完成したら、Projectモデルのスペックも作っていきます。
$ rails g rspec:model projectspec/models/project_spec.rbrequire 'rails_helper' RSpec.describe Project, type: :model do # ユーザー単位では重複したプロジェクト名を許可しないこと it "does not allow duplicate project names per user" do user = User.create( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) user.projects.create( name: "Test Project" ) new_project = user.projects.build( name: "Test Project" ) new_project.valid? expect(new_project.errors[:name]).to include("has already been taken") end # 二人のユーザーが同じ名前を使うことは許可すること it "allows two users to share a project name" do user = User.create( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) user.projects.create( name: "Test Project" ) other_user = User.create( name: "Skywalker", email: "hogehoge@hoge.com", password: "hogehoge" ) other_project = other_user.projects.build( name: "Test Project" ) expect(other_project).to be_valid end endProjectはUserに紐づいているので、今の書き方だとテストに必要なインスタンスを作るだけでコードが冗長化してしまいました。この辺りの問題は後で解決していきます。
クラスメソッド、インスタンスメソッドのテスト
このアプリには、Projectモデルに紐づいたNoteがあり、プロジェクトのメモとして文字列を格納できるようになっており、Noteモデルには検索機能を実装しています。
app/model/note.rbscope :search, ->(term) { where("LOWER(message) LIKE ?", "%#{term.downcase}%") }今回はこのクラスメソッドとスコープをテストしていきます。
$ rails g rspec:model notespec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do #検索文字列に一致するメモを返すこと it "returns notes that match the search term" do user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) project = user.projects.create( name: "Test Project" ) note1 = project.notes.create( message: "This is first note.", user: user ) note2 = project.notes.create( message: "This is second note.", user: user ) note3 = project.notes.create( message: "First, preheat the oven.", user: user ) expect(Note.search("first")).to include(note1, note3) expect(Note.search("first")).to_not include(note2) end #検索結果が見つからなければ空のコレクションを返すこと it "returns an empty collection when no results are found" do user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) project = user.projects.create( name: "Test Project" ) note1 = project.notes.create( message: "This is first note.", user: user ) note2 = project.notes.create( message: "This is second note.", user: user ) note3 = project.notes.create( message: "First, preheat the oven.", user: user ) expect(Note.search("message")).to be_empty end endマッチャについてもっと詳しく
これまで、3つのマッチャ(be_valid, include, be_enpty)を使ってきましたが、RSpecが提供するマッチャをもっと知りたい場合、rspec-expectationsを参照すると良いでしょう。
スペックをDRYにする
discribe, context, before, afterを使ってスペックをDRYにしていきます。
discribe, context
スペック群を分類してブロック内にまとめることができます。
Noteモデルのスペックを例に見ていきましょう。spec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do #バリデーション用のスペック群 #メッセージ検索機能のスペック群 discribe "search message for a term" do #一致するデータが見つかるとき context "when a match is found" do #一致する場合のexample群 end #一致するデータが見つからないとき context "when no match is found" do #一致しない場合のexample群 end end endbefore, after
全てのテストで使用するテストデータを一箇所にまとめることができます。
spec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do before do @user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) @project = user.projects.create( name: "Test Project" ) end #バリデーション用のスペック群 #メッセージ検索機能のスペック群 endbeforeには以下のオプションを設定できます。
before(:each)
describeまたはcontextブロック内の各(each)テストの前に実行before(:all)
describeまたはcontextブロック内の全(all)テストの前に一回だけ実行before(suite)
テストスイート全体の全ファイルを実行する前に実行exampleの後に後片付けが必要であれば、afterを使うことができます。
テストはDRYにし過ぎない!
テストは開発・本番環境とは違って可読性を優先してDRYにしていくので、もしスペックファイルの内容を確認するのにエディタのスクロールや、複数のファイルの行き来を頻繁に行っているならば、DRY過ぎます。
必要に応じてコードを重複させることを検討したり、ファイルを行き来しなくても役割のわかる変数・メソッド名をつけるよう心がけましょう。
- 投稿日:2020-12-15T16:07:06+09:00
Everyday Railsまとめ 3章『モデルスペック』
前回の続き。
モデルに対するテスト。モデルのバリデーションやクラスメソッド、インスタンスメソッドをテストしていきます。
モデルスペックの構造
モデルスペックには、以下の3つのテストを最低限入れます。
1.有効な属性で初期化された場合は、モデルの状態が有効(valid)になっていること
2.バリデーションを失敗させるデータであれば、モデルの状態が有効になっていないこと
3.クラスメソッドとインスタンスメソッドが期待通りに動作することモデルスペック作成
まずはUserモデルのテストです。
前回、スペックファイルの自動生成を設定しましたが、今回は既存のモデルに対するテストなので、手動でスペックファイルを生成します。$ rails g rspec:model userこのコマンドで、spec/models/user_spec.rbが生成されるので、このファイルにスペックを書いていきます。
spec/models/user_spec.rbrepuire 'rails_helper' #ヘルパーの読み込み RSpec.describe User, type: :model do #ブロック内にUserモデルのスペックをまとめている #name, email, passwordがあれば有効な状態であること it 'is valid with a name, email, and password' do #一つ一つのスペックはitで始まるブロック内に記述 user = User.new( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) expect(user).to be_valid #userが有効(valid)ならパスする end endRSpecでは、テストしたい値をexpect()に渡し、後ろに続くマッチャを呼び出すことでテストを行います。
今回の場合、userをbe_valid(有効か)というマッチャでテストしています。
また、(user)の後ろのtoは、テストする値がマッチャに適合すればsuccess、しなければfaildをテスト結果として返します。
toの反対の意味を持つto_notもよく使うので、覚えておくと良いでしょう。バリデーションのテスト
正しい値を与えたときにモデルが有効になっているかテストできたので、今度はバリデーションを失敗させるデータを与えたときにモデルが無効な状態かテストしていきます。
spec/models/user_spec.rb#nameがなければ無効であること it 'is invalid without a name' do user = User.new( name: nil email: "hoge@hoge.com" password: "hogehoge" ) user.valid? expect(user.errors[:name]).to include("can't be blank") end今回のスペックでは、userのnameをnilにした状態でvalid?メソッドを使ってuserの有効性を検証し、最後にuser.errors[:name]に"can't be blank"という内容のものが含まれていればパスするという内容です。
nameがないことがバリデーションに失敗した原因であるかを知りたいので、発生したエラーの内容をテストしています。
user.valid?で有効性を検証しないとエラーメッセージが出ないので、注意しましょう。この方法に沿って、他のバリデーションのスペックも書いていきましょう。
spec/models/user_spec.rb#emailが重複していれば無効であること it 'is invalid without a duplicate email address' do User.create( neme: "zeisho" email: "hoge@hoge.com" password: "hogehoge" ) user = User.new( name: "Skywalker" email: "hoge@hoge.com" password: "hogehoge" ) user.valid? expect(user.errors[:name]).to include("has already been taken") endcreateを使ってユーザーを保存し、その後で同じemailを持ったユーザーを生成するとemailの重複エラーが出るかという内容のスペックです。
この調子でUserモデルのスペックを完成させましょう。
完成したら、Projectモデルのスペックも作っていきます。
$ rails g rspec:model projectspec/models/project_spec.rbrequire 'rails_helper' RSpec.describe Project, type: :model do # ユーザー単位では重複したプロジェクト名を許可しないこと it "does not allow duplicate project names per user" do user = User.create( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) user.projects.create( name: "Test Project" ) new_project = user.projects.build( name: "Test Project" ) new_project.valid? expect(new_project.errors[:name]).to include("has already been taken") end # 二人のユーザーが同じ名前を使うことは許可すること it "allows two users to share a project name" do user = User.create( name: "Zeisho", email: "hoge@hoge.com", password: "hogehoge" ) user.projects.create( name: "Test Project" ) other_user = User.create( name: "Skywalker", email: "hogehoge@hoge.com", password: "hogehoge" ) other_project = other_user.projects.build( name: "Test Project" ) expect(other_project).to be_valid end endProjectはUserに紐づいているので、今の書き方だとテストに必要なインスタンスを作るだけでコードが冗長化してしまいました。この辺りの問題は後で解決していきます。
クラスメソッド、インスタンスメソッドのテスト
このアプリには、Projectモデルに紐づいたNoteがあり、プロジェクトのメモとして文字列を格納できるようになっており、Noteモデルには検索機能を実装しています。
app/model/note.rbscope :search, ->(term) { where("LOWER(message) LIKE ?", "%#{term.downcase}%") }今回はこのクラスメソッドとスコープをテストしていきます。
$ rails g rspec:model notespec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do #検索文字列に一致するメモを返すこと it "returns notes that match the search term" do user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) project = user.projects.create( name: "Test Project" ) note1 = project.notes.create( message: "This is first note.", user: user ) note2 = project.notes.create( message: "This is second note.", user: user ) note3 = project.notes.create( message: "First, preheat the oven.", user: user ) expect(Note.search("first")).to include(note1, note3) expect(Note.search("first")).to_not include(note2) end #検索結果が見つからなければ空のコレクションを返すこと it "returns an empty collection when no results are found" do user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) project = user.projects.create( name: "Test Project" ) note1 = project.notes.create( message: "This is first note.", user: user ) note2 = project.notes.create( message: "This is second note.", user: user ) note3 = project.notes.create( message: "First, preheat the oven.", user: user ) expect(Note.search("message")).to be_empty end endマッチャについてもっと詳しく
これまで、3つのマッチャ(be_valid, include, be_enpty)を使ってきましたが、RSpecが提供するマッチャをもっと知りたい場合、rspec-expectationsを参照すると良いでしょう。
スペックをDRYにする
discribe, context, before, afterを使ってスペックをDRYにしていきます。
discribe, context
スペック群を分類してブロック内にまとめることができます。
Noteモデルのスペックを例に見ていきましょう。spec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do #バリデーション用のスペック群 #メッセージ検索機能のスペック群 discribe "search message for a term" do #一致するデータが見つかるとき context "when a match is found" do #一致する場合のexample群 end #一致するデータが見つからないとき context "when no match is found" do #一致しない場合のexample群 end end endbefore, after
全てのテストで使用するテストデータを一箇所にまとめることができます。
spec/models/note_spec.rbrequire 'rails_helper' RSpec.describe Note, type: :model do before do @user = User.cerate( name: "Zeisho" email: "hoge@hoge.com" password: "hogehoge" ) @project = user.projects.create( name: "Test Project" ) end #バリデーション用のスペック群 #メッセージ検索機能のスペック群 endbeforeには以下のオプションを設定できます。
before(:each)
describeまたはcontextブロック内の各(each)テストの前に実行before(:all)
describeまたはcontextブロック内の全(all)テストの前に一回だけ実行before(suite)
テストスイート全体の全ファイルを実行する前に実行exampleの後に後片付けが必要であれば、afterを使うことができます。
テストはDRYにし過ぎない!
テストは開発・本番環境とは違って可読性を優先してDRYにしていくので、もしスペックファイルの内容を確認するのにエディタのスクロールや、複数のファイルの行き来を頻繁に行っているならば、DRY過ぎます。
必要に応じてコードを重複させることを検討したり、ファイルを行き来しなくても役割のわかる変数・メソッド名をつけるよう心がけましょう。
- 投稿日:2020-12-15T15:27:55+09:00
[Ruby] 配列の中から数字を探せ
一言
最近なぜか忙しい・・・。
仕事はそんなに多くないが・・・。
なぜか
契約切れるから引っ越し準備してたら事務所も引っ越して・・・。
なんてタイミングということで本題
標題的に何か、眼鏡をかけた赤と白のボーダーの服を着ている人を連想させますが・・・。配列の中から任意の数字を探せ
今日も朝学習で前にもやった課題にチャレンジ(2回目)
array=[1,3,5,6,9,10,13,20,26,31]
※出力例:
検索したい数字を入力してください
5 5は配列の2番目に存在します
7 7は配列内に存在しませんこんな感じで出せと
ちょっと忘れててわからず解説見てしまいました。
真ん中のデータを取り出して、左右で大小の比較、それを繰り返し処理で範囲を絞っていって最終的にどの数字かを当てていくやり方。
所謂、二分探索(バイナリーサーチ)というやつで、恥ずかしながら昨日あげた記事にコメントいただいた内容で初めて知った言葉で「線形探索(リニアサーチ)」なる言葉をご教授いただき、このやり方でやってみようと思い投稿しました。
def binary_search(array, num) array.each_with_index do |n, index| if n == num return index end end return 0 end array=[1,3,5,6,9,10,13,20,26,31] puts "検索したい数字を入力してください" num = gets.to_i result = binary_search(array, num) if result == 0 puts "#{num}は配列内に存在しません" else puts "#{num}は配列の#{result+1}番目に存在します " endこれで同じような出力結果が出せました。
線形探索などの言葉は意識してなかったですが、いつもやってるような感じだったのでこっちの方が個人的にはやりやすかった。めでたし
- 投稿日:2020-12-15T15:19:12+09:00
RSpecのテスト中にMySQL client is not connected
開発環境
macOS Catalina 10.15.7
Ruby on Rails 6.0.0
RSpec 4.0.1
pry rails 0.3.9
FactoryBot 6.1.0エラー内容
consoleFailure/Error: _query(sql, @query_options.merge(options)) ActiveRecord::StatementInvalid: Mysql2::Error: MySQL client is not connectedどうやらMySQLクライアントとの接続が確立できていないようだ。
定義を見る限り、client が初期化されているにも関わらず、network socket (file descriptor) が無効な状態だとこのエラーになるみたいですね。
Mysql2 の "MySQL client is not connected" について検証
テストの実行結果を見ると、途中まではテストが成功しているため、ひとまずbinding.pryで処理を止めながらテスト内容を確認してみたところ、なぜかすべてのテストが成功した。consoleFinished in 16.16 seconds (files took 2.21 seconds to load) 15 examples, 0 failures仮説
FactoryBotのインスタンス生成の記述を増やしたタイミングでエラーがエラーが発生しはじめたため、ここで負荷がかかって処理が止まった可能性があると考えた。
対処法1
インスタンスを生成するタイミングで
sleepで処理を待機させることにした。RSpec.describe OrderItem, type: :model do describe '購入情報の保存' do before do @user = FactoryBot.create(:user) @item = FactoryBot.create(:item) @order_item = FactoryBot.build(:order_item) sleep 0.1 # 0.1秒待機 end # 省略結果
エラーを吐かずにテストが安定して成功するようになった。
対処法2
config/environments/test.rbに以下の記述をすることでも対処出来た。config/environments/test.rbRails.application.configure do config.active_job.queue_adapter = :inline # 省略 end参考リンク
- 投稿日:2020-12-15T14:46:50+09:00
authenticateメゾットについて。
user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password])user.authenticate(password)で
合っていればユーザーの情報を出す。
間違っていれば、falseを返すメゾット。主にログイン等に使われる。
if user && user.authenticate(params[:session][:password])この文は&&の論理積(and)はnilとfalse以外は
Rubyではnilとfalse以外のすべてのオブジェクトは、真偽値ではtrueになるという性質を利用して、ログインできるかできないかをif文を使って実行している。真偽値についての理解が浅かったため理解するのに時間がかかった。。。
- 投稿日:2020-12-15T14:44:19+09:00
Railsのform_withについて掘り下げる
軽く自己紹介
はじめましての方は初めまして。伊東と申します。
大卒後未経験でプログラマーになりまして、気づけば7,8年くらい働いています。
2020年3月からDMM WebCampのメンターをしています。対象読者
- Ruby on Railsで初めてMVCフレームワークを触った
- 検証ツールであまり見ない
- 初学者の方
前提
$ rails -v Rails 5.2.4.3 $ ruby -v ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-linux] $Rubyのバージョン、Rails5系だよってだけです。バージョン互換などは考慮しておりませんが、
書く内容はそんなに影響するような話じゃないのでは??って思っています。authenticity_tokenの箇所でデフォルトの挙動が違うぽいです...本題というわけではないので各バージョンで調べてくださいmm伝えたいこと
検証ツール見ていきましょうって話です。
普段からinput nameとかの属性をちゃんと見てる人は読む必要がない記事になってます。
本当に基本的なことしか書かないので悪しからずそもそもActionView::Helpersってなに??
よく使うのはform系ではないかな?と思います。
- form_with
- text_field_tag
- text_area_tag
- submit_tagなど
詳しくは以下リンクから見てみてください。
https://api.rubyonrails.org/classes/ActionView/Helpers.html事前準備
細かい説明は割愛して作っていきます。
プロジェクト作成らへん
ちなみに行頭の
$マークはrootユーザー#じゃないって意味です。$ rails new without_action-view-helpers # プロジェクト作成 # 省略 $ cd without_action-view-helpers/ $ rails g controller books index new # コントローラー(アクション含)作成 # 省略 $ rails g model Book title:string price:integer description:text # モデル作成 # 省略 $ rails db:migrate # DB反映 # 省略 $ルーティングの設定
config/routes.rbRails.application.routes.draw do resources :books, only: [:index, :new, :create, :show, :edit, :update] endルーティングの確認$ rails routes | grep book # grepは文字列検索しています books GET /books(.:format) books#index POST /books(.:format) books#create new_book GET /books/new(.:format) books#new edit_book GET /books/:id/edit(.:format) books#edit book GET /books/:id(.:format) books#show PATCH /books/:id(.:format) books#update PUT /books/:id(.:format) books#update $controller
めーっちゃシンプルな形。
app/controllers/books_controller.rbclass BooksController < ApplicationController def index @books = Book.all end def new @book = Book.new end def create book = Book.new(book_params) book.save redirect_to books_path end def show @book = Book.find(params[:id]) end def edit @book = Book.find(params[:id]) end def update book = Book.find(params[:id]) book.update(book_params) redirect_to books_path end private def book_params params.require(:book).permit(:title, :price, :description) end endview
app/views/books/new.html.erb<h1>Books#new</h1> <div> <%= render 'books/form', book: @book %> </div>部分テンプレート化しています。
app/views/books/_form.html.erb<%= form_with model: book, local: true do |f| %> <div> <%= f.label :title %> <%= f.text_field :title %> </div> <div> <%= f.label :price %> <%= f.number_field :price %> </div> <div> <%= f.label :description %> <%= f.text_area :description %> </div> <div> <%= f.submit :submit %> </div> <% end %>ブラウザの検証ツールを使用して掘り下げて見ていきます
new
まずformタグやinput>hiddenの部分から見ていきます<form action="/books" accept-charset="UTF-8" method="post"> <input name="utf8" type="hidden" value="✓"> <input type="hidden" name="authenticity_token" value="cgX+LsxfzE7A7F5Tm8GAlF2KdaifSdqdrWd6cOgazlcbZx8OQ4c6LazJtdRYIdPjzmfmPUGHkuBoxSQhdSqiIA=="> </form>action?accept-charset?method?hidden?指定していない項目がいくつもでてきました。
method
methodはsubmitされた時のhttpリクエストメソッドです。
action
actionはformタグ内のsubmitボタンがポチィされた時に実行されるURI(パス)を指定します。
/booksで methodがpostになっているので、books_controllerのcreateアクションに飛ぶという仕組みです。form_withでaction指定してなくない?
urlというパラメーターの指定が無いときはmodelからactionのpathを勝手に作ってくれています。モデル名とコントローラー名が一致している場合は 省略可能です。
model: bookと書くだけでいいので簡単ですね。ちなみにnamespaceを使用している場合は
model: [:admin, book]としてあげれば、/admin/booksというURIを作ってくれます。便利hidden
name="utf8" については調べてないです!割愛します
name="authenticity_token" についてはCSRFの対策として自動発行されているものです。他のサイトから不正なリクエストを受けないように発行してるものです。
今回は5.2系で試しているのでデフォルトで有効になっているようですがそれ以外のバージョンであれば必要に応じて設定すれば有効になります。inputタグ
入力項目はどのようになっているか<div> <label for="book_title">Title</label> <input type="text" name="book[title]" id="book_title"> </div>inputタグを見てみると
<input type="text" name="book[title]" id="book_title">となっています。
type="text"は単純にテキストを入力出来るという意味です。text以外にもあるのでこれ見ればわかると思います。 (詳しくはこれ: http://www.htmq.com/html5/input.shtml )
name="book[title]"これが多分一番大事になってくると思います。Rails的にはコントローラー側でparams[:book][:title]で入力された値が取得出来るという意味になります。これを理解していれば、入力された値がDBに保存できない?みたいなことは少なくなると思います。edit
適宜省略してます<form action="/books/1" accept-charset="UTF-8" method="post"> <input type="hidden" name="_method" value="patch"> <div> <label for="book_title">Title</label> <input type="text" value="たーいとる" name="book[title]" id="book_title"> </div> <div> <input type="submit" name="commit" value="submit" data-disable-with="submit"> </div> </form>formタグ, hiddenタグ
action部分は
action="/books/1"になっています。model: bookで指定している変数bookが保存済みの場合URIを自動的にbook.idを設定してくれています。hiddenタグで新規の時には無かったものが存在しています。
<input type="hidden" name="_method" value="patch">これでhttpメソッドをpatchにしています。
なんでformタグで指定しないの??ってなると思うんですけど、多分ですがform>methodではpost/getしか指定できないからだと思います(間違ってたら指摘してもらえると)。 http://www.htmq.com/html5/form.shtml
inputタグ
<input type="text" value="たーいとる" name="book[title]" id="book_title">value="たーいとる" という項目が増えています。これで入力部分の値を指定しています。それ以外は同じなので割愛します。
まとめ
HTMLも単純に画面に表示されたものを見るのではなく、どのようにHTMLが生成されているかを確認したり意識することで、HTMLであったりRailsの仕組みを少しずつ理解出来るようになっていくと思います。
あまり長い文章書くの得意じゃないんですが、役に立てると嬉しく思います。。。
またお声掛け頂いたメンターの方ありがとうございます。
- 投稿日:2020-12-15T14:09:34+09:00
gem install時の"can't find header files"エラーの解決
はじめに
EC2インスタンス上のマシンでbundle install実行時に
can't find header filesエラー。解決した方法をメモとして残す。環境
- EC2インスタンス
- ubuntu 18.04LTS
- ruby 2.5.1
bundle install時のエラー
$ bundle install --path .bundle [DEPRECATED] The `--path` flag is deprecated because it relies on being remembered across bundler inv ocations, which bundler will no longer do in future versions. Instead please use `bundle config set p ath '.bundle'`, and stop using this flag Fetching gem metadata from https://rubygems.org/....... Using bundler 2.1.4 Using multipart-post 2.1.1 Using ruby2_keywords 0.0.2 Using faraday 1.1.0 Using faraday_middleware 1.0.0 Using gli 2.19.2 Using hashie 4.1.0 Using websocket-extensions 0.1.5 Fetching websocket-driver 0.7.3 Installing websocket-driver 0.7.3 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /home/ubuntu/slack_bots/.bundle/ruby/2.5.0/gems/websocket-driver-0.7.3/ext/websocket-driver /usr/bin/ruby2.5 -r ./siteconf20201215-14622-s23dju.rb extconf.rb mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h extconf failed, exit code 1 Gem files will remain installed in /home/ubuntu/slack_bots/.bundle/ruby/2.5.0/gems/websocket-driver-0.7.3 for inspection. Results logged to /home/ubuntu/slack_bots/.bundle/ruby/2.5.0/extensions/x86_64-linux/2.5.0/websocket-driver-0.7.3/gem_make.out An error occurred while installing websocket-driver (0.7.3), and Bundler cannot continue. Make sure that `gem install websocket-driver -v '0.7.3' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: slack-ruby-client was resolved to 0.15.1, which depends on websocket-driver
websocket-driver (0.7.3)というgemをインストール時にエラーになった模様。
メッセージに表示されているようにgem install websocket-driver -v '0.7.3' --source 'https://rubygems.org/'を単体で実行してみたところ、今度は以下のようなエラーが発生。$ sudo gem install websocket-driver -v '0.7.3' --source 'https://rubygems.org/' Building native extensions. This could take a while... ERROR: Error installing websocket-driver: ERROR: Failed to build gem native extension. current directory: /var/lib/gems/2.5.0/gems/websocket-driver-0.7.3/ext/websocket-driver /usr/bin/ruby2.5 -r ./siteconf20201215-14722-9uc5to.rb extconf.rb mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h extconf failed, exit code 1 Gem files will remain installed in /var/lib/gems/2.5.0/gems/websocket-driver-0.7.3 for inspection. Results logged to /var/lib/gems/2.5.0/extensions/x86_64-linux/2.5.0/websocket-driver-0.7.3/gem_make.outメッセージ
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.hにあるようにruby用のヘッダファイルが見つからないのが原因のよう。
自分では解決策がわからなかったので素直にエラーメッセージでぐぐったら、同じような問題に遭遇している方がいらっしゃったので参考にさせてもらった。解決方法
自身の使っているrubyのバージョンに合わせた
ruby-devをインストールすることで解決。
今回はruby2.5だったのでruby2.5-devをインストール。$ sudo apt install ruby2.5-devその後
bundle installを実行するとできた。$ bundle install --path .bundle [DEPRECATED] The `--path` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use `bundle config set path '.bundle'`, and stop using this flag Fetching gem metadata from https://rubygems.org/....... Using bundler 2.1.4 Using multipart-post 2.1.1 Using ruby2_keywords 0.0.2 Using faraday 1.1.0 Using faraday_middleware 1.0.0 Using gli 2.19.2 Using hashie 4.1.0 Using websocket-extensions 0.1.5 Fetching websocket-driver 0.7.3 Installing websocket-driver 0.7.3 with native extensions Fetching slack-ruby-client 0.15.1 Installing slack-ruby-client 0.15.1 Bundle complete! 1 Gemfile dependency, 10 gems now installed. Bundled gems are installed into `./.bundle`おわりに
gemのインストール時に表示される
with native extensionsっていうのは、そのgemがc言語のライブラリに依存していて別途コンパイルが必要だということ。今回はwebsocket-driverをコンパイルするためのライブラリがマシンにインストールされていなかった。
- 投稿日:2020-12-15T13:48:41+09:00
未経験で大学を休学したエンジニアが2020年に学んだこと
CAMPFIRE Communityでエンジニアをしております。Matsuiです。
趣味はスポーツ観戦、旅行などです。
2020年はプロ野球、UEFAチャンピオンズリーグ、F1&F2が個人的に盛り上がりました。好きなドライバーはMax Verstappenです。(誰にも伝わらないと思いますが、Daniel Ricciardoとのコンビのシーンがすごく好きです。)2020年は大学を休学し未経験からエンジニアに挑戦をした一年でした。このアドベントカレンダーを利用して2020年に私が経験したこと、学んだこと、意識したことをまとめます。
エンジニアにチャレンジする前(2020/1時点)のスペック
- プログラミング経験はほぼゼロ。(SQLのみ若干経験あり) - コンピュータやインターネットには比較的馴染みがある。(作る側ではなく、利用する側として。) - 国立大学文系学部に通っている。やったこと
2020年1~2月
- 初めて
rails newをする。- Railsガイドのチュートリアル?をやる。
2~3月
- CAMPFIREのコードを読むようになる。
- 社内でデータ取得用のカラム追加とタスクに日時挿入の処理を追加する。
4~6月
- サポートサービスのコード分離とサービス追加。
- 密結合状態にあったコードをコントローラーレベルで切り分け、コードの分離を行いました。
- またクラウドファンディングとコミュニティではサービスの性質が異なる部分もあるため、コミュニティ独自のサポートサービスを追加しました。
7~8月
- コミュニティページのデザイン変更に合わせて必要になったバックエンドの開発。
- 密結合状態のコードの分離を行いました。
9~11月
- サポートサービスをDBで管理の上、社内の管理画面から追加、編集などをできるようにしました。
- これまでは追加、編集のためにデプロイをエンジニアに依頼する必要がありましたが、エンジニアなしで追加、編集できるようにしました。
11~12月
- 社内で使用する管理画面の機能改修、脱ライブラリ化。
- CAMPFIRE全体で社内で使用する管理画面を脱ライブラリ化し、独自のシステムを構築し移行する流れがあったのでそれに合わせて、主にコミュニティに関する機能の移行を担当しました。(しています。現在も進行中です。)
意識したこと、学び
※個人の意見です
組織をどうしたいか、プロダクトをどうしたいかは一旦考えるのをやめた。
- 組織のことを考えることも大事ですが、自分の実力が不足している状態だったのでとにかく自分のことに集中しました。自分のことに集中することが組織のためくらい割り切っていました。
一通り業務がこなせるようになるまでは、情報もシャットアウト。
- 技術は進歩も早く常に新しい情報が流れてくるのでできるだけキャッチアップしたほうが良いですが、基礎が無いままキャッチアップをしても効率も低いままですし、アウトプットにつながらないのでまずは基礎固めに集中しました。(特に初期は。)
GitHubのPR,issue,Slackの議論やレビューなどはできる限り目を通す。
- どこに学びが転がっているか分からないのでGitHubのPR,issue,Slackの議論やレビューなどは自分に関係ないことでも、意識して積極的に目を通すようにしていました。(そして実装の際は徹底的に
パクり参考にしました。他人の思考や脳は外部ライブラリだと思っています。)分からない、困ったは一定時間たったら詳しい人に質問するべき。
- ありきたりですが、初期はこれが出来ず聞いたら迷惑なのでは、自分で突き詰めて考えた方が良いのではと思っていました。しかし、わからないことは一定時間(1時間など)以上悩んでも大体解決しないのでスパッと聞いたほうが良いと考えるようになりました。聞くことは迷惑でもないですし、迷惑だったとしても他のところで取り返せば良いくらいに開き直るようになりました。
さいごに
足りない部分、課題はありすぎて書ききれない、認識しているものが全てでないので省略します。
最後まで読んでいただきありがとうございます。
質問、ご意見、アドバイスなどあればコメントまでお願いします。
大学は卒業する予定です。おわり。
- 投稿日:2020-12-15T12:51:04+09:00
[Rails]resources, member, collection ルーティング/名前付きパス/URLの覚え方[初学者向け]
はじめに
初めまして。ながえもんと申します。
3ヶ月超かけて、ようやくRailsチュートリアルと言う山を登り切ったので、
- resourcesで生成されるRESTfulな7つのルーティング
- memberメソッド、collectionメソッドで生成される任意ルーティング
- それらのアクション名、URL、名前付きパス
についてまとめました。
resourcesで生成されるパス/URLは非常に便利な一方で、
「あれ、名前付きパスはuser_path?users_path?」「URLは/users? /user? /:id?」と混乱する場面が多く、何となくモヤモヤしたままチュートリアルを一周してしまった方も多いはずです(…よね?)。そんな初学者仲間の皆様の参考になれば幸いです。
[環境]
ruby 2.6.3
Rails 6.0.3.4RESTfulな7つのルーティング(基礎)
リソース名は、(例によって)users リソースとします。
routes.rbresources :usersこのコードを実装すると生成される7つのルーティングは、
アクション順でみると
action名 HTTPreq URL 名前付きパス users#index GET /users users_path users#show GET /users/:id user_path users#new GET /users/:id/new new_user_path users#create POST /users users_path users#edit GET /users/:id/edit edit_user_path users#update PATCH/PUT /users/:id user_path users#destroy DELETE /users/:id user_path こうですね。
(※アクション名は コントローラ名#アクション名 と言う記法に倣っています)7つのアクション名とその順番に関しては、…頑張って覚えましょう。
ちょっとした覚えるコツとしては、
indexとshowはペア。indexはuserの集合(全体)を、showはuserの個体を「表示する機能」としてまとめて覚えましょう。
newとcreateもペア。newテンプレートのフォームに入力した情報を基に、createアクションに繋げてuserインスタンスを生成する事が多いでしょう。
editとupdateもペア。editテンプレートのフォームに入力した情報を基に、updateアクションに繋げてuserインスタンスの情報を更新する事が多いでしょう。
最後にdestroy。これはペアがいない孤独なアクション君です。URLと名前付きパスの関係
次に、URLや名前付きパスの2つの命名ルールを理解しましょう。
【ルール①】 /:id の有無で分類
まず前提として、URLは/users で始まります。ここは複数形で統一です。
その後ろに/:idが有るか無いかで分類します。/:idなしの時→ 名前付きパスは「複数形」のusers_path
/:idありの時→ 名前付きパスは「単数形」のuser_path【ルール②】 URLの後ろに付くオプションは、名前付きパスでは文頭に
ここで言うオプションとは、/users/○○
/users/:id/○○上の○○部分。
このパターンのURLの名前付きパスはルール①も踏まえて
オプション名_users_path または
オプション名_user_path となります。このように、頭にオプション名がくると言うルールで命名されています。
以上の2つのルールが分かれば、
/users/:id/new の名前付きパスは new_user_path同様に
/users/:id/edit の名前付きパスは edit_user_pathとなる事が理解できますね。
(この法則は、後で出てくるmemberやcollectionで生成する任意ルーティングにも当てはまりますので、覚えておきましょう!)
名前付きパスから7つのルーティングを再整理
今度は、今覚えた名前付きパスごとにルーティングを再整理しましょう。
名前付きパス HTTPreq URL 反応するaction users_path GET /users users#index 〃 POST /users users#create new_user_path GET /users/:id/new users#new edit_user_path GET /users/:id/edit users#edit user_path GET /users/:id users#show 〃 PATCH/PUT /users/:id users#update 〃 DELETE /users/:id users#destroy こうなります。
indexとcreateの2つがusers_path。
これはuserの集合(コレクション)に対するアクションと覚えましょう。
index:コレクション全体を表示する
create:コレクション全体に、1個インスタンスを加えるあとの5つはuser_pathがベースになります。
newとeditは先ほど見たようにオプション付きの名前付きパスですね。このように2つの方向から整理すると、だいぶ理解も深まって来たでしょうか。
ちなみに、$ rails routes を実行した際にはこちらの「名前付きパスごと」にルーティングが表示されるので、じっくり眺めてみると更に理解が深まるでしょう。
memberメソッドとcollectionメソッド
次に、usersリソースに任意のルーティングを加えるmemberメソッド、collectionメソッド
この2つのメソッドが生成するURLと名前付きパスについても見ていきましょう。これらは、resources :users にブロックとして渡します(ネストします)。
routes.rbresources :users do member do get :foo, :bar end collection do get :hogehoge end end各メソッドの特性:
memberメソッドは、「user_path」に対してアクションを追加。
collectionメソッドは、「users_path」に対してアクションを追加。メソッドの書き方:
get :foo, :bar の行に注目して下さい。
「HTTPリクエストの型 :任意アクション名, :任意アクション名 」と言う書き方になります。
※アクション名は複数渡すことも可能です。上の例では、users#foo, users#bar, users#hogehoge
と言う3つのアクション(メソッド)が生成されました。各アクションに対応するルーティングは、
『アクション名=URLや名前付きパスのオプション』
と考えると分かりやすいです。
オプションですから、URLでは「後ろ」に、名前付きパスでは「文頭」に来るルールでしたね。生成されたfooアクション、barアクション、hogehogeアクションを含めて
名前付きパスごとにルーティングを整理し直します。今の皆様方であれば、なぜこのような名前付きパスになるのか。
なぜこのようなURLなのか、理解できるはずです。
名前付きパス HTTPreq URL 反応するaction hogehoge_users_path GET /users/hogehoge users#hogehoge foo_user_path GET /users/:id/foo users#foo bar_user_path GET /users/:id/bar users#bar users_path GET /users users#index 〃 POST /users users#create new_user_path GET /users/:id/new users#new edit_user_path GET /users/:id/edit users#edit user_path GET /users/:id users#show 〃 PATCH/PUT /users/:id users#update 〃 DELETE /users/:id users#destroy 以上となります。
最後までお付き合い頂きありがとうございました。
拙い内容だったかと思いますが、
本記事が少しでも名前付きパスで混乱していた方の助けになれば幸いです。またよろしくお願いいたします
ながえもん
- 投稿日:2020-12-15T12:19:10+09:00
【Ruby】pryによるREPL駆動開発
Rubyの強力なREPLであるpryの基本的な使い方と、RubyのREPL駆動開発について記します。
REPL駆動開発とは、REPLを中心とする開発スタイルのことです。一般的な開発フローは、
- プロジェクトのソースコードを編集する
- 実行可能な段階になったら、開発環境にデプロイするなどして動作を確認する
- 上手くいかないところを直す
- 1.に戻る
ですが、2.の段階まで仕上げるのは結構時間がかかり、その間に間違いも犯しやすいです。一方、REPLを開発の中心に据えると、コードを手軽に実行でき、内部の状態も把握しやすいため、コーディングのフィードバックが非常に早く得られます。
pryのインストール
pryをインストールするには、以下のコマンドを実行します。
$ gem install pry pry-docMacやLinuxで、ルート権限でない場合は、以下のコマンドを実行します。
$ sudo gem intall pry pry-docpryは本体。pry-docがあると、Ruby組み込みのクラスやメソッドのドキュメントやソースコードを見ることができます。
インストールできたらpryを起動します。
$ pry [1] pry(main)>少し触ってみると、まずirbとは異なり、シンタックスハイライトや、Tabによる入力補完が効くことが分かります。
コードを書いてファイルに保存する
とりあえず、適当なコードを書いてみます。
[1] pry(main)> def fizzbuzz(num) [1] pry(main)* 1.upto(num) do |n| [1] pry(main)* if n % 3 == 0 [1] pry(main)* print "Fizz\n" [1] pry(main)* elsif n % 5 == 0 [1] pry(main)* print "Buzz\n" [1] pry(main)* elsif n % 15 == 0 [1] pry(main)* print "FizzBuzz\n" [1] pry(main)* else [1] pry(main)* print n.to_s + "\n" [1] pry(main)* end [1] pry(main)* end [1] pry(main)* end => :fizzbuzz [2] pry(main)> fizzbuzz(15) 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 Fizz => 1 [3] pry(main)>3の倍数なら
Fizzに、5の倍数ならBuzzに、15の倍数ならFizzBuzzに変換して、1から15までの整数を出力するコードです。でも、何かおかしいですね。15がFizzBuzzではなく、Fizzになっています。15は3の倍数でもあるから、3つ目の条件n % 15 == 0よりも先に、1つ目の条件n % 3 == 0にマッチしてしまったのが原因です。
fizzbuzzメソッド を書き直さなければいけません。しかし、また13行もタイプするのは面倒です。editコマンドを使えば、指定したメソッドをエディタで編集できます。[3] pry(main)>edit fizzbuzz起動したエディタで、メソッドの内容を以下のように修正し、保存してエディタを閉じれば、pryのセッションに戻ります。
def fizzbuzz(num) 1.upto(num) do |n| if n % 15 == 0 print "FizzBuzz\n" elsif n % 3 == 0 print "Fizz\n" elsif n % 5 == 0 print "Buzz\n" else print n.to_s + "\n" end end end修正した
fizzbuzzメソッドをREPLで実行してみます。[4] pry(main)> fizzbuzz(15) 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz => 1今度は上手くいきました。
ちなみに、
editコマンドで立ち上がるエディタはデフォルトでは、MacやLinuxではnano、Windowsではnotepadだと思いますが、pryの設定ファイルで変更できます。pryの設定ファイルは、~/.pryrc、エディタのプロパティは、Pry.editorです。したがって、たとえばエディタをvimに変更したければ、以下のようにします。$ echo "Pry.editor = 'vim'" >> ~/.pryrc
'vim'の部分は、'emacs'でも'code'でも、PATHの通ったディレクトリにある任意のエディタを指定して下さい。プログラムが完成したので、このメソッドはファイルに保存します。ファイルに保存するには、
save-file {メソッド名} --to {ファイル名}を実行します。たとえば、fizzbuzzメソッドをカレントディレクトリのfizzbuzz.rbに保存したければ、次のようにします。[5] pry(main)>save-file fizzbuzz --to './fizzbuzz.rb'新たにpryを起動する時に、既存のファイルを読み込む場合は、
-rオプションを使います。たとえば、fizzbuzz/rbを読み込んでpryを起動したければ、以下のようにします。$ pry -r "./fizzbuzz.rb"コードをデバッグする
続いて、もう少し複雑なプログラムを書いてみます。バブルソートを書くことにします。隣り合う要素を比較し、昇順になっていなければ順番を入れ替えるアルゴリズムです。
[6] pry(main)> def bubble_sort(nums) [6] pry(main)* right_end = nums.length - 1 [6] pry(main)* while right_end > 0 [6] pry(main)* (0..right_end).each do |i| [6] pry(main)* if nums[i] > nums[i + 1] [6] pry(main)* nums[i], nums[i + 1] = nums[i + 1], nums[i] [6] pry(main)* end [6] pry(main)* end [6] pry(main)* right_end -= 1 [6] pry(main)* end [6] pry(main)* return nums [6] pry(main)* end => :bubble_sort [7] pry(main)> bubble_sort([3, 2, 1]) ArgumentError: comparison of Integer with nil failed from (pry):5:in `>' [8] pry(main)>エラーが出てしまいました。Integerとnilを比較することはできないと言われています。例外がスローされているのは5行目ですから、
iが何れかの値の場合に、num[i]またはnum[i + 1]がnilとなり、それを比較しようとしたのが原因です。エラーの原因を突き止め、修正します。エディタでbubble_sortを編集します。[9] pry(main)>edit bubble_sortエラーが発生する
iの値を確かめるために、以下のコードを挿入します。def bubble_sort(nums) right_end = nums.length - 1 while right_end > 0 (0..right_end).each do |i| binding.pry if nums[i].nil? || nums[i + 1].nil? # <= 挿入したコード if nums[i] > nums[i + 1] nums[i], nums[i + 1] = nums[i + 1], nums[i] end end right_end -= 1 end return nums end
binding.pryがRuby処理系に評価されると、そのコンテクストでpryが起動します。どういうことかというと、こういうことです。[10] pry(main)> bubble_sort([3, 2, 1]) From: /XXXXXXXX/pry-redefined(0x3fdb8dc62bec#bubble_sort):5 Object#bubble_sort: 1: def bubble_sort(nums) 2: right_end = nums.length - 1 3: while right_end > 0 4: (0..right_end).each do |i| => 5: binding.pry if nums[i].nil? || nums[i + 1].nil? 6: if nums[i] > nums[i + 1] 7: nums[i], nums[i + 1] = nums[i + 1], nums[i] 8: end 9: end 10: right_end -= 1 11: end 12: return nums 13: end [1] pry(main)>
bubble_sortの5行目を実行した時点で時が止まっています。この状態では以下のように、このスコープ内の変数などをREPLで評価することができます。[1] pry(main)> i => 2 [2] pry(main)> nums[i].nil? => false [3] pry(main)> nums[i + 1].nil? => true [4] pry(main)> nums.length => 3 [5] pry(main)>エラーが出る直前の変数を調べて判ったことは、
iが2のとき、i + 1が配列の境界外を指しているため、nums[i + 1]がnilになってしまったということです。つまり、numsのi番目とi + 1番目を参照するのだから、「iが0からright_end」までではなく、「i + 1が1からright_endまで、すなわちiは0からright_end - 1まで」の範囲を動かなければいけないということです。したがって、そのように修正します。コードを再実行するには、
exitを実行します。[5] pry(main)> exit元のコンテクストに戻ってきたら、
edit bubble_sortでコードを修正します。def bubble_sort(nums) right_end = nums.length - 1 while right_end > 0 (0..(right_end - 1)).each do |i| # <= right_end を right_end - 1 に # binding.pry を削除 if nums[i] > nums[i + 1] nums[i], nums[i + 1] = nums[i + 1], nums[i] end end right_end -= 1 end return nums end編集内容を保存して、pryのセッションに戻り、修正を確認します。
[10] pry(main)> bubble_sort([3, 2, 1]) => [1, 2, 3] [11] pry(main)> bubble_sort([3, 2]) => [2, 3] [12] pry(main)> bubble_sort([3]) => [3] [13] pry(main)> bubble_sort([]) => [] [14] pry(main)>修正が確認できましたので、ソースコードを保存します。
[15] pry(main)>save-file bubble_sort --to './bubble_sort.rb'上の例では、変数
iの値を参照しただけでしたが、もちろん通常REPLで行うのと同じように、ローカル変数に値を再代入してからコードを再実行することもできます。[1] pry(main)> def add_tax(price) [1] pry(main)* tax = 1.08 [1] pry(main)* binding.pry [1] pry(main)* return (price * tax).to_i [1] pry(main)* end => :add_tax [2] pry(main)> add_tax(1000) From: (pry):3 Object#add_tax: 1: def add_tax(price) 2: tax = 1.08 => 3: binding.pry 4: return (price * tax).to_i 5: end [1] pry(main)> tax = 1.10 => 1.1 [2] pry(main)> exit => 1100 [3] pry(main)>このように、pryを使うことでコードの検証や修正が非常に迅速に行えます。
コンテクストを移動する
コンテクストとは、今どのオブジェクトやメソッドの内部にいるのかという情報です。コンテクストを移動すると、そのオブジェクトのインスタンス変数やメソッドのローカル変数を、参照したり変更したりできます。
例として、FIFOのデータ構造を表すクラスQueueと、そのインスタンスを2つ作成します。
[16] pry(main)* def initialize(initial_list) [16] pry(main)* @queue = initial_list [16] pry(main)* end [16] pry(main)* [16] pry(main)* attr_reader :queue [16] pry(main)* [16] pry(main)* def enqueue(value) [16] pry(main)* @queue.push(value) [16] pry(main)* end [16] pry(main)* [16] pry(main)* def dequeue() [16] pry(main)* @queue.shift [16] pry(main)* end [16] pry(main)* end => :dequeue [17] pry(main)> q1 = Queue.new([]) => #<Thread::Queue:0x00007fa61bf0b4f0 @queue=[]> [18] pry(main)> q2 = Queue.new([]) => #<Thread::Queue:0x00007fa617f08240 @queue=[]> [19] pry(main)> q1.enqueue(1) => [1] [20] pry(main)> q2.enqueue(2) => [2] [21] pry(main)> q2.enqueue(3) => [2, 3] [22] pry(main)>コンテクストを移動するには、
cd {移動先のオブジェクト}を実行します。たとえば、先程作成したq1に移動するには、以下のようにします。[22] pry(main)> cd q1 [23] pry(#<Thread::Queue>):1>
lsで、現在のコンテクストで参照可能なオブジェクトを一覧できます。[23] pry(#<Thread::Queue>):1> ls Thread::Queue#methods: << clear close closed? deq dequeue empty? enq enqueue initial_list length marshal_dump num_waiting pop push shift size self.methods: __pry__ instance variables: @queue locals: _ __ _dir_ _ex_ _file_ _in_ _out_ pry_instance [24] pry(#<Thread::Queue>):1> @queue => [1] [25] pry(#<Thread::Queue>):1>現在のコンテクストから抜けて、元のコンテクストに戻るには、
exitを実行します。[25] pry(#<Thread::Queue>):1> exit => #<Thread::Queue:0x00007fa72c80ca48 @queue=[1]>同様に、
q2のコンテクストも覗いてみると、インスタンス変数@queueの値が異なることが確認できます。[26] pry(main)> cd q2 [27] pry(#<Thread::Queue>):1> ls Thread::Queue#methods: << clear close closed? deq dequeue empty? enq enqueue initial_list length marshal_dump num_waiting pop push shift size self.methods: __pry__ instance variables: @queue locals: _ __ _dir_ _ex_ _file_ _in_ _out_ pry_instance [28] pry(#<Thread::Queue>):1> @queue => [2, 3] [29] pry(#<Thread::Queue>):1> exit => #<Thread::Queue:0x00007fa72429c4d0 @queue=[2, 3]> [30] pry(#<Thread::Queue>):1>また、前セクションに書いたように、
binding.pryを用いれば、実行中のメソッドのローカル変数を確認することもできます。ドキュメントを読み書きする
作成したクラスにはドキュメントを付けておきます。ドキュメントは、所定の形式で記せばpryから閲覧することができますし、他のコマンドラインツールでHTMLなどに自動で変換することもできます。
まずは、先程作成した
Queueクラスをファイルに保存します。カレントディレクトリのqueue.rbに保存するには、以下のようにします。[30] pry(main)> save-file Queue --to './queue.rb' queue.rb successfully saved [31] pry(main)>
edit {ファイル名}で、保存したソースコードを編集できます。今回は、RubyのメジャーなドキュメンテーションスタイルであるYARDに従ってドキュメントを書きます。[31] pry(main)> edit './queue.rb'queue.rb# FIFOのデータ構造 class Queue def initialize(initial_list) @queue = initial_list end # @return [Array] 現在のキュー attr_reader :queue # キューに値を格納する # @param value [*object] キューに格納するオブジェクト。 # @return [Array] 値を格納した後のキュー def enqueue(value) @queue.push(value) end # キューから値を取り出す # @return [object | nil] 最も古い要素。要素が一つもない場合はnil。 def dequeue() @queue.shift end end保存してエディタを閉じると、pryのセッションに戻り、編集後のコードが自動で読み込まれます。
ドキュメントを閲覧するには、
show-source {クラス/メソッド名} -dを実行します。show-sourceには$というエイリアスもあります。[32] pry(main)> $ Queue -d From: queue.rb:1 Class name: Thread::Queue Number of lines: 23 FIFOのデータ構造 # ソースコード。長いので略。 [33] pry(main)>[33] pry(main)> $ Queue#queue -d From: queue.rb:7: Owner: Thread::Queue Visibility: public Signature: queue() Number of lines: 3 return [Array] 現在のキュー attr_reader :queue [34] pry(main)>[34] pry(main)> $ Queue#enqueue -d From: queue.rb:10: Owner: Thread::Queue Visibility: public Signature: enqueue(value) Number of lines: 7 キューに値を格納する param value [*object] キューに格納するオブジェクト。 return [Array] 値を格納した後のキュー def enqueue(value) @queue.push(value) end [35] pry(main)>[35] pry(main)> $ Queue#dequeue -d From: queue.rb:17: Owner: Thread::Queue Visibility: public Signature: dequeue() Number of lines: 6 キューから値を取り出す return [object | nil] 最も古い要素。要素が一つもない場合はnil。 def dequeue() @queue.shift end [36] pry(main)>その他
binding.pryで停止した後にステップ実行するにはpry-byebugが、pryをRailsで使うにはpry-railsがあります。
- 投稿日:2020-12-15T11:58:20+09:00
【個人アプリ作成】情報管理アプリ制作14日目
また、時間が空いてしまいました。。継続性の難しさを痛感します。
今日までやってきたことをまとめたいと思います。実装できてきたこと
・マイページのレイアウト修正
・顧客データの要件定義
・顧客データ詳細画面
・ログイン画面の修正
・情報の取得
・プルダウンの実装マイページ
少しヘッダーを整理してレイアウトをすっきりさせました。
今後はヘッダーのユーザーのところにJSで装飾を施したり、検索機能の実装を行う予定です。今のところ、顧客情報は社名だけといった感じですが、かなり寂しいので「何の情報を開示できると有益なのか」をもう少し要件定義した方が良さそうです。
実装はruby on railsで、Seeds.rbにデータを入れて取得させています。顧客データ詳細画面
基本情報の部分は一通り必要な情報を印字させて、先方関係者やグループ会社、トラブル・クレームは履歴データで持たせたいので、テーブルを分けアソシエーションで対応したいと思います。
まだ、顧客データの登録機能や、情報編集機能には着手できていないのでこれから実装していきたいと思います。
これまでやってきた感想
かなり進捗が悪く、原因を追求してみました。
・本業の仕事がかなり忙しい(いい意味で)。
→夜遅くなるのが日常茶飯事なので時間調整をして20時や21時にはこちらに着手できるようにする
・ある意味、色々やりすぎていた。特に実績もないのにPFを頑張って作ろうとしていたのはまずかった。今はアプリ開発とQiita、Githubに力を入れる。協業は随時進める。
・プライベートの慌ただしさ。
こればっかりは結婚だの同棲だの異動だので不可避なのでありきで考える。これまで考えられていなかった。想定される工数の見積もりが甘いのとあらためて仕事は影響の出ない範囲で調整をかけていくようにしたい。









