From 1d692b31f8fe2287a28aaa4a4efe62a41da956b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Geonizeli?= Date: Sun, 27 Feb 2022 17:02:37 -0300 Subject: [PATCH] add post --- app/controllers/posts_controller.rb | 53 ++++++++++++++ app/models/post.rb | 27 +++++++ app/views/posts/_post.json.jbuilder | 4 ++ app/views/posts/index.json.jbuilder | 1 + app/views/posts/show.json.jbuilder | 1 + config/routes.rb | 3 +- db/migrate/20220227194054_create_posts.rb | 11 +++ db/schema.rb | 14 +++- db/seeds.rb | 3 + spec/factories/posts.rb | 7 ++ spec/models/post_spec.rb | 31 ++++++++ spec/requests/posts_spec.rb | 88 +++++++++++++++++++++++ spec/routing/posts_routing_spec.rb | 18 +++++ 13 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 app/controllers/posts_controller.rb create mode 100644 app/models/post.rb create mode 100644 app/views/posts/_post.json.jbuilder create mode 100644 app/views/posts/index.json.jbuilder create mode 100644 app/views/posts/show.json.jbuilder create mode 100644 db/migrate/20220227194054_create_posts.rb create mode 100644 spec/factories/posts.rb create mode 100644 spec/models/post_spec.rb create mode 100644 spec/requests/posts_spec.rb create mode 100644 spec/routing/posts_routing_spec.rb diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb new file mode 100644 index 0000000..9b33ff2 --- /dev/null +++ b/app/controllers/posts_controller.rb @@ -0,0 +1,53 @@ +class PostsController < ApplicationController + before_action :set_post, only: %i[ show update destroy ] + + # GET /posts + # GET /posts.json + def index + @posts = Post.all + end + + # GET /posts/1 + # GET /posts/1.json + def show + end + + # POST /posts + # POST /posts.json + def create + @post = Post.new(post_params) + + if @post.save + render :show, status: :created, location: @post + else + render json: @post.errors, status: :unprocessable_entity + end + end + + # PATCH/PUT /posts/1 + # PATCH/PUT /posts/1.json + def update + if @post.update(post_params) + render :show, status: :ok, location: @post + else + render json: @post.errors, status: :unprocessable_entity + end + end + + # DELETE /posts/1 + # DELETE /posts/1.json + def destroy + @post.destroy + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_post + @post = Post.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def post_params + params.require(:post).permit(:content, :user_id, :quoted_post_id) + end +end diff --git a/app/models/post.rb b/app/models/post.rb new file mode 100644 index 0000000..112cb49 --- /dev/null +++ b/app/models/post.rb @@ -0,0 +1,27 @@ +class Post < ApplicationRecord + belongs_to :user + belongs_to :quoted_post, optional: true, class_name: 'Post' + + validates :content, length: { maximum: 777 } + validates :content, presence: true, if: :quoted_post? + + def kind + if quoted_post? + :quoted_post + elsif repost? + :repost + else + :post + end + end + + private + + def quoted_post? + content.present? && quoted_post_id + end + + def repost? + content.blank? && quoted_post_id + end +end diff --git a/app/views/posts/_post.json.jbuilder b/app/views/posts/_post.json.jbuilder new file mode 100644 index 0000000..4aeaa5c --- /dev/null +++ b/app/views/posts/_post.json.jbuilder @@ -0,0 +1,4 @@ +json.extract! post, :id, :content, :user_id, :created_at, :updated_at +json.url post_url(post, format: :json) + +json.quoted_post(post.quoted_post) \ No newline at end of file diff --git a/app/views/posts/index.json.jbuilder b/app/views/posts/index.json.jbuilder new file mode 100644 index 0000000..a3c6f4a --- /dev/null +++ b/app/views/posts/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @posts, partial: "posts/post", as: :post diff --git a/app/views/posts/show.json.jbuilder b/app/views/posts/show.json.jbuilder new file mode 100644 index 0000000..5274482 --- /dev/null +++ b/app/views/posts/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "posts/post", post: @post diff --git a/config/routes.rb b/config/routes.rb index 4a94fda..04a1cd9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,5 @@ Rails.application.routes.draw do + resources :posts, only: [:index, :show, :create] resources :user_follows, only: [:create, :destroy] resources :users, only: [:show] - - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html end diff --git a/db/migrate/20220227194054_create_posts.rb b/db/migrate/20220227194054_create_posts.rb new file mode 100644 index 0000000..85da41a --- /dev/null +++ b/db/migrate/20220227194054_create_posts.rb @@ -0,0 +1,11 @@ +class CreatePosts < ActiveRecord::Migration[7.0] + def change + create_table :posts do |t| + t.text :content, null: true + t.references :user, null: false, references: :user, foreign_key: { to_table: :users } + t.references :quoted_post, null: true, references: :post, foreign_key: { to_table: :posts } + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index afe9957..3143550 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,20 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2022_02_27_175028) do +ActiveRecord::Schema[7.0].define(version: 2022_02_27_194054) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "posts", force: :cascade do |t| + t.text "content" + t.bigint "user_id", null: false + t.bigint "quoted_post_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["quoted_post_id"], name: "index_posts_on_quoted_post_id" + t.index ["user_id"], name: "index_posts_on_user_id" + end + create_table "user_follows", force: :cascade do |t| t.bigint "follower_id" t.bigint "followed_id" @@ -30,6 +40,8 @@ ActiveRecord::Schema[7.0].define(version: 2022_02_27_175028) do t.index ["username"], name: "index_users_on_username" end + add_foreign_key "posts", "posts", column: "quoted_post_id" + add_foreign_key "posts", "users" add_foreign_key "user_follows", "users", column: "followed_id" add_foreign_key "user_follows", "users", column: "follower_id" end diff --git a/db/seeds.rb b/db/seeds.rb index 5f4f465..b84423e 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -4,3 +4,6 @@ geonizeli = User.find_or_create_by(username: 'geonizeli') UserFollow.find_or_create_by(follower_id: xpto.id, followed_id: geonizeli.id) UserFollow.find_or_create_by(follower_id: geonizeli.id, followed_id: xpto.id) + +first_post = Post.find_or_create_by(content: 'Hello World!', user_id: xpto.id) +Post.find_or_create_by(content: 'Hello World!', user_id: xpto.id, quoted_post_id: first_post.id) \ No newline at end of file diff --git a/spec/factories/posts.rb b/spec/factories/posts.rb new file mode 100644 index 0000000..b9ad7fe --- /dev/null +++ b/spec/factories/posts.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :post do + content { Faker::Lorem.paragraph } + user + quoted_post { nil } + end +end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb new file mode 100644 index 0000000..34a8f05 --- /dev/null +++ b/spec/models/post_spec.rb @@ -0,0 +1,31 @@ +require 'rails_helper' + +RSpec.describe Post, type: :model do + describe '#kind' do + context 'when post have other post reference and a content' do + it 'returns :quoted_post' do + quoted_post = create(:post) + post = create(:post, quoted_post: quoted_post) + + expect(post.kind).to eq(:quoted_post) + end + end + + context 'when post have other post reference and dont have a content' do + it 'returns :repost' do + quoted_post = create(:post) + post = create(:post, quoted_post: quoted_post, content: nil) + + expect(post.kind).to eq(:repost) + end + end + + context 'when post dont have other post reference and have a content' do + it 'returns :post' do + post = create(:post) + + expect(post.kind).to eq(:post) + end + end + end +end diff --git a/spec/requests/posts_spec.rb b/spec/requests/posts_spec.rb new file mode 100644 index 0000000..da2237d --- /dev/null +++ b/spec/requests/posts_spec.rb @@ -0,0 +1,88 @@ +require 'rails_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to test 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. + +RSpec.describe "/posts", type: :request do + let(:quoted_post) { create(:post) } + + let(:valid_attributes) { + { + content: "Quo dolorem recusandae. Vero laborum deleniti. Qui ipsam illum.", + user_id: create(:user).id, + } + } + + let(:invalid_attributes) { + { + content: "Quo dolorem recusandae. Vero laborum deleniti. Qui ipsam illum." + } + } + + # This should return the minimal set of values that should be in the headers + # in order to pass any filters (e.g. authentication) defined in + # PostsController, or in your router and rack + # middleware. Be sure to keep this updated too. + let(:valid_headers) { + {} + } + + describe "GET /index" do + it "renders a successful response" do + Post.create! valid_attributes + get posts_url, headers: valid_headers, as: :json + expect(response).to be_successful + end + end + + describe "GET /show" do + it "renders a successful response" do + post = Post.create! valid_attributes + get post_url(post), as: :json + expect(response).to be_successful + end + end + + describe "POST /create" do + context "with valid parameters" do + it "creates a new Post" do + expect { + post posts_url, + params: { post: valid_attributes }, headers: valid_headers, as: :json + }.to change(Post, :count).by(1) + end + + it "renders a JSON response with the new post" do + post posts_url, + params: { post: valid_attributes }, headers: valid_headers, as: :json + expect(response).to have_http_status(:created) + expect(response.content_type).to match(a_string_including("application/json")) + end + end + + context "with invalid parameters" do + it "does not create a new Post" do + expect { + post posts_url, + params: { post: invalid_attributes }, as: :json + }.to change(Post, :count).by(0) + end + + it "renders a JSON response with errors for the new post" do + post posts_url, + params: { post: invalid_attributes }, headers: valid_headers, as: :json + expect(response).to have_http_status(:unprocessable_entity) + expect(response.content_type).to match(a_string_including("application/json")) + end + end + end +end diff --git a/spec/routing/posts_routing_spec.rb b/spec/routing/posts_routing_spec.rb new file mode 100644 index 0000000..19482f0 --- /dev/null +++ b/spec/routing/posts_routing_spec.rb @@ -0,0 +1,18 @@ +require "rails_helper" + +RSpec.describe PostsController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/posts").to route_to("posts#index") + end + + it "routes to #show" do + expect(get: "/posts/1").to route_to("posts#show", id: "1") + end + + + it "routes to #create" do + expect(post: "/posts").to route_to("posts#create") + end + end +end