From 36f5d168d8544aefa22d0670c3e38ec6dd9db9fc Mon Sep 17 00:00:00 2001 From: Ridhwana Date: Tue, 4 Oct 2022 19:02:44 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=82=EF=B8=8F=E2=9C=82=EF=B8=8F=E2=9C=82?= =?UTF-8?q?=EF=B8=8F=20=20Part=202:=20Remove=20Sponsorship=20(#18522)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: remove initializeSponsorshipVisibility and related code * feat: remove sponsorships from sidebar * feat: remove sponsorships from the admin - route, controller, view, spec * feat: remove the admin menu item * feat: remove the i8n for admin sponsors controller * feat: sponsorship decorator was not being used anywhere * feat: sponsorship slack messenger was not being used anywhere * feat: remove the sponsorship_headline that gets configures on the admin * feat: remove the /sponsors page * feat: remove renedring of single_sponsor partial and associated partials * feat: remove the navigation link rake task for sponsors * feat: remove sponsorship from tags * feat: remove i8n constants used * remove sponsor references in text to the privacy page * feat: remove the sponsorship detail from the organization page * feat: remove the sponsors css that was used for app/views/pages/sponsors.html.erb * feat: remove the sponsorship i8n that was used in the slack messengers * feat: swap out the decorators to use Article as an example * feat: remove spec to show sponsors on home page * feat: update the specs to use Article Decorator instead of the Sponsorship Decorator * fix: use direct and not all * fix: remove tests for tag sponsorship * fix: remove organization sponsorship test * feat: remove more i8n * remove unused css from app/views/organizations/_sidebar_additional.html.erb * feat: remove sponsorship relationship from tag and organization * remove seed * feat: remove all Sponsor related specs * feat: remove model, associated i8n and specs * feat: remove sponsor from navbar * feat: add a guard clause if we're unable to constantinize the purchase_type * feat; remove sponsorship related tests * feat: remove sponsorship relationship * feat: remov especs related to sponsorship * feat: remove tag sponsorship validation * feat: remove the sponsorship factory --- app/assets/stylesheets/widgets.scss | 23 +--- app/models/organization.rb | 1 - app/models/sponsorship.rb | 57 --------- app/models/tag.rb | 5 +- app/models/user.rb | 1 - app/services/credits/ledger.rb | 3 + config/locales/models/en.yml | 8 -- config/locales/models/fr.yml | 8 -- config/locales/views/main/en.yml | 1 - config/locales/views/main/fr.yml | 1 - db/seeds.rb | 14 -- spec/factories/sponsorships.rb | 7 - spec/models/organization_spec.rb | 3 +- spec/models/sponsorship_spec.rb | 120 ------------------ spec/models/tag_spec.rb | 2 - spec/requests/credits_spec.rb | 43 ------- spec/requests/stories/tagged_articles_spec.rb | 22 ---- spec/requests/user/user_profile_spec.rb | 5 - spec/services/credits/ledger_spec.rb | 10 -- spec/services/users/delete_spec.rb | 8 -- 20 files changed, 6 insertions(+), 336 deletions(-) delete mode 100644 app/models/sponsorship.rb delete mode 100644 spec/factories/sponsorships.rb delete mode 100644 spec/models/sponsorship_spec.rb diff --git a/app/assets/stylesheets/widgets.scss b/app/assets/stylesheets/widgets.scss index 9d67a4107..e350b2c5a 100644 --- a/app/assets/stylesheets/widgets.scss +++ b/app/assets/stylesheets/widgets.scss @@ -278,29 +278,8 @@ } } } + // Sidebar sponsors -.sidebar-sponsor { - color: var(--card-color-tertiary); - img { - width: 100%; - max-width: 140px; - margin-bottom: var(--su-2); - } -} - -// Todo: Not really sure what that is.. some sort of sponsorship widget... -.sidebar-sponsorship-level { - text-align: center; - font-weight: bold; - width: calc(100% - 6px); - padding: 26px 0px; - border-radius: 3px; - margin-bottom: 10px; - font-family: $helvetica-condensed; - font-stretch: condensed; - background: var(--card-secondary-bg); -} - .crayons-sponsorship-article { margin-bottom: 10px; diff --git a/app/models/organization.rb b/app/models/organization.rb index f0f5b0432..63baf7d59 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -29,7 +29,6 @@ class Organization < ApplicationRecord has_many :notifications, dependent: :delete_all has_many :organization_memberships, dependent: :delete_all has_many :profile_pins, as: :profile, inverse_of: :profile, dependent: :destroy - has_many :sponsorships, dependent: :destroy has_many :unspent_credits, -> { where spent: false }, class_name: "Credit", inverse_of: :organization has_many :users, through: :organization_memberships diff --git a/app/models/sponsorship.rb b/app/models/sponsorship.rb deleted file mode 100644 index a5361c120..000000000 --- a/app/models/sponsorship.rb +++ /dev/null @@ -1,57 +0,0 @@ -class Sponsorship < ApplicationRecord - LEVELS = %w[gold silver bronze tag media devrel].freeze - METAL_LEVELS = %w[gold silver bronze].freeze - STATUSES = %w[none pending live].freeze - SPONSORABLE_TYPES = %w[Tag ActsAsTaggableOn::Tag].freeze - # media has no fixed amount of credits - CREDITS = { - gold: 6_000, - silver: 500, - bronze: 100, - tag: 300, - devrel: 500 - }.with_indifferent_access.freeze - - belongs_to :user - belongs_to :organization, inverse_of: :sponsorships - belongs_to :sponsorable, polymorphic: true, optional: true - - validates :level, presence: true, inclusion: { in: LEVELS } - validates :status, presence: true, inclusion: { in: STATUSES } - validates :url, url: { allow_blank: true, no_local: true, schemes: %w[http https] } - validates :featured_number, presence: true - validates :sponsorable_type, inclusion: { - in: SPONSORABLE_TYPES, - allow_blank: true, - message: I18n.t("models.sponsorship.invalid_type") - } - - validate :validate_tag_uniqueness, if: proc { level.to_s == "tag" } - validate :validate_level_uniqueness, if: proc { METAL_LEVELS.include?(level) } - - LEVELS.each do |level| - scope level, -> { where(level: level) } - end - - scope :live, -> { where(status: :live) } - scope :pending, -> { where(status: :pending) } - - scope :unexpired, -> { where("expires_at > ?", Time.current) } - - private - - def validate_tag_uniqueness - return unless self.class.where(sponsorable: sponsorable, level: :tag) - .exists?(["expires_at > ? AND id != ?", Time.current, id.to_i]) - - errors.add(:level, I18n.t("models.sponsorship.already_sponsored")) - end - - def validate_level_uniqueness - return unless self.class.where(organization: organization) - .exists?(["level IN (?) AND expires_at > ? AND id != ?", METAL_LEVELS, Time.current, id.to_i]) - - levels = METAL_LEVELS.map { |l| I18n.t("models.sponsorship.level.#{l}") }.to_sentence(locale: I18n.locale) - errors.add(:level, I18n.t("models.sponsorship.only_one_level", levels: levels)) - end -end diff --git a/app/models/tag.rb b/app/models/tag.rb index 1758290ae..644482abf 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -2,8 +2,7 @@ # define what we mean when we "tag" something. # # These tags can be arbitrary or supported (e.g. `tag.supported == -# true`). We allow for sponsorship of tags (see `belongs_to -# :sponsorship`). Some tags have moderators. These tags can create a +# true`). Some tags have moderators. These tags can create a # defacto "sub-community" within a Forem. # # Sometimes we need to consolidate tags (e.g. rubyonrails and rails). @@ -39,8 +38,6 @@ class Tag < ActsAsTaggableOn::Tag has_many :articles, through: :taggings, source: :taggable, source_type: "Article" - has_one :sponsorship, as: :sponsorable, inverse_of: :sponsorable, dependent: :destroy - mount_uploader :profile_image, ProfileImageUploader mount_uploader :social_image, ProfileImageUploader diff --git a/app/models/user.rb b/app/models/user.rb index cfeea23e9..e16fb055b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -110,7 +110,6 @@ class User < ApplicationRecord has_many :subscribers, through: :source_authored_user_subscriptions, dependent: :destroy has_many :tweets, dependent: :nullify has_many :devices, dependent: :delete_all - has_many :sponsorships, dependent: :delete_all mount_uploader :profile_image, ProfileImageUploader diff --git a/app/services/credits/ledger.rb b/app/services/credits/ledger.rb index 6b349d991..373d0924e 100644 --- a/app/services/credits/ledger.rb +++ b/app/services/credits/ledger.rb @@ -54,6 +54,9 @@ module Credits credits_purchases_by_type = credits_purchases_with_purchase.select do |row| row.purchase_type == purchase_type end + + next unless purchase_type.constantize + purchase_set = purchase_type.constantize.where(id: credits_purchases_by_type.map(&:purchase_id)) credits_purchases_by_type.each do |credit_purchase| purchase = purchase_set.detect { |set| set.id == credit_purchase.purchase_id } diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml index a1972494d..fda103670 100644 --- a/config/locales/models/en.yml +++ b/config/locales/models/en.yml @@ -95,14 +95,6 @@ en: the_community_mascot: The community mascot user_experience: message: 'must be be a 3 or 6 character hex (starting with #)' - sponsorship: - invalid_type: is not a sponsorable type - already_sponsored: The tag is already sponsored - only_one_level: You can have only one sponsorship of %{levels} - level: - gold: gold - silver: silver - bronze: bronze tag: alias_for: alias_for must refer to an existing tag tag_adjustment: diff --git a/config/locales/models/fr.yml b/config/locales/models/fr.yml index eb90e055e..245f290c1 100644 --- a/config/locales/models/fr.yml +++ b/config/locales/models/fr.yml @@ -95,14 +95,6 @@ fr: the_community_mascot: The community mascot user_experience: message: 'must be be a 3 or 6 character hex (starting with #)' - sponsorship: - invalid_type: is not a sponsorable type - already_sponsored: The tag is already sponsored - only_one_level: You can have only one sponsorship of %{levels} - level: - gold: gold - silver: silver - bronze: bronze tag: alias_for: alias_for must refer to an existing tag tag_adjustment: diff --git a/config/locales/views/main/en.yml b/config/locales/views/main/en.yml index e9804a8f3..d79c3780e 100644 --- a/config/locales/views/main/en.yml +++ b/config/locales/views/main/en.yml @@ -39,7 +39,6 @@ en: Podcasts: Podcasts Privacy Policy: Privacy Policy Reading List: Reading List - Sponsors: Sponsors Tags: Tags Terms of use: Terms of use Videos: Videos diff --git a/config/locales/views/main/fr.yml b/config/locales/views/main/fr.yml index ab16e3495..61776336f 100644 --- a/config/locales/views/main/fr.yml +++ b/config/locales/views/main/fr.yml @@ -39,7 +39,6 @@ fr: Podcasts: Podcasts Privacy Policy: Privacy Policy Reading List: Reading List - Sponsors: Sponsors Tags: Tags Terms of use: Terms of use Videos: Videos diff --git a/db/seeds.rb b/db/seeds.rb index e32b2bf8d..2023175c0 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -577,20 +577,6 @@ Faker::Config.locale = loc ############################################################################## -seeder.create_if_none(Sponsorship) do - organizations = Organization.take(3) - organizations.each do |organization| - Sponsorship.create!( - organization: organization, - user: User.order(Arel.sql("RANDOM()")).first, - level: "silver", - blurb_html: Faker::Hacker.say_something_smart, - ) - end -end - -############################################################################## - seeder.create_if_none(NavigationLink) do Rake::Task["navigation_links:update"].invoke end diff --git a/spec/factories/sponsorships.rb b/spec/factories/sponsorships.rb deleted file mode 100644 index 1f3625087..000000000 --- a/spec/factories/sponsorships.rb +++ /dev/null @@ -1,7 +0,0 @@ -FactoryBot.define do - factory :sponsorship do - user - organization - level { "bronze" } - end -end diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb index e105b3d42..8acef551a 100644 --- a/spec/models/organization_spec.rb +++ b/spec/models/organization_spec.rb @@ -15,7 +15,6 @@ RSpec.describe Organization, type: :model do it { is_expected.to have_many(:notifications).dependent(:delete_all) } it { is_expected.to have_many(:organization_memberships).dependent(:delete_all) } it { is_expected.to have_many(:profile_pins).dependent(:destroy) } - it { is_expected.to have_many(:sponsorships).dependent(:destroy) } it { is_expected.to have_many(:unspent_credits).class_name("Credit") } it { is_expected.to have_many(:users).through(:organization_memberships) } @@ -256,7 +255,7 @@ RSpec.describe Organization, type: :model do # Create an organization and article, verify the article has cached the initial profile # image of the organization. - original_org = create(:organization, profile_image: File.open(Rails.root.join("app/assets/images/1.png"))) + original_org = create(:organization, profile_image: Rails.root.join("app/assets/images/1.png").open) article = create(:article, organization: original_org) expect(article.cached_organization.profile_image_url).to eq(original_org.profile_image_url) diff --git a/spec/models/sponsorship_spec.rb b/spec/models/sponsorship_spec.rb deleted file mode 100644 index edaa540fd..000000000 --- a/spec/models/sponsorship_spec.rb +++ /dev/null @@ -1,120 +0,0 @@ -require "rails_helper" - -RSpec.describe Sponsorship, type: :model do - describe "constants" do - it "has the correct values for constants" do - expect(Sponsorship::LEVELS).to eq(%w[gold silver bronze tag media devrel]) - expect(Sponsorship::METAL_LEVELS).to eq(%w[gold silver bronze]) - expect(Sponsorship::STATUSES).to eq(%w[none pending live]) - expected_credits = { gold: 6000, silver: 500, bronze: 100, tag: 300, devrel: 500 }.with_indifferent_access - expect(Sponsorship::CREDITS).to eq(expected_credits) - end - end - - describe "#url" do - let(:sponsorship) { build(:sponsorship) } - - it "accepts a blank url" do - sponsorship.url = "" - expect(sponsorship).to be_valid - end - - it "accepts a HTTP url" do - sponsorship.url = "http://example.com" - expect(sponsorship).to be_valid - end - - it "accepts a HTTPS url" do - sponsorship.url = "https://example.com" - expect(sponsorship).to be_valid - end - - it "does not accept an invalid url" do - sponsorship.url = "example.com" - expect(sponsorship).not_to be_valid - end - end - - describe "validations" do - let(:user) { create(:user, :org_member) } - let(:org) { user.organizations.first } - - describe "builtin validations" do - it { is_expected.to belong_to(:organization).inverse_of(:sponsorships) } - it { is_expected.to belong_to(:sponsorable).optional } - it { is_expected.to belong_to(:user) } - - it { is_expected.to have_db_index(%i[sponsorable_id sponsorable_type]) } - it { is_expected.to have_db_index(:level) } - it { is_expected.to have_db_index(:status) } - - it { is_expected.to validate_inclusion_of(:level).in_array(Sponsorship::LEVELS) } - it { is_expected.to validate_inclusion_of(:status).in_array(Sponsorship::STATUSES) } - - it { is_expected.to validate_presence_of(:level) } - it { is_expected.to validate_presence_of(:status) } - - it { is_expected.not_to allow_values(nil).for(:featured_number) } - it { is_expected.to allow_values(nil).for(:expires_at) } - end - - it "forbids an org to have multiple 'expiring' (bronze-silver-gold) sponsorships" do - create(:sponsorship, level: :gold, organization: org, expires_at: 2.days.from_now) - bronze_sponsorship = build(:sponsorship, level: :bronze, organization: org) - expect(bronze_sponsorship).not_to be_valid - expect(bronze_sponsorship.errors[:level]).to be_present - end - - it "allows to create a new sponsorship for the same org if the previous one is expired" do - create(:sponsorship, expires_at: 1.day.ago, user: user, organization: org, level: :bronze) - bronze_sponsorship = build(:sponsorship, level: :bronze, user: user, organization: org) - expect(bronze_sponsorship).to be_valid - end - - it "allows to create a new sponsorship for the same level for another org" do - create(:sponsorship, level: :gold, organization: org, expires_at: 2.days.from_now) - other_org = create(:organization) - gold_sponsorship = build(:sponsorship, level: :gold, organization: other_org) - expect(gold_sponsorship).to be_valid - end - - context "when tag sponsorships" do - let(:python) { create(:tag, name: "python") } - let(:ruby) { create(:tag, name: "ruby") } - - it "allows an org to have multiple tag sponsorships on different tags" do - create(:sponsorship, level: :tag, organization: org, expires_at: 2.days.from_now, sponsorable: python) - tag_sponsorship = build(:sponsorship, level: :tag, organization: org, sponsorable: ruby) - expect(tag_sponsorship).to be_valid - end - - it "allows to create a new tag sponsorship if the previous one is expired" do - create(:sponsorship, user: user, organization: org, expires_at: 1.day.ago, level: :tag, sponsorable: ruby) - ruby_sponsorship = build(:sponsorship, user: user, organization: org, level: :tag, sponsorable: ruby) - expect(ruby_sponsorship).to be_valid - end - - it "forbids an org to have multiple active tag sponsorships on the same tag" do - tag = create(:tag, name: "python") - create(:sponsorship, level: :tag, organization: org, sponsorable: tag, expires_at: 2.days.from_now) - tag_sponsorship = build(:sponsorship, level: :tag, organization: org, sponsorable: tag) - expect(tag_sponsorship).not_to be_valid - expect(tag_sponsorship.errors[:level]).to be_present - end - - it "forbids different orgs to have active sponsorships on the same tag" do - other_org = create(:organization) - create(:sponsorship, level: :tag, organization: org, sponsorable: python, expires_at: 2.days.from_now) - tag_sponsorship = build(:sponsorship, level: :tag, organization: other_org, sponsorable: python) - expect(tag_sponsorship).not_to be_valid - expect(tag_sponsorship.errors[:level]).to be_present - end - - it "ensures sponsorable_type is loadable when present" do - sponsorship = build(:sponsorship, user: user, organization: org, sponsorable_type: "Unsponsorable") - expect(sponsorship).not_to be_valid - expect(sponsorship.errors[:sponsorable_type]).to include("is not a sponsorable type") - end - end - end -end diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 9c78c8ddf..3235e2901 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -14,8 +14,6 @@ RSpec.describe Tag, type: :model do subject { tag } it { is_expected.to belong_to(:badge).optional } - it { is_expected.to have_one(:sponsorship).inverse_of(:sponsorable).dependent(:destroy) } - it { is_expected.to validate_length_of(:name).is_at_most(30) } it { is_expected.to validate_presence_of(:category) } diff --git a/spec/requests/credits_spec.rb b/spec/requests/credits_spec.rb index c11c9967b..5d6ea0a79 100644 --- a/spec/requests/credits_spec.rb +++ b/spec/requests/credits_spec.rb @@ -43,49 +43,6 @@ RSpec.describe "Credits", type: :request do expect(response.body).to include(listing.title) end - it "does not show sponsorship purchases to org members" do - org = org_member.organizations.first - sponsorship = create(:sponsorship, organization: org, user: org_admin) - purchase_params = { organization: org, purchase_type: sponsorship.class.name, purchase_id: sponsorship.id } - create(:credit, params.merge(purchase_params)) - - sign_in org_member - get credits_path - - expect(response.body).to include("Purchase history") - expect(response.body).not_to include("Sponsorship") - expect(response.body).not_to include(sponsorship.level) - end - - it "shows sponsorship purchases to org admins" do - org = org_admin.organizations.first - sponsorship = create(:sponsorship, organization: org, user: org_admin) - purchase_params = { organization: org, purchase_type: sponsorship.class.name, purchase_id: sponsorship.id } - create(:credit, params.merge(purchase_params)) - - sign_in org_admin - get credits_path - - expect(response.body).to include("Purchase history") - expect(response.body).to include("Sponsorship") - expect(response.body).to include(sponsorship.level.titleize) - end - - it "shows tag sponsorship purchases to org admins" do - org = org_admin.organizations.first - tag = create(:tag) - sponsorship = create(:sponsorship, organization: org, user: org_admin, level: :tag, sponsorable: tag) - purchase_params = { organization: org, purchase_type: sponsorship.class.name, purchase_id: sponsorship.id } - create(:credit, params.merge(purchase_params)) - - sign_in org_admin - get credits_path - - expect(response.body).to include("Purchase history") - expect(response.body).to include("Sponsorship") - expect(response.body).to include(tag.name) - end - it "shows unattributed purchases" do purchase_params = { user: user } create(:credit, params.merge(purchase_params)) diff --git a/spec/requests/stories/tagged_articles_spec.rb b/spec/requests/stories/tagged_articles_spec.rb index 7d2092189..a903e4c6e 100644 --- a/spec/requests/stories/tagged_articles_spec.rb +++ b/spec/requests/stories/tagged_articles_spec.rb @@ -20,18 +20,6 @@ RSpec.describe "Stories::TaggedArticlesIndex", type: :request do create(:article, tags: tag.name, score: 5) end - def create_live_sponsor(org, tag) - create( - :sponsorship, - level: :tag, - blurb_html: "

Oh Yeah!!!

", - status: "live", - organization: org, - sponsorable: tag, - expires_at: 30.days.from_now, - ) - end - context "with caching headers" do it "renders page and sets proper headers", :aggregate_failures do get "/t/#{tag.name}" @@ -114,16 +102,6 @@ RSpec.describe "Stories::TaggedArticlesIndex", type: :request do expect(response).to have_http_status(:moved_permanently) end - it "does not render sponsor if not live" do - sponsorship = create( - :sponsorship, level: :tag, tagline: "Oh Yeah!!!", status: "pending", organization: org, sponsorable: tag - ) - - get "/t/#{tag.name}" - expect(response.body).not_to include("is sponsored by") - expect(response.body).not_to include(sponsorship.tagline) - end - it "shows meta keywords if set" do allow(Settings::General).to receive(:meta_keywords).and_return({ tag: "software engineering, ruby" }) get "/t/#{tag.name}" diff --git a/spec/requests/user/user_profile_spec.rb b/spec/requests/user/user_profile_spec.rb index 5ba165193..445a68bc5 100644 --- a/spec/requests/user/user_profile_spec.rb +++ b/spec/requests/user/user_profile_spec.rb @@ -122,11 +122,6 @@ RSpec.describe "UserProfiles", type: :request do expect(response.body).to include user.profile_image_url end - it "renders no sponsors if not sponsor" do - get organization.path - expect(response.body).not_to include "Gold Community Sponsor" - end - it "renders organization name properly encoded" do organization.update(name: "Org & < ' \" 1") get organization.path diff --git a/spec/services/credits/ledger_spec.rb b/spec/services/credits/ledger_spec.rb index 75084b32f..d06c27c38 100644 --- a/spec/services/credits/ledger_spec.rb +++ b/spec/services/credits/ledger_spec.rb @@ -5,7 +5,6 @@ RSpec.describe Credits::Ledger, type: :service do let(:org) { create(:organization) } let(:user_listing) { create(:listing, user: user) } let(:org_listing) { create(:listing, organization: org) } - let(:sponsorship) { create(:sponsorship, user: user, organization: org) } def buy(purchaser, purchase, cost) params = { @@ -47,13 +46,4 @@ RSpec.describe Credits::Ledger, type: :service do items = described_class.call(user)[[Organization.name, org.id]] expect(items).to be_nil end - - it "returns sponsorships purchases" do - create(:organization_membership, user_id: user.id, organization_id: org.id, type_of_user: "admin") - buy(org, sponsorship, 3) - items = described_class.call(user)[[Organization.name, org.id]] - expect(items.length).to be(1) - item = items.first - expect(item.purchase.is_a?(Sponsorship)).to be(true) - end end diff --git a/spec/services/users/delete_spec.rb b/spec/services/users/delete_spec.rb index f3f31c8d3..24a5a2f5b 100644 --- a/spec/services/users/delete_spec.rb +++ b/spec/services/users/delete_spec.rb @@ -21,14 +21,6 @@ RSpec.describe Users::Delete, type: :service do expect(cache_bust).to have_received(:call).with("/#{user.username}") end - it "deletes user's sponsorships" do - create(:sponsorship, user: user) - - expect do - described_class.call(user) - end.to change(Sponsorship, :count).by(-1) - end - it "deletes user's follows" do create(:follow, follower: user) create(:follow, followable: user)