- 投稿日:2020-06-25T22:41:16+09:00
FreeBSD12.1+apacheでgitサーバ(smart http)
gitレポジトリのサーバを立てるにあたり、プロトコルはsshでいいや、と思っていたのだけど、意外とhttpプロトコルでの設定も簡単にできるとのこと。
とりあえず、試してみました。
環境
- VirtualBox on ubuntu20.04
- FreeBSD 12.1
上記の環境で、インストールしてネットワークが接続できるところまで設定し終わっている事を前提とします。
設定
apacheとgitのインストール。
pkg install apache24 gitapacheの設定。
「/usr/local/etc/apache24/Include/000-default.conf
」というファイルを作成して、設定します。/usr/local/etc/apache24/Include/000-default.conf<VirtualHost *:80> ServerName hoge.example.com SetEnv GIT_PROJECT_ROOT /var/html/git SetEnv GIT_HTTP_EXPORT_ALL ScriptAlias /git /usr/local/libexec/git-core/git-http-backend <Location /git> AuthName "Git Repository" AuthType Basic AuthUserFile /usr/local/etc/apache24/git.passwd Require valid-user </Location> </VirtualHost>「
/etc/rc.conf
」で、apacheサービスを有効設定にします。/etc/rc.confapache24_enable="YES"パスワードファイルを作成します。
以下のコマンドを実行すると、パスワードを尋ねられるので、入力します。htpasswd -c /usr/local/etc/apache24/git.passwd katsukoレポジトリを作成します。
mkdir -p /var/html/git git init --bare --shared /var/html/git/project.git cd /var/html/git/project.git git update-server-info chown -R www:www .サービスを起動します。
service apache24 startこれで、他のクライアントから「
git clone http://katsuko@hoge.example.com/git/project.git
」でクローンができる…と思いきや、fatal: repository 'http://hoge.example.com/git/project.git/' not foundとのエラーが。
ふと「/usr/local/etc/apache24/httpd.conf
」を見てみると、/usr/local/etc/apache24/httpd.conf<IfModule !mpm_prefork_module> #LoadModule cgid_module libexec/apache24/mod_cgid.so </IfModule> <IfModule mpm_prefork_module> #LoadModule cgi_module libexec/apache24/mod_cgi.so </IfModule>というように、
mod_cgi
がコメントアウトされているので、このコメントを外してやりましょう。
いやー、CGI動かすの久々なんで、ちょっと悩みましたよ。ちなみに、「
Options ExecCGI
」は要らないのかな、と思ったのだけど、apacheのマニュアルによると、必要なのは「ScriptAlias
」の指定以外でCGIを動かす場合のようです。
- 投稿日:2020-06-25T21:42:05+09:00
Rails Tutorial 第6版 学習まとめ 第10章
概要
この記事は私の知識をより確実なものにするためにRailsチュートリアル解説記事を書くことで理解を深め
勉強の一環としています。稀にとんでもない内容や間違えた内容が書いてあるかもしれませんので
ご了承ください。
できればそれとなく教えてくれますと幸いです・・・この章でやること
ユーザーの作成、ログイン、ログイン情報の記憶が実装できたので
次はユーザーリソースで放置していた更新、表示、削除機能を作成する。ユーザーを更新する
ユーザーを更新するにはeditアクションを編集する。
今迄に実装してきたsessionsコントローラのnewアクションや
Usersコントローラのnewアクションのようにフォームを用意し、
フォームの入力値をupdateアクションに送るという動作を実装すればいい。
もちろん編集が可能なのはユーザー本人だがこれまでに実装した認証を使って
アクセス制御を実装していく。編集フォーム
editページはURLに対象のユーザーのIDが含まれる
ex) users/1/editこれを利用してURLのIDからユーザーを取り出しインスタンス変数に保存しておく。
def edit @user = User.find(id: params[:id]) endこうすることで次に作成するフォームでモデルオブジェクトに
@user
オブジェクトを指定する。edit.html.erb<% provide(:title, "Edit user") %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, local: true) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Save changes", class: "btn btn-primary" %> <% end %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="https://gravatar.com/emails" target="_blank">change</a> </div> </div> </div>フォームに無効な値を入力した際のエラーメッセージを表示するために_error_messagesパーシャルを再利用している。
またgravatarのリンク部に
target="_blank"
とあるが、このように記述することで新しいタブでリンク先を表示できる。さらにフォームの入力欄には@user変数に現在入っている値が自動入力される。
Rails側で自動的に保存されている属性情報を引っ張ってきて表示してくれるらしい。ぐう有能。このerbから生成される実際のHTMLをのぞくと
<input type="hidden" name="_method" value="patch">
このような記述がある。Webブラウザは更新のリクエストであるPATCHリクエストを送信できないためRailsが
隠しinputフィールドにpatchを指定して、疑似的にPATCHリクエストとして偽造している。もう一つ覚えておきたい事項としてnewアクションとeditアクションでほぼ変わらないerbコードを使っているのに
なぜRailsが新規のユーザーか既存のユーザーか判別できるかだが
ActiveRecordのnew_record?
メソッドで新規か既存か判別できるからである。>> new_user = User.new (1.3ms) SELECT sqlite_version(*) => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_digest: nil> >> user1 = User.first User Load (0.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User id: 1, name: "take", email: "take.webengineer@gmail.com", created_at: "2020-06-14 02:57:10", updated_at: "2020-06-20 03:53:57", password_digest: [FILTERED], remember_digest: "$2a$12$tYO.HIfYezXpTk2zRp9s6uqJY4wUkPM28NfYuJ7vxq/..."> >> new_user.new_record? => true >> user1.new_record? => false >>実際のerbでは
form_withを使った際モデルオブジェクトに対して
new_record?```の結果を見て
postかpatchかを判定する。最後にナビゲーションバーのeditアクションへのリンクを設定する。
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
演習
1.興味がわいたので具体的にどのような脆弱性があるか調べた結果、以下の記事が参考になったので置いておく。
https://webegins.com/target-blank/
"noopener"超重要!2.newビューとeditビューではフォームの部分がほぼ共通で、違う点といったら
submitボタンのテキストぐらいである。
そのため、provideメソッドを使ってsubmitボタンのテキストコンテンツを変更するようにし、
パーシャルにまとめてリファクタリングする。_form.html.erb<%= form_with(model: @user, local: true) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit yield(:btn_text), class: "btn btn-primary" %> <% end %>new.html.erb<% provide(:title, 'Sign up') %> <% provide(:btn_text, "Create my account") %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= render 'form' %> </div> </div>edit.html.erb<% provide(:title, "Edit user") %> <% provide(:btn_text, "Save changes") %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= render 'form' %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a> </div> </div> </div>編集の失敗
ユーザー登録と同じく、無効な値で更新しようとした際の編集の失敗に関して実装していく。
updateアクションを実装していくがcreateアクションでparamsを使ってユーザーを作成したのと同じく
editアクションから送信されたparamsを使って更新する。
構造はかなり似ている。
もちろんparamsでDBを直接更新するのは危険なため今回もStrongParameter(以前定義したuser_paramsメソッド)を使う。def update @user = User.find(params[:id]) if @user.update(user_params) else render 'edit' end end現時点でUserモデルのバリデーションと_error_messagesパーシャルが存在するため
無効な値にはエラーを返してくれるようになっている。演習
編集失敗時のテスト
ユーザーの編集関連の統合テストを作成する。
今回は題目通り編集失敗時のテストを書いていく。
rails g integration_test user_edit
users_edit_test.rbrequire 'test_helper' class UserEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "unsuccessful edit" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user) , params:{user:{name: "", email: "foo@bar", password: "foo", password_confirmation: "bar"}} assert_template 'users/edit' end end1.editページにGETリクエスト、editページが描画されているか確認
2.updateアクションにpatchリクエスト、無効な値を送信
3.editページが再描画されているか確認。の順のテストになる。
演習
1.
assert_select 'div.alert', "The form contains 4 errors."
TDDで編集を成功させる
今度は成功時の動作を実装していく。
ユーザ⁻画像はGravatarで実装しているためすでに動作する。
name,email,passwordなどほかの属性の編集の成功を実装していく。機能を実装する前に統合テストを書き、機能を実装し終わったときその機能が受け入れ可能な状態かどうか決めるテスト
を「受け入れテスト」と呼ぶ。
実際にTDDで編集成功を実装してみる。先程実装した失敗時のテストを参考に実装していくとわかりやすい。(もちろん有効なデータを送信するが)
test "successful edit" do get edit_user_path(@user) assert_template 'users/edit' name = "foo" email = "foo@bar.com" patch user_path(@user) , params:{user:{name: name, email: email, password: "", password_confirmation: ""}} assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email endり確実なものにするためにRailsチュートリアル解説記事を書くことで理解を深め
勉強の一環としています。稀にとんでもない内容や間違えた内容が書いてあるかもしれませんので
ご了承ください。
できればそれとなく教えてくれますと幸いです・・・この章でやること
ユーザーの作成、ログイン、ログイン情報の記憶が実装できたので
次はユーザーリソースで放置していた更新、表示、削除機能を作成する。ユーザーを更新する
ユーザーを更新するにはeditアクションを編集する。
今迄に実装してきたsessionsコントローラのnewアクションや
Usersコントローラのnewアクションのようにフォームを用意し、
フォームの入力値をupdateアクションに送るという動作を実装すればいい。
もちろん編集が可能なのはユーザー本人だがこれまでに実装した認証を使って
アクセス制御を実装していく。編集フォーム
editページはURLに対象のユーザーのIDが含まれる
ex) users/1/editこれを利用してURLのIDからユーザーを取り出しインスタンス変数に保存しておく。
def edit @user = User.find(id: params[:id]) endこうすることで次に作成するフォームでモデルオブジェクトに
@user
オブジェクトを指定する。edit.html.erb<% provide(:title, "Edit user") %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_with(model: @user, local: true) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Save changes", class: "btn btn-primary" %> <% end %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="https://gravatar.com/emails" target="_blank">change</a> </div> </div> </div>フォームに無効な値を入力した際のエラーメッセージを表示するために_error_messagesパーシャルを再利用している。
またgravatarのリンク部に
target="_blank"
とあるが、このように記述することで新しいタブでリンク先を表示できる。さらにフォームの入力欄には@user変数に現在入っている値が自動入力される。
Rails側で自動的に保存されている属性情報を引っ張ってきて表示してくれるらしい。ぐう有能。このerbから生成される実際のHTMLをのぞくと
<input type="hidden" name="_method" value="patch">
このような記述がある。Webブラウザは更新のリクエストであるPATCHリクエストを送信できないためRailsが
隠しinputフィールドにpatchを指定して、疑似的にPATCHリクエストとして偽造している。もう一つ覚えておきたい事項としてnewアクションとeditアクションでほぼ変わらないerbコードを使っているのに
なぜRailsが新規のユーザーか既存のユーザーか判別できるかだが
ActiveRecordのnew_record?
メソッドで新規か既存か判別できるからである。>> new_user = User.new (1.3ms) SELECT sqlite_version(*) => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_digest: nil> >> user1 = User.first User Load (0.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User id: 1, name: "take", email: "take.webengineer@gmail.com", created_at: "2020-06-14 02:57:10", updated_at: "2020-06-20 03:53:57", password_digest: [FILTERED], remember_digest: "$2a$12$tYO.HIfYezXpTk2zRp9s6uqJY4wUkPM28NfYuJ7vxq/..."> >> new_user.new_record? => true >> user1.new_record? => false >>実際のerbでは
form_withを使った際モデルオブジェクトに対して
new_record?```の結果を見て
postかpatchかを判定する。最後にナビゲーションバーのeditアクションへのリンクを設定する。
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
演習
1.興味がわいたので具体的にどのような脆弱性があるか調べた結果、以下の記事が参考になったので置いておく。
https://webegins.com/target-blank/
"noopener"超重要!2.newビューとeditビューではフォームの部分がほぼ共通で、違う点といったら
submitボタンのテキストぐらいである。
そのため、provideメソッドを使ってsubmitボタンのテキストコンテンツを変更するようにし、
パーシャルにまとめてリファクタリングする。_form.html.erb<%= form_with(model: @user, local: true) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit yield(:btn_text), class: "btn btn-primary" %> <% end %>new.html.erb<% provide(:title, 'Sign up') %> <% provide(:btn_text, "Create my account") %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= render 'form' %> </div> </div>edit.html.erb<% provide(:title, "Edit user") %> <% provide(:btn_text, "Save changes") %> <h1>Update your profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= render 'form' %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="https://gravatar.com/emails" target="_blank" rel="noopener">change</a> </div> </div> </div>編集の失敗
ユーザー登録と同じく、無効な値で更新しようとした際の編集の失敗に関して実装していく。
updateアクションを実装していくがcreateアクションでparamsを使ってユーザーを作成したのと同じく
editアクションから送信されたparamsを使って更新する。
構造はかなり似ている。
もちろんparamsでDBを直接更新するのは危険なため今回もStrongParameter(以前定義したuser_paramsメソッド)を使う。def update @user = User.find(params[:id]) if @user.update(user_params) else render 'edit' end end現時点でUserモデルのバリデーションと_error_messagesパーシャルが存在するため
無効な値にはエラーを返してくれるようになっている。演習
編集失敗時のテスト
ユーザーの編集関連の統合テストを作成する。
今回は題目通り編集失敗時のテストを書いていく。
rails g integration_test user_edit
users_edit_test.rbrequire 'test_helper' class UserEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "unsuccessful edit" do get edit_user_path(@user) assert_template 'users/edit' patch user_path(@user) , params:{user:{name: "", email: "foo@bar", password: "foo", password_confirmation: "bar"}} assert_template 'users/edit' end end1.editページにGETリクエスト、editページが描画されているか確認
2.updateアクションにpatchリクエスト、無効な値を送信
3.editページが再描画されているか確認。の順のテストになる。
演習
1.
assert_select 'div.alert', "The form contains 4 errors."
TDDで編集を成功させる
今度は成功時の動作を実装していく。
ユーザ⁻画像はGravatarで実装しているためすでに動作する。
name,email,passwordなどほかの属性の編集の成功を実装していく。機能を実装する前に統合テストを書き、機能を実装し終わったときその機能が受け入れ可能な状態かどうか決めるテスト
を「受け入れテスト」と呼ぶ。
実際にTDDで編集成功を実装してみる。先程実装した失敗時のテストを参考に実装していくとわかりやすい。(もちろん有効なデータを送信するが)
test "successful edit" do get edit_user_path(@user) assert_template 'users/edit' name = "foo" email = "foo@bar.com" patch user_path(@user) , params:{user:{name: name, email: email, password: "", password_confirmation: ""}} assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email endもちろんテストは失敗する。
まず、flashメッセージを実装していないこと。リダイレクトの指定をしていないこと。この二つが引っかかる。
そして一番大切な部分。パスワードの値を空にしているため、バリデーションに引っかかり、正常に更新できない。前者の二つはこの行で実装する。
def update @user = User.find(params[:id]) if @user.update(user_params) flash[:success] = "Profile updated" redirect_to @user else render 'edit' end endこの時点では@user.updateはパスワードが空欄でバリデーションに引っかかり、else文に分岐するため、
テストもうまく動かない。
この対策としてパスワードが空の時の例外処理を加える。
このような際にはallow_nil: true
というオプションを使うと便利。
これで空欄でもバリデーションにひっかからなくなる。
このオプションで存在性の検証を通過してしまうが、has_secure_password
メソッド側のオブジェクト生成時の
存在性のバリデーションが働くため、新規作成時はnilをはじき、更新時はnilならパスワードを変更しないという
動作を実現できる。
さらにこのallow_nil: true
オプションを追加したことでモデルに定義したバリデーションと
has_secure_password
メソッドのバリデーションが重複して同じエラーメッセージが表示される不具合も解決する。演習
認可
Webアプリにおける認証はユーザーを識別すること。認可はユーザーの実行可能な操作範囲を管理すること。
今まで実装してきたupdate,editアクションでは大きな欠陥があり、
現在の状態ではどのユーザーがログインしていようとすべてのユーザーを編集できてしまう。
ナビゲーションバーのSettingリンクはログインしているユーザーのeditページを表示するが
直接URLに様々なユーザーのeditアクションを指定してしまえばアクセス可能だし、更新もできてしまう。これはまずいので動作を正しいものに変える。
具体的には
未ログイン時はログインページに転送する+メッセージを表示。
ログイン済みだが別のユーザーにアクセスしようとしている場合はルートURLに転送する。ユーザーにログインを要求する
Usersコントローラでbeforeフィルターを使ってedit,updateアクションが実行される前に
必ずログインを強制するよう実装していく。before_action :logged_in_user, only:[:edit,update] . . . def logged_in_user unless logged_in? flash[:danger] = "Please log in." redirect_to login_url end endこのように実装することでedit,updateアクションの実行前に必ずlogged_in_userメソッドが実行され
ログインしていない際にはフラッシュメッセージでログインを促すメッセージを表示し、
ログインページにリダイレクトする。そして今の段階ではログインしていない状態でeditビューにアクセスするとログインページに
飛ばされてしまうようになったためテストは失敗する。テストが通るようにuser_edit_test.rbではeditアクションにアクセスする前にログインするようにする。
log_in_asメソッドをテスト用に定義しているためそれをつかう。これでテストはパスするようになる。だがbefore_actionの行をコメントアウトしてもテストではじかれない。
これは重大なセキュリティホールでテストではじかれなければまずいので
しっかりテストではじくよう修正していく。test "should redirect edit when not logged in" do get edit_user_path(@user) assert_not flash.empty? assert_redirected_to login_path end test "should redirect update when not logged in" do patch user_path(@user), params:{ user: {name: @user.name, email: @user.email }} assert_not flash.empty? assert_redirected_to login_path endこのようにテストを追加していくことで
edit,updateアクションを実行する前にlog_in_userが実行されているかを必ずテストするため
セキュリティホールをテストではじいてくれるようになる。演習
1.newページやsignupアクションが実行できなくなりエラーとなる。
FAIL["test_should_get_new", #<Minitest::Reporters::Suite:0x00007f1d1cf4dab8 @name="UsersControllerTest">, 0.06502773099964543] test_should_get_new#UsersControllerTest (0.07s) Expected response to be a <2XX: success>, but was a <302: Found> redirect to <http://www.example.com/login> Response body: <html><body>You are being <a href="http://www.example.com/login">redirected</a>.</body></html> test/controllers/users_controller_test.rb:10:in `block in <class:UsersControllerTest>' FAIL["test_invalid_signup_information", #<Minitest::Reporters::Suite:0x00007f1d1cff74c8 @name="UsersSignupTest">, 0.08553676799965615] test_invalid_signup_information#UsersSignupTest (0.09s) expecting <"users/new"> but rendering with <[]> test/integration/users_signup_test.rb:12:in `block in <class:UsersSignupTest>' FAIL["test_valid_signup_information", #<Minitest::Reporters::Suite:0x00007f1d1d0494d0 @name="UsersSignupTest">, 0.09624041300003228] test_valid_signup_information#UsersSignupTest (0.10s) "User.count" didn't change by 1. Expected: 2 Actual: 1 test/integration/users_signup_test.rb:20:in `block in <class:UsersSignupTest>' 9/9: [================================================================] 100% Time: 00:00:01, Time: 00:00:01正しいユーザーを要求する
次はログインしていても本人でなければ編集できないようにしていく。
TDDで進めていく。まずは別のユーザーでログインする状況を作るためにfixtureに2人目のユーザーを追加する。
users.ymlarcher: name: Sterling Archer email: duchess@example.gov password_digest: <%= User.digest('password') %>続いて、テストで@other_userとしてログインし、
@userの更新を行うテストを書く。test "should redirect edit when logged in as wrong user" do log_in_as(@other_user) get edit_user_path(@user) assert flash.empty? assert_redirected_to root_path end test "should redirect update when logged in as wrong user" do log_in_as(@other_user) patch user_path(@user), params:{ user: { name: @user.name, email: @user.email}} assert flash.empty? assert_redirected_to root_path endflashメッセージは特に表示せずルートURLに飛ばすだけなのでこのようなテストになる。
テストを書き、もちろんパスしないので
テストをパスさせるようにコードを書く。具体的には
correct_user
メソッドを作成し、edit,updateアクション実行前にユーザーがあっていなかったら
ルートURLに飛ばす処理を書く。users_controller.rbbefore_action :correct_user, only:[:edit,:update] private def correct_user @user = User.find(params[:id]) redirect_to root_url unless @user == current_user endこれでテストがパスするようになる。
最後に
current_user?
メソッドを定義し、先ほど定義したcorrect_user
メソッドに組み込むdef correct_user @user = User.find(params[:id]) redirect_to root_url unless current_user?(@user) enddef current_user?(user) user && user == current_user end演習
1.updateアクションを保護しなかった場合、editアクション(editページ)を経由せずcurlコマンドなどで直接
値を送った場合に更新できてしまうから。2.editアクションのほうが簡単にテストできる。(実際にログインして別のユーザのeditパスを表示すればいい)
フレンドリーフォワーディング
さらにこの更新の機能を便利にする。具体的には
logged_in_user
メソッドで未ログインのユーザーのeditページへのアクセスをはじいてログインページに飛ばした際
そのままログインすると問答無用でユーザー詳細ページ(show)に飛ばされてしまうが、
editページにアクセスしたくてログインしたのにshowページが表示されてしまうのは少々不便である。
これを改良しログインしたらeditページに飛ばしてくれるようにする(フレンドリーフォワーディング)テストもこの通り実装すればいいので
未ログインでeditページにアクセスし、ログインしたらeditページにリダイレクトすることをチェックする。test "successful edit with friendly forwarding" do get edit_user_path(@user) log_in_as(@user) assert_redirected_to edit_user_url(@user) name = "foo" email = "foo@bar.com" patch user_path(@user) , params:{user:{name: name, email: email, password: "", password_confirmation: ""}} assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end現時点で失敗するテストが書けたのでつぎはこのテストがパスするようにコードを書いていく。
リクエスト時のページを保存しておき、ログイン時にそこへリダイレクトする処理を書く。sessions_helperにメソッドを定義する。
sessions_helper.rbdef redirect_back_or(default) redirect_to(session[:forwarding_url] || default) session.delete(:forwarding_url) end def store_location session[:forwarding_url] = request.original_url if request.get? end一時セッションにリクエスト先のURLを保存する処理を書いている。 この時GETリクエストだけを保存するようにしないと 万が一ログインしてフォームページににアクセスし、意図的に保存されたログイン情報のcookiesを削除した上で フォームの内容を送信するとpost,patchなどのURLが保存されてしまう。 その状態で```redirect_back_or```メソッドを使うとリダイレクトでpost,patchなどを期待するURLに対してGETリクエストが 送られてしまい、エラーが発生する可能性が高い。 GETリクエストに絞ることでこういったリスクを回避できる。 ```logged_in_user```メソッドに```store_location```メソッドを入れて、リクエスト先URLを保存し、 sessions_controllerのcreateアクションに```redirect_back_orメソッド```を入れることで、 ログイン時、もしセッションに保存されたURLがあればそちらにリダイレクトするようにする。 ```rb:sessions_controller.rb def create @user = User.find_by(email: params[:session][:email].downcase) if @user&.authenticate(params[:session][:password]) log_in(@user) params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) redirect_back_or @user else flash.now[:danger] = "Invalid email/password combination" render 'new' end endusers_controller.rbdef logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end endちなみにreturnやメソッドの最終行の直接呼出しがない限りは
リダイレクトはメソッドの最後で行われる。上記の内容でテストはパスする。
演習
1.ログインしてeditページにリダイレクトした時点で保存したURLが消えていることを確認すればいい。
user_edit_test.rbtest "successful edit with friendly forwarding" do get edit_user_path(@user) log_in_as(@user) assert_nil session[:forwarding_url] #ここを追加 assert_redirected_to edit_user_url(@user) name = "foo" email = "foo@bar.com" patch user_path(@user) , params:{user:{name: name, email: email, password: "", password_confirmation: ""}} assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal name, @user.name assert_equal email, @user.email end2.
(byebug) session[:forwarding_url] "https://12b7e3b6aec94b45960b81560e233372.vfs.cloud9.us-east-2.amazonaws.com/users/1/edit" (byebug) request.get? true最後に
だんだん章の最後にやることが増えてきたので
とりあえずまとめておく。rails t git add -A git commit -m "Finish user edit, update, index and destroy actions" git co master git merge updating-users git push rails t git push heroku heroku pg:reset DATABASE heroku run rails db:migrate heroku run rails db:seed本番用DBは
pg:reset DATABASE
で行う。なお、間違いを防ぐためにDBをリセットするアプリ名の
入力を求められるので入力してリセット
それか--confirmオプションを使って
heroku pg:reset DATABASE -c アプリ名
としてもいい。あとはheroku上でマイグレートとサンプルの追加を行って終わり。
- 投稿日:2020-06-25T16:18:52+09:00
【やらかし別】commit履歴を整理するためのGitコマンド
ケース別commit履歴の整理法
正直な話、たった一人でプロダクトを開発していると、僕みたいな人間はcommitの粒度などに無頓着になってくるのですが、最近は開発したプロダクトのコードレビューをしていただく機会が多くなったこともあり、気をつけ始めています。
相手としても読みにくいだろうし、見せるこちらとしてもやはり恥ずかしいですもんね。。。この記事は、ローカルのcommit履歴を整理するための手順をまとめたものです。
僕自身の備忘録でもあり、おそらく今後もよく見返すであろう手順をここにまとめています。僕は驚くほど無知なので、もし何かより良い提案などございましたら、ぜひ教えていただければ幸いです。
(テンションおかしいのは無視してください)
ケース1「あっ。直前のcommitを修正したい...」
やだーーー!!
fugas_controllerのhogeアクションとviewのhoge.html.erbを消して「delete fugas#hoge」とcommitしたのに、ルーティングの設定が残ったままじゃん!このcommitを取り消そう。
commitを取り消すにしても、commitを間違えた歴史は残したくないから、revert
じゃなくて、reset
ですね。
(参考記事:Git commit 取り消したい )indexとワーキングツリーはそのままでいいから、
--hard
じゃなくて、--soft
ですね。
(参考記事:[git reset (--hard/--soft)]ワーキングツリー、インデックス、HEADを使いこなす方法 )取り消すのは直前のcommitなので、
HEAD^
ですね。git reset --soft HEAD^
routes.rbを変更したから、indexに追加しよう。
git add config/routes.rb
変更はちゃんと全部indexに追加されてるかなぁ〜〜〜???
git status
で確認しよう。git status
どれどれ〜??
出力結果modified: app/controllers/fugas_controller.rb deleted: app/views/fugas/hoge.html.erb modified: config/routes.rb
問題ないですね。
よし、commitしよう。
git commit -m "delete fuga#hoge" -m "fugaコントローラーのhogeアクションを削除した。"完了!!
ケース2「げっ!いくつか前のcommitを修正したい...」
やだーーー!!
fugas_(ryそれもいくつか前のcommitだなぁ...。修正しなきゃ〜...。
で?修正したいのは、どれくらい前のcommitだっけ?
git log
で確認や!
修正内容まで見なくても、コミットメッセージだけ表示してくれればわかるので、--oneline
つけとこう。git log --oneline
どれどれ??
出力結果dc77776b (HEAD -> refactoring) modify footer 3fb04106 delete fugas#hoge 0764bcaf delete landing_page 29970ee1 fix pagenate bug"delete fugas#hoge"は2つ前のcommitか。
了解了解。
q
を入力して離脱しよう。さて、ここからがちょっと面倒くさい。
commit履歴が少し過去にまで遡るので、delete fugas#hoge
のコミットまでgit resetしてしまうと、modify footer
のコミットまで消えてしまいます。だからここで使うコマンドは、
git rebase -i <変更したいコミットの「1つ前」のハッシュ値>
です。これを使えば、ピンポイントで特定のcommitの内容だけを修正できる。
注意すべきは、rebase -iで指定するのは、「変更したいコミットの1つ前のコミットのハッシュ値」だということ。
ここではつまり、delete landing_page
のハッシュ値になる。
だから打つべきコマンドは、下になる。git rebase -i 0764bcaf
すると次の入力画面が現れる。
出力結果(変更前)pick 3fb04106 delete fugas#hoge pick dc77776b modify footer # Rebase 0764bcaf..dc77776b onto 0764bcaf (2 commands) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup <commit> = like "squash", but discard this commit's log message # x, exec <command> = run command (the rest of the line) using shellさて、過去のcommitを書き換えるには、この画面の1行目の
pick 3fb04106 delete fugas#hoge
を、edit 3fb04106 delete fugas#hoge
に書き換えなくてはならない。
しかし、初心者にとっては、ここが意外にも難所だったりします。
ここで表示される入力画面は、普段触る機会の少ない『Vim』と呼ばれるクセのあるエディターだからです。
Vimの使い方については、下の記事が詳しいです。
(参考記事:知識0から始めるVim講座)
読む暇もないという方は、これから僕が唱える呪文を順番に打ってください。
表示されたばかりの状態では文字入力はできないので、まずはi
のキーを叩くことで、文字入力できる状態にします。
叩いた?文字入力できるようになった?
では次に、1行目のpick 3fb04106 delete fugas#hoge
を、edit 3fb04106 delete fugas#hoge
に書き換えます。
書き換えられた?
次に、escキー
を押します。
押した?また文字入力できなくなった?
次に、:wq
を押して、保存して終了します。
終了した?
もし下の画面が出力されたら、とりあえず成功です。出力結果Stopped at 3fb04106... delete fugas#hoge You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continueそしたら、
delete fugas#hoge
のコミットに追加したいファイルなり変更なりをstagingしましょう。git add config/routes.rb
そしていつものようにcommmitするのですが、ここでちょっと注意。
オプションは--amend
にするのだ。git commit --amend
commitした??
すると、またこんな感じでVimの画面が表示されたと思う。出力結果delete fugas#hoge # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Thu Jun 25 15:06:15 2020 +0900 # # interactive rebase in progress; onto 7339e063 # Last command done (1 command done): # edit 3fb04106 delete fugas#hoge # Next commands to do (2 remaining commands): # pick dc77776b modify footerとくにコミットメッセージなど変更するつもりがないなら、
:wq
で編集画面を終了しましょう。
メッセージを修正したいなら、さっき紹介したi
から始まる手順を踏めばいい。
さて、これでcommitできたわけだけれど、ここで安心するのはまだ早い。
まだrebase作業は終わっていない。
最後に下のコマンドを入力しましょう。git rebase --continue
これで終わり!!!
お疲れ様!!ケース3「あ〜。1つのファイルに複数の変更を加えてstagingしちゃったけど、変更はそれぞれ分けてcommitしたいな...」
とりあえず、
git diff
でどのへんを変更したのか確認しよう。
もうデータはstagingしちゃったので、変更前との差分を見たいなら--staged
をつけよう。
(参考記事:git 差分を見る)git diff --staged
どれどれ??
出力結果diff --git a/config/routes.rb b/config/routes.rb index 02fcf8e4..ebcf4fa1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,14 +18,14 @@ Rails.application.routes.draw do #get "how_to_use", to: "static_pages#how_to_use" # - #get '/landing_page', to: 'static_pages#landing_page' index 02fcf8e4..ebcf4fa1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,14 +18,14 @@ Rails.application.routes.draw do #get "how_to_use", to: "static_pages#how_to_use" # - #get '/landing_page', to: 'static_pages#landing_page'なるほどね。
まずはconfig/routes.rbをindexから外さないとね。
indexからファイルをunstagingするには、git reset <ファイル名>
かgit rm --cached <ファイル名>
ですね。
(参考記事:git add の取り消し方法と、関連コマンドまとめ)
git reset config/routes
さて、unstagingしたこのファイル変更箇所の一部分だけをstagingし直すには...。
ヘェ〜、git add -p <ファイル名>
なんてあるのか。なるほど。
(参考記事:Git 変更のあるファイルの一部だけをコミットしたい。)git add -p config/routes.rb
どれどれ?
出力結果diff --git a/config/routes.rb b/config/routes.rb index 02fcf8e4..ebcf4fa1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,14 +18,14 @@ Rails.application.routes.draw do #get "how_to_use", to: "static_pages#how_to_use" # - #get '/landing_page', to: 'static_pages#landing_page' + get "/select_plan", to: "static_pages#select_plan" get "/update", to: "static_pages#update" get "/invitation_or_premium", to: "static_pages#invitation_or_premium" - get "/new", to: "static_pages#new" + (1/1) Stage this hunk [y,n,q,a,d,s,e,?]?hunk?
あぁ、stagingする単位のことか。
って、変更が全部1つにまとまっちゃってんじゃ〜ん!
変更を分割するには、s
を入力してenter。出力結果(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? s Split into 2 hunks. @@ -18,10 +18,10 @@ #get "how_to_use", to: "static_pages#how_to_use" # - #get '/landing_page', to: 'static_pages#landing_page' + get "/select_plan", to: "static_pages#select_plan" get "/update", to: "static_pages#update" get "/invitation_or_premium", to: "static_pages#invitation_or_premium" (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?この変更をstagingしよう。
y
を入力して、enter。出力結果(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y @@ -22,10 +22,10 @@ get "/select_plan", to: "static_pages#select_plan" get "/update", to: "static_pages#update" get "/invitation_or_premium", to: "static_pages#invitation_or_premium" - get "/new", to: "static_pages#new" + (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]?この変更はあとでcommitするので、今はstagingしない。
n
を入力してスキップ!
OK、終了終了。
よし、これで1つのファイルの2つの変更のうち1つだけstagingできたぞ〜!
できてるよね??
不安なので、git status
でindexを確認しよう。git status
どれどれ〜?
出力結果On branch fix_bug Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: config/routes.rb Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: config/routes.rbよしよし。ちゃんとconfig/routesrbが分割でstagingされてる。
じゃあ、まずは今stagingしたファイルをcommitしよう。
git commit -m "delete landing_page_path" -m "ランディングページのルーティングを削除"
それから残りの変更もstagingして、
git add config/routes.rb
commit!
git commit -m "delete new_path" -m "static_pages#newのルーティングを削除"終わり!!!
ケース4「あぁ〜、いくつか前のcommit、粒度粗いし分割したい〜〜〜」
過去のcommitの分割は、「ケース1」「ケース2」「ケース3」を合わせた知識でできます。
rebase -i
で、過去のcommitに移動したあと、
そのcommitをgit reset --soft HEAD^
で取り消す。その後は、すでにindexにある分割してcommitしたいファイルを、
git reset <ファイル名>
でunstagingしたり、
「ケース3」で紹介した手順で、1つのファイルの複数の変更を分割してstagingして、何回かに分けてcommitするなどすれば、commitの粒度を細かくできます。このときのcommitは、
commit --amend
ではなく、commit -m "コミットメッセージ"
を使います。最後に
git rebase --continue
を打ってrebaseを終了し、
git log --oneline
で確認すれば、
きちんとcommitが分割されていることがわかります。宣伝
Terminalの英語が読めない僕とあなたのために、BooQsという学習の科学に基づいた英単語学習サービスを開発しています。
BooQsの中でもとくに人気のあるコンテンツは、NGSL(New General Service List)と呼ばれる、一般的な英文の9割を網羅した英単語帳です。
よろしければぜひ!BooQs(ブックス) は、
— 挫折させない英単語サービス『BooQs』 (@BooQs_net) March 19, 2020
楽しく効率的に英単語を覚えられるサービスです。
現在、『基礎英単語』『学術英単語』『TOEIC英単語』『ビジネス英単語』を学ぶことができます。
ぜひご活用ください!!#BooQshttps://t.co/Ygvf3QeZKL
- 投稿日:2020-06-25T10:14:56+09:00
変更内容を"別ブランチに"反映させない方法
はじめに
git-flowやGitHub Flowを学習し始めると、ブランチを分けてファイルを管理するようになります。
そして、変更内容がなぜか別ブランチにも反映されるなんて珍事件を一度は経験すると思います。
それではブランチを分ける意味がなくなってしまいます。この記事では、専門用語を解説したうえで、
変更内容を"別ブランチに"反映させない方法を解説していきます。対象者
「変更内容がなぜか別ブランチにも反映されて困っている」
「とりあえずGitの勉強をしたい」というGit初心者向けです。
この記事では変更内容を"別ブランチに"反映させない方法について説明しています。
そのため、Gitコマンドの仕組みなどについては詳しくは説明していません。
わからない人向けに参考サイトを用意しましのでご参照ください。
では、さっそく進めていきましょう!専門用語
Git...どのファイルを追加したか削除したかなどを管理するツール。
GitHub...Gitと提携してみんなとやり取りができるWebサイト。
ブランチ...作業を枝分かれさせる事。
masterブランチ...完成品用のブランチ。
developブランチ...開発用のブランチ。
リポジトリ...ファイルやフォルダを保存する場所。
コミット...どのファイルを追加したか削除したかなどを登録する事。
スタッシュ...コミットせずファイルを一時待避する事。環境
OS: macOS Catelina (バージョン10.15.4)
ターミナル: Terminal.app
git version: 2.24.21.原因
変更内容をコミットまたはスタッシュしないで
別ブランチに移動した場合、
移動先の別ブランチにも同じ内容が反映されます。コミットするかスタッシュして、別ブランチに反映させないようにしましょう。
スタッシュは、とあるブランチで作業中だけど別のブランチで他のことがやりたい
但し、作業が中途半端だからコミットはしたくない場合に使用します。
コミットする場合は2-1へ。
スタッシュする場合は2-2へ。2.対処法
Gitコマンドの仕組みはこちらの記事が参考になります。
Gitでよく使うコマンド一覧2-1.コミットする場合
コミットしたい物を全て選択するには下記Gitコマンドを入力します。
git add .コミットするには下記Gitコマンドを入力します。
xxxにはコミットのメッセージを入力します。git commit -m "xxx"GitHubのリポジトリに反映するには下記Gitコマンドを入力します。
xxxにはブランチ名を入力します。git push origin xxxコミット後に別ブランチを移動しましょう。
ブランチを移動するには下記Gitコマンドを入力します。
xxxには移動先のブランチ名を入力します。git switch xxxこれで完了です。
2-2.スタッシュする場合
スタッシュするには下記Gitコマンドを入力します。
git stashスタッシュ後に別ブランチを移動しましょう。
別のブランチでの作業が終わったら、
元のブランチに移動しましょう。
ブランチを移動するには下記Gitコマンドを入力します。
xxxには移動先のブランチ名を入力します。git switch xxx退避した変更内容を戻しましょう。
まずは、退避した作業の一覧を確認しましょう。
確認するには下記Gitコマンドを入力します。git stash list下記のような表示が出てきます。
xxxは変更作業を行なったブランチ名です。stash@{0}: WIP on xxx stash@{1}: WIP on xxx . . . stash@{5}: WIP on xxx退避した作業を戻すには下記Gitコマンドを入力します。
例)stashの0番を戻すときはxxxには0を入力して下さい。
例)stashの5番を戻すときはxxxには5を入力して下さい。git stash apply stash@{xxx}これで完了です。
おわりに
お疲れ様でした!
これでこの問題を対処できるようになりましたね。この記事をきっかけに、より良いGit生活が送れていただければ嬉しいです!
参考
masterからdevelopのブランチをきって、
— 霧崎さくや@Web Developer (@Sakuya_wd) June 12, 2020
developでファイル修正した後、
masterにcheckout/switchすると、
developのファイル修正が
masterにも反映されてるけど、
どうしたら反映させずにすむのだろうか?
Gitのプロの方わかりますか?#プログラミング初心者 #github
修正後にコミットしなければ残るので、そのことかと思いました!
— ウホーイ (@the_uhooi) June 12, 2020
ブランチを切り替える前にコミットするのがよくて、もしまだコミットしたくないならスタッシュに入れるのがよさそうです?
- 投稿日:2020-06-25T00:40:48+09:00
gitコマンドでワーキングツリーがクリーンかどうか判定する
用語について
- ワーキングツリー …… リポジトリからチェックアウトされたファイルのツリー
- クリーン …… ワーキングツリーの内容がHEADが指すリビジョンと一致していることを表す
- ダーティー …… ワーキングツリー内に、コミットされていない変更が含まれていることを表す
やり方
おそらく、可能なやり方はたくさん考えられると思いますが、ふつうはこれを使えばよさそうというやり方を紹介します。
また、シェルスクリプトなどで機械的に判定を行う上で便利なやり方を紹介します。なお、動作確認時のgitのバージョンはv2.17.1です。
gitで追跡されていないファイルを含めて判定する
当然
git status
でわかるわけですが、そのままでは機械的に扱うには少々不便です。
--short|-s
オプションを使うと出力がよりシンプルな扱いやすい形になります。例$ git status -s D bar.txt D baz.txt M foo.txt ?? new.txt上の出力結果で、
D
は削除されたものを、M
は差分のあるファイルを、??
はuntrackedな(git管理外の)ファイルを表しています。
ワーキングツリーがクリーンなときには何も表示されないところが、コマンドで判定する上で嬉しいポイントです。従って、シェルスクリプト上では例えば次のように判定できます:
判定例if [ $(git status --short | wc -l) -ne 0 ]; then # dirty! fi他のやり方としては、
git add
コマンドを-N|--intent-to-add
オプションを付けて実行した上で、git diff
コマンドを使う、といったやり方もあるようです。
こちらについては、後掲のreboooot․netさんの記事をご覧ください。gitで追跡されているファイルのみを対象とする
この場合、
git status
だけでなく、git diff
コマンドでも判別できます。
機械的な正否判定にはgit diff --quiet
が便利です。判定例if ! git diff --quiet; then # dirty! fi
--quiet
オプションを付けると一切の出力をせず、--exit-code
オプション同様に差分のあるときにexit 1
してくれるので、検出が可能です。参考記事
- Chapter 11. GIT 用語集
- https://git-scm.com/docs/git-status
- https://git-scm.com/docs/git-diff
- https://git-scm.com/docs/git-describe
- git status -s でちょっと幸せになれる - Qiita
- Git での新規ファイル作成を含んだファイル変更有無の判定方法 - reboooot․net
- Git その445 - ワーキングツリーがダーティーな時に出力結果にサフックスを追加して、コミットオブジェクトから到達できる最初の注釈付きのタグを表示する(git describe) - kledgeb ... こちらが使えるケースもありそうだと思いましたが、リポジトリに注釈付きタグがないと使えないという制限があるので、本文中では割愛しました。また、機械的に扱いたいときには
git diff --exit-code|--quiet
の方が便利でしょう。