[deploy] Email signups liquid tag backend (#8375)

* Backend MVP

- Migration
- Model
- Associations

* Add factory, spec, and validations

- Add unqiue index for validations

* Fix specs

* Add author to email_subscriptions

* Add user association to set_author_id

* Add clarifying comment to set_author_id

* Writing is hard sometimes

* Rename email_subscriptions to subscription_sources

* UserSubscription and user_subscription_sourceable

* subscribers --> sourced_subscribers

* Add comment to explain UserSubscription model
This commit is contained in:
Alex 2020-06-15 09:33:34 -04:00 committed by GitHub
parent 80d2b731b1
commit 6af063eebf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 1 deletions

View file

@ -28,6 +28,8 @@ class Article < ApplicationRecord
counter_culture :organization
has_many :comments, as: :commentable, inverse_of: :commentable
has_many :user_subscriptions, as: :user_subscription_sourceable
has_many :sourced_subscribers, class_name: "User", through: :user_subscriptions, source: :subscriber, foreign_key: :user_id
has_many :top_comments,
-> { where("comments.score > ? AND ancestry IS NULL and hidden_by_commentable_user is FALSE and deleted is FALSE", 10).order("comments.score DESC") },
as: :commentable,

View file

@ -67,6 +67,8 @@ class User < ApplicationRecord
has_many :display_ad_events, dependent: :destroy
has_many :email_authorizations, dependent: :delete_all
has_many :email_messages, class_name: "Ahoy::Message", dependent: :destroy
has_many :user_subscriptions, foreign_key: :author_id, inverse_of: :author, dependent: :destroy
has_many :subscribers, through: :user_subscriptions, dependent: :destroy
has_many :field_test_memberships, class_name: "FieldTest::Membership", as: :participant, dependent: :destroy
has_many :github_repos, dependent: :destroy
has_many :html_variants, dependent: :destroy

View file

@ -0,0 +1,15 @@
# This model handles a user (subscriber) subscribing to another user (author).
# We also record the source of the subscription (Article, Comment, etc.) via a
# polymorphic association (user_subscription_source/able).
class UserSubscription < ApplicationRecord
ALLOWED_TYPES = %w[Article].freeze
belongs_to :author, class_name: "User", foreign_key: :author_id, inverse_of: :user_subscriptions
belongs_to :subscriber, class_name: "User", foreign_key: :subscriber_id, inverse_of: :user_subscriptions
belongs_to :user_subscription_sourceable, polymorphic: true
validates :author_id, presence: true
validates :subscriber_id, presence: true, uniqueness: { scope: %i[user_subscription_sourceable_type user_subscription_sourceable_id] }
validates :user_subscription_sourceable_id, presence: true
validates :user_subscription_sourceable_type, presence: true, inclusion: { in: ALLOWED_TYPES }
end

View file

@ -0,0 +1,18 @@
class CreateUserSubscriptions < ActiveRecord::Migration[6.0]
def change
create_table :user_subscriptions do |t|
t.references :user_subscription_sourceable, polymorphic: true, null: false, index: { name: :index_on_user_subscription_sourcebable_type_and_id }
t.references :subscriber, references: :users, foreign_key: { to_table: :users }, null: false
t.references :author, references: :users, foreign_key: { to_table: :users }, null: false
t.timestamps
end
add_index(
:user_subscriptions,
%i[subscriber_id user_subscription_sourceable_id user_subscription_sourceable_type],
unique: true,
name: :index_on_subscriber_id_user_subscription_sourceable_type_and_id
)
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_06_09_191943) do
ActiveRecord::Schema.define(version: 2020_06_09_192545) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -1140,6 +1140,19 @@ ActiveRecord::Schema.define(version: 2020_06_09_191943) do
t.index ["user_id"], name: "index_user_optional_fields_on_user_id"
end
create_table "user_subscriptions", force: :cascade do |t|
t.bigint "author_id", null: false
t.datetime "created_at", precision: 6, null: false
t.bigint "subscriber_id", null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "user_subscription_sourceable_id", null: false
t.string "user_subscription_sourceable_type", null: false
t.index ["author_id"], name: "index_user_subscriptions_on_author_id"
t.index ["subscriber_id", "user_subscription_sourceable_id", "user_subscription_sourceable_type"], name: "index_on_subscriber_id_user_subscription_sourceable_type_and_id", unique: true
t.index ["subscriber_id"], name: "index_user_subscriptions_on_subscriber_id"
t.index ["user_subscription_sourceable_type", "user_subscription_sourceable_id"], name: "index_on_user_subscription_sourcebable_type_and_id"
end
create_table "users", id: :serial, force: :cascade do |t|
t.integer "articles_count", default: 0, null: false
t.string "available_for"
@ -1326,6 +1339,8 @@ ActiveRecord::Schema.define(version: 2020_06_09_191943) do
add_foreign_key "user_blocks", "users", column: "blocker_id"
add_foreign_key "user_counters", "users", on_delete: :cascade
add_foreign_key "user_optional_fields", "users"
add_foreign_key "user_subscriptions", "users", column: "author_id"
add_foreign_key "user_subscriptions", "users", column: "subscriber_id"
add_foreign_key "users_roles", "users", on_delete: :cascade
add_foreign_key "webhook_endpoints", "oauth_applications"
add_foreign_key "webhook_endpoints", "users"

View file

@ -0,0 +1,8 @@
FactoryBot.define do
factory :user_subscription do
association :subscriber, factory: :user, strategy: :create
association :user_subscription_sourceable, factory: :article
author { user_subscription_sourceable.user }
end
end

View file

@ -0,0 +1,14 @@
require "rails_helper"
RSpec.describe UserSubscription, type: :model do
subject { build(:user_subscription) }
describe "validations" do
it { is_expected.to validate_presence_of(:user_subscription_sourceable_id) }
it { is_expected.to validate_presence_of(:user_subscription_sourceable_type) }
it { is_expected.to validate_presence_of(:subscriber_id) }
it { is_expected.to validate_presence_of(:author_id) }
it { is_expected.to validate_inclusion_of(:user_subscription_sourceable_type).in_array(%w[Article]) }
it { is_expected.to validate_uniqueness_of(:subscriber_id).scoped_to(:user_subscription_sourceable_type, :user_subscription_sourceable_id) }
end
end