diff --git a/app/models/classified_listing.rb b/app/models/classified_listing.rb index bd4789bf9..967c6017e 100644 --- a/app/models/classified_listing.rb +++ b/app/models/classified_listing.rb @@ -4,6 +4,7 @@ class ClassifiedListing < ApplicationRecord SEARCH_SERIALIZER = Search::ClassifiedListingSerializer SEARCH_CLASS = Search::ClassifiedListing + # TODO: remove this once we are live with the new ClassifiedListingCategory model CATEGORIES_AVAILABLE = { cfp: { cost: 1, name: "Conference CFP", rules: "Currently open for proposals, with link to form." }, forhire: { cost: 1, name: "Available for Hire", rules: "You are available for hire." }, @@ -18,8 +19,13 @@ class ClassifiedListing < ApplicationRecord misc: { cost: 1, name: "Miscellaneous", rules: "Must not fit in any other category." } }.with_indifferent_access.freeze + # TODO: remove this once we are live with the new ClassifiedListingCategory model + before_validation :assign_classified_listing_category + attr_accessor :action + # TODO: this is optional for now to ease the migration + belongs_to :classified_listing_category, optional: true belongs_to :user belongs_to :organization, optional: true before_save :evaluate_markdown @@ -109,4 +115,12 @@ class ClassifiedListing < ApplicationRecord def create_slug self.slug = title.to_s.downcase.parameterize.tr("_", "") + "-" + rand(100_000).to_s(26) end + + def assign_classified_listing_category + category_name = CATEGORIES_AVAILABLE.dig(attributes["category"], :name) + category = ClassifiedListingCategory.find_by(name: category_name) + return unless category + + self.classified_listing_category_id = category.id + end end diff --git a/app/models/classified_listing_category.rb b/app/models/classified_listing_category.rb new file mode 100644 index 000000000..417246e82 --- /dev/null +++ b/app/models/classified_listing_category.rb @@ -0,0 +1,6 @@ +class ClassifiedListingCategory < ApplicationRecord + has_many :classified_listings + + validates :name, :cost, :rules, :slug, presence: true + validates :name, :slug, uniqueness: true +end diff --git a/db/migrate/20200409043946_create_classified_listing_categories.rb b/db/migrate/20200409043946_create_classified_listing_categories.rb new file mode 100644 index 000000000..2aa9d839f --- /dev/null +++ b/db/migrate/20200409043946_create_classified_listing_categories.rb @@ -0,0 +1,14 @@ +class CreateClassifiedListingCategories < ActiveRecord::Migration[5.2] + def change + create_table :classified_listing_categories do |t| + t.string :name, null: false + t.integer :cost, null: false + t.string :rules, null: false + t.string :slug, null: false + + t.timestamps + end + add_index(:classified_listing_categories, :name, unique: true) + add_index(:classified_listing_categories, :slug, unique: true) + end +end diff --git a/db/migrate/20200409050122_add_listing_category_to_classified_listing.rb b/db/migrate/20200409050122_add_listing_category_to_classified_listing.rb new file mode 100644 index 000000000..99f9735ed --- /dev/null +++ b/db/migrate/20200409050122_add_listing_category_to_classified_listing.rb @@ -0,0 +1,10 @@ +class AddListingCategoryToClassifiedListing < ActiveRecord::Migration[5.2] + disable_ddl_transaction! + + def change + add_reference :classified_listings, + :classified_listing_category, + foreign_key: true, + index: { algorithm: :concurrently } + end +end diff --git a/db/schema.rb b/db/schema.rb index 5ea2943cd..61fef7575 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_04_07_091449) do - +ActiveRecord::Schema.define(version: 2020_04_09_050122) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -311,11 +310,23 @@ ActiveRecord::Schema.define(version: 2020_04_07_091449) do t.index ["slug"], name: "index_chat_channels_on_slug", unique: true end + create_table "classified_listing_categories", force: :cascade do |t| + t.integer "cost", null: false + t.datetime "created_at", null: false + t.string "name", null: false + t.string "rules", null: false + t.string "slug", null: false + t.datetime "updated_at", null: false + t.index ["name"], name: "index_classified_listing_categories_on_name", unique: true + t.index ["slug"], name: "index_classified_listing_categories_on_slug", unique: true + end + create_table "classified_listings", force: :cascade do |t| t.text "body_markdown" t.datetime "bumped_at" t.string "cached_tag_list" t.string "category" + t.bigint "classified_listing_category_id" t.boolean "contact_via_connect", default: false t.datetime "created_at", null: false t.datetime "expires_at" @@ -328,6 +339,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_091449) do t.string "title" t.datetime "updated_at", null: false t.bigint "user_id" + t.index ["classified_listing_category_id"], name: "index_classified_listings_on_classified_listing_category_id" t.index ["organization_id"], name: "index_classified_listings_on_organization_id" t.index ["user_id"], name: "index_classified_listings_on_user_id" end @@ -1232,6 +1244,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_091449) do add_foreign_key "badge_achievements", "users" add_foreign_key "chat_channel_memberships", "chat_channels" add_foreign_key "chat_channel_memberships", "users" + add_foreign_key "classified_listings", "classified_listing_categories" add_foreign_key "classified_listings", "users", on_delete: :cascade add_foreign_key "email_authorizations", "users", on_delete: :cascade add_foreign_key "identities", "users", on_delete: :cascade diff --git a/lib/tasks/temporary/classified_listings.rake b/lib/tasks/temporary/classified_listings.rake new file mode 100644 index 000000000..ae6084957 --- /dev/null +++ b/lib/tasks/temporary/classified_listings.rake @@ -0,0 +1,13 @@ +namespace :temporary do + namespace :classified_listings do + desc "Backfill classified listings categories" + task backfill_categories: :environment do + ClassifiedListing::CATEGORIES_AVAILABLE.each do |key, attributes| + category = ClassifiedListingCategory.find_or_create_by!(attributes.merge(slug: key)) + ClassifiedListing. + where(category: key.to_s). + update_all(classified_listing_category_id: category.id) + end + end + end +end diff --git a/spec/factories/classified_listing_categories.rb b/spec/factories/classified_listing_categories.rb new file mode 100644 index 000000000..1b3268414 --- /dev/null +++ b/spec/factories/classified_listing_categories.rb @@ -0,0 +1,12 @@ +FactoryBot.define do + factory :classified_listing_category do + name { "Education/Courses" } + cost { [1, 5, 25].sample } + rules { Faker::Hipster.paragraph(sentence_count: 1) } + slug { "education" } + + trait :cfp do + name { "Conference CFP" } + end + end +end diff --git a/spec/models/classified_listing_category_spec.rb b/spec/models/classified_listing_category_spec.rb new file mode 100644 index 000000000..560f22033 --- /dev/null +++ b/spec/models/classified_listing_category_spec.rb @@ -0,0 +1,16 @@ +require "rails_helper" + +RSpec.describe ClassifiedListingCategory, type: :model do + describe "validations" do + # The uniqueness validation didn't work without this, see section "Caveat" at + # https://www.rubydoc.info/github/thoughtbot/shoulda-matchers/Shoulda%2FMatchers%2FActiveRecord:validate_uniqueness_of + subject { create(:classified_listing_category) } + + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_presence_of(:cost) } + it { is_expected.to validate_presence_of(:rules) } + it { is_expected.to validate_presence_of(:slug) } + it { is_expected.to validate_uniqueness_of(:name) } + it { is_expected.to validate_uniqueness_of(:slug) } + end +end diff --git a/spec/models/classified_listing_spec.rb b/spec/models/classified_listing_spec.rb index f7d6c4b3c..1e6b1be8d 100644 --- a/spec/models/classified_listing_spec.rb +++ b/spec/models/classified_listing_spec.rb @@ -32,6 +32,18 @@ RSpec.describe ClassifiedListing, type: :model do end end + # TODO: remove this once we are live with the new ClassifiedListingCategory model + describe "classified listing category" do + it "automatically assigns a category on save" do + create(:classified_listing_category) + + cl = build(:classified_listing, user_id: user.id) + cl.save + expect(cl).to be_valid + expect(cl.reload.classified_listing_category).to be_present + end + end + describe "body html" do it "converts markdown to html" do expect(classified_listing.processed_html).to include("
")