commit: 4289ed1d13b9c3b91663581c44635105c4bc0412
parent: 256e3adc1d9508423aab8fcfb13745c9f85ff948
Author: Matt Jankowski <mjankowski@thoughtbot.com>
Date: Tue, 23 May 2017 12:11:39 -0400
Refactor of API timeline actions (#3263)
- Increase coverage to exercise all parts of each action
- Move into namespace to share common code
- Misc refactor of each action for smaller methods, simpler code
Diffstat:
12 files changed, 330 insertions(+), 129 deletions(-)
diff --git a/app/controllers/api/v1/timelines/base_controller.rb b/app/controllers/api/v1/timelines/base_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Api::V1::Timelines
+ class BaseController < ApiController
+ respond_to :json
+ after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
+
+ private
+
+ def cache_collection(raw)
+ super(raw, Status)
+ end
+
+ def pagination_params(core_params)
+ params.permit(:local, :limit).merge(core_params)
+ end
+
+ def insert_pagination_headers
+ set_pagination_headers(next_path, prev_path)
+ end
+
+ def next_path
+ raise 'Override in child controllers'
+ end
+
+ def prev_path
+ raise 'Override in child controllers'
+ end
+ end
+end
diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Api::V1::Timelines
+ class HomeController < BaseController
+ before_action -> { doorkeeper_authorize! :read }, only: [:show]
+ before_action :require_user!, only: [:show]
+
+ def show
+ @statuses = load_statuses
+ end
+
+ private
+
+ def load_statuses
+ cached_home_statuses.tap do |statuses|
+ set_maps(statuses)
+ end
+ end
+
+ def cached_home_statuses
+ cache_collection home_statuses
+ end
+
+ def home_statuses
+ account_home_feed.get(
+ limit_param(DEFAULT_STATUSES_LIMIT),
+ params[:max_id],
+ params[:since_id]
+ )
+ end
+
+ def account_home_feed
+ Feed.new(:home, current_account)
+ end
+
+ def next_path
+ api_v1_timelines_home_url pagination_params(max_id: @statuses.last.id)
+ end
+
+ def prev_path
+ api_v1_timelines_home_url pagination_params(since_id: @statuses.first.id)
+ end
+ end
+end
diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Api::V1::Timelines
+ class PublicController < BaseController
+ def show
+ @statuses = load_statuses
+ end
+
+ private
+
+ def load_statuses
+ cached_public_statuses.tap do |statuses|
+ set_maps(statuses)
+ end
+ end
+
+ def cached_public_statuses
+ cache_collection public_statuses
+ end
+
+ def public_statuses
+ public_timeline_statuses.paginate_by_max_id(
+ limit_param(DEFAULT_STATUSES_LIMIT),
+ params[:max_id],
+ params[:since_id]
+ )
+ end
+
+ def public_timeline_statuses
+ Status.as_public_timeline(current_account, params[:local])
+ end
+
+ def next_path
+ api_v1_timelines_public_url pagination_params(max_id: @statuses.last.id)
+ end
+
+ def prev_path
+ api_v1_timelines_public_url pagination_params(since_id: @statuses.first.id)
+ end
+ end
+end
diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Api::V1::Timelines
+ class TagController < BaseController
+ before_action :load_tag
+
+ def show
+ @statuses = load_statuses
+ end
+
+ private
+
+ def load_tag
+ @tag = Tag.find_by(name: params[:id].downcase)
+ end
+
+ def load_statuses
+ cached_tagged_statuses.tap do |statuses|
+ set_maps(statuses)
+ end
+ end
+
+ def cached_tagged_statuses
+ cache_collection tagged_statuses
+ end
+
+ def tagged_statuses
+ if @tag.nil?
+ []
+ else
+ tag_timeline_statuses.paginate_by_max_id(
+ limit_param(DEFAULT_STATUSES_LIMIT),
+ params[:max_id],
+ params[:since_id]
+ )
+ end
+ end
+
+ def tag_timeline_statuses
+ Status.as_tag_timeline(@tag, current_account, params[:local])
+ end
+
+ def next_path
+ api_v1_timelines_tag_url params[:id], pagination_params(max_id: @statuses.last.id)
+ end
+
+ def prev_path
+ api_v1_timelines_tag_url params[:id], pagination_params(since_id: @statuses.first.id)
+ end
+ end
+end
diff --git a/app/controllers/api/v1/timelines_controller.rb b/app/controllers/api/v1/timelines_controller.rb
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-class Api::V1::TimelinesController < ApiController
- before_action -> { doorkeeper_authorize! :read }, only: [:home]
- before_action :require_user!, only: [:home]
-
- respond_to :json
-
- def home
- @statuses = Feed.new(:home, current_account).get(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
- @statuses = cache_collection(@statuses)
-
- set_maps(@statuses)
-
- next_path = api_v1_home_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
- prev_path = api_v1_home_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
-
- set_pagination_headers(next_path, prev_path)
-
- render :index
- end
-
- def public
- @statuses = Status.as_public_timeline(current_account, params[:local]).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
- @statuses = cache_collection(@statuses)
-
- set_maps(@statuses)
-
- next_path = api_v1_public_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
- prev_path = api_v1_public_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
-
- set_pagination_headers(next_path, prev_path)
-
- render :index
- end
-
- def tag
- @tag = Tag.find_by(name: params[:id].downcase)
- @statuses = @tag.nil? ? [] : Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
- @statuses = cache_collection(@statuses)
-
- set_maps(@statuses)
-
- next_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
- prev_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
-
- set_pagination_headers(next_path, prev_path)
-
- render :index
- end
-
- private
-
- def cache_collection(raw)
- super(raw, Status)
- end
-
- def pagination_params(core_params)
- params.permit(:local, :limit).merge(core_params)
- end
-end
diff --git a/app/views/api/v1/timelines/index.rabl b/app/views/api/v1/timelines/base/show.rabl
diff --git a/config/routes.rb b/config/routes.rb
@@ -136,9 +136,11 @@ Rails.application.routes.draw do
end
end
- get '/timelines/home', to: 'timelines#home', as: :home_timeline
- get '/timelines/public', to: 'timelines#public', as: :public_timeline
- get '/timelines/tag/:id', to: 'timelines#tag', as: :hashtag_timeline
+ namespace :timelines do
+ resource :home, only: :show, controller: :home
+ resource :public, only: :show, controller: :public
+ resources :tag, only: :show
+ end
get '/search', to: 'search#index', as: :search
diff --git a/spec/controllers/api/v1/timelines/home_controller_spec.rb b/spec/controllers/api/v1/timelines/home_controller_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Api::V1::Timelines::HomeController do
+ render_views
+
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), current_sign_in_at: 1.day.ago) }
+
+ before do
+ allow(controller).to receive(:doorkeeper_token) { token }
+ end
+
+ context 'with a user context' do
+ let(:token) { double acceptable?: true, resource_owner_id: user.id }
+
+ describe 'GET #show' do
+ before do
+ follow = Fabricate(:follow, account: user.account)
+ PostStatusService.new.call(follow.target_account, 'New status for user home timeline.')
+ end
+
+ it 'returns http success' do
+ get :show
+
+ expect(response).to have_http_status(:success)
+ expect(response.headers['Link'].links.size).to eq(2)
+ end
+ end
+ end
+
+ context 'without a user context' do
+ let(:token) { double acceptable?: true, resource_owner_id: nil }
+
+ describe 'GET #show' do
+ it 'returns http unprocessable entity' do
+ get :show
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.headers['Link']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/controllers/api/v1/timelines/public_controller_spec.rb b/spec/controllers/api/v1/timelines/public_controller_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Api::V1::Timelines::PublicController do
+ render_views
+
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+
+ before do
+ allow(controller).to receive(:doorkeeper_token) { token }
+ end
+
+ context 'with a user context' do
+ let(:token) { double acceptable?: true, resource_owner_id: user.id }
+
+ describe 'GET #show' do
+ before do
+ PostStatusService.new.call(user.account, 'New status from user for federated public timeline.')
+ end
+
+ it 'returns http success' do
+ get :show
+
+ expect(response).to have_http_status(:success)
+ expect(response.headers['Link'].links.size).to eq(2)
+ end
+ end
+
+ describe 'GET #show with local only' do
+ before do
+ PostStatusService.new.call(user.account, 'New status from user for local public timeline.')
+ end
+
+ it 'returns http success' do
+ get :show, params: { local: true }
+
+ expect(response).to have_http_status(:success)
+ expect(response.headers['Link'].links.size).to eq(2)
+ end
+ end
+ end
+
+ context 'without a user context' do
+ let(:token) { double acceptable?: true, resource_owner_id: nil }
+
+ describe 'GET #show' do
+ it 'returns http success' do
+ get :show
+
+ expect(response).to have_http_status(:success)
+ expect(response.headers['Link']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/controllers/api/v1/timelines/tag_controller_spec.rb b/spec/controllers/api/v1/timelines/tag_controller_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Api::V1::Timelines::TagController do
+ render_views
+
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+
+ before do
+ allow(controller).to receive(:doorkeeper_token) { token }
+ end
+
+ context 'with a user context' do
+ let(:token) { double acceptable?: true, resource_owner_id: user.id }
+
+ describe 'GET #show' do
+ before do
+ PostStatusService.new.call(user.account, 'It is a #test')
+ end
+
+ it 'returns http success' do
+ get :show, params: { id: 'test' }
+ expect(response).to have_http_status(:success)
+ expect(response.headers['Link'].links.size).to eq(2)
+ end
+ end
+ end
+
+ context 'without a user context' do
+ let(:token) { double acceptable?: true, resource_owner_id: nil }
+
+ describe 'GET #show' do
+ it 'returns http success' do
+ get :show, params: { id: 'test' }
+ expect(response).to have_http_status(:success)
+ expect(response.headers['Link']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/controllers/api/v1/timelines_controller_spec.rb b/spec/controllers/api/v1/timelines_controller_spec.rb
@@ -1,65 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Api::V1::TimelinesController, type: :controller do
- render_views
-
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-
- before do
- allow(controller).to receive(:doorkeeper_token) { token }
- end
-
- context 'with a user context' do
- let(:token) { double acceptable?: true, resource_owner_id: user.id }
-
- describe 'GET #home' do
- it 'returns http success' do
- get :home
- expect(response).to have_http_status(:success)
- end
- end
-
- describe 'GET #public' do
- it 'returns http success' do
- get :public
- expect(response).to have_http_status(:success)
- end
- end
-
- describe 'GET #tag' do
- before do
- PostStatusService.new.call(user.account, 'It is a #test')
- end
-
- it 'returns http success' do
- get :tag, params: { id: 'test' }
- expect(response).to have_http_status(:success)
- end
- end
- end
-
- context 'without a user context' do
- let(:token) { double acceptable?: true, resource_owner_id: nil }
-
- describe 'GET #home' do
- it 'returns http unprocessable entity' do
- get :home
- expect(response).to have_http_status(:unprocessable_entity)
- end
- end
-
- describe 'GET #public' do
- it 'returns http success' do
- get :public
- expect(response).to have_http_status(:success)
- end
- end
-
- describe 'GET #tag' do
- it 'returns http success' do
- get :tag, params: { id: 'test' }
- expect(response).to have_http_status(:success)
- end
- end
- end
-end
diff --git a/spec/routing/api_timelines_spec.rb b/spec/routing/api_timelines_spec.rb
@@ -0,0 +1,18 @@
+require 'rails_helper'
+
+describe 'API timeline routes' do
+ it 'routes to home timeline' do
+ expect(get('/api/v1/timelines/home')).
+ to route_to('api/v1/timelines/home#show')
+ end
+
+ it 'routes to public timeline' do
+ expect(get('/api/v1/timelines/public')).
+ to route_to('api/v1/timelines/public#show')
+ end
+
+ it 'routes to tag timeline' do
+ expect(get('/api/v1/timelines/tag/test')).
+ to route_to('api/v1/timelines/tag#show', id: 'test')
+ end
+end