diff --git a/Gemfile b/Gemfile index 017ff14..db05a5b 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,8 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.7.1' +gem 'haml' +gem "haml-rails", "~> 2.0" # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' gem 'rails', '~> 6.1.3' # Use postgresql as the database for Active Record @@ -33,6 +35,7 @@ group :development, :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails', '~> 4.0.2' gem 'factory_bot_rails' + gem 'rails-controller-testing' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 8c0665f..64db31b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -80,6 +80,7 @@ GEM crass (1.0.6) diff-lcs (1.4.4) erubi (1.10.0) + erubis (2.7.0) factory_bot (6.1.0) activesupport (>= 5.0.0) factory_bot_rails (6.1.0) @@ -88,6 +89,20 @@ GEM ffi (1.14.2) globalid (0.4.2) activesupport (>= 4.2.0) + haml (5.2.1) + temple (>= 0.8.0) + tilt + haml-rails (2.0.1) + actionpack (>= 5.1) + activesupport (>= 5.1) + haml (>= 4.0.6, < 6.0) + html2haml (>= 1.0.1) + railties (>= 5.1) + html2haml (2.2.0) + erubis (~> 2.7.0) + haml (>= 4.0, < 6) + nokogiri (>= 1.6.0) + ruby_parser (~> 3.5) i18n (1.8.9) concurrent-ruby (~> 1.0) jbuilder (2.11.2) @@ -139,6 +154,10 @@ GEM bundler (>= 1.15.0) railties (= 6.1.3) sprockets-rails (>= 2.0.0) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) @@ -172,6 +191,8 @@ GEM rspec-mocks (~> 3.10) rspec-support (~> 3.10) rspec-support (3.10.2) + ruby_parser (3.15.1) + sexp_processor (~> 4.9) rubyzip (2.3.0) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -187,6 +208,7 @@ GEM childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) semantic_range (2.3.1) + sexp_processor (4.15.2) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) spring (2.1.1) @@ -197,6 +219,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + temple (0.8.2) thor (1.1.0) tilt (2.0.10) turbolinks (5.2.1) @@ -233,12 +256,15 @@ DEPENDENCIES byebug capybara (>= 3.26) factory_bot_rails + haml + haml-rails (~> 2.0) jbuilder (~> 2.7) listen (~> 3.3) pg (~> 1.1) puma (~> 5.0) rack-mini-profiler (~> 2.0) rails (~> 6.1.3) + rails-controller-testing rspec-rails (~> 4.0.2) sass-rails (>= 6) selenium-webdriver diff --git a/app/assets/stylesheets/answers.scss b/app/assets/stylesheets/answers.scss new file mode 100644 index 0000000..ce16dd0 --- /dev/null +++ b/app/assets/stylesheets/answers.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the answers controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/assets/stylesheets/questions.scss b/app/assets/stylesheets/questions.scss new file mode 100644 index 0000000..b42accd --- /dev/null +++ b/app/assets/stylesheets/questions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the questions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb new file mode 100644 index 0000000..0c4a66c --- /dev/null +++ b/app/controllers/answers_controller.rb @@ -0,0 +1,45 @@ +class AnswersController < ApplicationController + def new + end + + def create + @answer = question.answers.new(answer_params) + if @answer.save + redirect_to @answer.question + else + render :new + end + end + + def edit + end + + def update + if answer.update(answer_params) + redirect_to answer.question + else + render :edit + end + end + + def destroy + answer.destroy + redirect_to answer.question + end + + private + + def answer + @answer ||= params[:id] ? Answer.find(params[:id]) : question.answers.new + end + + helper_method :answer + + def question + @question ||= Question.find(params[:question_id]) + end + + def answer_params + params.require(:answer).permit(:body) + end +end diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb new file mode 100644 index 0000000..be6ed41 --- /dev/null +++ b/app/controllers/questions_controller.rb @@ -0,0 +1,49 @@ +class QuestionsController < ApplicationController + def index + @questions = Question.all + end + + def show + end + + def new + end + + def edit + end + + def create + @question = Question.new(question_params) + + if @question.save + redirect_to @question + else + render :new + end + end + + def update + if question.update(question_params) + redirect_to @question + else + render :edit + end + end + + def destroy + question.destroy + redirect_to questions_path + end + + private + + def question + @question ||= params[:id] ? Question.find(params[:id]) : Question.new + end + + helper_method :question + + def question_params + params.require(:question).permit(:title, :body) + end +end diff --git a/app/helpers/answers_helper.rb b/app/helpers/answers_helper.rb new file mode 100644 index 0000000..b7cdb29 --- /dev/null +++ b/app/helpers/answers_helper.rb @@ -0,0 +1,2 @@ +module AnswersHelper +end diff --git a/app/helpers/questions_helper.rb b/app/helpers/questions_helper.rb new file mode 100644 index 0000000..2eaab4a --- /dev/null +++ b/app/helpers/questions_helper.rb @@ -0,0 +1,2 @@ +module QuestionsHelper +end diff --git a/app/views/answers/edit.html.haml b/app/views/answers/edit.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/app/views/answers/new.html.haml b/app/views/answers/new.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/app/views/questions/edit.html.haml b/app/views/questions/edit.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/app/views/questions/index.html.haml b/app/views/questions/index.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/app/views/questions/new.html.haml b/app/views/questions/new.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/app/views/questions/show.html.haml b/app/views/questions/show.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/config/application.rb b/config/application.rb index 782c96f..2f02588 100644 --- a/config/application.rb +++ b/config/application.rb @@ -18,5 +18,14 @@ class Application < Rails::Application # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") + config.generators do |g| + g.test_framework :rspec, + controller_specs: true, + view_specs: false, + helper_specs: false, + routing_specs: false, + request_specs: false + + end end end diff --git a/config/routes.rb b/config/routes.rb index c06383a..352f024 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,5 @@ Rails.application.routes.draw do - # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html + resources :questions do + resources :answers, shallow: true, except: %i[index show] + end end diff --git a/db/migrate/20210301193229_create_answers.rb b/db/migrate/20210301193229_create_answers.rb index 94c74ad..4a17add 100644 --- a/db/migrate/20210301193229_create_answers.rb +++ b/db/migrate/20210301193229_create_answers.rb @@ -1,7 +1,7 @@ class CreateAnswers < ActiveRecord::Migration[6.1] def change create_table :answers do |t| - t.string :body, null: false + t.text :body, null: false t.references :question, null: false, foreign_key: true t.timestamps diff --git a/db/schema.rb b/db/schema.rb index ee2b9c0..4440d68 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -16,7 +16,7 @@ enable_extension "plpgsql" create_table "answers", force: :cascade do |t| - t.string "body", null: false + t.text "body", null: false t.bigint "question_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false diff --git a/spec/controllers/answers_controller_spec.rb b/spec/controllers/answers_controller_spec.rb new file mode 100644 index 0000000..5be3f26 --- /dev/null +++ b/spec/controllers/answers_controller_spec.rb @@ -0,0 +1,88 @@ +require 'rails_helper' + +RSpec.describe AnswersController, type: :controller do + let!(:answer) { create(:answer) } + + describe 'GET #new' do + before { get :new, params: { question_id: answer.question } } + + it 'renders new view' do + expect(response).to render_template :new + end + end + + describe 'POST #create' do + context 'with valid attributes' do + it 'saves a new answer in the database' do + expect { post :create, params: { answer: attributes_for(:answer), + question_id: answer.question } + }.to change(Answer, :count).by(1) + end + + it 'redirects to associated question' do + post :create, params: { answer: attributes_for(:answer), question_id: answer.question } + expect(response).to redirect_to answer.question + end + end + + context 'with invalid attributes' do + it 'does not save the answer' do + expect { post :create, params: { answer: attributes_for(:answer, :invalid_answer), + question_id: answer.question } + }.to_not change(Answer, :count) + end + + it 're-renders new view' do + post :create, params: { answer: attributes_for(:answer, :invalid_answer), question_id: answer.question } + expect(response).to render_template :new + end + end + end + + describe 'GET #edit' do + before { get :edit, params: { id: answer } } + + it 'renders edit view' do + expect(response).to render_template :edit + end + end + + describe 'PATCH #update' do + context 'with valid attributes' do + it 'changes answer attributes' do + patch :update, params: { id: answer, answer: { body: 'new body' } } + answer.reload + expect(answer.body).to eq 'new body' + end + + it 'redirects to associated question' do + patch :update, params: { id: answer, answer: attributes_for(:answer) } + expect(response).to redirect_to answer.question + end + end + + context 'with invalid attributes' do + before { patch :update, params: { id: answer, answer: attributes_for(:answer, :invalid_answer) } } + + it 'does not change answer' do + answer.reload + expect(answer.body).to eq "MyText" + end + + it 're-renders edit view' do + expect(response).to render_template :edit + end + end + end + + describe 'DELETE #destroy' do + it 'deletes the answer' do + expect { delete :destroy, params: { id: answer } }.to change(Answer, :count).by(-1) + end + + it 'redirects to associated question' do + delete :destroy, params: { id: answer } + expect(response).to redirect_to answer.question + end + end +end diff --git a/spec/controllers/questions_controller_spec.rb b/spec/controllers/questions_controller_spec.rb new file mode 100644 index 0000000..a12d733 --- /dev/null +++ b/spec/controllers/questions_controller_spec.rb @@ -0,0 +1,110 @@ +require 'rails_helper' + +RSpec.describe QuestionsController, type: :controller do + let(:question) { create(:question) } + + describe 'GET #index' do + let(:questions) { create_list(:question, 3) } + before { get :index } + + it 'populates an array of all questions' do + expect(assigns(:questions)).to match_array(questions) + end + + it 'renders index view' do + expect(response).to render_template :index + end + end + + describe 'GET #show' do + before { get :show, params: { id: question } } + + it 'renders show view' do + expect(response).to render_template :show + end + end + + describe 'GET #new' do + before { get :new } + + it 'renders new view' do + expect(response).to render_template :new + end + end + + describe 'GET #edit' do + before { get :edit, params: { id: question } } + + it 'renders edit view' do + expect(response).to render_template :edit + end + end + + describe 'POST #create' do + context 'with valid attributes' do + it 'saves a new question in the database' do + expect { post :create, params: { question: attributes_for(:question) } }.to change(Question, :count).by(1) + end + + it 'redirects to show view' do + post :create, params: { question: attributes_for(:question) } + expect(response).to redirect_to assigns(:question) + end + end + + context 'with invalid attributes' do + it 'does not save the question' do + expect { post :create, params: { question: attributes_for(:question, :invalid_question) } } + .to_not change(Question, :count) + end + + it 're-renders new view' do + post :create, params: { question: attributes_for(:question, :invalid_question) } + expect(response).to render_template :new + end + end + end + + describe 'PATCH #update' do + context 'with valid attributes' do + it 'changes question attributes' do + patch :update, params: { id: question, question: { title: 'new title', body: 'new body' } } + question.reload + expect(question.title).to eq 'new title' + expect(question.body).to eq 'new body' + end + + it 'redirects to updated question' do + patch :update, params: { id: question, question: attributes_for(:question) } + expect(response).to redirect_to question + end + end + + context 'with invalid attributes' do + before { patch :update, params: { id: question, question: attributes_for(:question, :invalid_question) } } + + it 'does not change question' do + question.reload + expect(question.title).to eq 'MyString' + expect(question.body).to eq 'MyText' + end + + it 're-renders edit view' do + expect(response).to render_template :edit + end + end + end + + describe 'DELETE #destroy' do + let!(:question) { create(:question) } + + it 'deletes the question' do + expect { delete :destroy, params: { id: question } }.to change(Question, :count).by(-1) + end + + it 'redirects to index' do + delete :destroy, params: { id: question } + expect(response).to redirect_to questions_path + end + end +end diff --git a/spec/factories/answers.rb b/spec/factories/answers.rb index 72955ae..1349d25 100644 --- a/spec/factories/answers.rb +++ b/spec/factories/answers.rb @@ -1,6 +1,10 @@ FactoryBot.define do factory :answer do - body { "MyString" } - question { nil } + body { "MyText" } + association :question + end + + trait :invalid_answer do + body { nil } end end diff --git a/spec/factories/questions.rb b/spec/factories/questions.rb index 0b51d2a..f11d400 100644 --- a/spec/factories/questions.rb +++ b/spec/factories/questions.rb @@ -3,4 +3,8 @@ title { "MyString" } body { "MyText" } end + + trait :invalid_question do + title { nil } + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 5c61d26..b72aa0e 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -31,6 +31,7 @@ exit 1 end RSpec.configure do |config| + config.include FactoryBot::Syntax::Methods # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{::Rails.root}/spec/fixtures"