From 547c553ba98014f4c6f486b6263b820e6808797f Mon Sep 17 00:00:00 2001 From: Nihad Abbasov Date: Sat, 4 Apr 2015 17:22:59 +0500 Subject: [PATCH] implement category subscriptions --- app/controllers/categories_controller.rb | 28 +++++++++- app/helpers/categories_helper.rb | 15 ++++++ app/models/category.rb | 1 + app/models/user.rb | 18 +++++++ app/views/categories/show.html.slim | 9 ++-- app/views/categories/subscribe.js.erb | 1 + app/views/categories/unsubscribe.js.erb | 1 + config/routes.rb | 9 +++- .../controllers/categories_controller_spec.rb | 54 +++++++++++++++++++ spec/factories/subscriptions.rb | 5 +- spec/models/user_spec.rb | 34 ++++++++++++ 11 files changed, 169 insertions(+), 6 deletions(-) create mode 100644 app/helpers/categories_helper.rb create mode 100644 app/views/categories/subscribe.js.erb create mode 100644 app/views/categories/unsubscribe.js.erb diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 19e378f..a6d4faf 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -1,10 +1,36 @@ class CategoriesController < ApplicationController + before_action :authenticate_user!, only: [:subscribe, :unsubscribe] + before_action :find_category, only: [:show, :subscribe, :unsubscribe] + def index @categories = Category.order(:name) end def show - @category = Category.find_by_permalink! params[:permalink] @questions = @category.questions.includes(:answers, :author).recent.page(params[:page]).per(20) end + + def subscribe + current_user.subscribe_to @category + + respond_to do |format| + format.html { redirect_to @category } + format.js + end + end + + def unsubscribe + current_user.unsubscribe_from @category + + respond_to do |format| + format.html { redirect_to @category } + format.js + end + end + + private + + def find_category + @category = Category.find_by_permalink! params[:permalink] + end end diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb new file mode 100644 index 0000000..faf33d3 --- /dev/null +++ b/app/helpers/categories_helper.rb @@ -0,0 +1,15 @@ +module CategoriesHelper + def link_to_category_subscription(category) + return link_to('subscribe', new_user_session_path) unless user_signed_in? + + if current_user.subscribed_to? category + title = 'unsubscribe' + path = unsubscribe_category_path(category) + else + title = 'subscribe' + path = subscribe_category_path(category) + end + + link_to title, path, method: :post, remote: true, class: 'btn btn-primary', data: { category_id: category.id } + end +end diff --git a/app/models/category.rb b/app/models/category.rb index b3080b5..0425198 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -2,6 +2,7 @@ class Category < ActiveRecord::Base acts_as_url :name, url_attribute: :permalink has_many :questions, dependent: :destroy + has_many :subscriptions, as: :subscribable, dependent: :destroy validates :name, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index e63ffd6..3e15934 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -24,6 +24,24 @@ def to_param username end + def subscribe_to(subscribable) + subscriptions.create(subscribable: subscribable) unless subscribed_to?(subscribable) + end + + def unsubscribe_from(subscribable) + subscriptions.where( + subscribable_id: subscribable.id, + subscribable_type: subscribable.class + ).destroy_all if subscribed_to?(subscribable) + end + + def subscribed_to?(subscribable) + subscriptions.where( + subscribable_id: subscribable.id, + subscribable_type: subscribable.class + ).any? + end + private def self.find_for_database_authentication(warden_conditions) diff --git a/app/views/categories/show.html.slim b/app/views/categories/show.html.slim index a394d59..25b592c 100644 --- a/app/views/categories/show.html.slim +++ b/app/views/categories/show.html.slim @@ -6,9 +6,12 @@ h2 = link_to @category.name, @category p No questions in this category. - else - h2 - ' Recent questions in - = link_to @category.name, @category + .clearfix + h2.pull-left + ' Recent questions in + = link_to @category.name, @category + .pull-right + = link_to_category_subscription(@category) .question-list = render partial: 'questions/question', collection: @questions diff --git a/app/views/categories/subscribe.js.erb b/app/views/categories/subscribe.js.erb new file mode 100644 index 0000000..c58ef3c --- /dev/null +++ b/app/views/categories/subscribe.js.erb @@ -0,0 +1 @@ +$('a[data-category-id=<%= @category.id %>]').replaceWith('<%= escape_javascript(link_to_category_subscription @category) %>'); diff --git a/app/views/categories/unsubscribe.js.erb b/app/views/categories/unsubscribe.js.erb new file mode 100644 index 0000000..c58ef3c --- /dev/null +++ b/app/views/categories/unsubscribe.js.erb @@ -0,0 +1 @@ +$('a[data-category-id=<%= @category.id %>]').replaceWith('<%= escape_javascript(link_to_category_subscription @category) %>'); diff --git a/config/routes.rb b/config/routes.rb index c68ebf6..4c35124 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,6 +17,13 @@ end end + concern :subscribable do + member do + post :subscribe + post :unsubscribe + end + end + get '/ask', to: redirect('/questions/new') get '/q/:id', to: redirect('/questions/%{id}'), as: :question_short get '/settings', to: redirect('/settings/profile') @@ -33,7 +40,7 @@ end resources :tags, only: [:show], param: :name - resources :categories, only: [:index, :show], param: :permalink + resources :categories, only: [:index, :show], param: :permalink, concerns: :subscribable resources :users, only: [:show], param: :username do member do get :answers diff --git a/spec/controllers/categories_controller_spec.rb b/spec/controllers/categories_controller_spec.rb index a3dd217..209db54 100644 --- a/spec/controllers/categories_controller_spec.rb +++ b/spec/controllers/categories_controller_spec.rb @@ -2,6 +2,7 @@ RSpec.describe CategoriesController, type: :controller do let(:category) { create :category } + let(:user) { create :confirmed_user } describe "GET #index" do it "returns http success" do @@ -16,4 +17,57 @@ expect(response).to be_success end end + + describe "POST #subscribe" do + context "when not signed in" do + it "redirects to sign in page" do + post :subscribe, permalink: category.permalink + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before { sign_in user } + + it "subscribes user to category and redirects to category page" do + expect { + post :subscribe, permalink: category.permalink + }.to change(category.subscriptions, :count).by(1) + expect(response).to redirect_to(category) + end + + it "returns http success for remote request" do + xhr :post, :subscribe, permalink: category.permalink + expect(response).to be_success + end + end + end + + describe "POST #unsubscribe" do + context "when not signed in" do + it "redirects to sign in page" do + post :unsubscribe, permalink: category.permalink + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in user + user.subscriptions.create subscribable: category + end + + it "unsubscribes user from category and redirects to category page" do + expect { + post :unsubscribe, permalink: category.permalink + }.to change(category.subscriptions, :count).by(-1) + expect(response).to redirect_to(category) + end + + it "returns http success for remote request" do + xhr :post, :unsubscribe, permalink: category.permalink + expect(response).to be_success + end + end + end end diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb index ef814a8..9941100 100644 --- a/spec/factories/subscriptions.rb +++ b/spec/factories/subscriptions.rb @@ -1,6 +1,9 @@ FactoryGirl.define do factory :subscription do subscriber - # association :subscribable, factory: :category + end + + factory :category_subscription, parent: :subscription do + association :subscribable, factory: :category end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 45358f0..30e6ba3 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,6 +1,9 @@ require 'rails_helper' RSpec.describe User, type: :model do + let(:user) { create :confirmed_user } + let(:category) { create :category } + describe "relations" do it { should have_many(:answers).conditions(anonymous: false). with_foreign_key('author_id').dependent(:destroy) } @@ -19,4 +22,35 @@ it { should allow_value('', nil).for(:fullname) } it { should validate_length_of(:bio).is_at_most(400) } end + + describe "#subscribe_to" do + it "subscribes user to subscribable" do + subscribable = category + expect { user.subscribe_to(subscribable) }.to change(user.subscriptions, :count).by(1) + + subscription = user.subscriptions.last + expect(subscription.subscribable_id).to eq(subscribable.id) + expect(subscription.subscribable_type).to eq(subscribable.class.to_s) + expect(subscription.subscriber_id).to eq(user.id) + end + end + + describe "#unsubscribe_from" do + it "unsubscribes user from subscribable" do + subscribable = category + create :subscription, subscribable: subscribable, subscriber: user + + expect { user.unsubscribe_from(subscribable) }.to change(user.subscriptions, :count).by(-1) + end + end + + describe "#subscribed_to?" do + it "checks if user subscribed to subscribable" do + create :subscription, subscribable: category, subscriber: user + category2 = create :category + + expect(user.subscribed_to?(category)).to be true + expect(user.subscribed_to?(category2)).to be false + end + end end