diff --git a/Gemfile b/Gemfile index 5ec28d6..566e9ef 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,7 @@ gem 'betterlorem' group :development, :test do gem 'rspec-rails', '~> 3.5' + gem 'shoulda' end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 06d83cf..4c4707f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -138,6 +138,12 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) + shoulda (3.5.0) + shoulda-context (~> 1.0, >= 1.0.1) + shoulda-matchers (>= 1.4.1, < 3.0) + shoulda-context (1.2.2) + shoulda-matchers (2.8.0) + activesupport (>= 3.0.0) spring (2.0.0) activesupport (>= 4.2) spring-watcher-listen (2.0.1) @@ -184,6 +190,7 @@ DEPENDENCIES rails (~> 5.0.0, >= 5.0.0.1) rspec-rails (~> 3.5) sass-rails (~> 5.0) + shoulda spring spring-watcher-listen (~> 2.0.0) turbolinks (~> 5) diff --git a/app/assets/javascripts/comments.coffee b/app/assets/javascripts/comments.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/comments.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/comments.scss b/app/assets/stylesheets/comments.scss new file mode 100644 index 0000000..e730912 --- /dev/null +++ b/app/assets/stylesheets/comments.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Comments controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 0000000..1d7d975 --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,74 @@ +class CommentsController < ApplicationController + before_action :set_comment, only: [:show, :edit, :update, :destroy] + + # GET /comments + # GET /comments.json + def index + @comments = Comment.all + end + + # GET /comments/1 + # GET /comments/1.json + def show + end + + # GET /comments/new + def new + @comment = Comment.new + end + + # GET /comments/1/edit + def edit + end + + # POST /comments + # POST /comments.json + def create + @comment = Comment.new(comment_params) + + respond_to do |format| + if @comment.save + format.html { redirect_to @comment, notice: 'Comment was successfully created.' } + format.json { render :show, status: :created, location: @comment } + else + format.html { render :new } + format.json { render json: @comment.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /comments/1 + # PATCH/PUT /comments/1.json + def update + respond_to do |format| + if @comment.update(comment_params) + format.html { redirect_to @comment, notice: 'Comment was successfully updated.' } + format.json { render :show, status: :ok, location: @comment } + else + format.html { render :edit } + format.json { render json: @comment.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /comments/1 + # DELETE /comments/1.json + def destroy + @comment.destroy + respond_to do |format| + format.html { redirect_to comments_url, notice: 'Comment was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_comment + @comment = Comment.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def comment_params + params.require(:comment).permit(:body, :author, :product_id) + end +end diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb new file mode 100644 index 0000000..0ec9ca5 --- /dev/null +++ b/app/helpers/comments_helper.rb @@ -0,0 +1,2 @@ +module CommentsHelper +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 0000000..2b0e0b1 --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,5 @@ +class Comment < ApplicationRecord + validates :body, presence: true + + belongs_to :product +end diff --git a/app/views/comments/_comment.json.jbuilder b/app/views/comments/_comment.json.jbuilder new file mode 100644 index 0000000..d61e296 --- /dev/null +++ b/app/views/comments/_comment.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! comment, :id, :body, :author, :product_id, :created_at, :updated_at +json.url comment_url(comment, format: :json) \ No newline at end of file diff --git a/app/views/comments/_form.html.erb b/app/views/comments/_form.html.erb new file mode 100644 index 0000000..fccb507 --- /dev/null +++ b/app/views/comments/_form.html.erb @@ -0,0 +1,32 @@ +<%= form_for(comment) do |f| %> + <% if comment.errors.any? %> +
+

<%= pluralize(comment.errors.count, "error") %> prohibited this comment from being saved:

+ + +
+ <% end %> + +
+ <%= f.label :body %> + <%= f.text_area :body %> +
+ +
+ <%= f.label :author %> + <%= f.text_field :author %> +
+ +
+ <%= f.label :product_id %> + <%= f.text_field :product_id %> +
+ +
+ <%= f.submit %> +
+<% end %> diff --git a/app/views/comments/edit.html.erb b/app/views/comments/edit.html.erb new file mode 100644 index 0000000..a20be1d --- /dev/null +++ b/app/views/comments/edit.html.erb @@ -0,0 +1,6 @@ +

Editing Comment

+ +<%= render 'form', comment: @comment %> + +<%= link_to 'Show', @comment %> | +<%= link_to 'Back', comments_path %> diff --git a/app/views/comments/index.html.erb b/app/views/comments/index.html.erb new file mode 100644 index 0000000..8c6e0f0 --- /dev/null +++ b/app/views/comments/index.html.erb @@ -0,0 +1,31 @@ +

<%= notice %>

+ +

Comments

+ + + + + + + + + + + + + <% @comments.each do |comment| %> + + + + + + + + + <% end %> + +
BodyAuthorProduct
<%= comment.body %><%= comment.author %><%= comment.product %><%= link_to 'Show', comment %><%= link_to 'Edit', edit_comment_path(comment) %><%= link_to 'Destroy', comment, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Comment', new_comment_path %> diff --git a/app/views/comments/index.json.jbuilder b/app/views/comments/index.json.jbuilder new file mode 100644 index 0000000..d34efcf --- /dev/null +++ b/app/views/comments/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @comments, partial: 'comments/comment', as: :comment \ No newline at end of file diff --git a/app/views/comments/new.html.erb b/app/views/comments/new.html.erb new file mode 100644 index 0000000..f8b43b6 --- /dev/null +++ b/app/views/comments/new.html.erb @@ -0,0 +1,5 @@ +

New Comment

+ +<%= render 'form', comment: @comment %> + +<%= link_to 'Back', comments_path %> diff --git a/app/views/comments/show.html.erb b/app/views/comments/show.html.erb new file mode 100644 index 0000000..cf3836b --- /dev/null +++ b/app/views/comments/show.html.erb @@ -0,0 +1,19 @@ +

<%= notice %>

+ +

+ Body: + <%= @comment.body %> +

+ +

+ Author: + <%= @comment.author %> +

+ +

+ Product: + <%= @comment.product %> +

+ +<%= link_to 'Edit', edit_comment_path(@comment) %> | +<%= link_to 'Back', comments_path %> diff --git a/app/views/comments/show.json.jbuilder b/app/views/comments/show.json.jbuilder new file mode 100644 index 0000000..3ca9fbf --- /dev/null +++ b/app/views/comments/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "comments/comment", comment: @comment \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 3d028bd..a1bebab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ Rails.application.routes.draw do + resources :comments root 'products#index' resources :products diff --git a/db/migrate/20161108180841_create_comments.rb b/db/migrate/20161108180841_create_comments.rb new file mode 100644 index 0000000..932ec8a --- /dev/null +++ b/db/migrate/20161108180841_create_comments.rb @@ -0,0 +1,11 @@ +class CreateComments < ActiveRecord::Migration[5.0] + def change + create_table :comments do |t| + t.text :body + t.string :author + t.references :product, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 0f775c8..121d2b1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,20 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161107221656) do +ActiveRecord::Schema.define(version: 20161108180841) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "comments", force: :cascade do |t| + t.text "body" + t.string "author" + t.integer "product_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["product_id"], name: "index_comments_on_product_id", using: :btree + end + create_table "products", force: :cascade do |t| t.float "price" t.string "name" @@ -23,4 +32,5 @@ t.datetime "updated_at", null: false end + add_foreign_key "comments", "products" end diff --git a/spec/controllers/products_controller_spec.rb b/spec/controllers/products_controller_spec.rb deleted file mode 100644 index 0cad075..0000000 --- a/spec/controllers/products_controller_spec.rb +++ /dev/null @@ -1,159 +0,0 @@ -require 'rails_helper' - -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. - -RSpec.describe ProductsController, type: :controller do - - # This should return the minimal set of attributes required to create a valid - # Product. As you add validations to Product, be sure to - # adjust the attributes here as well. - let(:valid_attributes) { - skip("Add a hash of attributes valid for your model") - } - - let(:invalid_attributes) { - skip("Add a hash of attributes invalid for your model") - } - - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # ProductsController. Be sure to keep this updated too. - let(:valid_session) { {} } - - describe "GET #index" do - it "assigns all products as @products" do - product = Product.create! valid_attributes - get :index, params: {}, session: valid_session - expect(assigns(:products)).to eq([product]) - end - end - - describe "GET #show" do - it "assigns the requested product as @product" do - product = Product.create! valid_attributes - get :show, params: {id: product.to_param}, session: valid_session - expect(assigns(:product)).to eq(product) - end - end - - describe "GET #new" do - it "assigns a new product as @product" do - get :new, params: {}, session: valid_session - expect(assigns(:product)).to be_a_new(Product) - end - end - - describe "GET #edit" do - it "assigns the requested product as @product" do - product = Product.create! valid_attributes - get :edit, params: {id: product.to_param}, session: valid_session - expect(assigns(:product)).to eq(product) - end - end - - describe "POST #create" do - context "with valid params" do - it "creates a new Product" do - expect { - post :create, params: {product: valid_attributes}, session: valid_session - }.to change(Product, :count).by(1) - end - - it "assigns a newly created product as @product" do - post :create, params: {product: valid_attributes}, session: valid_session - expect(assigns(:product)).to be_a(Product) - expect(assigns(:product)).to be_persisted - end - - it "redirects to the created product" do - post :create, params: {product: valid_attributes}, session: valid_session - expect(response).to redirect_to(Product.last) - end - end - - context "with invalid params" do - it "assigns a newly created but unsaved product as @product" do - post :create, params: {product: invalid_attributes}, session: valid_session - expect(assigns(:product)).to be_a_new(Product) - end - - it "re-renders the 'new' template" do - post :create, params: {product: invalid_attributes}, session: valid_session - expect(response).to render_template("new") - end - end - end - - describe "PUT #update" do - context "with valid params" do - let(:new_attributes) { - skip("Add a hash of attributes valid for your model") - } - - it "updates the requested product" do - product = Product.create! valid_attributes - put :update, params: {id: product.to_param, product: new_attributes}, session: valid_session - product.reload - skip("Add assertions for updated state") - end - - it "assigns the requested product as @product" do - product = Product.create! valid_attributes - put :update, params: {id: product.to_param, product: valid_attributes}, session: valid_session - expect(assigns(:product)).to eq(product) - end - - it "redirects to the product" do - product = Product.create! valid_attributes - put :update, params: {id: product.to_param, product: valid_attributes}, session: valid_session - expect(response).to redirect_to(product) - end - end - - context "with invalid params" do - it "assigns the product as @product" do - product = Product.create! valid_attributes - put :update, params: {id: product.to_param, product: invalid_attributes}, session: valid_session - expect(assigns(:product)).to eq(product) - end - - it "re-renders the 'edit' template" do - product = Product.create! valid_attributes - put :update, params: {id: product.to_param, product: invalid_attributes}, session: valid_session - expect(response).to render_template("edit") - end - end - end - - describe "DELETE #destroy" do - it "destroys the requested product" do - product = Product.create! valid_attributes - expect { - delete :destroy, params: {id: product.to_param}, session: valid_session - }.to change(Product, :count).by(-1) - end - - it "redirects to the products list" do - product = Product.create! valid_attributes - delete :destroy, params: {id: product.to_param}, session: valid_session - expect(response).to redirect_to(products_url) - end - end - -end diff --git a/spec/helpers/comments_helper_spec.rb b/spec/helpers/comments_helper_spec.rb new file mode 100644 index 0000000..729cd87 --- /dev/null +++ b/spec/helpers/comments_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the CommentsHelper. For example: +# +# describe CommentsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe CommentsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb new file mode 100644 index 0000000..a6e2eab --- /dev/null +++ b/spec/models/comment_spec.rb @@ -0,0 +1,6 @@ +require 'rails_helper' + +RSpec.describe Comment, type: :model do + + it { should validate_presence_of(:body) } +end diff --git a/spec/requests/comments_spec.rb b/spec/requests/comments_spec.rb new file mode 100644 index 0000000..cb523f0 --- /dev/null +++ b/spec/requests/comments_spec.rb @@ -0,0 +1,10 @@ +require 'rails_helper' + +RSpec.describe "Comments", type: :request do + describe "GET /comments" do + it "works! (now write some real specs)" do + get comments_path + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/routing/comments_routing_spec.rb b/spec/routing/comments_routing_spec.rb new file mode 100644 index 0000000..9cc6a2e --- /dev/null +++ b/spec/routing/comments_routing_spec.rb @@ -0,0 +1,39 @@ +require "rails_helper" + +RSpec.describe CommentsController, type: :routing do + describe "routing" do + + it "routes to #index" do + expect(:get => "/comments").to route_to("comments#index") + end + + it "routes to #new" do + expect(:get => "/comments/new").to route_to("comments#new") + end + + it "routes to #show" do + expect(:get => "/comments/1").to route_to("comments#show", :id => "1") + end + + it "routes to #edit" do + expect(:get => "/comments/1/edit").to route_to("comments#edit", :id => "1") + end + + it "routes to #create" do + expect(:post => "/comments").to route_to("comments#create") + end + + it "routes to #update via PUT" do + expect(:put => "/comments/1").to route_to("comments#update", :id => "1") + end + + it "routes to #update via PATCH" do + expect(:patch => "/comments/1").to route_to("comments#update", :id => "1") + end + + it "routes to #destroy" do + expect(:delete => "/comments/1").to route_to("comments#destroy", :id => "1") + end + + end +end diff --git a/spec/views/products/edit.html.erb_spec.rb b/spec/views/products/edit.html.erb_spec.rb deleted file mode 100644 index f9ca556..0000000 --- a/spec/views/products/edit.html.erb_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'rails_helper' - -RSpec.describe "products/edit", type: :view do - before(:each) do - @product = assign(:product, Product.create!( - :price => 1.5, - :name => "MyString", - :description => "MyText" - )) - end - - it "renders the edit product form" do - render - - assert_select "form[action=?][method=?]", product_path(@product), "post" do - - assert_select "input#product_price[name=?]", "product[price]" - - assert_select "input#product_name[name=?]", "product[name]" - - assert_select "textarea#product_description[name=?]", "product[description]" - end - end -end diff --git a/spec/views/products/index.html.erb_spec.rb b/spec/views/products/index.html.erb_spec.rb deleted file mode 100644 index f44f0c6..0000000 --- a/spec/views/products/index.html.erb_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'rails_helper' - -RSpec.describe "products/index", type: :view do - before(:each) do - assign(:products, [ - Product.create!( - :price => 2.5, - :name => "Name", - :description => "MyText" - ), - Product.create!( - :price => 2.5, - :name => "Name", - :description => "MyText" - ) - ]) - end - - it "renders a list of products" do - render - assert_select "tr>td", :text => 2.5.to_s, :count => 2 - assert_select "tr>td", :text => "Name".to_s, :count => 2 - assert_select "tr>td", :text => "MyText".to_s, :count => 2 - end -end diff --git a/spec/views/products/new.html.erb_spec.rb b/spec/views/products/new.html.erb_spec.rb deleted file mode 100644 index 67c91d5..0000000 --- a/spec/views/products/new.html.erb_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'rails_helper' - -RSpec.describe "products/new", type: :view do - before(:each) do - assign(:product, Product.new( - :price => 1.5, - :name => "MyString", - :description => "MyText" - )) - end - - it "renders new product form" do - render - - assert_select "form[action=?][method=?]", products_path, "post" do - - assert_select "input#product_price[name=?]", "product[price]" - - assert_select "input#product_name[name=?]", "product[name]" - - assert_select "textarea#product_description[name=?]", "product[description]" - end - end -end diff --git a/spec/views/products/show.html.erb_spec.rb b/spec/views/products/show.html.erb_spec.rb deleted file mode 100644 index 8157c1b..0000000 --- a/spec/views/products/show.html.erb_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'rails_helper' - -RSpec.describe "products/show", type: :view do - before(:each) do - @product = assign(:product, Product.create!( - :price => 2.5, - :name => "Name", - :description => "MyText" - )) - end - - it "renders attributes in

" do - render - expect(rendered).to match(/2.5/) - expect(rendered).to match(/Name/) - expect(rendered).to match(/MyText/) - end -end