Sponsorship: add model (step 1) (#3438)

* Add Sponsorship model

* Add Sponsorship to admin

* Add correct relationships between models

* Add temporary script to migrate sponsorship data
This commit is contained in:
rhymes 2019-07-10 21:58:37 +02:00 committed by Ben Halpern
parent 11be56f38c
commit ad79116366
13 changed files with 275 additions and 3 deletions

View file

@ -0,0 +1,21 @@
module Admin
class SponsorshipsController < Admin::ApplicationController
# To customize the behavior of this controller,
# you can overwrite any of the RESTful actions. For example:
#
# def index
# super
# @resources = Sponsorship.
# page(params[:page]).
# per(10)
# end
# Define a custom finder by overriding the `find_resource` method:
# def find_resource(param)
# Sponsorship.find_by!(slug: param)
# end
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
# for more information
end
end

View file

@ -36,6 +36,7 @@ module Admin
text_color_hex
keywords_for_search
buffer_profile_id_code
sponsor_organization_id
]
convert_empty_string_to_nil
params.require(:tag).permit(accessible)

View file

@ -26,6 +26,7 @@ class DashboardManifest
badges
badge_achievements
html_variants
sponsorships
].freeze
# DASHBOARDS = [
# :users,

View file

@ -0,0 +1,87 @@
require "administrate/base_dashboard"
class SponsorshipDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
user: Field::BelongsTo,
organization: Field::BelongsTo,
id: Field::Number,
level: Field::String,
status: Field::String,
expires_at: Field::DateTime,
blurb_html: Field::Text,
featured_number: Field::Number,
instructions: Field::Text,
instructions_updated_at: Field::DateTime,
tagline: Field::String,
url: Field::String,
sponsorable_id: Field::Number,
sponsorable_type: Field::String,
created_at: Field::DateTime,
updated_at: Field::DateTime
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = %i[
user
organization
id
level
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = %i[
user
organization
id
level
status
expires_at
blurb_html
featured_number
instructions
instructions_updated_at
tagline
url
sponsorable_id
sponsorable_type
created_at
updated_at
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = %i[
user
organization
level
status
expires_at
blurb_html
featured_number
instructions
instructions_updated_at
tagline
url
sponsorable_id
sponsorable_type
].freeze
# Overwrite this method to customize how sponsorships are displayed
# across all pages of the admin dashboard.
#
# def display_resource(sponsorship)
# "Sponsorship ##{sponsorship.id}"
# end
end

View file

@ -22,7 +22,7 @@ class TagDashboard < Administrate::BaseDashboard
pretty_name: Field::String,
profile_image: CarrierwaveField,
social_image: CarrierwaveField,
sponsorship_organization_id: Field::Number,
sponsor_organization: Field::BelongsTo.with_options(class_name: "Organization"),
sponsorship_status: Field::String,
bg_color_hex: Field::String,
text_color_hex: Field::String,
@ -41,6 +41,7 @@ class TagDashboard < Administrate::BaseDashboard
name
supported
taggings_count
sponsor_organization
].freeze
# SHOW_PAGE_ATTRIBUTES
@ -64,6 +65,7 @@ class TagDashboard < Administrate::BaseDashboard
text_color_hex
keywords_for_search
buffer_profile_id_code
sponsor_organization
].freeze
# FORM_ATTRIBUTES
@ -83,7 +85,7 @@ class TagDashboard < Administrate::BaseDashboard
social_image
bg_color_hex
text_color_hex
sponsorship_organization_id
sponsor_organization
sponsorship_status
keywords_for_search
buffer_profile_id_code

View file

@ -15,6 +15,7 @@ class Organization < ApplicationRecord
has_many :unspent_credits, -> { where spent: false }, class_name: "Credit", inverse_of: :organization
has_many :classified_listings
has_many :profile_pins, as: :profile, inverse_of: :profile
has_many :sponsorships
validates :name, :summary, :url, :profile_image, presence: true
validates :name,

10
app/models/sponsorship.rb Normal file
View file

@ -0,0 +1,10 @@
class Sponsorship < ApplicationRecord
belongs_to :user
belongs_to :organization, inverse_of: :sponsorships
belongs_to :sponsorable, polymorphic: true, optional: true
validates :user, :organization, :featured_number, presence: true
validates :level, inclusion: { in: %w[gold silver bronze tag media devrel] }
validates :status, inclusion: { in: %w[none pending live] }
validates :url, url: { allow_blank: true, no_local: true, schemes: %w[http https] }
end

View file

@ -0,0 +1,24 @@
class CreateNewSponsorships < ActiveRecord::Migration[5.2]
def change
create_table :sponsorships do |t|
t.references :user, foreign_key: true
t.references :organization, foreign_key: true
t.string :level, null: false
t.string :status, null: false, default: "none"
t.datetime :expires_at
t.text :blurb_html
t.integer :featured_number, null: false, default: 0
t.text :instructions
t.datetime :instructions_updated_at
t.string :tagline
t.string :url
t.bigint :sponsorable_id
t.string :sponsorable_type
t.timestamps
end
add_index :sponsorships, :level
add_index :sponsorships, :status
add_index :sponsorships, [:sponsorable_id, :sponsorable_type]
end
end

View file

@ -12,7 +12,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_07_09_192214) do
ActiveRecord::Schema.define(version: 2019_07_10_081915) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -820,6 +820,29 @@ ActiveRecord::Schema.define(version: 2019_07_09_192214) do
t.index ["google_result_path"], name: "index_search_keywords_on_google_result_path"
end
create_table "sponsorships", force: :cascade do |t|
t.text "blurb_html"
t.datetime "created_at", null: false
t.datetime "expires_at"
t.integer "featured_number", default: 0, null: false
t.text "instructions"
t.datetime "instructions_updated_at"
t.string "level", null: false
t.bigint "organization_id"
t.bigint "sponsorable_id"
t.string "sponsorable_type"
t.string "status", default: "none", null: false
t.string "tagline"
t.datetime "updated_at", null: false
t.string "url"
t.bigint "user_id"
t.index ["level"], name: "index_sponsorships_on_level"
t.index ["organization_id"], name: "index_sponsorships_on_organization_id"
t.index ["sponsorable_id", "sponsorable_type"], name: "index_sponsorships_on_sponsorable_id_and_sponsorable_type"
t.index ["status"], name: "index_sponsorships_on_status"
t.index ["user_id"], name: "index_sponsorships_on_user_id"
end
create_table "tag_adjustments", force: :cascade do |t|
t.string "adjustment_type"
t.integer "article_id"
@ -1079,4 +1102,6 @@ ActiveRecord::Schema.define(version: 2019_07_09_192214) do
add_foreign_key "messages", "chat_channels"
add_foreign_key "messages", "users"
add_foreign_key "push_notification_subscriptions", "users"
add_foreign_key "sponsorships", "organizations"
add_foreign_key "sponsorships", "users"
end

View file

@ -0,0 +1,51 @@
namespace :sponsorships do
desc "Migrate existing sponsorships data to the Sponsorship model"
task migrate_data: :environment do
ActiveRecord::Base.transaction do
puts "Migrating organization data..."
Organization.find_each do |org|
next unless org.sponsorship_level
org_admin = org.users.where(organization_memberships: { type_of_user: "admin" }).take!
Sponsorship.create!(
organization: org,
user: org_admin,
level: org.sponsorship_level,
status: org.sponsorship_status,
expires_at: org.sponsorship_expires_at,
blurb_html: org.sponsorship_blurb_html,
featured_number: org.sponsorship_featured_number,
instructions: org.sponsorship_instructions,
instructions_updated_at: org.sponsorship_instructions_updated_at,
tagline: org.sponsorship_tagline,
url: org.sponsorship_url,
)
puts "Migrated organization '#{org.name}'"
end
puts "Migrating Tag sponsorship data..."
Tag.where.not(sponsor_organization_id: nil).includes(:sponsor_organization).find_each do |tag|
org = tag.sponsor_organization
org_admin = org.users.where(organization_memberships: { type_of_user: "admin" }).take!
Sponsorship.create!(
organization: org,
user: org_admin,
level: "tag",
# when an org purchases a tag sponsorship, its status isn't set
# so I ise tag.sponsorship_status (which was previously checked in app/views/tags/index.html.erb)
status: tag.sponsorship_status,
sponsorable: tag,
)
puts "Migrated tag '#{tag.name}' sponsorship by '#{org.name}'"
end
end
puts "All done now!"
puts "Sponsorships without any traceable info in the DB need to be created manually!"
end
end

View file

@ -0,0 +1,7 @@
FactoryBot.define do
factory :sponsorship do
user
organization
level { "bronze" }
end
end

View file

@ -4,6 +4,8 @@ RSpec.describe Organization, type: :model do
let(:user) { create(:user) }
let(:organization) { create(:organization) }
it { is_expected.to have_many(:sponsorships) }
describe "#name" do
it "rejects names with over 50 characters" do
organization.name = Faker::Lorem.characters(51)

View file

@ -0,0 +1,40 @@
require "rails_helper"
RSpec.describe Sponsorship, type: :model do
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:organization).inverse_of(:sponsorships) }
it { is_expected.to belong_to(:sponsorable).optional }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:organization) }
it { is_expected.to validate_inclusion_of(:level).in_array(%w[gold silver bronze tag media devrel]) }
it { is_expected.to validate_inclusion_of(:status).in_array(%w[none pending live]) }
it { is_expected.to allow_values(nil).for(:expires_at) }
it { is_expected.not_to allow_values(nil).for(:featured_number) }
it { is_expected.to have_db_index(:level) }
it { is_expected.to have_db_index(:status) }
it { is_expected.to have_db_index(%i[sponsorable_id sponsorable_type]) }
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
end