forked from OpenNeo/impress
Save last trade activity time onto User
In impress-2020, we do a big slow query to figure out which users have
been active in trades recently. Now, we cache that timestamp on the
User model.
This won't have any immediate effect; it's to clear the way for Classic
DTI to receive the better trade ratios feature people like from 2020.
I also added some unit testing infra because I finally wanted it! for
all the ways you can trigger this timestamp lol
Note too that this is a bit of an unusually complex migration, but my
hope is that the batching and query structure and such helps it run
surprisingly fast! 🤞
This commit is contained in:
parent
b84942d77c
commit
470c805880
15 changed files with 394 additions and 25 deletions
|
@ -27,6 +27,8 @@
|
||||||
// we partially override `config/database.yml` to connect to `db`!
|
// we partially override `config/database.yml` to connect to `db`!
|
||||||
"DATABASE_URL_PRIMARY_DEV": "mysql2://db",
|
"DATABASE_URL_PRIMARY_DEV": "mysql2://db",
|
||||||
"DATABASE_URL_OPENNEO_ID_DEV": "mysql2://db",
|
"DATABASE_URL_OPENNEO_ID_DEV": "mysql2://db",
|
||||||
|
"DATABASE_URL_PRIMARY_TEST": "mysql2://db",
|
||||||
|
"DATABASE_URL_OPENNEO_ID_TEST": "mysql2://db",
|
||||||
|
|
||||||
// HACK: Out of the box, this dev container doesn't allow installation to
|
// HACK: Out of the box, this dev container doesn't allow installation to
|
||||||
// the default GEM_HOME, because of a weird thing going on with RVM.
|
// the default GEM_HOME, because of a weird thing going on with RVM.
|
||||||
|
|
5
Gemfile
5
Gemfile
|
@ -79,3 +79,8 @@ gem "stackprof", "~> 0.2.25"
|
||||||
# For monitoring errors in production.
|
# For monitoring errors in production.
|
||||||
gem "sentry-ruby", "~> 5.12"
|
gem "sentry-ruby", "~> 5.12"
|
||||||
gem "sentry-rails", "~> 5.12"
|
gem "sentry-rails", "~> 5.12"
|
||||||
|
|
||||||
|
# For automated testing.
|
||||||
|
group :test do
|
||||||
|
gem 'sqlite3', '~> 1.7'
|
||||||
|
end
|
||||||
|
|
|
@ -297,6 +297,8 @@ GEM
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
|
sqlite3 (1.7.0)
|
||||||
|
mini_portile2 (~> 2.8.0)
|
||||||
stackprof (0.2.25)
|
stackprof (0.2.25)
|
||||||
stringio (3.0.8)
|
stringio (3.0.8)
|
||||||
temple (0.8.2)
|
temple (0.8.2)
|
||||||
|
@ -359,6 +361,7 @@ DEPENDENCIES
|
||||||
sentry-rails (~> 5.12)
|
sentry-rails (~> 5.12)
|
||||||
sentry-ruby (~> 5.12)
|
sentry-ruby (~> 5.12)
|
||||||
sprockets (~> 4.2)
|
sprockets (~> 4.2)
|
||||||
|
sqlite3 (~> 1.7)
|
||||||
stackprof (~> 0.2.25)
|
stackprof (~> 0.2.25)
|
||||||
terser (~> 1.1, >= 1.1.17)
|
terser (~> 1.1, >= 1.1.17)
|
||||||
thread-local (~> 1.1)
|
thread-local (~> 1.1)
|
||||||
|
|
|
@ -4,6 +4,7 @@ class ClosetHanger < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
delegate :name, to: :item, prefix: true
|
delegate :name, to: :item, prefix: true
|
||||||
|
delegate :log_trade_activity, to: :user
|
||||||
|
|
||||||
validates :item_id, :uniqueness => {:scope => [:user_id, :owned, :list_id]}
|
validates :item_id, :uniqueness => {:scope => [:user_id, :owned, :list_id]}
|
||||||
validates :quantity, :numericality => {:greater_than => 0}
|
validates :quantity, :numericality => {:greater_than => 0}
|
||||||
|
@ -16,6 +17,32 @@ class ClosetHanger < ApplicationRecord
|
||||||
joins(:item => :translations).where(it[:locale].eq(I18n.locale)).
|
joins(:item => :translations).where(it[:locale].eq(I18n.locale)).
|
||||||
order(it[:name].asc)
|
order(it[:name].asc)
|
||||||
}
|
}
|
||||||
|
scope :trading, -> {
|
||||||
|
ch = arel_table
|
||||||
|
cl = ClosetList.arel_table
|
||||||
|
u = User.arel_table
|
||||||
|
joins(:user, :list).where(
|
||||||
|
# sigh… our default-lists continue to be a pain
|
||||||
|
(
|
||||||
|
ch[:list_id].not_eq(nil).and(cl[:visibility].gteq(
|
||||||
|
ClosetVisibility[:trading].id))
|
||||||
|
).or(
|
||||||
|
(
|
||||||
|
ch[:list_id].eq(nil).and(ch[:owned].eq(true))
|
||||||
|
).and(
|
||||||
|
u[:owned_closet_hangers_visibility].gteq(
|
||||||
|
ClosetVisibility[:trading].id)
|
||||||
|
)
|
||||||
|
).or(
|
||||||
|
(
|
||||||
|
ch[:list_id].eq(nil).and(ch[:owned].eq(false))
|
||||||
|
).and(
|
||||||
|
u[:wanted_closet_hangers_visibility].gteq(
|
||||||
|
ClosetVisibility[:trading].id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
scope :newest, -> { order(arel_table[:created_at].desc) }
|
scope :newest, -> { order(arel_table[:created_at].desc) }
|
||||||
scope :owned_before_wanted, -> { order(arel_table[:owned].desc) }
|
scope :owned_before_wanted, -> { order(arel_table[:owned].desc) }
|
||||||
scope :unlisted, -> { where(:list_id => nil) }
|
scope :unlisted, -> { where(:list_id => nil) }
|
||||||
|
@ -36,6 +63,9 @@ class ClosetHanger < ApplicationRecord
|
||||||
|
|
||||||
before_validation :merge_quantities, :set_owned_by_list
|
before_validation :merge_quantities, :set_owned_by_list
|
||||||
|
|
||||||
|
after_save :log_trade_activity, if: :trading?
|
||||||
|
after_destroy :log_trade_activity, if: :trading?
|
||||||
|
|
||||||
def possibly_null_closet_list
|
def possibly_null_closet_list
|
||||||
list || user.null_closet_list(owned)
|
list || user.null_closet_list(owned)
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,10 +6,15 @@ class ClosetList < ApplicationRecord
|
||||||
validates :user, :presence => true
|
validates :user, :presence => true
|
||||||
validates :hangers_owned, :inclusion => {:in => [true, false], :message => "can't be blank"}
|
validates :hangers_owned, :inclusion => {:in => [true, false], :message => "can't be blank"}
|
||||||
|
|
||||||
|
delegate :log_trade_activity, to: :user
|
||||||
|
|
||||||
scope :alphabetical, -> { order(:name) }
|
scope :alphabetical, -> { order(:name) }
|
||||||
scope :publicly_visible, -> {
|
scope :publicly_visible, -> {
|
||||||
where(arel_table[:visibility].gteq(ClosetVisibility[:public].id))
|
where(arel_table[:visibility].gteq(ClosetVisibility[:public].id))
|
||||||
}
|
}
|
||||||
|
scope :trading, -> {
|
||||||
|
where(arel_table[:visibility].gteq(ClosetVisibility[:trading].id))
|
||||||
|
}
|
||||||
scope :visible_to, ->(user) {
|
scope :visible_to, ->(user) {
|
||||||
condition = arel_table[:visibility].gteq(ClosetVisibility[:public].id)
|
condition = arel_table[:visibility].gteq(ClosetVisibility[:public].id)
|
||||||
condition = condition.or(arel_table[:user_id].eq(user.id)) if user
|
condition = condition.or(arel_table[:user_id].eq(user.id)) if user
|
||||||
|
@ -17,6 +22,12 @@ class ClosetList < ApplicationRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
after_save :sync_hangers_owned!
|
after_save :sync_hangers_owned!
|
||||||
|
after_save :log_trade_activity, if: :trading?
|
||||||
|
after_destroy :log_trade_activity, if: :trading?
|
||||||
|
|
||||||
|
def trading?
|
||||||
|
visibility >= ClosetVisibility[:trading].id
|
||||||
|
end
|
||||||
|
|
||||||
def sync_hangers_owned!
|
def sync_hangers_owned!
|
||||||
if hangers_owned_changed?
|
if hangers_owned_changed?
|
||||||
|
|
|
@ -25,6 +25,12 @@ class User < ApplicationRecord
|
||||||
scope :top_contributors, -> { order('points DESC').where('points > 0') }
|
scope :top_contributors, -> { order('points DESC').where('points > 0') }
|
||||||
|
|
||||||
after_update :sync_name_with_auth_user!, if: :saved_change_to_name?
|
after_update :sync_name_with_auth_user!, if: :saved_change_to_name?
|
||||||
|
after_update :log_trade_activity, if: -> user {
|
||||||
|
(user.saved_change_to_owned_closet_hangers_visibility? &&
|
||||||
|
user.owned_closet_hangers_visibility >= ClosetVisibility[:trading].id) ||
|
||||||
|
(user.saved_change_to_wanted_closet_hangers_visibility? &&
|
||||||
|
user.wanted_closet_hangers_visibility >= ClosetVisibility[:trading].id)
|
||||||
|
}
|
||||||
|
|
||||||
def sync_name_with_auth_user!
|
def sync_name_with_auth_user!
|
||||||
auth_user.name = name
|
auth_user.name = name
|
||||||
|
@ -169,6 +175,10 @@ class User < ApplicationRecord
|
||||||
contact_neopets_connection.try(:neopets_username)
|
contact_neopets_connection.try(:neopets_username)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_trade_activity
|
||||||
|
touch(:last_trade_activity_at)
|
||||||
|
end
|
||||||
|
|
||||||
def self.points_required_to_pass_top_contributor(offset)
|
def self.points_required_to_pass_top_contributor(offset)
|
||||||
user = User.top_contributors.select(:points).limit(1).offset(offset).first
|
user = User.top_contributors.select(:points).limit(1).offset(offset).first
|
||||||
user ? user.points : 0
|
user ? user.points : 0
|
||||||
|
|
|
@ -24,6 +24,32 @@ development:
|
||||||
sql_mode: TRADITIONAL
|
sql_mode: TRADITIONAL
|
||||||
migrations_paths: db/openneo_id_migrate
|
migrations_paths: db/openneo_id_migrate
|
||||||
|
|
||||||
|
test:
|
||||||
|
primary:
|
||||||
|
# You can override these default settings with this environment variable,
|
||||||
|
# fully or partially. We do this in the .devcontainer setup!
|
||||||
|
url: <%= ENV['DATABASE_URL_PRIMARY_TEST'] %>
|
||||||
|
adapter: mysql2
|
||||||
|
database: openneo_impress_test
|
||||||
|
username: impress_dev
|
||||||
|
password: impress_dev
|
||||||
|
pool: 5
|
||||||
|
variables:
|
||||||
|
sql_mode: TRADITIONAL
|
||||||
|
|
||||||
|
openneo_id:
|
||||||
|
# You can override these default settings with this environment variable,
|
||||||
|
# fully or partially. We do this in the .devcontainer setup!
|
||||||
|
url: <%= ENV['DATABASE_URL_OPENNEO_ID_TEST'] %>
|
||||||
|
adapter: mysql2
|
||||||
|
database: openneo_id_test
|
||||||
|
username: impress_dev
|
||||||
|
password: impress_dev
|
||||||
|
pool: 2
|
||||||
|
variables:
|
||||||
|
sql_mode: TRADITIONAL
|
||||||
|
migrations_paths: db/openneo_id_migrate
|
||||||
|
|
||||||
production:
|
production:
|
||||||
primary:
|
primary:
|
||||||
url: <%= ENV['DATABASE_URL_PRIMARY'] %>
|
url: <%= ENV['DATABASE_URL_PRIMARY'] %>
|
||||||
|
|
|
@ -35,7 +35,7 @@ Rails.application.configure do
|
||||||
config.action_controller.allow_forgery_protection = false
|
config.action_controller.allow_forgery_protection = false
|
||||||
|
|
||||||
# Store uploaded files on the local file system in a temporary directory.
|
# Store uploaded files on the local file system in a temporary directory.
|
||||||
config.active_storage.service = :test
|
# config.active_storage.service = :test
|
||||||
|
|
||||||
config.action_mailer.perform_caching = false
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
# Make sure your secret_key_base is kept private
|
# Make sure your secret_key_base is kept private
|
||||||
# if you're sharing your code publicly.
|
# if you're sharing your code publicly.
|
||||||
if Rails.env.development?
|
if Rails.env.development? || Rails.env.test?
|
||||||
# In development, we use a hardcoded secret key, because it doesn't actually
|
# In development, we use a hardcoded secret key, because it doesn't actually
|
||||||
# need to be secret!
|
# need to be secret!
|
||||||
OpenneoImpressItems::Application.config.secret_key_base = "7584841652f89044a8b5a428efa6dfac2461449eb24741a33668cd642130d79f93b0347766ebf4a4d7d5033a263c36431594ad56b5735a7325c8cdda991219c2"
|
OpenneoImpressItems::Application.config.secret_key_base = "7584841652f89044a8b5a428efa6dfac2461449eb24741a33668cd642130d79f93b0347766ebf4a4d7d5033a263c36431594ad56b5735a7325c8cdda991219c2"
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
class AddLastTradeActivityAtToUsers < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
add_column :users, :last_trade_activity_at, :timestamp
|
||||||
|
|
||||||
|
reversible do |direction|
|
||||||
|
direction.up do
|
||||||
|
User.find_in_batches do |users|
|
||||||
|
# Find the last ClosetList/ClosetHanger updated_at timestamp for each
|
||||||
|
# user, for trading lists/hangers only.
|
||||||
|
max_closet_list_updated_at_by_user_id = ClosetList.
|
||||||
|
trading.
|
||||||
|
group(:user_id).
|
||||||
|
where(user_id: users.map(&:id)).
|
||||||
|
maximum(:updated_at)
|
||||||
|
max_closet_hanger_updated_at_by_user_id = ClosetHanger.
|
||||||
|
trading.
|
||||||
|
group(:user_id).
|
||||||
|
where(user_id: users.map(&:id)).
|
||||||
|
maximum(:updated_at)
|
||||||
|
|
||||||
|
# Set `last_trade_activity_at` to the largest such `updated_at` for
|
||||||
|
# that user, or nil if there's none.
|
||||||
|
User.transaction do
|
||||||
|
users.each do |user|
|
||||||
|
user.last_trade_activity_at = [
|
||||||
|
max_closet_list_updated_at_by_user_id[user.id],
|
||||||
|
max_closet_hanger_updated_at_by_user_id[user.id],
|
||||||
|
].filter(&:present?).max
|
||||||
|
user.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,7 +11,7 @@
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2023_08_07_005748) do
|
ActiveRecord::Schema[7.1].define(version: 2023_08_07_005748) do
|
||||||
create_table "users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", force: :cascade do |t|
|
create_table "users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t|
|
||||||
t.string "name", limit: 20, null: false
|
t.string "name", limit: 20, null: false
|
||||||
t.string "encrypted_password", limit: 64, null: false
|
t.string "encrypted_password", limit: 64, null: false
|
||||||
t.string "email", limit: 50, null: false
|
t.string "email", limit: 50, null: false
|
||||||
|
|
45
db/schema.rb
45
db/schema.rb
|
@ -10,8 +10,8 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
ActiveRecord::Schema[7.1].define(version: 2024_01_19_061745) do
|
||||||
create_table "auth_servers", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "auth_servers", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.string "short_name", limit: 10, null: false
|
t.string "short_name", limit: 10, null: false
|
||||||
t.string "name", limit: 40, null: false
|
t.string "name", limit: 40, null: false
|
||||||
t.text "icon", size: :medium, null: false
|
t.text "icon", size: :medium, null: false
|
||||||
|
@ -19,7 +19,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.string "secret", limit: 64, null: false
|
t.string "secret", limit: 64, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "campaigns", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "campaigns", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "progress", default: 0, null: false
|
t.integer "progress", default: 0, null: false
|
||||||
t.integer "goal", null: false
|
t.integer "goal", null: false
|
||||||
t.boolean "active", null: false
|
t.boolean "active", null: false
|
||||||
|
@ -33,7 +33,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.string "name"
|
t.string "name"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "closet_hangers", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "closet_hangers", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "item_id"
|
t.integer "item_id"
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "quantity"
|
t.integer "quantity"
|
||||||
|
@ -49,7 +49,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["user_id"], name: "index_closet_hangers_on_user_id"
|
t.index ["user_id"], name: "index_closet_hangers_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "closet_lists", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "closet_lists", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name"
|
||||||
t.text "description"
|
t.text "description"
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
|
@ -60,7 +60,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["user_id"], name: "index_closet_lists_on_user_id"
|
t.index ["user_id"], name: "index_closet_lists_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "color_translations", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "color_translations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "color_id"
|
t.integer "color_id"
|
||||||
t.string "locale"
|
t.string "locale"
|
||||||
t.string "name"
|
t.string "name"
|
||||||
|
@ -70,13 +70,13 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["locale"], name: "index_color_translations_on_locale"
|
t.index ["locale"], name: "index_color_translations_on_locale"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "colors", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "colors", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.boolean "basic"
|
t.boolean "basic"
|
||||||
t.boolean "standard"
|
t.boolean "standard"
|
||||||
t.boolean "prank", default: false, null: false
|
t.boolean "prank", default: false, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "contributions", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "contributions", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.string "contributed_type", limit: 8, null: false
|
t.string "contributed_type", limit: 8, null: false
|
||||||
t.integer "contributed_id", null: false
|
t.integer "contributed_id", null: false
|
||||||
t.integer "user_id", null: false
|
t.integer "user_id", null: false
|
||||||
|
@ -85,14 +85,14 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["user_id"], name: "index_contributions_on_user_id"
|
t.index ["user_id"], name: "index_contributions_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "donation_features", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "donation_features", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "donation_id", null: false
|
t.integer "donation_id", null: false
|
||||||
t.integer "outfit_id"
|
t.integer "outfit_id"
|
||||||
t.datetime "created_at", precision: nil, null: false
|
t.datetime "created_at", precision: nil, null: false
|
||||||
t.datetime "updated_at", precision: nil, null: false
|
t.datetime "updated_at", precision: nil, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "donations", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "donations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "amount", null: false
|
t.integer "amount", null: false
|
||||||
t.string "charge_id", null: false
|
t.string "charge_id", null: false
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
|
@ -104,7 +104,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.integer "campaign_id", null: false
|
t.integer "campaign_id", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "item_outfit_relationships", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "item_outfit_relationships", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "item_id"
|
t.integer "item_id"
|
||||||
t.integer "outfit_id"
|
t.integer "outfit_id"
|
||||||
t.boolean "is_worn"
|
t.boolean "is_worn"
|
||||||
|
@ -149,7 +149,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["modeling_status_hint"], name: "items_modeling_status_hint"
|
t.index ["modeling_status_hint"], name: "items_modeling_status_hint"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "login_cookies", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "login_cookies", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "user_id", null: false
|
t.integer "user_id", null: false
|
||||||
t.integer "series", null: false
|
t.integer "series", null: false
|
||||||
t.integer "token", null: false
|
t.integer "token", null: false
|
||||||
|
@ -157,20 +157,20 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["user_id"], name: "login_cookies_user_id"
|
t.index ["user_id"], name: "login_cookies_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "modeling_logs", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "modeling_logs", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.datetime "created_at", precision: nil, null: false
|
t.datetime "created_at", precision: nil, null: false
|
||||||
t.text "log_json", null: false
|
t.text "log_json", null: false
|
||||||
t.string "pet_name", limit: 128, null: false
|
t.string "pet_name", limit: 128, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "neopets_connections", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "neopets_connections", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.string "neopets_username"
|
t.string "neopets_username"
|
||||||
t.datetime "created_at", precision: nil, null: false
|
t.datetime "created_at", precision: nil, null: false
|
||||||
t.datetime "updated_at", precision: nil, null: false
|
t.datetime "updated_at", precision: nil, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "outfits", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "outfits", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "pet_state_id"
|
t.integer "pet_state_id"
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.datetime "created_at", precision: nil
|
t.datetime "created_at", precision: nil
|
||||||
|
@ -193,7 +193,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["swf_asset_id"], name: "parents_swf_assets_swf_asset_id"
|
t.index ["swf_asset_id"], name: "parents_swf_assets_swf_asset_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "pet_loads", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "pet_loads", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.string "pet_name", limit: 20, null: false
|
t.string "pet_name", limit: 20, null: false
|
||||||
t.text "amf", size: :medium, null: false
|
t.text "amf", size: :medium, null: false
|
||||||
t.datetime "created_at", precision: nil, null: false
|
t.datetime "created_at", precision: nil, null: false
|
||||||
|
@ -225,17 +225,17 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["species_id", "color_id"], name: "pet_types_species_color", unique: true
|
t.index ["species_id", "color_id"], name: "pet_types_species_color", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "pets", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "pets", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.string "name", limit: 20, null: false
|
t.string "name", limit: 20, null: false
|
||||||
t.integer "pet_type_id", limit: 3, null: false
|
t.integer "pet_type_id", limit: 3, null: false
|
||||||
t.index ["name"], name: "pets_name", unique: true
|
t.index ["name"], name: "pets_name", unique: true
|
||||||
t.index ["pet_type_id"], name: "pets_pet_type_id"
|
t.index ["pet_type_id"], name: "pets_pet_type_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "species", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "species", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "species_translations", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "species_translations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "species_id"
|
t.integer "species_id"
|
||||||
t.string "locale"
|
t.string "locale"
|
||||||
t.string "name"
|
t.string "name"
|
||||||
|
@ -267,7 +267,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["zone_id"], name: "idx_swf_assets_zone_id"
|
t.index ["zone_id"], name: "idx_swf_assets_zone_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "users", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "users", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.string "name", limit: 20, null: false
|
t.string "name", limit: 20, null: false
|
||||||
t.integer "auth_server_id", limit: 1, null: false
|
t.integer "auth_server_id", limit: 1, null: false
|
||||||
t.integer "remote_id", null: false
|
t.integer "remote_id", null: false
|
||||||
|
@ -278,9 +278,10 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.integer "owned_closet_hangers_visibility", default: 1, null: false
|
t.integer "owned_closet_hangers_visibility", default: 1, null: false
|
||||||
t.integer "wanted_closet_hangers_visibility", default: 1, null: false
|
t.integer "wanted_closet_hangers_visibility", default: 1, null: false
|
||||||
t.integer "contact_neopets_connection_id"
|
t.integer "contact_neopets_connection_id"
|
||||||
|
t.timestamp "last_trade_activity_at"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "zone_translations", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "zone_translations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "zone_id"
|
t.integer "zone_id"
|
||||||
t.string "locale"
|
t.string "locale"
|
||||||
t.string "label"
|
t.string "label"
|
||||||
|
@ -291,7 +292,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||||
t.index ["zone_id"], name: "index_zone_translations_on_zone_id"
|
t.index ["zone_id"], name: "index_zone_translations_on_zone_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "zones", id: :integer, charset: "latin1", force: :cascade do |t|
|
create_table "zones", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||||
t.integer "depth"
|
t.integer "depth"
|
||||||
t.integer "type_id"
|
t.integer "type_id"
|
||||||
end
|
end
|
||||||
|
|
13
test/test_helper.rb
Normal file
13
test/test_helper.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
ENV["RAILS_ENV"] = "test"
|
||||||
|
require File.expand_path('../../config/environment', __FILE__)
|
||||||
|
require 'rails/test_help'
|
||||||
|
|
||||||
|
class ActiveSupport::TestCase
|
||||||
|
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
|
||||||
|
#
|
||||||
|
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
||||||
|
# -- they do not yet inherit this setting
|
||||||
|
fixtures :all
|
||||||
|
|
||||||
|
# Add more helper methods to be used by all tests here...
|
||||||
|
end
|
232
test/trade_activity_test.rb
Normal file
232
test/trade_activity_test.rb
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class TradeActivityTest < ActiveSupport::TestCase
|
||||||
|
test "New user's last trade activity is nil" do
|
||||||
|
user = create_user
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Adding or removing items in a Trading list updates last trade activity" do
|
||||||
|
user = create_user
|
||||||
|
|
||||||
|
list = create_closet_list(
|
||||||
|
user: user, visibility: ClosetVisibility[:trading].id)
|
||||||
|
hanger = create_closet_hanger(user: user, list: list)
|
||||||
|
created_at = Time.now
|
||||||
|
|
||||||
|
assert_equal created_at, user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
hanger.destroy!
|
||||||
|
|
||||||
|
assert_equal created_at + 1.day, user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Adding or removing items in a Public list does not update last trade activity" do
|
||||||
|
user = create_user
|
||||||
|
|
||||||
|
list = create_closet_list(
|
||||||
|
user: user, visibility: ClosetVisibility[:public].id)
|
||||||
|
hanger = create_closet_hanger(user: user, list: list)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
hanger.destroy!
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Adding or removing items in a Private list does not update last trade activity" do
|
||||||
|
user = create_user
|
||||||
|
|
||||||
|
list = create_closet_list(
|
||||||
|
user: user, visibility: ClosetVisibility[:private].id)
|
||||||
|
hanger = create_closet_hanger(user: user, list: list)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
hanger.destroy!
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Adding or removing items in a Trading default-list updates last trade activity" do
|
||||||
|
user = create_user(
|
||||||
|
owned_closet_hangers_visibility: ClosetVisibility[:trading].id,
|
||||||
|
wanted_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||||
|
)
|
||||||
|
|
||||||
|
hanger = create_closet_hanger(user: user, owned: true)
|
||||||
|
created_at = Time.now
|
||||||
|
|
||||||
|
assert_equal created_at, user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
hanger.destroy!
|
||||||
|
|
||||||
|
assert_equal created_at + 1.day, user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Adding or removing items in a Public default-list does not update last trade activity" do
|
||||||
|
user = create_user(
|
||||||
|
owned_closet_hangers_visibility: ClosetVisibility[:public].id,
|
||||||
|
wanted_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||||
|
)
|
||||||
|
|
||||||
|
hanger = create_closet_hanger(user: user, owned: true)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
hanger.destroy!
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Adding or removing items in a Private default-list does not update last trade activity" do
|
||||||
|
user = create_user(
|
||||||
|
owned_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||||
|
wanted_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||||
|
)
|
||||||
|
|
||||||
|
hanger = create_closet_hanger(user: user, owned: true)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
hanger.destroy!
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Creating, editing, or deleting a Trading list updates last trade activity" do
|
||||||
|
user = create_user
|
||||||
|
list = create_closet_list(
|
||||||
|
user: user, visibility: ClosetVisibility[:trading].id
|
||||||
|
)
|
||||||
|
created_at = Time.now
|
||||||
|
|
||||||
|
assert_equal created_at, user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
list.update!(description: "Hello, world!")
|
||||||
|
|
||||||
|
assert_equal created_at + 1.day, user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
list.destroy!
|
||||||
|
|
||||||
|
assert_equal created_at + 2.day, user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Creating, editing, or deleting a Public list does not update last trade activity" do
|
||||||
|
user = create_user
|
||||||
|
list = create_closet_list(
|
||||||
|
user: user, visibility: ClosetVisibility[:public].id
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
list.update!(description: "Hello, world!")
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
list.destroy!
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Creating, editing, or deleting a Private list does not update last trade activity" do
|
||||||
|
user = create_user
|
||||||
|
list = create_closet_list(
|
||||||
|
user: user, visibility: ClosetVisibility[:private].id
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
list.update!(description: "Hello, world!")
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
travel 1.day
|
||||||
|
list.destroy!
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Updating default-list visibility to Trading updates last trade activity" do
|
||||||
|
user = create_user(
|
||||||
|
owned_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
user.update!(
|
||||||
|
owned_closet_hangers_visibility: ClosetVisibility[:trading].id,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_equal Time.now, user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Updating default-list visibility to Public does not update last trade activity" do
|
||||||
|
user = create_user(
|
||||||
|
owned_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
user.update!(
|
||||||
|
owned_closet_hangers_visibility: ClosetVisibility[:public].id,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Updating default-list visibility to Private does not update last trade activity" do
|
||||||
|
user = create_user(
|
||||||
|
owned_closet_hangers_visibility: ClosetVisibility[:public].id,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
|
||||||
|
user.update!(
|
||||||
|
owned_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_nil user.last_trade_activity_at
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
freeze_time # to compare timestamps accurately
|
||||||
|
|
||||||
|
Item.create!(
|
||||||
|
thumbnail_url: "https://images.neopets.com/foo.png",
|
||||||
|
zones_restrict: "",
|
||||||
|
price: 123,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_user(**args)
|
||||||
|
auth_user = AuthUser.create!(
|
||||||
|
name: 'test', email: 'test@example.com', password: 'test123!'
|
||||||
|
)
|
||||||
|
auth_user.user.update!(**args) unless args.empty?
|
||||||
|
auth_user.user
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_closet_list(**args)
|
||||||
|
num = ClosetList.count + 1
|
||||||
|
ClosetList.create!(name: "Test List #{num}", hangers_owned: true, **args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_closet_hanger(**args)
|
||||||
|
ClosetHanger.create!(item: Item.first, quantity: 1, **args)
|
||||||
|
end
|
||||||
|
end
|
BIN
vendor/cache/sqlite3-1.7.0-x86_64-linux.gem
vendored
Normal file
BIN
vendor/cache/sqlite3-1.7.0-x86_64-linux.gem
vendored
Normal file
Binary file not shown.
Loading…
Reference in a new issue