Rails と Rack について
この記事は、「フィヨルドブートキャンプ Part 2 Advent Calendar 2023」15日目の記事です。
フィヨルドブートキャンプ Part 2 Advent Calendar 2023 - Adventar
Part 1はこちらになります。
フィヨルドブートキャンプ Part 1 Advent Calendar 2023 - Adventar
前回は、dawaさんの「テストとは。最初のイシューでエラーに遭遇した話。」でした。
この記事は、Rails.application
を調べた過程で学んだことをまとめたものになります。
この記事で伝えたいことは、次の2つです。
では、私がRails.application
の存在を知ったきっかけから始めましょう。
Rails.applicationとの出会い
以前Railsコンソール上でpolymorphic_url
の挙動を調べようとしたことがあり、その時にapp
メソッドの存在を知りました。
このメソッドを利用すると、次の様に名前付きルーティングヘルパーを呼び出したり、リクエストを投げることが可能です。
irb(main):001> app.books_path => "/books" irb(main):002> app.polymorphic_url(Book.new) => "http://www.example.com/books" irb(main):003> app.get "http://localhost:3000/books" Started GET "/books" for 127.0.0.1 at 2023-12-10 10:53:13 +0900 ActiveRecord::SchemaMigration Load (0.5ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC Processing by BooksController#index as HTML Rendering layout layouts/application.html.erb Rendering books/index.html.erb within layouts/application Book Load (0.6ms) SELECT "books".* FROM "books" Rendered books/_book.html.erb (Duration: 0.3ms | Allocations: 275) Rendered books/_book.html.erb (Duration: 0.0ms | Allocations: 54) Rendered books/_book.html.erb (Duration: 0.0ms | Allocations: 53) Rendered books/_book.html.erb (Duration: 0.0ms | Allocations: 53) Rendered books/index.html.erb within layouts/application (Duration: 2.7ms | Allocations: 2243) Everything's up-to-date. Nothing to do Rendered layout layouts/application.html.erb (Duration: 15.9ms | Allocations: 15124) Completed 200 OK in 30ms (Views: 17.9ms | ActiveRecord: 0.2ms | Allocations: 19944) => 200
RailsAPIにはapp
メソッドの説明もありました。
reference the global “app” instance, created on demand. To recreate the instance, pass a non-false value as the parameter.
「グローバルな"app"インスタンスを参照する」とあります。 グローバルな"app"インスタンスってなんだ?と気になり、コードを見てみることに。
module Rails module ConsoleMethods # reference the global "app" instance, created on demand. To recreate the # instance, pass a non-false value as the parameter. def app(create = false) @app_integration_instance = nil if create @app_integration_instance ||= new_session end # create a new session. If a block is given, the new session will be yielded # to the block before being returned. def new_session app = Rails.application session = ActionDispatch::Integration::Session.new(app) # This makes app.url_for and app.foo_path available in the console session.extend(app.routes.url_helpers) session.extend(app.routes.mounted_helpers) session end
rails/railties/lib/rails/console/app.rb at 6b93fff8af32ef5e91f4ec3cfffb081d0553faf0 · rails/rails
app
メソッド内で呼ばれているnew_session
メソッドは、Rails.application
を引数にしてActionDispatch::Integration::Session
をインスタンス化したものを返しています。
ActionDispatch::Integration::Session
のインスタンスは、テストプロセスによって順次実行されるリクエストとレスポンスのセットを表す、とのこと。
Rails.application
に関しては、Railsガイドに説明がありました。
Rails.applicationは、Railsアプリケーションにおける主要なRackアプリケーションです。Rackに準拠したWebサーバーで、Railsアプリケーションを提供するには、Rails.applicationオブジェクトを使う必要があります。
Rack って聞いたことはあるけど何なんだろう?と気になったので、Rack について調べてみることにしました。
Rackとは?
Rackについて簡単にまとめると、次の様になります。
「Rackとは、WebサーバーとRuby製Webアプリケーションがやりとりを行うための規約」のこと。
1PumaやUnicornといったWebサーバーとRailsアプリケーションがやりとりを行えるのは、RailsがRackの規約に沿って実装されているからなのです。
WebサーバーとRailsアプリケーションのやり取りの中身は、Rackアプリケーションの仕様から知ることができます。
Rackアプリケーションとは、以下の条件を満たすRubyオブジェクトのことを言います。
environment
という引数一つを受け取る、call
メソッドを呼び出すことができる。status
headers
body
という三つの値を含む、凍結されていない配列を返す
rack/SPEC.rdoc at main · rack/rack
Webサーバーは「envハッシュ」と呼ばれるHTTPリクエストの情報が格納されたハッシュを用意し、それを引数としてcall
メソッドを呼び出します。
Railsアプリケーションはcall
メソッドの返り値として、以下の三つの値を格納した配列をWebサーバーに返します。
status
: HTTPステータスコードを表す数値headers
: レスポンスヘッダーを含むハッシュbody
: レスポンスのボディとなるもので、通常は文字列の配列など
では、実際にどのような値が返ってくるのか確認してみましょう!
コンソール上で実験
Railsコンソール上で、Rails.application
に対しcall`メソッドを呼び出してみます。
下準備として、rails new hello
でアプリを新規作成し、/hello
にアクセスするとHello, World!
と表示される様にします。
call
メソッドに渡す「envハッシュは」、rack gemのRack::MockRequest.env_for
を利用して用意します。
irb(main):001> env = Rack::MockRequest.env_for('http://localhost:3000/hello') => {"rack.input"=>#<StringIO:0x00000001077b1760>, ...
そのままcall
メソッドに渡すとBlocked hostと言われてしまうため、「envハッシュ」にHTTP_HOST
ヘッダーを追加します。
irb(main):002> env['HTTP_HOST'] = 'localhost' => "localhost"
では、「envハッシュ」を引数にRails.application
に対してcall
メソッドを呼び出します。
irb(main):003> status, headers, body = Rails.application.call(env) Started GET "/hello" for at 2023-12-10 15:31:40 +0900 Cannot render console from ! Allowed networks: 127.0.0.0/127.255.255.255, ::1 ActiveRecord::SchemaMigration Load (0.3ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC Processing by HellosController#index as HTML Rendering layout layouts/application.html.erb Rendering hellos/index.html.erb within layouts/application Rendered hellos/index.html.erb within layouts/application (Duration: 0.2ms | Allocations: 209) Everything's up-to-date. Nothing to do Rendered layout layouts/application.html.erb (Duration: 12.8ms | Allocations: 13220) Completed 200 OK in 20ms (Views: 14.4ms | ActiveRecord: 0.0ms | Allocations: 17125) MiniProfiler storage failure: no implicit conversion of nil into String => [200,
Completed 200 OK
とあるので、このリクエストは成功した様ですね。
返り値を確認してみると、HTTPステータス、レスポンスヘッダー、レスポンスボディに対応した値が格納されていることがわかります。
irb(main):004> status => 200 irb(main):005> headers => {"x-frame-options"=>"SAMEORIGIN", "x-xss-protection"=>"1; mode=block", "x-content-type-options"=>"nosniff", "x-download-options"=>"noopen", "x-permitted-cross-domain-policies"=>"none", "referrer-policy"=>"strict-origin-when-cross-origin", "link"=> "</assets/application.debug-ead91a25923de99455378da7f1f1bb5a6839a249af27af911ec2b81709b046b7.css>; rel=preload; as=style; nopush,</packs/js/application-d4389a205e3a305a430b.js>; rel=preload; as=script; nopush", "content-type"=>"text/html; charset=utf-8", "etag"=>"W/\"cd556a718561e5e4f0c288ed64067335\"", "cache-control"=>"max-age=0, private, must-revalidate", "set-cookie"=> ["_hello_session=oNirYlMTT8MTrQFwUPjyqDIKnSZbe1SNGXcQMfesZ5FqJkYFwgpcfBeeGJ7sinDzFkrpGj40DUfjR9G8fOGHTys7Rx3fxmEa06ErCuNjge%2Bu1hWdjLpryWkBIElXgQF9dPBKvRJ2J2m50%2Bvhm5PRHfyy9Dp%2BN6fFvRxnuNtz2igBGQbqdVt9iNnW5Fl4zNlOM4516%2FQxiSQvtdAUhjGMRIQwwU3dHkjGMiYP0jZCPZCKq%2BoqlNDGke19aXZpiu5lealK4wA%2Fu3KIGv1KoEmuXla2tQcPLw%3D%3D--%2BaxhXWvOw%2FzjtOZL--bigBnV371hxh0QYi0EMAUw%3D%3D; path=/; httponly; SameSite=Lax", "__profilin=p%3Dt; path=/; httponly; SameSite=Lax"], "x-request-id"=>"f404e8ff-5504-4808-8e44-a87547285fab", "x-runtime"=>"0.048442"} irb(main):006> headers => #<Rack::BodyProxy:0x0000000107adc390 @block=#<Proc:0x0000000107adc368 /Users/kashiyamashintarou/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/actionpack-7.1.2/lib/action_dispatch/middleware/executor.rb:15>, 途中省略... @response= #<ActionDispatch::Response:0x0000000107d32630 途中省略... @stream= #<ActionDispatch::Response::Buffer:0x0000000106dc0ff8 @buf= ["<!DOCTYPE html>\n<html>\n <head>\n <title>Hello</title>\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n <meta name=\"csrf-param\" content=\"authenticity_token\" />\n<meta name=\"csrf-token\" content=\"TVqQW_tF8fRvPSePyWVN10xc4vV8Fc-nwR8ffja8FhyYM_x59CsJ8KRxawd2VPBiL30Tf0xhAzxQarpFGZONjQ\" />\n \n \n <link rel=\"stylesheet\" href=\"/assets/application.debug-ead91a25923de99455378da7f1f1bb5a6839a249af27af911ec2b81709b046b7.css\" media=\"all\" data-turbolinks-track=\"reloa d\" />\n <script src=\"/packs/js/application-d4389a205e3a305a430b.js\" data-turbolinks-track=\"reload\"></script>\n </head>\n\n <body>\n <p>Hello World!</p>\n\n </body>\ n</html>\n"],
Rackアプリケーションの仕様に基づいて、Rails.application
がstatus・headers・bodyを返すことが分かりました👍
Rails.applicationのクラス
Rails.application
はRackアプリケーションであることが分かりましたが、それはどの様なオブジェクトなのでしょうか?
クラスを確認してみます。
irb(main):001> Rails.application.class => Hello::Application
Hello::Application
と表示されましたね。
Railsの起動プロセスについて調べてみると、次の様な記述がありました。
The application is also responsible for setting up and executing the booting process. From the moment you require config/application.rb in your app, the booting process goes like this:
アプリケーション内でconfig/application.rb
をrequire
した瞬間から起動プロセスが実行され、3番目に「Rails.application
をMyApp::Application < Rails::Application
として定義する」とあります。
起動プロセスを通じて、Rails.application
はアプリケーション名::Application
となることが分かりました。
config/application.rb
を確認してみると、アプリケーション名::Application
となっていますね。
require_relative "boot" require "rails/all" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module Hello class Application < Rails::Application #...
試しに名前空間を変更してみると、Rails.application
のクラス名も変更されることが確認できます。
module GoodBye # HelloからGoodByeに変更 class Application < Rails::Application
irb(main):001> Rails.application.class => GoodBye::Application
最後に、Rails.application
の継承関係を見てみましょう。
祖先にはRails::Engine
やRails::Railtie
というクラスが存在していることが分かりました。
irb(main):001> Rails.application.class => Hello::Application irb(main):002> Rails.application.class.superclass => Rails::Application irb(main):003> Rails.application.class.superclass.superclass => Rails::Engine irb(main):004> Rails.application.class.superclass.superclass.superclass => Rails::Railtie irb(main):005> Rails.application.class.superclass.superclass.superclass.superclass => Object
終わりに
色々と調べる中で、Railsアプリケーションに対する解像度が少し上がった気がします。
Railsの中身を調べることは楽しいですね(コードを読んだり調べたりするのは大変ですが😇)。
今回の調査で十分理解できなかった箇所(Railsの起動プロセスの詳細など)は、レベルアップしてから再び調べることにします。
これからも自分の好奇心を大切に、興味を持ったことをどんどん深掘りしていきたいと思います!
参考文献
小ネタ : 開発環境でRailsアプリの日付を変更する
はじめに
開発環境で特定の日付に移動したい時ってありませんか?
私は チーム開発1のプラクティスを行っていて、「祝日の場合ダッシュボードの表示を変更したい」というIssueに取り組みました。
実装を行って祝日の表示確認を行う際、アプリ内の日付を祝日に変更する必要に迫られたのです。
以下の方法で開発環境の日付を一時的に変更することが可能ですので、今回ご紹介します。
変更方法
config/environments/development.rb
に以下を追加して、アプリを再起動する。
require "active_support/core_ext/integer/time" # 追加 require "active_support/testing/time_helpers" # 追加 include ActiveSupport::Testing::TimeHelpers # 追加 Rails.application.configure do config.after_initialize do travel_to Time.zone.parse('移動したい日付') # 追加 end end
試しに今日の日付を表示する簡単なアプリを作成して、アプリ内の日付が変更されているか確認します。
祝日であるかどうかの確認のために、holiday-jpというGemを利用しています。
(app/views/home/index.slim)
h2 今日の日付 p | 今日の日付は"#{l(Date.current, format: :long)}"です。 p | #{HolidayJp.holiday?(Date.current) ? '今日は祝日です!🥳' : '今日は祝日ではありません🫠'} h2 現在時刻 p | 現在時刻は"#{l(Time.current)}"です。
普通にアプリを起動した場合(日付は9/18日)。
アプリ内の日付を変更した場合(日付を9/25に変更)。
日付が変わっていることが分かります!
何を行っているの?
日付の変更を行うには、travel_to
メソッドを利用します。
通常travel_to
はテスト内でしか使用することができないため、travel_to
を使う下準備として次のことを行います。
travel_to
が定義してあるファイル(active_support/testing/time_helpers)をrequireする- モジュール(ActiveSupport::Testing::TimeHelpers)をincludeする
そして、config.after_initialize
ブロック内でtravel_to
を実行し、変更したい日付を渡します。
config.after_initialize
ブロック内に記述されたコードは、Railsによってアプリケーションの初期化が完了した後に実行されます。
Rails アプリケーションを設定する - Railsガイド
travel_to
は、Time.now
Date.today
DateTime.now
を引数で与えられた時間または日付を返すようにスタブ化することで、現在の時刻を特定の時刻に変更することができます。
スタブはテスト終了時に削除されます。
ActiveSupport::Testing::TimeHelpers
Time.currentとDate.currentについて
RailsAPIのtravel_to
の頁に、次のような記述がありました。
Dates are taken as their timestamp at the beginning of the day in the application time zone. Time.current returns said timestamp, and Time.now its equivalent in the system time zone. Similarly, Date.current returns a date equal to the argument, and Date.today the date according to Time.now, which may be different. (Note that you rarely want to deal with Time.now, or Date.today, in order to honor the application time zone please always use Time.current and Date.current.)
簡単にまとめると、「Time.current
とDate.current
はアプリケーションのタイムゾーンに応じた時刻と日付を返し、Time.now
とDate,today
はシステムのタイムゾーンに応じた時刻と日付を返すため、アプリ内のタイムゾーンを尊重したい場合はTime.current
とDate.current
を使いましょう」ということが書かれています。
では、このTime.current
とDate.current
という2つのメソッドはどのように現在時刻や日付を返しているのでしょうか?
実装を見てみようと思います。
Time.current
Time.current
は次のようになっています。
def current ::Time.zone ? ::Time.zone.now : ::Time.now end
Time.zone
にはタイムゾーン情報が格納されており、これが存在する場合はTime.zone.now
を、存在しない場合はTime.now
を返しています。
config.time_zone
にタイムゾーンを設定すると、Time.zone
は次のようになります。
irb(main):001:0> Time.zone => #<ActiveSupport::TimeZone:0x000000010eab9618 @name="Tokyo", @utc_offset=nil, @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>>
では、Time.zone.now
を確認しましょう。
def now time_now.utc.in_time_zone(self) end
最初にtime_now
メソッドが呼ばれています。
このメソッドはただTime.now
を読んでいるだけです。
def time_now Time.now end
次のutc
メソッドですが、おそらくRubyの組み込みライブラリであるTime
クラスのメソッドが呼ばれているのではないかと思います。
https://docs.ruby-lang.org/ja/latest/method/Time/i/gmtime.html
最後にin_time_zone
メソッドが呼ばれています。
タイムゾーンが存在する場合はActiveSupport::TimeWithZone
のインスタンスを作成していることが分かります。
def in_time_zone(zone = ::Time.zone) time_zone = ::Time.find_zone! zone time = acts_like?(:time) ? self : nil if time_zone time_with_zone(time, time_zone) else time || to_time end end private def time_with_zone(time, zone) if time ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) else ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) end end
コンソール上で確認すると、確かにActiveSupport::TimeWithZone
のインスタンスが返っていますね。
time_zone
属性にタイムゾーンの情報も格納されていることもわかります。
irb(main):004:0> Time.current => Mon, 18 Sep 2023 14:41:00.422901000 JST +09:00 irb(main):005:0> Time.current.class => ActiveSupport::TimeWithZone irb(main):006:0> Time.current.time_zone => #<ActiveSupport::TimeZone:0x000000010e90e908 @name="Tokyo", @utc_offset=nil, @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>>
Time.current
の実装を簡単ですが追ってみました。
途中でTime.now
メソッドを読んでおり、travel_to
はTime.now
をスタブ化するのでTime.current
の時間も変わるということが分かりましたね。
Date.current
ではDate.current
の方も見てみましょう。
Date.current
はTime.current
と同じく、タイムゾーンが設定されていればTime.zone.today
を、そうでなければDate.today
を返します。
def current ::Time.zone ? ::Time.zone.today : ::Date.today end
では、Time.zone.today
はどうなっているでしょうか。
def today tzinfo.now.to_date end
tzinfo
が呼ばれていますね。
ActiveSupport::TimeZone
が初期化される時に@tzinfo
に値が格納されます。
attr_reader :name attr_reader :tzinfo def initialize(name, utc_offset = nil, tzinfo = nil) @name = name @utc_offset = utc_offset @tzinfo = tzinfo || TimeZone.find_tzinfo(name) end
TimeZone.find_tzinfo
内ではTZInfo::Timezone.get
が呼ばれています。
def find_tzinfo(name) TZInfo::Timezone.get(MAPPING[name] || name) end
TZInfo::Timezone.get
はtzinfoというGemにより提供されるもので、個別のタイムゾーンを表すTZInfo::Timezone
のインスタンスを得ることができます。
A TZInfo::Timezone instance representing an individual time zone can be obtained with TZInfo::Timezone.get
https://github.com/tzinfo/tzinfo#example-usage
Time.zone.today
内で呼ばれるtzinfo
は、ActiveSupport::TimeZone
初期化時に設定されたTZInfo::Timezone
のインスタンスであることが分かりました。
tzinfo.now
は次のようになっています。
Time.now
を渡してto_local
メソッドを呼び出していますね。
def now to_local(Time.now) end def to_local(time) raise ArgumentError, 'time must be specified' unless time Timestamp.for(time) do |ts| TimestampWithOffset.set_timezone_offset(ts, period_for(ts).offset) end end
最後にto_date
メソッドを呼び出し、日付の文字列が返ってくるようになっています。
次のサンプルコードを実行してわかる通り、タイムゾーンに合わせた現在日時を取得していることが分かりますね。
irb(main):001:0> tz = TZInfo::Timezone.get('America/New_York') => #<TZInfo::DataTimezone: America/New_York> irb(main):002:0> tz.now => 2023-09-18 02:25:04.614519 -0400 irb(main):003:0> tz.now.to_date => Mon, 18 Sep 2023 irb(main):004:0> tz_japan = TZInfo::Timezone.get('Asia/Tokyo') => #<TZInfo::DataTimezone: Asia/Tokyo> irb(main):005:0> tz_japan.now => 2023-09-18 15:24:17.173699 +0900 irb(main):006:0> tz_japan.now.to_date => Mon, 18 Sep 2023
Date.current
の実装も簡単ですが追ってみました。
Date.current
も途中でTime.now
を読んでおり、travel_to
の影響を受けることが分かりました。
終わりに
Railsアプリケーションの開発環境で日付を変更する方法と、Time.current
とDate.current
について書きました。
Rails内のコードを読むのは難しく完璧に理解はできませんでしたが、いい勉強になりました!
また気になるコードがあれば、コードリーディングに挑戦してみたいと思います💪
参考文献
RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い - Qiita
- フィヨルドブートキャンプの終盤にあるプラクティスで、フィヨルドブートキャンプアプリ内で振られたIssueの開発を行う、ということを行います。↩
コミット修正時に使う操作まとめ
かなり久しぶりの投稿となりました、cassy0220です。
前回の投稿から今までの間に、FBCで様々なプラクティスに取り組み、課題を提出してきました。
課題を提出する際、コミットを整理してから提出するようにしているのですが、コミットの修正を行う時に「この操作はこれで大丈夫かな?」と何度も調べてしまいます。
そこで、備忘録も兼ねて私がコミットを修正する際によく使う操作をまとめることにしました。
確認する操作
ファイルの変更差分を確認する
% git diff
コミットする前に変更差分を確認して、セルフレビューする癖をつけましょう(戒め)。
もし、ステージングに上げた変更差分を確認したい場合は、git diff --cached
で確認することが出来ます。
コミット履歴を確認する
% git log --oneline
git log --oneline
で、コミット履歴を分かりやすく表示することが出来ます。
コミットのハッシュ値を確認したい時や、rebase 中にコミット履歴を確認したい時などによく使います。
特定のコミットの内容を確認する
% git show commit_hash
最後にファイル名を指定すると、指定したコミット内で指定したファイルのコミット内容が表示されます。
% git show commit_hash filename
コミットの指定はHEADで行うことも可能です。
% git show HEAD^
コミットを修正する操作
コミットを取り消す
% git reset
git reset
コマンドを使うと、現在のHEADを指定された状態にリセットします。
※ HEADとは、一般的にそのブランチで最後のコミットを指します。
git reset
コマンドには次のオプションがあり、巻き戻す内容が異なります。
次のファイルを例に、それぞれのオプションの実行結果をみていきます。
--soft
HEADだけを動かし、コミットだけが消えます。ファイルはステージ上に残ります。
「git reset --soft HEAD^
でコミット取り消し → ファイルを修正 → コミットする」という流れをよくやります。
--mixed
HEADとステージを巻き戻します。ファイルの変更差分はそのまま残ります。
--hard
HEAD、ステージ、ファイルの変更差分全てを元に戻します。
コミットの順番を変更する
% git rebase -i commit_hash
次のコミット履歴を例に、順番を並び替えてみたいと思います。
「add name」と「add age」のコミットを入れ替えてみましょう。
この場合、git rebase -i
で指定するコミットハッシュは、「add name」のひとつ前のコミットを指定することに注意して下さい。
% git rebase -i 3fd37fd
すると、次のようなコミット編集画面が表示されます。
この画面で、「add name」と「add age」のコミットの順番を入れ替えて、保存します。
もしコミットを入れ替えてコンフリクトが発生した場合は、次のことを実行します。
- コンフリクトが発生した箇所を修正する
git add
git commit
でコミットするgit rebase --continue
でリベースを続ける
Successfully rebased and updated refs/heads/main.
と表示されれば完了です。
コミットの順番を確認してみると、入れ替わっていることが分かります。
コミットをまとめる
% git rebase -i commit_hash
コミットをまとめるには、コミット入れ替えと同じくgit rebase -i
を使います。
先ほどの例で、今度は「add age」と「add name」のコミットを1つにまとめてみましょう。
次のコマンドを実行して、コミット編集画面を開きます。
% git rebase -i 3fd37fd
コミット編集画面で、「add name」のコミットのpick
をs
に変更します。
s(quash)
を指定すると、指定したコミットとそのひとつ前のコミットを結合することができます。
編集画面を保存すると、結合したコミットのコミットメッセージを編集する画面が開きます。
適当なコミットメッセージを作成して保存すれば、コミットの結合は完了です。
ログと変更差分を確認してみると、うまくいっていることが分かります。
コミットの内容を分割する
% git rebase -i commit_hash
% git add -p
コミットの内容を分割する場合も、おなじみのgit rebase -i
を利用します。
また、git add -p
を使うと、変更差分の一部を指定してステージングすることが出来ます。
「コミットをまとめる」で「add age」と「add name」を1つのコミットにまとめましたが、今回はまとめたコミットをもとの別々のコミットに戻してみましょう。
次のコマンドを実行して、コミット編集画面を開きます。
% g rebase -i 3fd37fd
コミット編集画面で、「add age and name」コミットのpick
をe
に変更します。
e(dit)
を指定すると、指定したコミットで rebase が一時停止します。
「add age and name」コミットを修正したいので、まずコミットを巻き戻し、ステージング前の状態に戻します。
% git reset --mixed HEAD^
次に、変更差分を指定してステージングを行いたいので、git add -p
を実行します。
% git add -p sample.rb
すると、Stage this hunk [y,n,q,a,d,e,?]?
(hunk(変更差分の塊のこと)をステージングする?)と聞かれるので、e
を選択します。
どの変更差分をステージングするか、編集画面が起動します。
編集画面では、次のことを行います。
- 追加差分をステージングしたくない場合は、行を削除する
- 削除差分をステージングしたくない場合は、
-
を削除する
名前の追加だけをステージングしたいので、次のように編集して保存します。
これで、指定した箇所だけをステージングすることが出来ました。
git diff --cached
で確認すると、先ほど指定した箇所がステージングされていることが分かります。
この内容で、git commit
を行います。
残りの変更差分もコミットすれば、コミットの分割は完了です。
最後に、まだ rebase の途中なのでgit rebase --continue
して rebase を完了させます。
コミットログと変更差分を確認すると、うまくいっていることが分かります。
おわりに
FBCで勉強を始めた頃は git に対する苦手意識があったのですが、課題の提出時に何度もコミットの修正を行うことで、コミットに対する操作に慣れることができました。
これからの目標として、普段からきれいなコミットを積み上げることを大切にしつつ、問題が起きた時はすぐ修正できるようになっていきたいです。
参考
第6話 git reset 3種類をどこよりもわかりやすい図解で解説!【連載】マンガでわかるGit ~コマンド編~ - itstaffing エンジニアスタイル
[レポート] 『きれいなcommit, pull requestを知りたい/作りたい方のためのgit勉強会』に参加してきました | DevelopersIO
フィヨルドブートキャンプ 一ヶ月経過
こんにちは、プログラミング勉強中のカッシーです。
フィヨルドブートキャンプに入会して一ヶ月(正確には四週間)が経過しました。
今回は、この一ヶ月の振り返りと、反省点を書いていきたいと思います。
一ヶ月の学習内容
学習時間 : 182.5時間(一日あたり約6.5時間)
学習内容
学習による変化
- 英語のリファレンスや記事を(苦労しながらでも)読む癖がついた。
- 単にコピペするのではなく、コードの意味を理解する。
意識の変化としてはこの2つでしょうか。
英語を読むのは大変なのですが、公式リファレンス等大切な情報が英語で書かれている場合も多いので、頑張って読んでいます💦
一回調べた単語は記録しておいて、いつでも振り返れる様にしておいた方が良いかな。
「ただコピペするのではなく、きちんとコードの意味を理解して使うこと」という教訓は、プロを目指す上で大切にしていきたいと思います。
実践面での変化は、次の様なものになります。
- ブラウザやターミナルの操作で、意識的にショートカットキーを使う。
- Vimに対する恐怖感がなくなる。
- パスの通し方やシェルの設定が何をしているのか分かる。
- リモートサーバーにローカルのマシンでssh接続できる。
- Gitの操作中に競合が発生しても、(以前よりは)冷静に対応できる。
独学の時はよく分からなかったコードやコマンドも、学習を進めていくうちに理解が進んで、苦手意識がなくなる過程は楽しかったです😁
ただ、もちろん学習が順風満帆に進むわけもなく、途中で壁にぶつかることもあります。自分も以前そうなって気持ちが萎えたことがありました。
そんな時は、メンターさんからもらった「色々と試行錯誤した経験は、必ず自分の糧になっている」というアドバイスを思い出して、気持ちを奮い立たせている様にしています。
「自分には無理かもな...」と沈んだ気持ちで挑むのと、「この問題を解決したら、また一ついい武器が手に入るぞ!」という前向きな気持ちで挑むのでは、俄然やる気が違います🚀
課題
1番の課題は、自分がまだまだ受け身の姿勢で学習しているというところです。
先日伊藤さんのブログを読んで、そのことに気づきました。
学校の勉強とプログラミングの勉強は何が違うか(そして技術書をどう読むべきか) - give IT a try
内容としては(とても大雑把なまとめですが)、「プログラミングの勉強は試験勉強とは異なり、技術書の内容を全て完璧に理解する必要はない。技術書を読むときは、後から振り返って参照できるように、頭の中にインデックスを作れれば大丈夫。」ということが書いてあるのですが、その中で次の見出しを見てハッとしたのです。
本の内容を丸暗記したからといって必ず就職できるという保証はない
「カリキュラムをやりさえすれば就職できる」という、甘えた気持ちがあることを見透かされた様でした。
あくまでプログラムを書くことは手段で、目的はサービスを提供すること。就職する上で、プログラムを通じてどのように問題を解決できるのか、そこが大切になってくるのだと思います。
その問題解決の視点を養うために、まずサービスに興味を持って色々と使ってみる。そのサービスは「どうやって作っているの?」、「どうやって広めているの?」、「どうやって儲けているの?」と考えてみることから始めてみたいと思います📚
とりあえず、週に何時間か時間をとって、サービス探訪をやってみようかな。
来月の目標
最後に
メンターさんの日報や課題に対する温かいコメント、とても励みになっています!ありがとうございます🙇🏻
また、メンターさんや現役生の方の日報やブログなど、アウトプットを参考にさせていただいています🙏
まだまだ自分のプラクティスに追われ余裕のない状態ですが、少しずつでも自分の幅を広げていきたいと思います。
フィヨルドブートキャンプ 二週目
フィヨルドブートキャンプに参加して、二週間が経ちました。
今週はインプットが多い一週間で、エディタやコンソールを開きながら、何かしらのテキストを読んでいた記憶しかありません📘
そんな今週の振り返りになります。
先週の取り組み
学習の感想
- 「パスを通す」など、ただ写経するだけだったコードの意味を分かっていくのは楽しいです😊
- 先週はChromeのショートカットキーを使うことを意識して操作を行いました。タブの操作はコマンドでできる様になれたと思います。
- Vimの基本的な操作方法について学びましたが、幅広い操作を全部は覚えきれていないと思います。その都度調べながら、実践で身につけていくしかないですね。
- 目下Linuxの勉強中ですが、学ぶ分量が多いことに加え、zshなど横道に逸れると深みにはまるため、結構時間がかかっています💦今月中にLinuxのプラクティスを終了できるか怪しい...。
課題
- 日報は学習内容をまとめたものを書く。どうしてもダラダラと書いてしまいがちなのをなくしていきたい...。
- 学習の本筋とは関係ないところで詰まってしまい、結構な時間を無駄にしてしまった(4-5時間ほど)。自力で解決できたものの、ある程度のところで見切りをつけて質問する様にしないと貴重な時間が勿体ない。出来ない自分を曝け出すこと。
今週の予定
最後に
フィヨルドブートキャンプ 一週目
フィヨルドブートキャンプに参加して一週間が経ちました。
新しいことに取り組み、課題を進めていくうちに、あっという間に時間が過ぎている気がします💨
一週間という節目に、先週の振り返りと、今週の予定を立てていきたいと思います。
先週の取り組み
学習の感想
Discordに始まり、SNS、画面をキャプチャするアプリ、Web上でコードを試すことができるツールまで、未知のサービスを沢山使うようになりました。初めは面食らいましたが、自然と使いこなせるようになるまで、どんどん使っていきたいと思います。
課題作成前に読む参考文献がかなりのボリューム!独学では全く知らなかった箇所も多く、とても勉強になっています🙏
課題のレビューがとても丁寧で、分かりやすいです。褒めていただけることもあったりして、さらにモチベーションが上がります!
大変でも、公式のドキュメントをきちんと読むことが大事なのだと学びました。英語がなかなか入っていかない...。
課題
アウトプットに時間がとても掛かってしまう。文章で何かを表現することがあまりなかったので、とても苦戦しています。たくさん文章を書いて訓練あるのみですね。
まだ新しいことや課題に精一杯で、他の受講者の方のアウトプット(日報やDiscord等)にあまり目を通せていない。どんどん学習を進めながらも、フットワークの軽い先輩がいらっしゃるので、見習いたいです💪
今週の予定
ブラウザ操作の便利なコマンドを意識的に使って、少しでも定着させたいです。
学習時間は週50時間を目安に。
また一週間後に、経過観察のブログを書く!
最後に
メンターの方や他の受講生の方からの反応が、精神的な励ましになっています🙏
この調子で学習をどんどん進めていこうと思います!
自己紹介
初投稿
自己紹介
初めまして、カッシーと申します!
現在はフリーターをしており、プログラマーとして転職を目指して目下プログラミングの勉強中です。
独学で基礎的な勉強を行ってきたのですが、限界を感じて、に、フィヨルドブートキャンプに参加しました。
趣味は読書で、ジャンルに関係なく気になったものを読んでいます。
もう1つの趣味は観劇です。コロナ禍になってから2年ほど劇場に行けていないのですが、今年末に予定されている『薔薇とサムライ2』は観に行きたいな...
これからの予定
目下は課題をこなしながら、スクールに慣れていきたいと思っています。
ある程度慣れてきたら、将来ポートフォリオを作成するのに向けて、AWSやコンテナの勉強なんかも同時並行で進めていければと思います。
また、SNSでのコミュニケーションを全くしてこなかったので、その訓練も必要ですね。
まずはTwitterでフォローする方を少しずつ増やしていくことから始めようかな...。
最後に
フィヨルドブートキャンプに参加してから、初めてやること・やらなければいけないことが沢山あって目まぐるしいですが、ひとつひとつクリアしていければと思います。
失敗してもくよくよせず、前進せよ!