Everyday Rails - 5日目
P.57までの課題
contacts_controller_spec.rbにテストを追加。
describe 'PATCH hide_contact' do before do @contact = create(:contact) end it 'marks the contact as hidden' do patch :hide_contact, id: @contact expect(@contact.reload.hidden?).to be_truthy end it 'redirects to contacts#index' do patch :hide_contact, id: @contact expect(response).to redirect_to contacts_url end end
be_trueはRSpec3では使えなくなっているので、be_truthyを使うこと。
migrationを追加。
./bin/rails g migration add_hidden_contact
class AddHiddenContacts < ActiveRecord::Migration def change add_column :contacts, :hidden, :boolean end end
migration実行後、routing設定。
resources :contacts do patch :hide_contact, on: :member end
controller修正。before_actionは指定数的な問題でonlyからexceptに変えた。
# : before_action :set_contact, except: [:index, :new, :create] # : def hide_contact @contact.update(hidden: true) redirect_to contacts_url end # :
結果。
P.58の課題
phonesコントローラーを作る。
$ ./bin/rails g controller Phones show
routes.rb
resources :contacts do patch :hide_contact, on: :member resources :phones end
phones_contoller_spec.rb
describe PhonesController, type: :controller do describe 'GET #show' do it 'renders the :show template for the phone' do contact = create(:contact) get :show, id: contact.phones.first, contact_id: contact.id expect(response).to render_template :show end end end
P.60までの課題
contacts_controller.rbにCSV出力とJSON出力を追加。
def index if params[:letter] @contacts = Contact.by_letter(params[:letter]) else @contacts = Contact.order('lastname, firstname') end respond_to do |format| format.html format.json { render json: @contacts.to_json } format.csv do send_data Contact.to_csv(@contacts), type: 'text/csv; charset=utf-8; header=present', disposition: 'attachment; filename=contacts.csv' end end end
contact.rb(Contactモデル)に特異メソッドto_csv追加。
とりあえずControllerのテスト用に適当に。
これはモデルにもちゃんとspec追加しないといけないかな(今回は省略)。
def self.to_csv(contacts) contacts.inject('') do |csv, contact| csv << CSV.generate_line( [contact.firstname, contact.lastname, contact.email] ) end end
contacts_controller_spec.rb
describe 'GET #index' do # : context 'CSV output' do it 'return a CSV file' do get :index, format: :csv expect(response.headers['Content-Type']).to match 'text/csv' end it 'returns contact' do contact = create(:contact) get :index, format: :csv expect(response.body).to match( [contact.firstname, contact.lastname, contact.email].join(",") ) end end context 'JSON output' do it 'returns JSON-formatted contact' do contact = create(:contact) get :index, format: :json expect(response.body).to have_content contact.to_json end end end
P.77まで
shared_examples, support module、custom matcherを用いて簡潔に。
現段階では、custom matcherよりもRSpec自体の使い方をマスターするほうが優先に感じた。
require 'rails_helper' describe ContactsController, type: :controller do shared_examples 'public access to contacts' do before do @contact = create(:contact, firstname: 'Lawrence', lastname: 'Smith') end describe 'GET #index' do context 'with params[:letter]' do it 'populates an array of contacts starting with the letter' do jones = create(:contact, lastname: 'Jones') get :index, letter: 'S' expect(assigns(:contacts)).to match_array([@contact]) end it 'renders the :index template' do get :index, letter: 'S' expect(response).to render_template :index end end context 'without params[:letter]' do it 'populates an array of all contacts' do jones = create(:contact, lastname: 'Jones') get :index expect(assigns(:contacts)).to match_array([@contact, jones]) end it 'renders the :index template' do get :index expect(response).to render_template :index end end context 'CSV output' do it 'return a CSV file' do get :index, format: :csv expect(response.headers['Content-Type']).to match 'text/csv' end it 'returns contact' do get :index, format: :csv expect(response.body).to match( [@contact.firstname, @contact.lastname, @contact.email].join(",") ) end end context 'JSON output' do it 'returns JSON-formatted contact' do get :index, format: :json expect(response.body).to have_content @contact.to_json end end end describe 'GET #show' do it 'assigns the requested contact to @contact' do get :show, id: @contact expect(assigns(:contact)).to eq @contact end it 'renders the :new template' do get :show, id: @contact expect(response).to render_template :show end end end shared_examples 'full access to contacts' do describe 'GET #new' do it 'assigns a new Contact to @contact' do get :new expect(assigns(:contact)).to be_a_new Contact end it 'renders the :new template' do get :new expect(response).to render_template :new end end describe 'GET #edit' do it 'assigns the requested contact to @contact' do contact = create(:contact) get :edit, id: contact expect(assigns(:contact)).to eq contact end it 'renders the :edit template' do contact = create(:contact) get :edit, id: contact expect(response).to render_template :edit end end describe 'POST #create' do before do @phones = [ attributes_for(:phone), attributes_for(:phone), attributes_for(:phone), ] end context 'with valid attributes' do it 'save the new contact in the database' do expect { post :create, contact: attributes_for(:contact, phones_attributes: @phones) }.to change(Contact, :count).by(1) end it 'redirects to contacts#show' do post :create, contact: attributes_for(:contact, phones_attributes: @phones) expect(response).to redirect_to contact_url(assigns(:contact)) end end context 'with invalid attributes' do it 'dose not save the new contact in the database' do expect { post :create, contact: attributes_for(:invalid_contact) }.not_to change(Contact, :count) end it 're-renders the :new template' do post :create, contact: attributes_for(:invalid_contact) expect(response).to render_template :new end end end describe 'PATCH #update' do before do @firstname = 'Larry' @lastname = 'Smith' @contact = create(:contact, firstname: @firstname, lastname: @lastname) end context 'with valid attributes' do it 'updates the contact in the database' do patch :update, id: @contact, contact: attributes_for(:contact) expect(assigns(:contact)).to eq @contact end it "changes @contact's attribetus" do firstname = 'Larry' lastname = 'Wall' patch :update, id: @contact, contact: attributes_for( :contact, firstname: firstname, lastname: lastname ) @contact.reload expect(@contact.firstname).to eq firstname expect(@contact.lastname).to eq lastname end it 'redirects to the contact' do patch :update, id: @contact, contact: attributes_for(:contact) expect(response).to redirect_to @contact end end context 'with invalid attributes' do it 'dose not update the contact' do firstname = 'David' patch :update, id: @contact, contact: attributes_for(:contact, firstname: firstname, lastname: nil) @contact.reload expect(@contact.firstname).not_to eq firstname expect(@contact.lastname).to eq @lastname end it 're-renders the :edit template' do patch :update, id: @contact, contact: attributes_for(:invalid_contact) expect(response).to render_template :edit end end end describe 'DELETE #destroy' do before do @contact = create(:contact) end it 'deletes the contact from the database' do expect { delete :destroy, id: @contact }.to change(Contact, :count).by(-1) end it 'redirects to contacts#index' do delete :destroy, id: @contact expect(response).to redirect_to contacts_url end end describe 'PATCH hide_contact' do before do @contact = create(:contact) end it 'marks the contact as hidden' do patch :hide_contact, id: @contact expect(@contact.reload.hidden?).to be_truthy end it 'redirects to contacts#index' do patch :hide_contact, id: @contact expect(response).to redirect_to contacts_url end end end describe 'Administrator access' do before do set_user_session create(:admin) end it_behaves_like 'public access to contacts' it_behaves_like 'full access to contacts' end describe 'Guest access' do it_behaves_like 'public access to contacts' describe 'GET #new' do it 'returns login' do get :new expect(response).to require_login end end describe 'GET #edit' do it 'requires login' do contact = create(:contact) get :edit, id: contact expect(response).to redirect_to login_url end end describe 'POST #create' do it 'requires login' do post :create, contact: attributes_for(:contact) expect(response).to redirect_to login_url end end describe 'PATCH #update' do it 'requires login' do patch :update, id: create(:contact), contact: attributes_for(:contact) expect(response).to redirect_to login_url end end describe 'DELETE #destroy' do it 'requires login' do delete :destroy, id: create(:contact) expect(response).to redirect_to login_url end end end end