2010-11-06 15:08:42 -07:00
|
|
|
# This file is auto-generated from the current state of the database. Instead
|
|
|
|
# of editing this file, please use the migrations feature of Active Record to
|
|
|
|
# incrementally modify your database, and then regenerate this schema definition.
|
2010-05-15 08:38:45 -07:00
|
|
|
#
|
2023-08-06 17:46:47 -07:00
|
|
|
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
|
|
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
|
|
# be faster and is potentially less error prone than running all of your
|
|
|
|
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
|
|
# migrations use external dependencies or application code.
|
2010-05-15 08:38:45 -07:00
|
|
|
#
|
2023-07-29 11:19:37 -07:00
|
|
|
# It's strongly recommended that you check this file into your version control system.
|
|
|
|
|
2024-02-27 15:48:28 -08:00
|
|
|
ActiveRecord::Schema[7.1].define(version: 2024_02_27_233743) do
|
2024-02-01 05:55:19 -08:00
|
|
|
create_table "alt_styles", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
|
2024-01-24 03:25:23 -08:00
|
|
|
t.integer "species_id", null: false
|
|
|
|
t.integer "color_id", null: false
|
|
|
|
t.integer "body_id", null: false
|
2024-02-01 05:55:19 -08:00
|
|
|
t.datetime "created_at", precision: nil, null: false
|
|
|
|
t.datetime "updated_at", precision: nil, null: false
|
2024-02-28 15:53:48 -08:00
|
|
|
t.string "series_name", default: "<New?>", null: false
|
2024-01-24 03:25:23 -08:00
|
|
|
t.index ["color_id"], name: "index_alt_styles_on_color_id"
|
|
|
|
t.index ["species_id"], name: "index_alt_styles_on_species_id"
|
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "auth_servers", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.string "short_name", limit: 10, null: false
|
|
|
|
t.string "name", limit: 40, null: false
|
|
|
|
t.text "icon", size: :medium, null: false
|
|
|
|
t.text "gateway", size: :medium, null: false
|
|
|
|
t.string "secret", limit: 64, null: false
|
2023-07-29 11:19:37 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "campaigns", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "progress", default: 0, null: false
|
|
|
|
t.integer "goal", null: false
|
|
|
|
t.boolean "active", null: false
|
|
|
|
t.datetime "created_at", precision: nil, null: false
|
|
|
|
t.datetime "updated_at", precision: nil, null: false
|
|
|
|
t.boolean "advertised", default: true, null: false
|
|
|
|
t.text "description", null: false
|
|
|
|
t.string "purpose", default: "our hosting costs this year", null: false
|
|
|
|
t.string "theme_id", default: "hug", null: false
|
|
|
|
t.text "thanks"
|
|
|
|
t.string "name"
|
2013-01-28 00:10:25 -08:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "closet_hangers", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "item_id"
|
|
|
|
t.integer "user_id"
|
|
|
|
t.integer "quantity"
|
|
|
|
t.datetime "created_at", precision: nil
|
|
|
|
t.datetime "updated_at", precision: nil
|
|
|
|
t.boolean "owned", default: true, null: false
|
|
|
|
t.integer "list_id"
|
|
|
|
t.index ["item_id", "owned"], name: "index_closet_hangers_on_item_id_and_owned"
|
|
|
|
t.index ["list_id"], name: "index_closet_hangers_on_list_id"
|
|
|
|
t.index ["user_id", "list_id", "item_id", "owned", "created_at"], name: "index_closet_hangers_test_20131226"
|
|
|
|
t.index ["user_id", "owned", "item_id"], name: "index_closet_hangers_on_user_id_and_owned_and_item_id"
|
|
|
|
t.index ["user_id", "owned", "list_id", "updated_at"], name: "index_closet_hangers_for_last_trade_activity"
|
|
|
|
t.index ["user_id"], name: "index_closet_hangers_on_user_id"
|
2011-07-26 17:27:23 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "closet_lists", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.string "name"
|
|
|
|
t.text "description"
|
|
|
|
t.integer "user_id"
|
|
|
|
t.boolean "hangers_owned", null: false
|
|
|
|
t.datetime "created_at", precision: nil
|
|
|
|
t.datetime "updated_at", precision: nil
|
|
|
|
t.integer "visibility", default: 1, null: false
|
|
|
|
t.index ["user_id"], name: "index_closet_lists_on_user_id"
|
2011-07-12 16:37:16 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "colors", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2013-01-21 14:01:41 -08:00
|
|
|
t.boolean "basic"
|
|
|
|
t.boolean "standard"
|
2023-08-06 17:46:47 -07:00
|
|
|
t.boolean "prank", default: false, null: false
|
2024-01-23 05:10:43 -08:00
|
|
|
t.string "name", null: false
|
2012-01-12 15:17:59 -08:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "contributions", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.string "contributed_type", limit: 8, null: false
|
|
|
|
t.integer "contributed_id", null: false
|
|
|
|
t.integer "user_id", null: false
|
|
|
|
t.datetime "created_at", precision: nil, null: false
|
|
|
|
t.index ["contributed_id", "contributed_type"], name: "index_contributions_on_contributed_id_and_contributed_type"
|
|
|
|
t.index ["user_id"], name: "index_contributions_on_user_id"
|
2010-11-10 13:59:54 -08:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "donation_features", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "donation_id", null: false
|
|
|
|
t.integer "outfit_id"
|
|
|
|
t.datetime "created_at", precision: nil, null: false
|
|
|
|
t.datetime "updated_at", precision: nil, null: false
|
2014-09-09 21:16:02 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "donations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "amount", null: false
|
|
|
|
t.string "charge_id", null: false
|
|
|
|
t.integer "user_id"
|
|
|
|
t.string "donor_name"
|
|
|
|
t.string "secret"
|
|
|
|
t.datetime "created_at", precision: nil, null: false
|
|
|
|
t.datetime "updated_at", precision: nil, null: false
|
|
|
|
t.string "donor_email"
|
|
|
|
t.integer "campaign_id", null: false
|
2013-01-28 00:10:25 -08:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "item_outfit_relationships", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "item_id"
|
|
|
|
t.integer "outfit_id"
|
|
|
|
t.boolean "is_worn"
|
|
|
|
t.datetime "created_at", precision: nil
|
|
|
|
t.datetime "updated_at", precision: nil
|
|
|
|
t.index ["item_id"], name: "index_item_outfit_relationships_on_item_id"
|
|
|
|
t.index ["outfit_id", "is_worn"], name: "index_item_outfit_relationships_on_outfit_id_and_is_worn"
|
2010-05-16 12:01:38 -07:00
|
|
|
end
|
|
|
|
|
2024-02-27 15:28:05 -08:00
|
|
|
create_table "item_translations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
|
|
|
t.integer "item_id"
|
|
|
|
t.string "locale"
|
|
|
|
t.string "name"
|
|
|
|
t.text "description"
|
|
|
|
t.string "rarity"
|
|
|
|
t.datetime "created_at", precision: nil
|
|
|
|
t.datetime "updated_at", precision: nil
|
|
|
|
t.index ["item_id", "locale"], name: "index_item_translations_on_item_id_and_locale"
|
|
|
|
t.index ["item_id"], name: "index_item_translations_on_item_id"
|
|
|
|
t.index ["locale"], name: "index_item_translations_on_locale"
|
|
|
|
t.index ["name"], name: "index_item_translations_name"
|
|
|
|
end
|
|
|
|
|
2023-10-24 15:35:21 -07:00
|
|
|
create_table "items", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.text "zones_restrict", null: false
|
|
|
|
t.text "thumbnail_url", size: :medium, null: false
|
|
|
|
t.string "category", limit: 50
|
|
|
|
t.string "type", limit: 50
|
|
|
|
t.integer "rarity_index", limit: 2
|
|
|
|
t.integer "price", limit: 3, null: false
|
|
|
|
t.integer "weight_lbs", limit: 2
|
|
|
|
t.text "species_support_ids", size: :medium
|
|
|
|
t.datetime "created_at", precision: nil
|
|
|
|
t.datetime "updated_at", precision: nil
|
|
|
|
t.boolean "explicitly_body_specific", default: false, null: false
|
|
|
|
t.integer "manual_special_color_id"
|
|
|
|
t.column "modeling_status_hint", "enum('done','glitchy')"
|
|
|
|
t.boolean "is_manually_nc", default: false, null: false
|
2024-02-20 15:25:03 -08:00
|
|
|
t.string "name", null: false
|
|
|
|
t.text "description", default: "", null: false
|
|
|
|
t.string "rarity", default: "", null: false
|
2023-08-06 17:46:47 -07:00
|
|
|
t.index ["modeling_status_hint", "created_at", "id"], name: "items_modeling_status_hint_and_created_at_and_id"
|
|
|
|
t.index ["modeling_status_hint", "created_at"], name: "items_modeling_status_hint_and_created_at"
|
|
|
|
t.index ["modeling_status_hint", "id"], name: "items_modeling_status_hint_and_id"
|
|
|
|
t.index ["modeling_status_hint"], name: "items_modeling_status_hint"
|
2010-05-16 12:01:38 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "login_cookies", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-07-29 11:19:37 -07:00
|
|
|
t.integer "user_id", null: false
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "series", null: false
|
|
|
|
t.integer "token", null: false
|
|
|
|
t.index ["user_id", "series"], name: "login_cookies_user_id_and_series"
|
|
|
|
t.index ["user_id"], name: "login_cookies_user_id"
|
2014-01-18 19:54:11 -08:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "modeling_logs", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2024-02-20 16:46:19 -08:00
|
|
|
t.datetime "created_at", precision: nil, default: -> { "current_timestamp()" }, null: false
|
2023-08-06 17:46:47 -07:00
|
|
|
t.text "log_json", null: false
|
|
|
|
t.string "pet_name", limit: 128, null: false
|
2013-01-28 00:10:25 -08:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "neopets_connections", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "user_id"
|
|
|
|
t.string "neopets_username"
|
|
|
|
t.datetime "created_at", precision: nil, null: false
|
|
|
|
t.datetime "updated_at", precision: nil, null: false
|
2013-01-11 14:20:06 -08:00
|
|
|
end
|
2010-05-16 12:01:38 -07:00
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "outfits", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "pet_state_id"
|
|
|
|
t.integer "user_id"
|
|
|
|
t.datetime "created_at", precision: nil
|
|
|
|
t.datetime "updated_at", precision: nil
|
|
|
|
t.string "name"
|
|
|
|
t.boolean "starred", default: false, null: false
|
|
|
|
t.string "image"
|
|
|
|
t.string "image_layers_hash"
|
|
|
|
t.boolean "image_enqueued", default: false, null: false
|
2024-02-01 05:55:19 -08:00
|
|
|
t.bigint "alt_style_id"
|
|
|
|
t.index ["alt_style_id"], name: "index_outfits_on_alt_style_id"
|
2023-08-06 17:46:47 -07:00
|
|
|
t.index ["pet_state_id"], name: "index_outfits_on_pet_state_id"
|
|
|
|
t.index ["user_id"], name: "index_outfits_on_user_id"
|
2010-11-10 13:59:54 -08:00
|
|
|
end
|
|
|
|
|
2023-10-24 15:35:21 -07:00
|
|
|
create_table "parents_swf_assets", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "parent_id", limit: 3, null: false
|
2023-07-29 11:19:37 -07:00
|
|
|
t.integer "swf_asset_id", limit: 3, null: false
|
2023-08-06 17:46:47 -07:00
|
|
|
t.string "parent_type", limit: 8, null: false
|
|
|
|
t.index ["parent_id", "parent_type"], name: "index_parents_swf_assets_on_parent_id_and_parent_type"
|
|
|
|
t.index ["parent_id", "swf_asset_id"], name: "unique_parents_swf_assets", unique: true
|
|
|
|
t.index ["swf_asset_id"], name: "parents_swf_assets_swf_asset_id"
|
2010-05-16 12:01:38 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "pet_loads", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.string "pet_name", limit: 20, null: false
|
|
|
|
t.text "amf", size: :medium, null: false
|
|
|
|
t.datetime "created_at", precision: nil, null: false
|
2010-07-10 09:42:18 -07:00
|
|
|
end
|
|
|
|
|
2023-10-24 15:35:21 -07:00
|
|
|
create_table "pet_states", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "pet_type_id", limit: 3, null: false
|
|
|
|
t.text "swf_asset_ids", null: false
|
2012-05-23 17:00:38 -07:00
|
|
|
t.boolean "female"
|
|
|
|
t.integer "mood_id"
|
|
|
|
t.boolean "unconverted"
|
2023-08-06 17:46:47 -07:00
|
|
|
t.boolean "labeled", default: false, null: false
|
|
|
|
t.boolean "glitched", default: false, null: false
|
|
|
|
t.string "artist_neopets_username"
|
|
|
|
t.index ["pet_type_id"], name: "pet_states_pet_type_id"
|
2010-05-16 12:01:38 -07:00
|
|
|
end
|
|
|
|
|
2023-10-24 15:35:21 -07:00
|
|
|
create_table "pet_types", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.integer "color_id", limit: 1, null: false
|
|
|
|
t.integer "species_id", limit: 1, null: false
|
|
|
|
t.datetime "created_at", precision: nil, null: false
|
|
|
|
t.integer "body_id", limit: 2, null: false
|
|
|
|
t.string "image_hash", limit: 8
|
|
|
|
t.string "basic_image_hash"
|
|
|
|
t.index ["body_id", "color_id", "species_id"], name: "pet_types_body_id_and_color_id_and_species_id"
|
|
|
|
t.index ["body_id"], name: "pet_types_body_id"
|
|
|
|
t.index ["color_id", "species_id"], name: "pet_types_color_id_and_species_id"
|
|
|
|
t.index ["color_id"], name: "pet_types_color_id"
|
|
|
|
t.index ["species_id", "color_id"], name: "pet_types_species_color", unique: true
|
2010-05-16 12:01:38 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "pets", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.string "name", limit: 20, null: false
|
|
|
|
t.integer "pet_type_id", limit: 3, null: false
|
|
|
|
t.index ["name"], name: "pets_name", unique: true
|
|
|
|
t.index ["pet_type_id"], name: "pets_pet_type_id"
|
2010-05-16 12:01:38 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "species", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2024-01-23 05:10:43 -08:00
|
|
|
t.string "name", null: false
|
2013-01-21 12:55:48 -08:00
|
|
|
end
|
|
|
|
|
2023-10-24 15:35:21 -07:00
|
|
|
create_table "swf_assets", id: :integer, charset: "utf8mb3", collation: "utf8mb3_unicode_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.string "type", limit: 7, null: false
|
|
|
|
t.integer "remote_id", limit: 3, null: false
|
|
|
|
t.text "url", size: :medium, null: false
|
|
|
|
t.integer "zone_id", limit: 1, null: false
|
|
|
|
t.text "zones_restrict", null: false
|
|
|
|
t.datetime "created_at", precision: nil, null: false
|
|
|
|
t.integer "body_id", limit: 2, null: false
|
|
|
|
t.boolean "has_image", default: false, null: false
|
|
|
|
t.boolean "image_requested", default: false, null: false
|
|
|
|
t.datetime "reported_broken_at", precision: nil
|
|
|
|
t.datetime "converted_at", precision: nil
|
|
|
|
t.boolean "image_manual", default: false, null: false
|
|
|
|
t.text "manifest", size: :medium
|
2023-08-02 13:05:30 -07:00
|
|
|
t.timestamp "manifest_cached_at"
|
2023-08-06 17:46:47 -07:00
|
|
|
t.string "known_glitches", limit: 128, default: ""
|
Add manifest_url to swf_assets table
Ok so, impress-2020 guesses the manifest URL every time based on common
URL patterns. But the right way to do this is to read it from the
modeling data! But also, we don't have a great way to get the modeling
data directly. (Though as I write this, I guess we do have that
auto-modeling trick we use in the DTI 2020 codebase, I wonder if that
could work for this too?)
So anyway, in this change, we update the modeling code to save the
manifest URL, and also the migration includes a big block that attempts
to run impress-2020's manifest-guessing logic for every asset and save
the result!
It's uhh. Not fast. It runs at about 1 asset per second (a lot of these
aren't cache hits), and sometimes stalls out. And we have >600k assets,
so the estimated wall time is uhh. Seven days?
I think there's something we could do here around like, concurrent
execution? Though tbqh with the nature of the slowness being seemingly
about hitting the slow underlying images.neopets.com server, I don't
actually have a lot of faith that concurrency would actually be faster?
I also think it could be sensible to like… extract this from the
migration, and run it as a script to infer missing manifest URLs. That
would be easier to run in chunks and resume if something goes wrong.
Cuz like, I think my reasoning here was that backfilling this data was
part of the migration process… but the thing is, this migration can't
reliably get a manifest for everything (both cuz it depends on an
external service and cuz not everything has one), so it's a perfectly
valid migration to just leave the column as null for all the rows to
start, and fill this in later. I wish I'd written it like that!
But anyway, I'm just running this for now, and taking a break for the
night. Maybe later I'll come around and extract this into a separate
task to just try this on all assets missing manifests instead!
2023-11-09 21:42:51 -08:00
|
|
|
t.string "manifest_url"
|
2024-02-25 15:35:04 -08:00
|
|
|
t.datetime "manifest_loaded_at"
|
|
|
|
t.integer "manifest_status_code"
|
2023-08-06 17:46:47 -07:00
|
|
|
t.index ["body_id"], name: "swf_assets_body_id_and_object_id"
|
|
|
|
t.index ["type", "remote_id"], name: "swf_assets_type_and_id"
|
|
|
|
t.index ["zone_id"], name: "idx_swf_assets_zone_id"
|
2023-07-29 11:19:37 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "users", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2023-08-06 17:46:47 -07:00
|
|
|
t.string "name", limit: 20, null: false
|
|
|
|
t.integer "auth_server_id", limit: 1, null: false
|
|
|
|
t.integer "remote_id", null: false
|
|
|
|
t.integer "points", default: 0, null: false
|
|
|
|
t.boolean "beta", default: false, null: false
|
|
|
|
t.string "remember_token"
|
|
|
|
t.datetime "remember_created_at", precision: nil
|
|
|
|
t.integer "owned_closet_hangers_visibility", default: 1, null: false
|
|
|
|
t.integer "wanted_closet_hangers_visibility", default: 1, null: false
|
|
|
|
t.integer "contact_neopets_connection_id"
|
2024-01-19 00:00:46 -08:00
|
|
|
t.timestamp "last_trade_activity_at"
|
2024-01-29 04:21:19 -08:00
|
|
|
t.boolean "support_staff", default: false, null: false
|
2010-05-16 12:01:38 -07:00
|
|
|
end
|
|
|
|
|
2024-01-19 00:00:46 -08:00
|
|
|
create_table "zones", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
2013-01-21 17:34:39 -08:00
|
|
|
t.integer "depth"
|
|
|
|
t.integer "type_id"
|
2024-01-23 05:43:00 -08:00
|
|
|
t.string "label", null: false
|
|
|
|
t.string "plain_label", null: false
|
2010-05-15 08:38:45 -07:00
|
|
|
end
|
|
|
|
|
2024-01-24 03:25:23 -08:00
|
|
|
add_foreign_key "alt_styles", "colors"
|
|
|
|
add_foreign_key "alt_styles", "species"
|
2024-02-01 05:55:19 -08:00
|
|
|
add_foreign_key "outfits", "alt_styles"
|
2010-05-15 08:38:45 -07:00
|
|
|
end
|