- 投稿日:2019-07-07T23:14:49+09:00
【Ruby】モジュールのミックスインについて
はじめに
今回はRubyのモジュールについてまとめました!
モジュールには様々な用途があります。
主に以下のような用途です。・クラスにインスタンスメソッドを追加する(ミックスイン)
・クラスにクラスメソッドを追加する(ミックスイン)
・クラス名などの重複を防ぐためにネームスペース(名前空間)を作成する
・ミックスインせずにモジュール単体としてメソッドを定義
など。最初は何を言ってるか全くわけがわからないと思います。
全ての機能をここで説明するととても長文になってしまうので、今回は上記のうち上から2つ(ミックスイン)に関して説明します。モジュールとは
ここで説明する”ミックスインの機能を持つモジュール”とは、そもそも何でしょうか。
説明する前に、まずクラスに関してざっとおさらいしておきます。
継承関係にあるクラスは、親クラスのメソッドを子クラスでも呼び出すことが出来ました。
以下がその例です。test.rbclass User def name(name) conversation = "hello,#{name}" end end class Person < User end person = Person.new person.name("たろう") #出力結果 ==>"hello,たろう"親クラスがUserで、子クラスがPersonです。
class Person < UserでUserクラスを継承しています。
その為Personクラスのインスタンスに対して、Userクラスのnameメソッドを呼んだ際に、
ちゃんと"hello,たろう"と返っていることから親クラスのメソッドが使えていることがわかります。このように、継承していれば、親クラスのメソッドは使用できるのですが、逆に
継承していなければ他クラスのメソッドは使用できないとも言えます。継承関係にはしたくないけれども、他クラスと全く同じメソッドを使いたい!と言う場合。
継承できないから同じ内容を毎回毎回クラスごとに書かなくてはいけないんでしょうか?
確かにそれでも良いかもしれませんが、コードの量が増えますし、
理想的なコーディングとして重複した処理は避けるべきです。こんな時に役立つのがまさに!モジュールのミックスイン機能です!!
簡単に説明すると、共通のメソッドをモジュール内に定義しておき、
それをそれぞれのクラスで呼び出して使用することができる機能です。
このモジュールを呼び出すことをミックスインと呼びます。モジュールのミックスイン
ではさっそくモジュールのミックスインに関して解説します。
とりあえずモジュールの書き方に関して先に見ておいてください。以下のようになります!test.rbmodule モジュール名 #モジュールの内容(メソッドやクラスなど) end書き方はいたって簡単ですね。ではさっそく使い方を解説します!
include
まずはインスタンスメソッドをモジュール内に定義して継承関係ではない2つのクラスにそれぞれ適用するケースです。
module名Helloを最初に定義し、(#---①)
moduleを読み込むためにそれぞれのクラスでincludeします。
include モジュール名と書くだけで読み込むことができます。 (#---②)
Userクラス内のnameメソッド内でHelloモジュールのhelloメソッドを呼び出しています。(#---③)
同じくDogクラス内のnameメソッド内でもHelloモジュールのhelloメソッドを呼び出しています。(#---④)
その後、それぞれのクラスのインスタンスを作成し、nameメソッドを呼び出すと、ちゃんとnameメソッド内のhelloメソッドが呼び出されていることが確認できます。(#---⑤)test.rbmodule Hello #---① def hello(name) puts "hello,#{name}" end end class User include Hello #---② def name hello("たろう") #---③ end end class Dog include Hello #---② def name hello("ぽち") #---④ end end user = User.new user.name # 出力結果 ==>"hello,たろう" #--⑤ dog = Dog.new dog.name # 出力結果 ==>"hello,ぽち" #---⑤※モジュールもクラスも今回同じファイル内に書いてしまっていますが、もちろん別々にファイルを分けて頂いてOKです!
このように、モジュールで共通のメソッドをひとまとめにすることで、異なるクラスでもモジュール内のインスタンスメソッドが使用できます。extend
includeはインスタンスメソッドをモジュールから呼び出すために使用しましたが、クラスメソッドの呼び出しにはextendを使用します。
モジュールの定義方法はincludeの時と変わりません。test.rbmodule Hello def hello(name) puts "hello,#{name}" end end class User extend Hello def self.name hello("たろう") end end User.name # 出力結果 ==>"hello,たろう"つまり、モジュール内のメソッドをインスタンスメソッドとして使用するのか、クラスメソッドとして使用するのかでincludeとexcludeどちらを使うかが決まると言うことですね。
モジュール内の記述は変わらないので、同じモジュール内のメソッドでもクラスによってはインスタンスメソッドあるいはクラスメソッドになりうると言うことですね!とても便利です!さいごに
今回はモジュールの機能のうち、ミックスインに関して説明させて頂きました!
モジュールには他にも機能があるので、ぜひ気になる方は調べてみてください【参考】
書籍 プロを目指すためのRuby入門(伊藤 淳一 著)
- 投稿日:2019-07-07T23:01:43+09:00
Rails の migration の仕組みを理解する
普段 Rails で開発をしていて、migration の仕組みって、日付で migration ファイルが作成されて管理されてるってイメージで確認したことなかったので、動かしながら確認してみた。
とりあえず
rails newしてアプリを立ち上げるところまではやって DB を作るところからいじってみた。
DBは PostgreSQL を使っています。初期状態
rails➜ rails_migration git:(master) bin/rails db:create Created database 'rails_migration_development'この時点ではまだテーブルはない。
postgresqlrails_migration_development=# \d Did not find any relations.migration を実行して中身を確認
とりあえず model を作成
rails➜ rails_migration git:(master) bin/rails g model Article title body invoke active_record create db/migrate/20190707095850_create_articles.rb create app/models/article.rb ➜ rails_migration git:(master) ✗ bin/rails db:migrate == 20190707095850 CreateArticles: migrating =================================== -- create_table(:articles) -> 0.0129s == 20190707095850 CreateArticles: migrated (0.0131s) ==========================すると 4 つテーブルができる。今回見ていくのは
schema_migrations。postgresqlrails_migration_development=# \d List of relations Schema | Name | Type | Owner --------+----------------------+----------+-------- public | ar_internal_metadata | table | ikepon public | articles | table | ikepon public | articles_id_seq | sequence | ikepon public | schema_migrations | table | ikeponこの
schema_migrationsにはversionってカラムが一つだけある。postgresqlrails_migration_development=# \d schema_migrations Table "public.schema_migrations" Column | Type | Collation | Nullable | Default ---------+-------------------+-----------+----------+--------- version | character varying | | not null | Indexes: "schema_migrations_pkey" PRIMARY KEY, btree (version)中の値を見ると、さっきの migration ファイルの日付の情報が入ってる。
(db/migrate/20190707095850_create_articles.rb)postgresqlrails_migration_development=# SELECT * FROM schema_migrations; version ---------------- 20190707095850 (1 row)rollback してみる
これを rollback すると値も消える。
rails➜ rails_migration git:(master) ✗ bin/rails db:rollback == 20190707095850 CreateArticles: reverting =================================== -- drop_table(:articles) -> 0.0245s == 20190707095850 CreateArticles: reverted (0.0287s) ==========================postgresqlrails_migration_development=# SELECT * FROM schema_migrations; version --------- (0 rows)もう一つテーブルを追加
テーブルを2つにして確認してみる
rails➜ rails_migration git:(master) ✗ bin/rails g model User name email invoke active_record create db/migrate/20190707101406_create_users.rb create app/models/user.rb ➜ rails_migration git:(master) ✗ bin/rails db:migrate == 20190707095850 CreateArticles: migrating =================================== -- create_table(:articles) -> 0.0129s == 20190707095850 CreateArticles: migrated (0.0131s) ========================== == 20190707101406 CreateUsers: migrating ====================================== -- create_table(:users) -> 0.0054s == 20190707101406 CreateUsers: migrated (0.0056s) =============================すると PostgreSQL には 2つの
versionができる。
(db/migrate/20190707095850_create_articles.rb)
(db/migrate/20190707101406_create_users.rb)postgresqlrails_migration_development=# SELECT * FROM schema_migrations; version ---------------- 20190707095850 20190707101406 (2 rows)日付が前の migration を実行してみる
rollback して user のファイル名を
db/migrate/20190707101406_create_users.rbから
db/migrate/20190706101406_create_users.rbに修正して migration を実行する。すると変更した日付の値が入る。
postgresqlrails_migration_development=# SELECT * FROM schema_migrations; version ---------------- 20190707095850 20190706101406 (2 rows)
versionにない日付の migration は実行されないと判断されて、実行される。これを
bin/rails db:rollbackすると新しいものから消えていくpostgresqlrails_migration_development=# SELECT * FROM schema_migrations; version ---------------- 20190706101406 (1 row)同じファイルで日付を変えてみる
bin/rails db:migrateでテーブルを戻しておいて、 user のファイルをコピーして
db/migrate/20190707101406_create_users.rbと一番新しい日付に修正後 migration を実行する。rails➜ rails_migration git:(master) ✗ bin/rails db:migrate rails aborted! ActiveRecord::DuplicateMigrationNameError: Multiple migrations have the name CreateUsers.migration の名前を変えてみる
Multiple migrations have the name CreateUsers.同じ名前の migration があるって言われてるので、 migration の名前をclass CreateUser < ActiveRecord::Migration[5.2](Users の s を削除)に変えて実行してみる。するとエラーが変わった。
rails➜ rails_migration git:(master) ✗ bin/rails db:migrate == 20190707101406 CreateUser: migrating ======================================= -- create_table(:users) rails aborted! StandardError: An error has occurred, this and all later migrations canceled: PG::DuplicateTable: ERROR: relation "users" already existsさらに古い方(db/migrate/20190706101406_create_users.rb) を削除して再実行してみる。
rails➜ rails_migration git:(master) ✗ bin/rails db:migrate == 20190707101406 CreateUser: migrating ====================================== -- create_table(:users) rails aborted! StandardError: An error has occurred, this and all later migrations canceled: PG::DuplicateTable: ERROR: relation "users" already existsまとめ
- migration ファイルは日付で判断して、
schema_migrationsにないものを実行するようになっている。- migration 内では同じ名前の class は使えない。
class CreateUser < ActiveRecord::Migration[5.2] # ~~~~~~~~~~ この部分改めて手を動かして、テーブルのデータを確認しながら見ていくとちゃんと理解できた。
参考
- 投稿日:2019-07-07T23:00:57+09:00
rake db:migrateができない件について
rake db:migrateができない・・・!
経緯
deviseを使用し、ログイン機能を実装中
マイグレーションファイルに、追加のカラムを記入後
rake db:migrateをターミナルで行うとエラーが発生する。これまでの流れ
- deviseをGemfileに追記し、 bundle installを実行する
- rails g devise:installを実行する
- rails g devise userを実行し、テーブルを作成する
- マイグレーションファイルに追加したいカラムを記入する
![]()
- rake db:migrateを実行する
- 下記エラーが発生する
$ rake db:migrate rake aborted! ActiveRecord::NoDatabaseError: Unknown database 'アプリ名_development' Caused by: Mysql2::Error: Unknown database 'アプリ名_development' Tasks: TOP => db:migrate (See full trace by running task with --trace)データベースがないと怒られてしまった。
7. 下記を参考に、rails db:createを実行し、データベースを作成する
https://rails-ambassador.herokuapp.com/debugs/NoDatabaseError
https://blog.f-arts.work/archives/600$ rails db:create Created database 'アプリ名_development' Created database 'アプリ名_test'8.今度こそ!と、rake db:migrateを実行するとエラー発生
== 20190707130850 DeviseCreateUsers: migrating ================================ -- create_table(:users) rake aborted! StandardError: An error has occurred, all later migrations canceled: Mysql2::Error: Cannot add foreign key constraint: CREATE TABLE `users` (`id` int AUTO_INCREMENT PRIMARY KEY, `nickname` varchar(255) NOT NULL, `email` varchar(255) DEFAULT '' NOT NULL, `encrypted_password` varchar(255) DEFAULT '' NOT NULL, `user_image_id_id` int NOT NULL, `user_detail_id_id` int NOT NULL, `reset_password_token` varchar(255), `reset_password_sent_at` datetime, `remember_created_at` datetime, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, INDEX `index_users_on_user_image_id_id` (`user_image_id_id`), INDEX `index_users_on_user_detail_id_id` (`user_detail_id_id`), CONSTRAINT `fk_rails_938ec9574d` FOREIGN KEY (`user_image_id_id`) REFERENCES `user_image_ids` (`id`) , CONSTRAINT `fk_rails_ce7e2c90f2` FOREIGN KEY (`user_detail_id_id`) REFERENCES `user_detail_ids` (`id`) ) ENGINE=InnoDB /Users/自分/projects/アプリ名/db/migrate/20190707130850_devise_create_users.rb:5:in `change' Caused by: ActiveRecord::StatementInvalid: Mysql2::Error: Cannot add foreign key constraint: CREATE TABLE `users` (`id` int AUTO_INCREMENT PRIMARY KEY, `nickname` varchar(255) NOT NULL, `email` varchar(255) DEFAULT '' NOT NULL, `encrypted_password` varchar(255) DEFAULT '' NOT NULL, `user_image_id_id` int NOT NULL, `user_detail_id_id` int NOT NULL, `reset_password_token` varchar(255), `reset_password_sent_at` datetime, `remember_created_at` datetime, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, INDEX `index_users_on_user_image_id_id` (`user_image_id_id`), INDEX `index_users_on_user_detail_id_id` (`user_detail_id_id`), CONSTRAINT `fk_rails_938ec9574d` FOREIGN KEY (`user_image_id_id`) REFERENCES `user_image_ids` (`id`) , CONSTRAINT `fk_rails_ce7e2c90f2` FOREIGN KEY (`user_detail_id_id`) REFERENCES `user_detail_ids` (`id`) ) ENGINE=InnoDB /Users/自分/projects/アプリ名/db/migrate/20190707130850_devise_create_users.rb:5:in `change' Caused by: Mysql2::Error: Cannot add foreign key constraint /Users/自分/projects/アプリ名/db/migrate/20190707130850_devise_create_users.rb:5:in `change' Tasks: TOP => db:migrate (See full trace by running task with --trace)外部キー設定しているけど、参照先ないよ!って怒られました・・・そりゃあそうだ・・・
外部キー設定しているカラムは、一旦削除してrake db:migrateを実行したら、無事作成できました。== 20190707130850 DeviseCreateUsers: migrating ================================ -- create_table(:users) -> 0.0207s -- add_index(:users, :email, {:unique=>true}) -> 0.0219s -- add_index(:users, :reset_password_token, {:unique=>true}) -> 0.0118s == 20190707130850 DeviseCreateUsers: migrated (0.0547s) =======================http://localhost:3000/users/sign_in
を開くととりあえず、最低限のログインページはOK!
参考
外部キー制約の書き方
https://qiita.com/ryouzi/items/2682e7e8a86fd2b1ae47Rails「ユーザーのモデルを作成する」
https://qiita.com/macotok/items/a17a4b0d22db4e885678
- 投稿日:2019-07-07T20:16:57+09:00
HTMLでテーブルのヘッダー(上部と左部)を固定してスクロールさせる実装
はじめに
テーブルのヘッダー(上部と左部)を固定してスクロールさせる実装でプラグインなどを比較したため、備忘録を含めまとめたいと思います。
position:stickの実装方法
まず、大前提として本来であれば
position:stickyを使用することが望ましいかと思いました。
こちらの方の記事が大変わかりやすいです。
https://qiita.com/s0tter/items/14fb4ec2600828a21a22
CSSのみで完結するため上記手法が望ましいかと考えておりますが、2019年7月7日時点でIEに対応しておりません。IEのユーザーもアプリを活用されることが予想されるため、採用を見送りさせていただきました。
https://caniuse.com/#search=position%3A%20stickyposition:sticky対応状況
可能であれば、
position:stickyを使用したいのですが、今回はIEの関係で採用が難しいため、以下のプラグイン等を比較いたしました。プラグインを比較してみました。
当方Macしかパソコンを持ち合わせていないため、virtualBoxでIEを立ち上げて
動作状況を確認してみました。・DataTable
Github:https://github.com/DataTables/DataTables
https://datatables.net/extensions/fixedcolumns/examples/initialisation/two_columns.html
最終更新:2018年6月23日IEでの動作状況
・fixedTblHdrLftCol
Github:https://github.com/nkmrshn/fixedTblHdrLftCol
http://nkmrshn.com/fixedTblHdrLftCol/samples/sample_3_sync.html
最終更新:2014年6月13日IEでの動作状況
・FixwdMidashi
http://hp.vector.co.jp/authors/VA056612/fixed_midashi/manual/index.html
最終更新:2018年12月3日IEでの動作状況
・Grid
(まだまとめている最中です。)
IEで最も綺麗に動作しているのが、FixwdMidashiだったように思えます。
他に良い方法などございましたら、一声かけていただけますと幸いです。少しずつ追加させていただきます。
ご参考になれば幸いです。
- 投稿日:2019-07-07T18:54:10+09:00
Don't know how to build task 'YOUR CREATE TASK'
ファイル拡張子を
rakeにしましょう!rbにしてた
- 投稿日:2019-07-07T18:35:59+09:00
Herokuの本番環境でRansack検索した時にエラー「PG::SyntaxError: ERROR: syntax error at or near "DISTINCT"」
問題:Herokuの本番環境でRansack検索した時にエラー「PG::SyntaxError: ERROR: syntax error at or near "DISTINCT"」
ローカルでは問題ないのに本番環境で検索すると
「We're sorry, but something went wrong.」のエラーが出てしまったログを見てみるとこんな感じ
ActionView::Template::Error (PG::SyntaxError: ERROR: syntax error at or near "DISTINCT" 2019-07-07T08:13:50.904602+00:00 app[web.1]: LINE 1: SELECT DISTINCT DISTINCT ON (latitude)* FROM "events" WHERE ... 2019-07-07T08:13:50.904604+00:00 app[web.1]: ^ 2019-07-07T08:13:50.904606+00:00 app[web.1]: : SELECT DISTINCT DISTINCT ON (latitude)* FROM "events" WHERE "events"."prefecture_id" = $1 AND "events"."play_type" IN (1) ORDER BY latitude,id asc):distinct付近に何かしら問題があるぽい
controllerはこんな感じclass PrefecturesController < ApplicationController def show @prefecture = Prefecture.find(params[:id]) @events = @prefecture.events @q = Event.ransack if Rails.env == 'production' @venues = @events.select("DISTINCT ON (latitude)*").order("latitude,id asc") else @venues = @events.group(:latitude).order("id asc") end gon.venues = @venues duplicate = @events.group(:latitude).having('count(*)>=2').pluck(:latitude) gon.events = Event.where(latitude: duplicate).offset(1) end def search @prefecture = Prefecture.find(params[:id]) @q = @prefecture.events.ransack(search_params) @events = @q.result(distinct: true) if Rails.env == 'production' @venues = @events.select("DISTINCT ON (latitude)*").order("latitude,id asc") else @venues = @events.group(:latitude).order("id asc") end gon.venues = @venues duplicate = @events.group(:latitude).having('count(*)>=2').pluck(:latitude) gon.events = @events.where(latitude: duplicate).offset(1) end private def search_params params.require(:q).permit(:title_cont,{:dayw_in => []},{:level_in => []},{:play_type_in => []},:status_eq) end endshowとsearchアクションはご覧の通り殆ど一緒です
違うのは検索クエリ?の@q変数が関わっているところだけ
その中でdistinct使ってるのはこの一文のみ@events = @q.result(distinct: true)もしかしたらこいつのせいかもと思って消してみたら...
動いたー(distinct: true)の意味を調べてみる
そもそも(distinct: true)をオプションでつけると何が変わるのか確認
重複したレコードを除外してくれらしい
SQLはこんな感じSELECT DISTINCT `events`.* FROM `events` WHERE `events`.`prefecture_id` = 22 AND `events`.`play_type` IN (1) SELECT `events`.* FROM `events` WHERE `events`.`prefecture_id` = 22 AND `events`.`play_type` IN (1)仮説
@events = @q.result(distinct: true)この一文で全てのカラムが重複しないレコードを取り出しているのに
@venues = @events.select("DISTINCT ON (latitude)*").order("latitude,id asc")ここでまたlatitudeが一意のデータを抽出しようとしてるからエラーになったんだと思う
- 投稿日:2019-07-07T17:49:21+09:00
プログラミング初学者が独学でポートフォリオを完成させるまでの話~ポートフォリオ制作の流れ~
自己紹介
2019年3月初旬にエンジニアYouTuberの動画を見てHTML/CSSからProgateでプログラミングの勉強を始め、2019年6月末にRuby on Railsのポートフォリオを完成させてデプロイしました。2019年4月からは大学院の修士課程(農学)にいて、21卒でエンジニアを目指しています。プログラミングの勉強を始めた2019年3月時点ではプログラミングは完全未経験でした。
本記事では独学での勉強の流れや、はまりやすいことを書いているのでプログラミング学習の計画を立てる際の参考にしていただければと思います。また、ポートフォリオ制作の際にできなかったことも正直に書いているので、本記事を読むことで「こいつもこれができなかったけれど、こいつが最後まで独学できたんだから自分だってできる」というような感じで初学者の励みになればと思います。独学の大まかな流れ(筆者の場合)
- 2019年3月にエンジニアYouTuberの動画を見てエンジニアが稼げることを知り、Progateを始める
- プログラミングスクールに行こうとしていたが、3月下旬、あるスクールの担当者の提案から独学でポートフォリオ制作を頑張る方向に切り替える
- 4月中旬にポートフォリオの制作を開始し、ゴールデンウィーク期間中は連日ポートフォリオ制作を進める
- ゴールデンウィークが明けてからは忙しく、なかなかポートフォリオ制作ができない時期が続く
- 6月に入って忙しさがひと段落し、ポートフォリオ制作を再開。6月末にポートフォリオが完成し、デプロイ
各時期にしたことや、そのとき感じたこと
1. 2019年3月にエンジニアYouTuberの動画を見てエンジニアが稼げることを知り、Progateを始める
見ての通り、筆者はかなりミーハーな理由でプログラミングを始めています(笑)。今はエンジニア不足のため、きちんと戦力になるのであれば、そのような理由でプログラミングの勉強を始めてもよいのではないでしょうか。
プログラミングの勉強を始める前に、プログラミング言語の選択です。いろんな人の記事や動画を見て、どの言語にするかを総合的に判断しましょう。言語の人気などの情勢はすぐに変わるものなので、自分でしっかり調べて最新の情勢や行きたい企業をもとに判断するとよいと思います。筆者はRubyを選びました。
Progateの勉強法については、筆者はまず学習コースを一通りやった後、コードを残した状態で復習していました。そして、復習する際には書いたコードを説明とともに自分専用のライブラリとしてPCに保存していました。コードの意味を一度に理解しようとはせず、復習でコードを保存する際にある程度理解することを目標にしていました。エンジニアになりたいと思ってProgateをするのであれば、Progateには課金しましょう。きちんと集中して学習すれば課金は1000円で済みます。筆者は忙しさを言い訳にダラダラ学習していたので3000円課金しましたが(笑)
また、筆者は2019年3月までは大学4年生であり、2019年4月からは修士に進学することが決まっていました。しかし、エンジニアYouTuberの動画を見始めるようになってからは、大学院を中退してプログラミングスクールで勉強し、そのまま働こうと思うようになっていました。今となってはこの選択は捨て身の非常に危険な選択であることが分かりましたが、世間知らずの筆者は当時、この選択の危うさが分かっていませんでした。そんな筆者の間違った方向性を修正してくれた、ある出会いがあったのです。2. プログラミングスクールに行こうとしていたが、3月下旬、あるスクールの担当者の提案から独学でポートフォリオ制作を頑張る方向に切り替える
筆者は大学院を中退してそのまま働こうと思っていくつかのプログラミングスクールに話を聞きに行っていました。しかし、DMM WEB CAMPさんに説明を受けに行ったところ、個人面談の担当者の方から「新卒採用まで待った方がいい。修士の間にポートフォリオを作りなさい。新卒採用でポートフォリオを作ってくる人はほとんどいないから、そこでポートフォリオがあると優位になる。今後のことを考えると、新卒カードは絶対に捨てない方がいい。」という内容のことを言われました。このアドバイスは自分たちのプログラミングスクールに入らない方が良いと言っている内容であり、ビジネスチャンスを自分から断っていることになります。短期的な自分たちの利益を度外視しても筆者のためになるアドバイスをしていただき、本当に感謝しています。筆者はこのアドバイスのおかげで極端な選択をやめて現実的な方向に行けました。プログラミングスクールを選ぶ際には、一度DMM WEB CAMPさんにお話を伺いに行くと親身にアドバイスしていただけると思いますので、おすすめです。
3. 4月中旬にポートフォリオの制作を開始し、ゴールデンウィーク期間中は連日ポートフォリオ制作を進める
かくして筆者は独学でポートフォリオを作る方針に切り替えました。Progateでの勉強が一通り終わったので、ポートフォリオの制作を開始しました。とは言っても、ライブラリ作りがまだ終わっていなかったのでProgateへの課金は継続していました。
Progateは学習編と道場編がありますが、Railsコースの道場編は面倒臭くなったのでしませんでした。筆者の場合はライブラリさえ作れれば道場編をしなくてもポートフォリオを作れたので、道場編をしなくてもポートフォリオは作れます。しかし、実際にポートフォリオを作る段階になるとProgateの道場編以上に多くのエラーが出てくるので、ポートフォリオ制作で極力詰まりたくない人は道場編もしっかりしておくのが無難かもしれません。
ポートフォリオ制作の前にRubyとRailsの環境構築をしてRailsの初期画面を自分のPC上に表示させますが、これがかなり大変でした。Aという作業をするためにはBとCをインストールしないといけないが、BをインストールするためにはDとEとFが必要、といった感じで芋づる式に必要なものがたくさん出てくるため、自分が何をしているのか途中で分からなくなります。環境構築で挫折する人が一定数いるというのも納得で、環境構築が自分できちんとできた人は独学でポートフォリオを完成させることができるだけのリサーチ力と忍耐力があると思います。(あとはやる気が続くかどうか次第です)
ポートフォリオ制作の最初はProgateのコードのコピペです。筆者はProgateでRailsの道場コースをしていなかったため、ポートフォリオを作りながらコードの意味を理解していきました。そして意外だったのが、Progateのコードを完全にコピペしたコードでもエラーが出ることです。ググったところ、Progateのコードが必ずしもベストなコードではないことが分かったり、Rubyのバグが原因(Rubyのバージョンを切り替えると解決しました)だったりしました。
ゴールデンウィークの期間中に一気にポートフォリオ制作を進め、Progateのコピペ作業はゴールデンウィーク期間中に終わりました。4. ゴールデンウィークが明けてからは忙しく、なかなかポートフォリオ制作ができない時期が続く
エンジニアYouTuberやプログラミングスクールの人たちは会社に入るまでの学習期間を3か月程度と言っている人が多かったので、3月初旬の勉強開始から約3か月後になる5月末にポートフォリオを完成させようとしていました。しかし、連休が明けてからは大学院の方で課題の提出ラッシュがあったために忙しく、ポートフォリオの制作時間がなかなか取れない時期が続いていました。ポートフォリオ制作のモチベが下がることもありましたが、そういう時はエンジニアYouTuberの動画を見返してモチベを上げていました。エンジニアYouTuberたちは極端なことを言っていることもあるので(お前が言うなというツッコミはさておき)内容を安易に鵜呑みにしない方がいいと思いますが、プログラミングのモチベを維持するためには結構いいものです。
5. 6月に入って忙しさがひと段落し、ポートフォリオ制作を再開。6月末にポートフォリオが完成し、デプロイ
6月に入ると大学院での課題の提出ラッシュが終わって時間に余裕ができ、週末を中心にポートフォリオ作りをしていました。この頃になるとProgateで勉強していない機能を実装しようと頑張っていました。筆者の場合は、勝又健太さんがポートフォリオに入れた方がよい機能一覧として挙げている機能を一通り実装し、ポートフォリオの内容から筆者が追加したいと思った機能も追加しようとしていました。勝又さんは欲しい機能を10個ほど挙げていますが、Progateの内容ですでに実装できている機能が割と多いので、実装方法を最初からググらなければいけない機能は意外と少ないです。そのため、一部難易度が高い機能もありますがこれからポートフォリオを作る皆さんも勝又さんの挙げている機能一覧に挑戦してみてはどうでしょうか。
Progateでは扱っていない機能を実装する際には、当然のことながら実装方法をググります。すると、大抵の場合はすぐに実装方法を丁寧に書いてくれているページが見つかるのですが、大変なのはここからです。実装手順が5つぐらい書いてある場合、99%の確率で途中どこかでエラーが出ます。そのエラーの対処に時間がかかることも多く、ここは踏ん張りどころですね。
筆者の場合はほとんどの機能は実装できたのですが、テスト機能においてテストコード内でログインする方法が分からず、最終的にテスト機能の実装をあきらめました。このように実装をあきらめる機能が出てくるのは本来あまり良くないことですが、筆者は最後まで独学でポートフォリオを作ることを優先したので、挫折しないためにこのようにしました。独学だとこのような妥協が必要になることもあるかもしれません。また、実装をあきらめる機能があってもProgateにはない機能がポートフォリオにある方がいいため、ポートフォリオの設計段階ではポートフォリオに入れたい機能を多めに挙げておくといいかもしれません。
最終的にポートフォリオを完成させることができたらGitHubで公開します。実はこの記事を書いている2019年7月時点ではProgateのGitコースは無料で受講できるので、Progateをすでに解約していたとしてもGitHubの使い方をProgateで勉強できます。Gitの基礎はProgateで勉強し、分からないことがあったらググるというのが一番簡単だと思います。
筆者は最終的にはHerokuというサービスでポートフォリオをデプロイ(インターネット上で公開すること)しました。Railsアプリの場合、デプロイの前にマイグレーションファイルをカラムの過不足がないように一度整理しておくことをおすすめします。また、コードは合っているのにHerokuページ上だけエラーが出る場合はデータカラムがないことが原因かもしれないということを覚えておくといいかもしれません。
ポートフォリオを完成できた方は、おめでとうございます。ポートフォリオを持って企業の面接を受けに行きましょう。幸運を祈ります!最後に
筆者の場合は学業で忙しかった時期もあったことから勉強開始からポートフォリオ完成まで約4か月という少し長い期間がかかり、途中モチベーションが下がることもありました。仕事が忙しいなどといった理由で学習が長期化するのは仕方ないことだと思いますが、そうなった場合はモチベーション低下との戦いになります。モチベーションが下がったときは自分がプログラミングの勉強を始めたきっかけになったものを見返すといいかもしれません。ただ、ポートフォリオ完成までできた人は比較的短期間で勉強している人が多いので、短期集中の方が挫折しにくいことを覚えておくといいかもしれません。
長い記事になってしまいましたが、最後まで読んでくださり、ありがとうございました。本記事が参考になりましたら、いいねを押していただけると幸いです。
それではこれからプログラミングを始めるみなさん、まずは独学でポートフォリオを作ることに挑戦してみましょう。幸運を祈ります!
- 投稿日:2019-07-07T17:25:08+09:00
rubyのブロック処理における配列関連のメソッドメモ
チェリー本を参考にした。このあたりよく忘れるのでメモっておく
each
普通のfor文と同じ役割
num = [1,2,3,4] sum = 0 num.each do |n| sum += n end print sum #=> 10each_with_index
eachについて配列のインデックスも扱うことができる
num = [1,2,3,4] num.each_with_index do |i,n| p "#{i}:#{n}" end #出力は以下の通り #0:1 #1:2 #2:3 #3:4map
ある配列の要素に対する処理結果を別配列に返す
num = [1,2,3,4] new_num = [] new_num = num.map do |n| n * 10 end print new_num #=> [10,20,30,40]エイリアスメソッドであるcollectも同様の使い方が可能
select/find_all
ある配列の要素について、条件に一致しているものを返す
num = [1,2,3,4] new_num = num.select do |n| n.odd? #nが奇数のものをnew_numへ格納 end print new_num #=> [1,3]エイリアスメソッドであるfind_allも同様の使い方が可能
reject
ある配列の要素について、条件に一致していないものを返す
要するにselect/find_allの反対num = [1,2,3,4] new_num = num.select do |n| n.odd? #nが奇数でないものをnew_numへ格納 end print new_num #=> [2,4]find/detect
ある配列について、戻り値が真かつ最初の要素を返す
num = [1,2,3,4] new_num = num.find do |n| n.odd? end print new_num #=> 3エイリアスメソッドであるdetectも同様の使い方が可能
inject/reduce
ある値を初期値として配列各要素を足しこむ。eachの拡張か。
num = [1,2,3,4] sum = num.inject(0) do |result,n| result += n #初期値0に各要素をすべて足しこむ。初期値が異なれば結果も異なる。 end print sum #=> 10当然ながら文字列の結合処理にも利用可能
エイリアスメソッドであるreduceも同様の使い方が可能その他
map/collectとinject/reduceの思想の違いらしい。エイリアスというくらいなので多分map処理を名前変えてcollectとかしてんだろうな。要するに実装上の違いはないと思うがどうなのか。。。
https://magazine.rubyist.net/articles/0038/0038-MapAndCollect.html#map-%E3%81%A8-collect-%E3%81%AE%E7%99%BA%E6%83%B3%E3%81%AE%E9%81%95%E3%81%84
- 投稿日:2019-07-07T17:20:06+09:00
Rubyのゲッター、セッター、initializeについて
car.rbclass Car def initialize(color) @color = color end # ゲッター def color @color end # セッター def color=(color) @color = color end end # インスタンス化する際にinitializeメソッドに値を渡す car1 = Car.new("red") car2 = Car.new("green") car3 = Car.new("blue") # car1.color = "gray" # colorを呼び出すと代入された値が表示される puts car1.color puts car2.color puts car3.color↑こういうrbファイルがあったとする。
これをターミナル上で実行
ruby car.rbすると下記のような結果になるred green blue疑問に思ったこと
initialize(color)は引数(color)で
car1 = Car.new("red")の("red")を受け取ることができている。「えっ・・・これセッターを書く意味ある?」と疑問に思ったのだ。
試しにセッターをコメントアウトして、再度ターミナルの結果を見てみるcar.rbclass Car def initialize(color) @color = color end # ゲッター def color @color end # セッター # def color=(color) # @color = color # end end # インスタンス化する際にinitializeメソッドに値を渡す car1 = Car.new("red") car2 = Car.new("green") car3 = Car.new("blue") # car1.color = "gray" # colorを呼び出すと代入された値が表示される puts car1.color puts car2.color puts car3.color結果↓
red green blue「えっ・・・これホンマにセッター書く意味ないやん、、、
試しにcar1.color = "gray"でcar1.colorの値もちゃんと返ってくるか確かめてみよ」car.rbclass Car def initialize(color) @color = color end # ゲッター def color @color end # セッター # def color=(color) # @color = color # end end # インスタンス化する際にinitializeメソッドに値を渡す car1 = Car.new("red") car2 = Car.new("green") car3 = Car.new("blue") car1.color = "gray" #ここのコメントアウト外した # colorを呼び出すと代入された値が表示される puts car1.color puts car2.color puts car3.color結果↓
Traceback (most recent call last): class.rb:22:in `<main>': undefined method `color=' for #<Car:0x00007ffe6b172520 @color="red"> (NoMethodError) Did you mean? color「おっ!エラーはいた。(良かった〜)」
わかったこと
initializeメソッドは初期値を受けとれるだけ。
なので今回のcar1.color = "gray"のように後から代入する可能性も考えると
セッターの記述は必要。ちなみにセッターのコメントアウトも外して、再度実行すると
car1.colorの出力値を更新することができた。car.rbclass Car def initialize(color) @color = color end # ゲッター def color @color end # セッターのコメントアウトを外した def color=(color) @color = color end end # インスタンス化する際にinitializeメソッドに値を渡す car1 = Car.new("red") car2 = Car.new("green") car3 = Car.new("blue") car1.color = "gray" # colorを呼び出すと代入された値が表示される puts car1.color puts car2.color puts car3.color結果↓
gray green blueオブジェクト指向って難しいね〜(大満足)
- 投稿日:2019-07-07T16:55:39+09:00
プログラミング初学者が独学でポートフォリオを完成させるまでの話〜挫折しない方法〜
自己紹介
2019年3月初旬にエンジニアYouTuberの動画を見てHTML/CSSからProgateでプログラミングの勉強を始め、2019年6月末にRuby on Railsのポートフォリオを完成させてデプロイしました。2019年4月からは大学院の修士課程(農学)にいて、21卒でエンジニアを目指しています。プログラミングの勉強を始めた2019年3月時点ではプログラミングは完全未経験でした。
本記事がこれから独学でプログラミングの勉強を始める人の参考になれば嬉しいです。挫折しないためのポイント
結論から言うと、独学でプログラミングを勉強する際には以下の3点を守るようにすると挫折しにくくなります。
1. お手本のコードを自分専用のライブラリとして保存する
2. 勉強時間は余裕をもって設定する
3. ググっても分からないバグを無理に解決しようとしないそれでは、1つずつ解説していきます。
1. お手本のコードを自分専用のライブラリとして保存する
これは挫折しない方法というよりは勉強法になります。Progateで書いたコードのような正しいコードを解説付きでコンピューターに保存しておきましょう。ポートフォリオを制作する際は、そのコードを一旦コピペしてから必要部分を書き換えるようにすると文法ミス(syntax error)が減ってコードを書く速度が格段に上がります。
2. 勉強時間は余裕をもって設定する
時間に余裕がないことによる焦りは挫折の大きな原因になります。初学者の場合、エンジニアとしての最低限の技術とやる気の証明になるようなポートフォリオを制作するまでの作業時間はProgate等の基礎学習の時間を含めると最低500時間は確保しておいた方が無難です。当然のことながら個人差があるので、もっと時間がかかる人も多くいると思いますので、500時間以上かかったとしてもとにかく焦らないように気をつけましょう。
3. ググっても分からないバグを無理に解決しようとしない
ググっても分からないバグは分からないのですから、解決しようとするだけ無駄です。無理に解決しようとしても消耗するだけです。とはいってもバグは解決しなければいけません。
一通りググってもバグを解決できない場合は以下のような順番で対処することをおすすめします。3-1. 一晩寝かせて翌朝もう1回そのバグを見てみる
昨晩どれだけググっても解決できなかったバグが、翌朝もう1度見てみると驚くほどあっさり解決することがよくあります。そういう時は基礎的なことが抜けていた場合が本当に多いんですよね…。
この方法で解決できなかった場合、3-2に進んでください。3-2はA,B,Cと分岐します。バグの状況や自分の精神状態(笑)に応じて適切なものを選んでください。
3-2A. プログラミングスクールやプログラミングができる友人などに質問する
解決しなければ全く進めないバグに遭遇したときや挫折しそうな精神状態のときの対処法として推奨する方法です。一番確実に素早く答えを得られる方法ですが、この方法を使うことで失われるものがあることを知っておいた方がよいでしょう。
1. お金
プログラミングスクールは(無料期間のあるスクールがあるものの)基本的に10万円単位のお金がかかります。痛い出費ですが、挫折するよりもはるかにマシなので、選択肢の1つとして頭の片隅に入れておきましょう。友人に聞く場合も、ごはんを奢ってあげた方がいいですよね。
2. 自走能力の証明
初学者が独学で作ったポートフォリオにはエンジニアとしての「自走能力」(バグを自力でググって解決して次に進む能力)の証明という大きな要素があるのですが、プログラミングスクールや友人に質問するとポートフォリオからこの要素がなくなります。実はこちらがかなり大きいそうです。しかし、繰り返しますが挫折するよりもはるかにマシなので、精神的にやばいときはプログラミングスクールに行きましょう。誤解がないように言っておくと、筆者はこの方法は使っておりません。
3-2B. 質問サイトで質問する
teratailやStack Overflowなどのプログラミング専用の質問サイトがあるので、そこで質問してみましょう。ただし、質問してもなかなか答えが返ってこないことも多く、答えが得られる保証はありません。ただ、困っている内容を人にわかりやすく伝えるいい機会なので、解決できないバグが出てきたときにはこの方法を一度使ってみることをおすすめします。
3-2C. あきらめる
筆者が多く使った方法(?)です(笑)。ポートフォリオを制作するうえで必須ではない機能の部分で一通りググっても分からないことがあった場合、制作時間の短縮のためにその機能実装をあきらめました。初学者がポートフォリオを取りあえず完成させて形にするうえでは必要なことだと割り切ってこれをするのもありだと思います。筆者は独学にこだわって自走能力の証明をしたかったので、この方法を多く使いました。当然のことながら、「現場で実力がついたら、必ずこの機能を実装する」と誓ってポートフォリオでは実装できなかった機能をコメントアウトして残しています。実装をあきらめた機能があっても、とりあえず独学でポートフォリオを完成させたという事実は大きな自信につながるので、可能なら最後まで独学でポートフォリオを作りましょう。
最後に
最近では技術の発展によってプログラミング独学のハードルがどんどん下がっています。最後まで独学でポートフォリオを完成させられるかどうかに関しては個人の適性もありますが、できれば独学でがんばりましょう。
この記事に書ききれなかったポートフォリオ制作の流れを時系列で書いたものを別記事として作ります。プログラミングを独学する際の大まかな流れや心構えの参考になればと思って書きますので、そちらも読んでいただけると幸いです。
それでは、これからプログラミングの勉強を始めるみなさん、幸運を祈ります!
- 投稿日:2019-07-07T16:09:36+09:00
書籍『たのしいRuby』の練習問題を解いてみた Part.2
本題
今回は第13章の練習問題を解いていく。それでは早速解いてみる。
(1) 1~100までの整数が昇順に並ぶ配列aを作れ。1.rba=[] for i in 1..100 a<<i endaという空の配列に1~100まで詰め込んでいくようにした。
(2) (1)で作成した要素全てを100倍にした配列a2を作成せよ。また、新しい配列を作成せずに、全ての要素を100倍にした要素に置き換えよ。2_1.rba=[] for i in 1..100 a<<i end a2=a.map{|i| i * 100} p a2 #実行結果:[100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900, 4000, 4100, 4200, 4300, 4400, 4500, 4600, 4700, 4800, 4900, 5000, 5100, 5200, 5300, 5400, 5500, 5600, 5700, 5800, 5900, 6000, 6100, 6200, 6300, 6400, 6500, 6600, 6700, 6800, 6900, 7000, 7100, 7200, 7300, 7400, 7500, 7600, 7700, 7800, 7900, 8000, 8100, 8200, 8300, 8400, 8500, 8600, 8700, 8800, 8900, 9000, 9100, 9200, 9300, 9400, 9500, 9600, 9700, 9800, 9900, 10000]mapメソッドで別の要素に置き換えた。
2_2.rba=[] for i in 1..100 a<<i end a.map!{|i| i * 100} p a #実行結果:[100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900, 4000, 4100, 4200, 4300, 4400, 4500, 4600, 4700, 4800, 4900, 5000, 5100, 5200, 5300, 5400, 5500, 5600, 5700, 5800, 5900, 6000, 6100, 6200, 6300, 6400, 6500, 6600, 6700, 6800, 6900, 7000, 7100, 7200, 7300, 7400, 7500, 7600, 7700, 7800, 7900, 8000, 8100, 8200, 8300, 8400, 8500, 8600, 8700, 8800, 8900, 9000, 9100, 9200, 9300, 9400, 9500, 9600, 9700, 9800, 9900, 10000]mapの後に!を付けてレシーバの配列を変更するメソッドを使いました。mapの部分はcollectでも良いと思います。
(3) (1)の配列から3の倍数だけを取り出した新しい配列a3を作れ。また、新しい配列を作らずに3の倍数以外の数を削除せよ。3_1.rba=[] for i in 1..100 a<<i end a3 = a.select{|i| i % 3 == 0} #確認のため出力してみる p a3 #実行結果:[3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99]Enumerableモジュールの中のselectメソッドを使ってみた。find_allでも同じ挙動する。
3_2.rba=[] for i in 1..100 a<<i end a.delete_if{|i| i % 3 != 0} p aaの配列から3の倍数以外を削除しろという事なので、3で割り切れないものを削除するようにした。
(4) (1)の配列を逆順に並べ換えろ。
(5) (1)の配列に含まれる整数の和を求めよ。
(4と5はそこまで難しくないので一緒にします)4_1.rba=[] for i in 1..100 a<<i end #(5)のコード p a.sum #実行結果:5050 #(4)のコード a.reverse! p a #実行結果:[100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]reverseメッソド,sumメソッドを使うだけでした。
(6) 1~100の整数を含む配列aryから、1~10,11~20,21~30というように10個の要素を含む配列を10個取り出します。それを全て配列resultに格納せよ。6_1.rbary = [*1..100] result = Array.new 10.times do |i| result<<ary.slice!(0,10) end #出力して確認してみる p result #実行結果:[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12, 13, 14, 15, 16, 17, 18, 19, 20], [21, 22, 23, 24, 25, 26, 27, 28, 29, 30], [31, 32, 33, 34, 35, 36, 37, 38, 39, 40], [41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [51, 52, 53, 54, 55, 56, 57, 58, 59, 60], [61, 62, 63, 64, 65, 66, 67, 68, 69, 70], [71, 72, 73, 74, 75, 76, 77, 78, 79, 80], [81, 82, 83, 84, 85, 86, 87, 88, 89, 90], [91, 92, 93, 94, 95, 96, 97, 98, 99, 100]]slice!を使い指定した範囲を取り出し、それを一つの要素として配列resultに入れた。
(7) 数値からなる配列nums1とnums2に対して、それらの個々の要素を足し合わせた要素からなる配列を返すメソッドsum_arrayを定義せよ。7_1.rbdef sum_array(nums1,nums2) result=Array.new nums1.zip(nums2)do |a , b , c| result << a + b end return result end #確認のため実行してみる p sum_array([8,1,2] , [5,7,4]) #実行結果:[13, 8, 6]zipメソッドで処理させた。
最後に
今回は配列の操作がメインの内容だった。Enumerableモジュールには色々なメソッドがあるようなので、上手く扱っていきたい。
本自体は読み終わっているので、このまま継続して記事を出していこうと思います。
- 投稿日:2019-07-07T16:09:21+09:00
技術系資格試験は受ける意味があるのか?〜Ruby Silver編〜
前書き
世の中には様々な資格試験があります。
そして技術分野にも資格試験があります。
受ける理由は人それぞれだと思いますが、今回は資格試験は意味がある=業務に役立つのかをポイントに、個人的に感じたことをまとめてみます。前提と先に結果
私の経歴はこんな感じです。
- Java歴 1.5年 (新卒時です)
- Ruby/Rails歴 1年ちょっと (厳密には学生時代にも数ヶ月やったことがありました)
ただし仕事ではサービス開発/運用/セキュリティ全般を担当しているので、毎日フルでRubyを書いてるという訳でなく、AWSを触っていたりというよな時間も多いです。(ハードル下げ)
・受けた試験
Ruby技術者認定試験のSilverです。
Goldを取るつもりなのですが、それには下位であるSilverがまず必要だったので受けました。・受けたきっかけ
自分がどれだけRubyを理解できているか知りたかった、
Rubyの組み込みクラスを再実装する勉強会に行った際に理解が足りていないと感じた、
CTOが良い試験だと言っていたからです。・勉強時間と結果
勉強期間は2週間弱です。平日0.5~1時間、休日3時間程度です。
業務後に予定があったり、週末旅行に行ったりしたので毎日はできませんでしたが、前日の詰め込みなどを平均したらそれくらいです。
100点中75点が合格点で、90点でした。Ruby技術者認定試験は受ける意味があったか?
先に結論から言いますと、個人的にはあったと思います。
まずそう思った要因は、
1. この試験は業務に役立つ試験になるように作られている気がした
2. 実務を1年やってから受けているので、単なる暗記にならなかった
3. 達成感を得られる
です。以下で詳しく説明します。
1.業務に役立つ試験になるように作られている気がした
まず、「こういう記憶色の強い試験だと、重箱を隅を突くような出題があって、無駄な勉強が生まれるのでは」と私は危惧していました。
が、結果的には勉強に無駄が生まれないように工夫されていると感じました。
網羅的にメソッドが紹介されている参考書を読んでいる時、「これそんなに使うシーンあるかな・・」というメソッドがあったりするのですが、肌感ですがそれらは出題されなかったので、テスト作成者も使用頻度が高いメソッドを選んでいるんじゃないかな〜と個人的には感じました。※そもそもOSSであるRubyにメソッドが用意されているということは、一定以上の需要があるはずです。
なので、「これそんなに使うシーンあるかな・・」と感じるのは私の経験が浅いという理由もあると思っています。ただ全くなかったわけでなく、1つありました。それはいろんなパターンのHashの生成。
でもこれくらいでした。2. 実務を1年やってから受けているので、単なる暗記にならなかった
私はもともとJavaをやっており、間髪入れずにRubyの会社に転職をしたので業務をしながら覚えていきました。
入門はたのしいRubyとチェリー本だったので、網羅的に学ぶということはしていませんでした。合格教本で完全ではありませんが割と網羅的に学べたので、知らなかったメソッドを知れました。その際、「あそこの処理、このメソッドで置き換えた方がcoolかも」と考えながら勉強ができ、実際書き換えもしたのでとても有益でした。
また他によかった点として、毎回出くわす度に調べていた名前や挙動の紛らわしいメソッドをちゃんと覚えられたので、仕事も早くなるかなと思います。
あとはオブジェクト指向についての出題も多いので勉強し直したのですが、改めてJavaと比べてRubyの挙動は柔軟で面白いな〜とRubyをさらに好きになりました笑3. 達成感を得られる
Silverは踏み台として受けただけなのですが、それでも達成感はありました。
余談ですが、受講料について。
1万6千円は高すぎでしょ!?と思っていたのですが、試験2日ほど前には「1万6千円を無駄にできない」という猛烈なpressureで勉強が捗ったので、大人を本気にさせるとてもいい価格設定だったと思いました。勉強方法とかかった時間
先人達が色々とまとめられているので簡単に。
私がやったのは、
0. 勉強方法や気をつけるべきポイントを調べる
1. まず1回模擬試験を解いて、自分のできなさ具合に焦る&雰囲気を掴む
2. Ruby公式資格教科書 の該当箇所を読む。私はGoldも受ける気だったので、Silverの範囲はあまり気にせず読んでいました。
3. https://www.ruby.or.jp/ja/certification/examination/rex で模擬試験を4回くらいやる
です。あとRuby試験に限ったことではないのですが、中学/大学受験時の経験として、
紛らわしいポイントは抽出してまとめておき、1日数回、突如自分クイズを始めてなんども思い出して定着させることがかなり有効なのでやっていました。まとめ
Ruby技術者認定試験は、業務に活かせるか?というポイントにおいて、個人的には良い試験だと思います。
特に業務を始めて半年くらいたった人には良いと思います。
これからRubyをはじめるきっかけとしてこの試験に取り掛かるのは、個人的には暗記ゲーになってしまい、楽しくできるかは微妙なんじゃないかなと思っています。先にアプリを作ったりしてからやった方がいいかなと思います。もちろん自分がそうだった訳ではないのであくまで一意見ですが。あとがき
プロメトリックの回し者ではありません。
試験勉強は大好きです。
駆け出しRubyistのヒントになればいいな。
- 投稿日:2019-07-07T15:24:47+09:00
Rails で ActiveInteraction を使うメリット
はじめに
ActiveInteraction とは、rails で controller に書かれるビジネスロジックを整理するときに使えるgemです。プロダクトが大きくなるにつれて複雑になり、太った controller や model をスリムにすることができます。また、ActiveModel が名詞でわかりやすく扱えるのと同様に、ActiveInteraction は動詞で命名してわかりやすく扱うことができます。
使いかたについては、ActiveInteraction の README または、こちらの Qiitaの記事 を参考にしてみてください。
この記事では、ActiveInteraction のメリットについて書いていきます。
ActiveInteraction を使うメリット
複雑になりがちな controller を簡潔にできる
ActiveInteraction を使う一番のメリットは、複雑になった controller を簡潔にできることです。
例 : order と report モデルがあり、二つは関連を持っているとします。report は、create した時にステータスによって通知を送るべきかどうか変えたい、という要件があった場合、そのロジックを controller から分離することができます。
(簡略化して書いています)
def create # ここに書くべき内容を ActiveInteraction::Base を継承している create.rb に移動できる outcome = Reports::Create.run(reports_create_params) if outcome.valid? redirect_to order_url(@order), notice: 'create!' else flash.now[:alert] = 'error!' render :new end end# create.rb module Reports class Create < ActiveInteraction::Base object :order, class: Order object :account, class: Account string :content, default: nil array :images, default: [] string :to_state, default: nil def execute report = order.reports.build( content: content, images: images, account: account, date: Time.current.to_date ) ActiveRecord::Base.transaction do report.save! unless to_state&.to_sym == :accepted compose(Notifications::Send) end end report end end endcontroller がとてもシンプルで読みやすいですね。
複雑な controller は可読性が悪く、バグを生む原因にもなります。
ActiveInteraction を使うことで、controller で書くことをシンプルに保つことができ、ビジネスロジックを名前空間を使って他のところに移動させることができます。
このメリットはservice層を作ることで補うこともできるのですが、ActiveInteraction を使うことでよりシンプルにわかりやすくなります。ちなみに僕の会社では、もともとservice層にロジックを押し込んでいたこともあり、今はservice層という名前空間に
ActiveInteraction::Baseを継承させて利用しています。なぜ ActiveInteraction を使うとよりわかりやすくなるのか知りたい方は、こちらの記事をご覧ください。
バグを生みにくい(見つけやすい)
二つ目のメリットは、上記した通り、バグを生みにくいということです。
ActiveInteraction は静的型付けを行います。なので、型が原因で起こるバグを潰すことができます。型が決まっているので、複数人で開発している時でもその変数に何が入っているのか一目瞭然で可読性が高いです。
# このような感じでかけます object :purchase, class: Order object :account, class: Account string :content, default: nil array :images, default: []また、Validation をつけることができます。書き方は model と全く同じです。そもそも ActiveInteraction 内で Validation チェックすることがあまりないので利用頻度は高くないと思いますが、こちらもうまく使うことでバグの原因を潰すことができます。
ロジックのテストが描きやすい
三つ目のメリットは、ロジックだけを隔離することにより、テストが描きやすくなることです。
ActiveInteraction は一つのロジックを一つのファイルに納めるので、テストが一つのロジック毎に別れ、シンプルに保つことができます。また、テストする部分を明確にすることができます。最後に
ActiveInteraction は README がとてもわかりやすいので、困ったことがあれば大体は README で解決できます。
- 投稿日:2019-07-07T13:09:32+09:00
Ruby の Hash#select, reject のブロックパラメーター
Ruby のハッシュの
select,rejectなどのメソッドについて,公式リファレンスにも載っていない「あれ?」と思った仕様に気付いたので。おさらい
まず,Hash の
selectやrejectがどんなメソッドなのかを振り返る。Ruby 1.8 時代は仕様が違ったり,Ruby 2.1 の頃に黒歴史があったりしたが,この記事では最近のバージョンだけを考慮することにする。とりあえず Ruby 2.6 だ。(現時点で公式サポートが継続している Ruby 2.4 以降なら同じだと思う)
Hash には Enumerable が include してあるが,
selectやrejectは Hash で独自に実装されており,Enumerable#select や Enumerable#reject が使われることはない。
Hash#selectは自身の要素から特定の条件を満たす要素だけを採用した新たなハッシュを生成して返すもの。
Hash#rejectはその論理反転であり,自身の要素から特定の条件を満たす要素だけを取り除いた新たなハッシュを生成して返すもの。
いずれも条件はブロックで与える。以下の例では,条件として,キーが
'ba'を含み,かつ値が正であるもの,としよう。hash = {foo: 3, bar: 4, baz: 0} p hash.select{ |key, value| (/ba/ =~ key) && (value > 0) } # => {:bar=>4} p hash.reject{ |key, value| (/ba/ =~ key) && (value > 0) } # => {:foo=>3, :baz=>0}これで動作は一目瞭然。
公式リファレンス(Ruby 2.6.0)は以下のとおり。
rejectのほうの説明は「self を複製し」とあるが,これは間違いではないかと思う。selectもrejectもデフォルト値は引き継がれないし,Hash のサブクラスに適用してもサブクラスでなく Hash を返すから。なお,ブロックを与えない使い方もあるが,本題に関係ないので割愛。
ブロックパラメーターは一つでも
ここからが本題。
これらのメソッドは,前節のコード例のように,ブロックパラメーターとしてキーと値の二つを使うのが普通だ。公式リファレンスでもそのような例しか載っていない。しかし,実はブロックパラメーター一つの使い方がある。
hash = {foo: 3, bar: 4, baz: 0} p hash.select{ |key| /ba/ =~ key } # => {:bar=>4, :baz=>0} p hash.reject{ |key| /ba/ =~ key } # => {:foo=>3}見てのとおり,ブロックパラメーターを一つにした場合,要素のキーが渡ってくる。
これに気付いたとき,「え?」と思った。
ブロックパラメーターが二つであるべきところに一つしか記述しなかったら,配列の形で渡ってくるんじゃないのか?(後述するがこの認識は間違い)
だって,Hash#eachだったらhash = {foo: 3, bar: 4} hash.each do |a, b| p a, b end # => :foo # => 3 # => :bar # => 4 hash.each do |a| p a end # => [:foo, 3] # => [:bar, 4]になるよね?
てことは,
Hash#selectやHash#rejectは与えられたブロックのパラメーターの数1で挙動を変えているのか?(次節で述べるようにこれは誤解)特別な動作ではなかった
結論を先に言えば,ブロックパラメーターを一つだけ指定したときにキーだけが渡ってくるのは,特別な動作でも何でもなかった。だから公式リファレンスにも特に言及はなかったわけだ。
すっかり忘れていたが,メソッドから引数二つで
yieldしたとき,ブロックパラメーターが一つしかなかったら,yieldの最初の引数だけが渡され,二つ目は無視されるのだった。つまりこういうこと:
def foo yield 1, 2 end foo{ |x| p x } # => 1これは Ruby の
yieldの仕様であって,特定のクラスとかには関係ない。ではなぜ,私は配列が渡ってくるはずと勘違いしたのだろうか?
原因は明らかにHash#eachの動作を見たためだが,Hash#eachの動作はどのように理解すればいいのだろうか?どうやら,
Hash#eachは,キーと値を引数としてyieldしているのではなく,キーと値からなる配列を引数としてyieldしているらしい。
いや,Hash#eachは Ruby でなく C で実装しているから,そもそもyieldなんか呼んでいないのだが,「Ruby コードとして表現すれば」という話である。Ruby 2.6 の
Hash#eachの実装は↓ここであるらしい。
https://github.com/ruby/ruby/blob/ruby_2_6/hash.c#L2779-L2810私は C が読めないのでよく分からないが,ぼんやりとは分かる。
ブロックパラメーターの数で場合分けしてあるのは高速化のためのようだが,ともかく 0 個や 1 個の場合はeach_pair_iが呼ばれる。
each_pair_iの実装は↓ここ。
https://github.com/ruby/ruby/blob/ruby_2_6/hash.c#L2762-L2767ここに
rb_yield(rb_assoc_new(key, value));とある。
rb_assoc_newというのは引数をつなげた配列(?)を作る関数ぽいので,やはり配列をyieldしているのだ。ブロックパラメーターが 2 個以上のときは
each_pair_i_fastが呼ばれるが,その実装は↓ここ。
https://github.com/ruby/ruby/blob/ruby_2_6/hash.c#L2769-L2777よく分からないが,こちらも配列を作っているぽい。
Ruby では,配列を
yieldした場合,ブロックパラメーターが 1 個ならその配列がそのまま渡され,2 個なら展開されて渡される,という仕様であった。
つまりこういうこと:def foo yield [1, 2] end foo{ |a| p a } # => [1, 2] foo{ |a, b| p a; p b } # => 1 # => 2これで謎が解けた。
どちらかといえば,与えるブロックパラメーターの数について注意が必要なのは
Hash#eachのほうであったか。
ブロックが持つブロックパラメーターの数をそのブロックの arity という。一般にコンピューター用語で,関数などの引数が 1 個,2 個,3 個であることを unary, binary, ternary という。arity はここから作られた単語で,関数などの引数の個数のこと。Ruby ではメソッド側が,与えられたブロックのアリティーを知る手段を持っている。「有り体に申せ」ってね。 ↩
- 投稿日:2019-07-07T13:09:11+09:00
#Ruby の then / yield_self は メソッドチェーンで nil を防止するための初期値代入にも使えそうだけど?
Ruby's then / yield_self could also be used for initial value assignment to prevent nil in the method chain?
題材は多分不適切
こんなので nil を防ぎたい時
numbers = [1,2,3]; numbers&.max.yield_self { |max| max || 0 } + 1 # => 4then / yield_self での書き方
numbers = nil; numbers.then { |numbers| numbers || [] }.max.then { |max| max || 0 } + 1 # => 1 numbers = nil; numbers.yield_self { |numbers| numbers || [] }.max.yield_self { |max| max || 0 } + 1 # => 1かっこでくくる + ぼっち演算子での書き方
numbers = nil; (numbers&.max || 0) + 1 # => 1Original by Github issue
- 投稿日:2019-07-07T12:50:57+09:00
リクエストのスタブ化ができる WebMock の使い方を簡単に確認する
はじめに
外部 API のリクエストを含むテストを試したいと思ったときに、Webmock という gem でリクエストをスタブ化できることを知りました。
有名な gem なのかもしれませんが、使ったことがまだなかったので、簡単に使い方を試してみました。
前提環境
- Mac OS X 10.14.4
- Rails 5.2.2.1
- ruby 2.5.5p157 (2019-03-15 revision 67260)]
試し方
今回は WebMock の使い方をすぐに確認したかったので、予め準備しておいたサンプルの Rails アプリのコンソール上で、 Webmock のメソッドの使い方を確認します。
手順
インストール
Gemfileに Webmock をインストールします。Gemfilegem 'webmock'下準備
rails cでコンソールを起動したら、Webmock を利用できるよう、有効化の設定を入れます。require 'webmock' include WebMock::API WebMock.enable!こちらによって、WebMock のスタブへのリクエストに切り替わります。
README のサンプルを試す
下準備が終わったら、README を参考に、いくつかサンプルを試します。
URL のみのスタブリクエスト
以下のコードで、ダミーの URL である
www.example.comへのすべてのリクエストが許可される形になります。stub_request(:any, "www.example.com") Net::HTTP.get("www.example.com", "/") #=> ""クエリパラメータ付き URL のみのスタブリクエスト
クエリパラメータなど、変数に当たる部分は
{?}という形で囲むことで使用することができます。uri_template = Addressable::Template.new "www.example.com/users{?name}" stub_request(:any, uri_template) Net::HTTP.get('www.example.com', '/users?name=hoge') # => ""ダミーのレスポンスを指定
リクエスト先の URL に対してダミーのレスポンスを指定できます。
stub_request(:any, "www.example.com") .to_return(body: "aaa", status: 200 ) Net::HTTP.get('www.example.com', '/') # => "aaa"レスポンスをテキストファイルで指定
別で保存しておいたテキストファイルをレスポンスとして指定することもできます。
File.open('/tmp/response.txt', 'w') { |f| f.puts 'bbb' } stub_request(:any, "www.example.com"). to_return(body: File.new('/tmp/response.txt'), status: 200) Net::HTTP.get('www.example.com', '/') # => "bbb\n"おわりに
こちらを応用すれば、 外部通信が伴う API クライアントが絡んだテストも、リクエストの URL さえわかればスタブ化してテストすることができそうです。
今回は試していませんが、リクエストヘッダありの POST リクエストもスタブ化できるようなので、利用の幅が広いなと感じました。
これから試してみようと思います。
参考
- 投稿日:2019-07-07T12:27:42+09:00
たのしいOSSコードリーディング: Let's read "cookies"?
この記事は2019年7月6日に開催されたTama Ruby会議での発表「たのしいOSSコードリーディング: Let's read "cookies"?」を詳細解説するものです。
Railsアプリケーションで使用される
cookiesメソッドを題材に、このメソッドがどのように実装されているかを読んでいきます。
読みきれなかった部分・知識が曖昧な部分が残っているため、先輩方からの技術的指摘をお待ちしています。当日の発表資料はこちら
調査環境
- Rails 6.0.0.rc1
- TraceLocation 0.9.3.1
そもそもcookiesメソッドとは
cookies[:hoge] = "fuga"Railsが用意しているメソッド。
Cookieに「hoge=fuga」を設定するときに使用。事前知識
①Cookie
クライアントからサーバーへリクエストを送る際、HTTPリクエストヘッダはCookieヘッダを含んでいる。
クライアント?? → リクエスト[Cookie: hoge=fuga] → サーバー??Cookieヘッダの中身はこんな感じ。
NAME1=OPAQUE_STRING1; NAME2=OPAQUE_STRING2 ...サーバーからクライアントにレスポンスを送る際、HTTPレスポンスヘッダはSet-Cookieヘッダを含んでいる。
クライアント?? ← レスポンス[Set-Cookie: hoge=fuga] ← サーバー??Set-Cookieヘッダの中身はこんな感じ。
NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; ...いずれの場合も、Cookieは次のように名前と値を一つのペアとしている?
Cookieの名前=Cookieの値(Rubyスクリプトの中で扱う際、ハッシュの形式
{名前: 値}に変換したりする)②Rackミドルウェア
Rackとは
- 「Rack」は次のふたつの意味を持っている
- WebアプリケーションとWebサーバーを繋ぐプロトコル
- WebアプリケーションとWebサーバーを繋ぐライブラリ
- Railsアプリケーションは、「Rackプロトコルを満たし、かつRackライブラリを内部で使うRackアプリケーション」
Rackミドルウェアとは
- Webアプリケーションが持っているべき、「特定の汎用的な機能」を切り出したRackライブラリ
- Railsアプリケーションは多くのRackミドルウェアを使用している
- 今回見ていく
ActionDispatch::CookiesはRailsで使用されているRackミドルウェアのひとつ$ rails middleware use Webpacker::DevServerProxy use ActionDispatch::HostAuthorization use Rack::Sendfile use ActionDispatch::Static (略)...
ActionDispatch::Cookiesとは?
- Railsアプリケーションで使用されているRackミドルウェアのひとつ
- Cookieを保存するために使う
$ rails middleware (略)... use ActionDispatch::Cookies # ←これ use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ContentSecurityPolicy::Middleware use Rack::Head use Rack::ConditionalGet use Rack::ETag use Rack::TempfileReaper run Myapp::Application.routesそれでは早速読んでいきましょう?
cookies[:hoge] = "fuga"の全体図trace_locationを使用すると、cookiesメソッドを呼んだ際、次のように一連の処理が走ることが確認できます。
Logged by TraceLocation gem at 2019-06-05 20:31:31 +0900 https://github.com/yhirano55/trace_location [Tracing events] C: Call, R: Return C actionpack-6.0.0.rc1/lib/action_controller/metal/cookies.rb:12 [ActionController::Cookies#cookies] C actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:11 [ActionDispatch::Request#cookie_jar] C rack-2.0.7/lib/rack/request.rb:58 [Rack::Request::Env#fetch_header] R rack-2.0.7/lib/rack/request.rb:60 [Rack::Request::Env#fetch_header] R actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:15 [ActionDispatch::Request#cookie_jar] R actionpack-6.0.0.rc1/lib/action_controller/metal/cookies.rb:14 [ActionController::Cookies#cookies] C actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:374 [ActionDispatch::Cookies::CookieJar#[]=] C actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:350 [ActionDispatch::Cookies::CookieJar#handle_options] R actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:370 [ActionDispatch::Cookies::CookieJar#handle_options] R actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:392 [ActionDispatch::Cookies::CookieJar#[]=] Result: fuga要約するとこんな感じ
[Rails]ActionDispatch::Cookiesミドルウェアにアクセスする [Rails][Rack]リクエストから既存のCookieヘッダを見つけて返す 見つからなかった場合は空のCookieをセットする [Rails]ActionDispatch::Cookiesのインスタンスを生成 インスタンス変数を初期化する [Rails]ActionDispatch::Cookiesのインスタンス変数に 新しいCookieの値を追加する [Rails]ミドルウェアがアプリケーションを返すタイミングで インスタンス変数の内容をSet-Cookieヘッダに書き込む(?) ← ここは処理を追いきれず…RailsとRackの処理を行ったり来たりするため、手元にソースコードがある方は確認しながら進むことをお勧めします。
ひとつずつ読んでいきましょう?
rails/actionpack/lib/action_controller/metal/cookies.rb
7 C /vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.0.rc1/lib/action_controller/metal/cookies.rb:12#cookiesRails側から処理を追います?
まずはActionController::Cookies#cookiesメソッドを読んでみましょう。ActionController::Cookies#cookies
actionpack/lib/action_controller/metal/cookies.rb11 private 12 def cookies 13 request.cookie_jar 14 end一番最初に呼ばれる
#cookiesメソッドの中身はこんな感じ、レシーバに対して#cookie_jarメソッドを呼んでいるだけ。レシーバの
requestはActionDispatch::Requestのインスタンス(※include先で定義されている)。続いて
#cookie_jarメソッドを読んでみましょう。rails/actionpack/lib/action_dispatch/middleware/cookies.rb
8 C /vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:11#cookie_jar引き続きRails?
ActionDispatch::Request#cookie_jar
actionpack/lib/action_dispatch/middleware/cookies.rb11 def cookie_jar 12 fetch_header("action_dispatch.cookies") do 13 self.cookie_jar = Cookies::CookieJar.build(self, cookies) 14 end 15 end
#cookie_jarメソッドの中身はこんな感じ。
一行目でRackのメソッドであるfetch_header(Rack::Request::Env#fetch_header)を呼んでいます。続いて
Rack::Request::Env#fetch_headerを読んでみましょう。rack/lib/rack/request.rb
9 C /vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/request.rb:58#fetch_headerRack側に移ります?
Rack::Request::Env#fetch_header
rack/lib/rack/request.rb69 def fetch_header(name, &block) 70 @env.fetch(name, &block) 71 end
#fetch_headerメソッドの中身はこんな感じ。
ここで登場するオブジェクトを確認しましょう。nameとは
引数の
nameは"action_dispatch.cookies"(参考)
actionpack/lib/action_dispatch/middleware/cookies.rb12 fetch_header("action_dispatch.cookies") do 13 self.cookie_jar = Cookies::CookieJar.build(self, cookies) 14 end&blockとは
引数の
&blockはfetch_headerに渡されているブロック
self.cookie_jar = Cookies::CookieJar.build(self, cookies)。(参考)
actionpack/lib/action_dispatch/middleware/cookies.rb12 fetch_header("action_dispatch.cookies") do 13 self.cookie_jar = Cookies::CookieJar.build(self, cookies) 14 endレシーバ
@envとはHTTPヘッダを表すハッシュ。
例えばこんなRackアプリがあった場合['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']]真ん中の
{'Content-Type' => 'text/html'}というハッシュが、Rack上では@envというインスタンス変数に入っています。つまり…
fetch_headerは、ヘッダである@envハッシュに対して、Hash#fetchメソッドを呼ぶための処理。すなわち
@envが"action_dispatch.cookies"というkeyを持っていた場合:
- ➡︎そのvalueが返る
@envが"action_dispatch.cookies"というkeyを持っていなかった場合;
- ➡︎ブロック
self.cookie_jar = Cookies::CookieJar.build(self, cookies)が実行される続いて、ブロック
self.cookie_jar = Cookies::CookieJar.build(self, cookies)が実行された場合の処理を追います。rails/actionpack/lib/action_dispatch/middleware/cookies.rb
self.cookie_jar =は、ActionDispatch::Request#cookie_jar=としてメソッド定義されています。
早速読んでみましょう?ActionDispatch::Request#cookie_jar=
actionpack/lib/action_dispatch/middleware/cookies.rb28 def cookie_jar=(jar) 29 set_header "action_dispatch.cookies", jar 30 endここで登場する引数の
jarの正体はCookies::CookieJar.build(self, cookies)ですが、この処理は後で読むので一旦パス。二行目で、再びRackのメソッドである
set_headerを呼んでいます。
#set_headerの処理を読んでみましょう。rack/lib/rack/request.rb
再びRackへ?
Rack::Request::Env#set_header
lib/rack/request.rb79 def set_header(name, v) 80 @env[name] = v 81 end
#fetch_headerメソッドの中身はこんな感じ。
ここで登場するオブジェクトを確認しましょう。引数nameとは
引数の
nameは"action_dispatch.cookies"引数vとは
vは先程のjar(つまりCookies::CookieJar.build(self, cookies))具体的には
@env["action_dispatch.cookies"] = Cookies::CookieJar.build(self, cookies)という処理が走っています。
このとき、返り値はCookies::CookieJar.build(self, cookies)になります。ここまでのまとめ
アプリケーションで
cookiesメソッドが呼ばれると、次の2つの処理が走る。
1.@envハッシュにおける"action_dispatch.cookies"keyの存在を確認する。
2. 存在する場合はvalueを返す。
存在しない場合は新たに{"action_dispatch.cookies":というペアをつくる。
*Cookies::CookieJar.build(self, cookies)*}Let's read Rack::Request::Helpers#cookies?
ここからは、先ほどパスした
Cookies::CookieJar.build(self, cookies)を読んでいきます。ActionDispatch::Cookies::CookieJar.build
actionpack/lib/action_dispatch/middleware/cookies.rb11 def cookie_jar 12 fetch_header("action_dispatch.cookies") do 13 self.cookie_jar = Cookies::CookieJar.build(self, cookies) 14 end 15 end↑13行目で
self.cookie_jar =に渡しているCookies::CookieJar.build(self, cookies)が、その前に
注目
Cookies::CookieJar.build(self, cookies)ここで引数に入れたものは何?
(self, cookies)
selfは一番最初に出てきたrequest
cookiesは、これもRackのメソッドを呼んでいます。rack/lib/rack/request.rb
cookiesの正体を探るため、
Rack::Request::Helpers#cookiesメソッドを読んでいきましょう?Rack::Request::Helpers#cookies
lib/rack/request.rb215 def cookies 216 hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |k| 217 set_header(k, {}) 218 end 219 string = get_header HTTP_COOKIE 220 221 return hash if string == get_header(RACK_REQUEST_COOKIE_STRING) 222 hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE 223 set_header(RACK_REQUEST_COOKIE_STRING, string) 224 hash 225 end
#cookiesの中身はこんな感じ。一行づつ
lib/rack/request.rb216 hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |k| 217 set_header(k, {}) 218 end
fatch_headerは先程と同じメソッドで、@envハッシュに対してRACK_REQUEST_COOKIE_HASHkeyをfetchします。
- keyがあった場合
- ➡️valueを返す
- keyがなかった場合
- ➡️これも先程登場した
set_headerメソッドを使用して、@envハッシュにRACK_REQUEST_COOKIE_HASH: {}というペアを追加する- 返り値は空の
{}(参考)
lib/rack/request.rb79 def set_header(name, v) 80 @env[name] = v 81 endいずれの場合も、返り値を
hashに代入しています。RACK_REQUEST_COOKIE_HASHとは?
ここで登場する、
RACK_REQUEST_COOKIE_HASHの正体は、rack.request.cookie_hash。
Rackがサーバーに対して問い合わせするためのメソッド。
返り値はこんな感じ。
(例)example.rb{"_todo_session"=>"BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY1OWJmM2I3YWE3YzlhY2UzMmM5Mzk3NjZlMzJkNjU2BjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW5vOTdFQzRtQURmMDJoVlYzT2ZSK1hxQmw3M0ZrNFRwOGpCZWdXbmx3V1k9BjsARg==--c34c5fc6de928cde391cccd2b710547c7aab1d06"}(↑の例はたまたま下記の記事中で見つけたものです)
Rails で捕捉されない例外が発生したらメールを送るつづきまして
lib/rack/request.rb219 string = get_header HTTP_COOKIE
HTTP_COOKIEを引数として、get_headerメソッドを呼んでいます。
get_headerはRack::Request::Env#get_headerで、定義は以下の通り。lib/rack/request.rb63 def get_header(name) 64 @env[name] 65 end
@envハッシュからHTTP_COOKIEkeyを探し、valueを返し、返り値がstringに代入されます。
ただし、初めてCookieを使用する場合は@envハッシュにHTTP_COOKIEkeyが見つからないため、nilが返ります。HTTP_COOKIEとは?
ここで登場する
HTTP_COOKIEの正体は、リクエストメッセージに含まれるメタ変数で、Cookieヘッダを返しています。
中身はこんな感じ(Cookieの名前と値を=で結ぶことでペアとして表現している)
(例)
example.rb _todo_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY1OWJmM2I3YWE3YzlhY2UzMmM5Mzk3NjZlMzJkNjU2BjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW5vOTdFQzRtQURmMDJoVlYzT2ZSK1hxQmw3M0ZrNFRwOGpCZWdXbmx3V1k9BjsARg%3D%3D--c34c5fc6de928cde391cccd2b710547c7aab1d06
(↑の例も先ほどの記事よりお借りしました)
※メタ変数について:
参照例えば HTTP というプロトコルにおいては、 HTTP_USER_AGENT という名前のメタ変数が HTTP 要求メッセージの User-Agent: 欄の値を提供することになっています
つづき
初めてCookieを使用する場合、ここまでで
hashに{}stringにget_header HTTP_COOKIEの返り値(初めてCookieを使用する場合はnil)が代入されていることになります。
lib/rack/request.rb221 return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
RACK_REQUEST_COOKIE_STRINGを引数に、Rack::Request::Env#get_headerメソッドを呼んでstringと比較。(参考)
lib/rack/request.rb63 def get_header(name) 64 @env[name] 65 endRACK_REQUEST_COOKIE_STRINGとは?
ここで登場する
RACK_REQUEST_COOKIE_STRINGの正体はrack.request.cookie_stringで、こちらもサーバーに対して問い合わせするためのメソッド。
返り値はこんな感じ。
(例)example.rb_todo_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY1OWJmM2I3YWE3YzlhY2UzMmM5Mzk3NjZlMzJkNjU2BjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW5vOTdFQzRtQURmMDJoVlYzT2ZSK1hxQmw3M0ZrNFRwOGpCZWdXbmx3V1k9BjsARg%3D%3D--c34c5fc6de928cde391cccd2b710547c7aab1d06(御多分に洩れず↑の例も先ほどの記事より)
…
HTTP_COOKIEと同じですね!つづき
lib/rack/request.rb221 return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
==の場合はここでreturn hashなぜ?
次行以降はリクエストヘッダからやって来た
hash(初めてのCookieを使用する場合は空の{})をHTTP_COOKIEで更新する処理となっています。
ハッシュの中身が変更されていない場合、置き換える必要がないため。初めてCookieを使用する場合、
stringとget_header(RACK_REQUEST_COOKIE_STRING)の返り値はいずれもnilのため、ここでcookiesメソッドの処理はhash(空の{})を返して終了します。つづき(すでにCookieが存在する場合のみ)
lib/rack/request.rb222 hash.replace Utils.parse_cookies_header stringここでは、
Utils.parse_cookies_header(string)の返り値{ Cookieの名前: Cookieの値 }で、hash(fetch_header(RACK_REQUEST_COOKIE_HASH)の返り値)をreplaceしています。
Utils.parse_cookies_headerはRack::Utilsのメソッドで、
詳しくは読みませんが、
string(Cookieの名前=Cookieの値)を{Cookieの名前: Cookieの値}
のようなハッシュに変換する役割を担っています。(参考)
lib/rack/utils.rb209 def parse_cookies_header(header) 215 cookies = parse_query(header, ';,') { |s| unescape(s) rescue s } 216 cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v } 217 end(↑
each_with_objectを使用して{}に名前と値を代入している)つづき(すでにCookieが存在する場合のみ)
lib/rack/request.rb223 set_header(RACK_REQUEST_COOKIE_STRING, string) 224 hash 225 endすなわち、ここで
@env[RACK_REQUEST_COOKIE_STRING] = stringという処理を実行。
(参考)
lib/rack/request.rb79 def set_header(name, v) 80 @env[name] = v 81 end最後に
hash({Cookieの名前: Cookieの値}) を返しています。ここまでのまとめ
メソッド
cookiesを呼ぶとき
- 初めてCookieを使用する場合: ヘッダ(
@env)に{RACK_REQUEST_COOKIE_HASH: {} }が追加される
- 最終的な返り値は空の
{}- すでにCookieが存在する場合: ヘッダ(
@env)に{RACK_REQUEST_COOKIE_STRING: リクエストメッセージのCookieヘッダ}が追加される
- 最終的な返り値は
{Cookieの名前: Cookieの値}改めましてActionDispatch::Cookies::CookieJar.build
actionpack/lib/action_dispatch/middleware/cookies.rb
お待たせしました。
Cookies::CookieJar.buildを読んでいきましょう。
再びRailsへ?ActionDispatch::Cookies::CookieJar.build
actionpack/lib/action_dispatch/middleware/cookies.rb170 # class Cookies # ... 272 # class CookieJar # ... 289 def self.build(req, cookies) 290 new(req).tap do |jar| 291 jar.update(cookies) 292 end 293 end
Cookies::CookieJar.buildの中身はこんな感じ。
tapを使用して処理をチェーンしているのは、最終的にできあがったCookies::CookieJarインスタンスを返すためではないかと考えられます。
(tapを挟まない場合、返り値がjar.updateの実行結果になる)ここで登場するオブジェクトを確認しましょう。
reqとは
引数の
reqは一番最初のrequest(ActionDispatch::Requestのインスタンス)cookiesとは
cookiesは先ほど読んだRack::Request::Helpers#cookiesの返り値({Cookieの名前: Cookieの値}あるいは空の{})Cookies::CookieJar.buildは何をやっているのか
ここでの役割は2つ
Cookies::CoookieJarのインスタンスを作る- できたインスタンスに対して
Cookies::CookieJar#updateメソッドを呼ぶ順番に処理を読んでいきます。
new(req)
actionpack/lib/action_dispatch/middleware/cookies.rb170 # class Cookies # ... 272 # class CookieJar # ... 289 def self.build(req, cookies) 290 new(req).tap do |jar| # ← 291 jar.update(cookies) 292 end 293 end
new(req)(インスタンス化)すると、initializeが呼ばれます。actionpack/lib/action_dispatch/middleware/cookies.rb295 attr_reader :request 296 297 def initialize(request) 298 @set_cookies = {} 299 @delete_cookies = {} 300 @request = request 301 @cookies = {} 302 @committed = false 303 end
ActionDispatch::Cookies::CookieJar#initializeは各インスタンス変数を初期化するだけの処理。
@requestには引数で渡されている一番最初のrequestが入ります。
attr_readerによってメソッドとしてアクセスできるようになります。つづいてtap以下の処理
actionpack/lib/action_dispatch/middleware/cookies.rb170 # class Cookies # ... 272 # class CookieJar # ... 290 new(req).tap do |jar| 291 jar.update(cookies) # ← 292 end
tapブロックの中で実行されているupdateメソッドはこちらactionpack/lib/action_dispatch/middleware/cookies.rb334 def update(other_hash) 335 @cookies.update other_hash.stringify_keys 336 self 337 end引数other_hashとは
引数
other_hashは先ほどのcookies、つまり{Cookieの名前: Cookieの値}もしくは空の{}。
@cookiesとは
@cookiesはinitializeの中で初期化したこの部分301 @cookies = {}ここでは、この空の
{}に対してupdateメソッドを呼んでいます。
Hash#updateメソッドはHash#merge!のエイリアス。処理を実行すると、こうなります。
@cookies = {'Cookieの名前': 'Cookieの値'}(
other_hashが空の{}の場合は、何も起きない)その後、
self(=jar、つまりCookies::CookieJar.buildでできたインスタンス)を返しています。(余談)
↑の方でtapを使った理由を「インスタンスを返すため」と記述しているのですが、ここでもインスタンス自身を返しているため、どちらか片方の処理で良いのではという疑惑あり…PRチャンス?また、先述の通りこのインスタンスは
actionpack/lib/action_dispatch/middleware/cookies.rb13 self.cookie_jar = Cookies::CookieJar.build(self, cookies)ここで
self.cookie_jar=メソッドに引数として渡されて、actionpack/lib/action_dispatch/middleware/cookies.rb28 def cookie_jar=(jar) 29 set_header "action_dispatch.cookies", jar 30 endここで
@env = {"action_dispatch.cookies": できあがったインスタンス}としてヘッダーに追加されます。ここまでのまとめ
アプリケーションから
cookiesメソッドを呼ぶと、結果的には次のようなリクエストヘッダができる@env = { "action_dispatch.cookies": Cookies::CookieJarのインスタンス}, # 初めてCookieを使用する場合 RACK_REQUEST_COOKIE_HASH: {}, # すでにCookieが存在する場合 RACK_REQUEST_COOKIE_STRING: リクエストメッセージから受け取ったCookieヘッダ }最後に[]=
cookies[:hoge] = :fuga
cookiesに値を代入する処理を見ていきます?actionpack/lib/action_dispatch/middleware/cookies.rb
14 C /vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:374#[]=ここからはRailsのお話?
#[]=メソッドを読んでいきましょう。ActionDispatch::Cookies::CookieJar#[]=
actionpack/lib/action_dispatch/middleware/cookies.rb170 # class Cookies # ... 272 # class CookieJar # ... 372 # Sets the cookie named +name+. The second argument may be the cookie's 373 # value or a hash of options as documented above. 374 def []=(name, options) 375 if options.is_a?(Hash) 376 options.symbolize_keys! 377 value = options[:value] 378 else 379 value = options 380 options = { value: value } 381 end 382 383 handle_options(options) 384 385 if @cookies[name.to_s] != value || options[:expires] 386 @cookies[name.to_s] = value 387 @set_cookies[name.to_s] = options 388 @delete_cookies.delete(name.to_s) 389 end 390 391 value 392 end
ActionDispatch::Cookies::CookieJar#[]=の中身はこんな感じ。一行ずつ
actionpack/lib/action_dispatch/middleware/cookies.rb374 def []=(name, options)これを定義すると、
[:name] = optionsという形で値を代入できるようになります。
それではここで、Railsアプリからcookies[]= に渡せる値を確認しましょう?
cookie[:hoge] = "fuga"のように文字列を渡すことが多いと思いますが、オプションを細かく設定する場合、右辺にはハッシュを渡すこともできます。cookies[:hoge] = { value: 'Cookieの値', expires: 'Cookieの有効期限', path: 'Cookieが適用されるパス(デフォルトは/)', domain: 'Cookieが適用されるドメイン', tld_length: 'domain: :allの時、TLDの一部として解釈される短い(3文字以下の)ドメインを使用するときに、TLDの長さを明示的に設定', secure: '暗号化通信のみを有効化(デフォルトはfalse)', httponly: 'スクリプト経由もしくはHTTP通信のみを有効化(デフォルトはfalse)' }これを踏まえて、引き続き処理を読んでいきます。
①引数optionsがハッシュの場合
actionpack/lib/action_dispatch/middleware/cookies.rb375 if options.is_a?(Hash) 376 options.symbolize_keys! 377 value = options[:value] 378 else
cookies[:hoge] = {"value" => "fuga}の場合、symbolize_keys!で{:value => "fuga"}に変換。変数
valueに"fuga"を代入。②引数optionsがハッシュ以外の場合
actionpack/lib/action_dispatch/middleware/cookies.rb378 else 379 value = options 380 options = { value: value } 381 end
cookies[:hoge] = "fuga"の場合、変数valueに"fuga"を代入。
optionsに{value: "fuga"}を再代入。今の状態
①②いずれの場合も、
optionsは{value: "fuga"}valueはfugaとなっている。
つづき
actionpack/lib/action_dispatch/middleware/cookies.rb383 handle_options(options)同じクラスの
handle_options(options)を呼びます。ActionDispatch::Cookies::CookieJar#handle_options
handle_optionsはoptionsをいい感じにするためのメソッドです。actionpack/lib/action_dispatch/middleware/cookies.rb350 def handle_options(options) # :nodoc: 351 if options[:expires].respond_to?(:from_now) 352 options[:expires] = options[:expires].from_now 353 end 354 355 options[:path] ||= "/" 356 357 if options[:domain] == :all || options[:domain] == "all" 358 # If there is a provided tld length then we use it otherwise default domain regexp. 359 domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP 360 361 # If host is not ip and matches domain regexp. 362 # (ip confirms to domain regexp so we explicitly check for ip) 363 options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp) 364 ".#{$&}" 365 end 366 elsif options[:domain].is_a? Array 367 # If host matches one of the supplied domains without a dot in front of it. 368 options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") } 369 end 370 end中身はこんな感じ。
一行ずつ
actionpack/lib/action_dispatch/middleware/cookies.rb351 if options[:expires].respond_to?(:from_now) 352 options[:expires] = options[:expires].from_now 353 end
cookiesメソッドに有効期限を設定した場合(例:1.week)、from_nowで具体的な日時に変換してoptions[:expires]に再代入。
from_nowはsinceのエイリアス。actionpack/lib/action_dispatch/middleware/cookies.rb355 options[:path] ||= "/"
cookiesメソッドにパスが指定されていない場合、ルートパスを設定(デフォルト値として)actionpack/lib/action_dispatch/middleware/cookies.rb357 if options[:domain] == :all || options[:domain] == "all" 358 # If there is a provided tld length then we use it otherwise default domain regexp. 359 domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP 360 361 # If host is not ip and matches domain regexp. 362 # (ip confirms to domain regexp so we explicitly check for ip) 363 options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp) 364 ".#{$&}" 365 end 366 elsif options[:domain].is_a? Array 367 # If host matches one of the supplied domains without a dot in front of it. 368 options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") } 369 end 370 end誰か正規表現に強い方…………
[:domain]オプションが:allor"all"の場合
- [:tld_length]が指定されている場合は
/([^.]+\.?){#{options[:tld_length]}}$/- そうでない場合は
DOMAIN_REGEXPを変数
domain_regexpに代入
(※DOMAIN_REGEXPは/[^.]*\.([^.]*|..\...|...\...)$/)リクエストメッセージのホストが
/^[\d.]+$/に一致しない場合&&変数domain_regexpに一致する場合はoptions[:domain]を".#{$&}"で置き換える
[:domain]オプションが配列の場合
options[:domain]を、リクエストメッセージのホストが含まれる要素で置き換えるここでやったこと
- 有効期限を具体的な日時に変換
- パスの設定がない場合にデフォルト値を設定
- ドメインをいい感じに再代入(?)
ActionDispatch::Cookies::CookieJar#[]=のつづき
今の状態
①②いずれの場合も、
-optionsは{value: 'fuga'}
-valueはfugaとなっています。
ActionDispatch::Cookies::CookieJar#[]=のつづき
actionpack/lib/action_dispatch/middleware/cookies.rb385 if @cookies[name.to_s] != value || options[:expires] 386 @cookies[name.to_s] = value 387 @set_cookies[name.to_s] = options 388 @delete_cookies.delete(name.to_s) 389 end 390 391 value 392 end
@cookies["hoge"]のvalueが"fuga"でない場合(つまり値が変わっている場合)、もしくは有効期限の設定がない場合に、
@cookies["hoge"]に"fuga"を代入@set_cookiesにoption{value: 'fuga'}を代入@delete_cookiesから[:hoge]を削除するをして、最後に
"fuga"を返します。最後に
trace_locationでログに残っていたのはここまでですが、
先述の通りActionDispatch::Cookiesはcallメソッドを持ったRackミドルウェアとして実装されており、
Railsアプリケーションから使用されています。$ rails middleware use Webpacker::DevServerProxy use ActionDispatch::HostAuthorization use Rack::Sendfile use ActionDispatch::Static use ActionDispatch::Executor use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use ActionDispatch::RemoteIp use Sprockets::Rails::QuietAssets use Rails::Rack::Logger use ActionDispatch::ShowExceptions use WebConsole::Middleware use ActionDispatch::DebugExceptions use ActionDispatch::ActionableExceptions use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActionDispatch::Cookies # ←ここ use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ContentSecurityPolicy::Middleware use Rack::Head use Rack::ConditionalGet use Rack::ETag use Rack::TempfileReaper run Myapp::Application.routesこのことから、Railsアプリケーションにリクエストがあった際、Rackアプリとして次の処理が呼ばれることがわかります。
actionpack/lib/action_dispatch/middleware/cookies.rb170 # class Cookies # ... 637 def initialize(app) 638 @app = app 639 end 640 641 def call(env) 642 request = ActionDispatch::Request.new env 643 644 status, headers, body = @app.call(env) 645 646 if request.have_cookie_jar? 647 cookie_jar = request.cookie_jar 648 unless cookie_jar.committed? 649 cookie_jar.write(headers) 650 if headers[HTTP_HEADER].respond_to?(:join) 651 headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n") 652 end 653 end 654 end 655 656 [status, headers, body] 657 end中身は詳しく追っていきませんが、
actionpack/lib/action_dispatch/middleware/cookies.rb647 cookie_jar = request.cookie_jar 648 unless cookie_jar.committed? 649 cookie_jar.write(headers) 650 if headers[HTTP_HEADER].respond_to?(:join) 651 headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n") 652 end 653 endこの辺りでヘッダーへの書き込みが行われていることが確認できました。
今回追うことができなかった処理・残された疑問点
"rack.request.cookie_hash"がヘッダーに書き込まれるタイミングCookies::CookieJar#write→Cookies::CookieJar#make_set_cookie_headerから、最終的に呼ばれる::Rack::Utils.add_cookie_to_header(↑の資料では未出)
- ヘッダーに書き込む文字列の成形を行なっていることが確認できるが、その後実際にヘッダーに書き込まれるタイミング
おつかれさまでした?
参考資料
- Ruby on Railsチュートリアル 第9章 発展的なログイン機構 9.1.2 ログイン状態の保持
- RFC 6265 HTTP 状態管理の仕組み( “HTTP cookie” )
- SuikaWiki メタ変数 - プロトコル依存のメタ変数
- Rack: a Ruby Webserver Interface
- TraceLocation
おまけ
TraceLocationで作成したログは次の通りです(マークダウン形式)
Generated by trace_location at 2019-06-05 20:28:50 +0900
actionpack-6.0.0.rc1/lib/action_controller/metal/cookies.rb:12
ActionController::Cookies#cookies
def cookies request.cookie_jar end # called from /Users/misakishioi/Projects/myapp/app/controllers/blogs_controller.rb:8
actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:11
ActionDispatch::Request#cookie_jar
def cookie_jar fetch_header("action_dispatch.cookies") do self.cookie_jar = Cookies::CookieJar.build(self, cookies) end end # called from actionpack-6.0.0.rc1/lib/action_controller/metal/cookies.rb:13
rack-2.0.7/lib/rack/request.rb:58
Rack::Request::Env#fetch_header
def fetch_header(name, &block) @env.fetch(name, &block) end # called from actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:12
actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:374
ActionDispatch::Cookies::CookieJar#[]=
def []=(name, options) if options.is_a?(Hash) options.symbolize_keys! value = options[:value] else value = options options = { value: value } end handle_options(options) if @cookies[name.to_s] != value || options[:expires] @cookies[name.to_s] = value @set_cookies[name.to_s] = options @delete_cookies.delete(name.to_s) end value end # called from /Users/misakishioi/Projects/myapp/app/controllers/blogs_controller.rb:8
actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:350
ActionDispatch::Cookies::CookieJar#handle_options
def handle_options(options) # :nodoc: if options[:expires].respond_to?(:from_now) options[:expires] = options[:expires].from_now end options[:path] ||= "/" if options[:domain] == :all || options[:domain] == "all" # If there is a provided tld length then we use it otherwise default domain regexp. domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP # If host is not ip and matches domain regexp. # (ip confirms to domain regexp so we explicitly check for ip) options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp) ".#{$&}" end elsif options[:domain].is_a? Array # If host matches one of the supplied domains without a dot in front of it. options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") } end end # called from actionpack-6.0.0.rc1/lib/action_dispatch/middleware/cookies.rb:383
- 投稿日:2019-07-07T12:25:13+09:00
初心者)Twitter クローン課題の苦労した点ピックアップ
1.コメントコミット忘れがち。。
git add .
からの、、
git commit -m 'ここにコメント作成します'
メンターさんにも課題提出時に指摘されたのですが、コミットする粒度が荒い。要はもっとコマメにセーブポイントを作りなさいとのこと。
最初は細かすぎるくらいのイメージでやって徐々に粒度を荒くしていく感じがいいのかもしれない。例えばこんな感じでやればよかったのかも、、
データベース作成(ファーストコミット)
⬇
モデル作成・マイグレーション(コミット)
⬇
バリデーション作成(コミット)
⬇
コントローラー作成(コミット)
⬇
indexページの作成(コミット)
⬇
showページの作成(コミット)
⬇
......2.Confirm画面がややこしい
hidden_fieldというのがピンとこないんだが、
要はフォーム画面なんだけど、入力フォームを生成せずに値のみを保持する機能ということらしいです。
値の表示については下記のPタグで別途出力している。
(Pタグを削除すると値の表示はされないが、hidden_field で値は保持されているので登録は可能)ただこのままではNewアクションのForm_withはCreateに飛んでしまうので、確認画面に飛ぶようにルーティングし直す必要がある。
ヘルパーを使ってNewとEditの分岐をするのですが、その際はHTMLのFoamタグに必須のAction属性を使って分岐させる。
その際はFoamタグ作成の際にはAction属性(必須)とMethod属性(オプション)の指定を行う。
Action属性
1.formタグに指定する属性で、必ず指定しなければならない
2.フォームの送信ボタンを押して送信されるデータの送信先を指定する
Method属性
1.formタグに指定する属性で、必須ではない
2.送信するときの転送方法を指定する
3.postとgetがあるmodule BlogsHelper def choose_new_or_edit if action_name == 'new' || action_name == 'confirm' confirm_blogs_path elsif action_name == 'edit' blog_path end end end3.確認画面から値を保持しつつ「戻る」のがややこしい
hidden_fieldは値を保持しているので、これを使って値をnewのページを送り返している。戻るボタンのform_withはnewアクションを実行リクエストするため、HTTPメソッドがgetになるので注意してください。
さっき出てきたMethod属性-getを活用しています。
それはformタグのmethodをgetに指定すると、入力したフォーム内容のデータがURIにくっついて送信されるから。
ちなみにmethodをpostに指定すると、入力したフォーム内容はURIとは別の場所に保管されてデータが送信される。
入力したフォーム内容のデータは外側からの表示では見ることができないので安全性が高いのが利点となる。<%= form_with(model: @blog, url: new_blog_path, local: true, method: 'get') do |form| %> <%= form.hidden_field :title %> <%= form.hidden_field :content %> <%= form.submit "戻る" %> <% end %>(おまけ) 何故、formに"local: true"を入れるのか
検索から。。
local: trueがない場合、Rails 5ではAjaxによる送信という意味になります。ふつうにHTMLとしてフォームを送信する場合にlocal:trueが必要となります。
- 投稿日:2019-07-07T10:59:41+09:00
データベースに保存されている時刻とビューに表示される時刻が違う
問題:データベースの値とビューに表示される時刻が違う
仮説
1.DateTime.parse(string)で生成されたDateTimeクラスのインスタンスは協定世界時になる
2.datetime型のカラムに保存した時にJST時間に修正される解決
1.DateTime.parse(string)で生成されるインスタンスはUTCになるは正しかったぽい参考記事
parseはUTCのタイムゾーンを使う。(環境変数のタイムゾーンを無視する)
l DateTime.parse(str)
=> 2015/01/01 00:00:00, +00:00, DateTime2.MysqlのDATETIME型はActiveSupport::TimeWithZoneクラスであるので、レコードを保存する時に内部でこんな感じの処理が行われる、だと思う。
time = DateTime.parse(string) => Tue, 09 Jul 2019 12:00:00 +0000 Time.zone.parse(time.to_s) => Tue, 09 Jul 2019 21:00:00 JST +09:00 Time.zone.parse(time.to_s).class => ActiveSupport::TimeWithZone Time.zone.parse(time.to_s).strftime("%Y年%-m月%-d日 %-H時%-M分") => "2019年7月9日 21時0分"> string => "201907091200" > Time.zone.parse(string) => Tue, 09 Jul 2019 12:00:00 JST +09:00 #DateTime.parseした場合 > DateTime.parse(string) => Tue, 09 Jul 2019 12:00:00 +0000 //UTCで返ってくる > @event.deadline_date => Tue, 09 Jul 2019 21:00:00 JST +09:00 > @event.deadline_date.class => ActiveSupport::TimeWithZone #Time.parseした場合 > Time.parse(string) => 2019-07-09 12:00:00 +0900 //JST(アプリケーションで設定したタイムゾーン)で返ってくる > @event.deadline_date => Tue, 09 Jul 2019 12:00:00 JST +09:00 > @event.deadline_date.class => ActiveSupport::TimeWithZone仮説
データベースに保存される時にDateTimeクラスからTimeWithZoneクラスに変換される
> string => "201907060600" > DateTime.parse(string) => Sat, 06 Jul 2019 06:00:00 +0000 > Time.zone.parse(DateTime.parse(string).to_s) => Sat, 06 Jul 2019 15:00:00 JST +09:00でも違うのか?
DateTimeクラスのデータをデータベースに保存するときに保存先のデータ型に変換されて保存されると思っていたけれど、データベースには 2019-07-06 06:00:00 で格納されていた。(画像3行目)
でも引っ張り出すとTimeWithZone型でJSTの時刻になっていた。> @event.deadline_date => Sat, 06 Jul 2019 15:00:00 JST +09:00 > @event.deadline_date.class => ActiveSupport::TimeWithZone > @event.deadline_date.strftime("%Y年%-m月%-d日 %-H時%-M分") => "2019年7月6日 15時0分"解決
データベースに保存された時には実際にデータ型への変換が行われるが、UTCの時刻で表示されている
アプリケーション側のタイムゾーンをtokyoつまりJSTに設定している場合はTime.parseを使ったほうがいいのかもしれないご指摘ください
7月からエンジニアになりました
間違っているところがありましたらご指摘ください
時間ある時に修正します
何卒よろしくお願いします
- 投稿日:2019-07-07T10:21:31+09:00
Go言語とRubyの比較【入門】①
はじめに
記念すべき初めての投稿です。初心者エンジニアです。
仕事でどちらも使用するので
Go言語とRubyの基本的なところをまとめてみようと思います。Hello World!
Rubyの場合
helloworld.rbputs "Hello World!" #=> "Hello World!"Goの場合
helloworld.gopackage main import "fmt" func main() { fmt.Println("Hello World!") } //=> "Hello World!"変数宣言
Rubyの場合
variable.rbnum = 1 name = "test" pi = 3.14 t = true f = false puts num, name, pi, t, fRuby実行結果
1 test 3.14 true falseGoの場合
variable.gopackage main import "fmt" //基本 var ( i int = 1 s string = "test" f64 float64 = 3.14 t, f bool = true, false ) //省略型 func test() { xi := 1 xs := "test" xf64 := 3.14 xt, xf := true, false fmt.Println(xi, xs, xf64, xt, xf) } func main() { fmt.Println(i, s, f64, t, f) test() }Go実行結果
1 test 3.14 true false 1 test 3.14 true false定数
Rubyの場合
一度定義した定数に再代入を行うと警告は出るが値は変わる。※要注意
変更を防ぐためにfreezeメソッドを使う方法はまたの機会に。const.rb# 定数は必ず大文字で始める必要あり COUNTRY = "JAPAN" # 破壊的変更 COUNTRY.downcase! # 変更されてしまう COUNTRY #=> "japan" # 再代入 COUNTRY = "SPAIN" # 警告発生するが値は変更されてしまう warning: already initialized constant COUNTRY warning: previous definition of COUNTRY was here COUNTRY #=> "SPAIN"Goの場合
Rubyと違い、定数の定義後に値を代入することはできません。
const.gopackage main import "fmt" //constキーワードを使用して定数を定義 const Pi = 3.14 //まとめて定義 const A, B, C int = 0, 1, 2 const ( Username = "test_user" Password = "test_pass" ) func main() { fmt.Println(Pi) fmt.Println(A, B, C) fmt.Println(Username, Password) fmt.Printf("%T %T %T %T %T %T", Pi, A, B, C, Username, Password) //型確認 }Go実行結果
3.14 0 1 2 test_user test_pass float64 int int int string string
- 投稿日:2019-07-07T05:25:54+09:00
Railsで指定したディレクトリ以下のファイルを取得する時の解決方法
Railsで指定したディレクトリ以下のファイルを取得する時につまずいたので共有します。
環境
バージョン
$ rails -v Rails 5.2.3 $ ruby -v ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]ディレクトリ構成(該当部分のみ)
app/commands/command.rb app/commands/command/hoge_command.rb app/commands/command/fuga_command.rb app/commands/command/piyo_command.rbソースコード
# app/commands/command.rb class Command def initialize(command_name) files_abs_pass = Dir[File.expand_path("#{Rails.root}/app/commands/command/", __FILE__) << '/*.rb'] files_abs_pass.each { |f| puts f } # /Users/user/work/rails/rails_app/app/commands/command/hoge_command.rb # /Users/user/work/rails/rails_app/app/commands/command/fuga_command.rb # /Users/user/work/rails/rails_app/app/commands/command/piyo_command.rb end end# app/commands/command/hoge_command.rb class HogeCommand < Command end当初はこのページを参考にしていたのですが、ファイル一覧を取得できなかったので変えました。
# 掲載元のコード Dir[File.expand_path('../commands', __FILE__) << '/*.rb'].each do |file| require file endJavaのJSPでも同じような事象に悩まされていたので、解決方法はこれでいいんじゃないかって思えましたw(uriの指定)
もっとスマートに書けるはずなので、気が向いたらあとがきにでも書きます。。。(コメント頂けたらとても嬉しいです...!)
引用元







