From 4d24a9577fed24e5b20093c409ca101b7fd67afd Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 14:52:54 -0700 Subject: [PATCH 01/10] Only run `public_data:pull` if there are no pending migrations Oh this was a fun little dev environment bug: I ran `public_data:pull` on my laptop before migrating my database, so the `items` table pulled as the latest production version, which included the migrations, but they hadn't been marked as "run" yet. So Rails was still telling me I needed to run them, but the migrations themselves were crashing, with stuff like "there's already a column with this name!" This change ensures that `public_data:pull` won't run until migrations are done, to prevent silly accidents like that. --- lib/tasks/public_data.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/public_data.rake b/lib/tasks/public_data.rake index 7079c58e..789611da 100644 --- a/lib/tasks/public_data.rake +++ b/lib/tasks/public_data.rake @@ -63,7 +63,7 @@ namespace :public_data do end desc "Pull and import the latest public data from production (dev only)" - task :pull => :environment do + task :pull => ["db:abort_if_pending_migrations", :environment] do unless Rails.env.development? raise "Can only pull public data in development mode! This helps us " + "ensure we won't overwrite the production database accidentally." From 26dfe13b0ee1e3646482e9557c91f749147148e4 Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 14:59:09 -0700 Subject: [PATCH 02/10] Parse "Limited Dyeworks" items from Owls Yay thank you Owls team! I might also try to parse the dates too, the format seems to be "Owls: Limited Dyeworks - Dyeable Thru July 15" --- app/models/item.rb | 17 ++++++++++++++--- app/views/items/sources.html.haml | 6 ++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/models/item.rb b/app/models/item.rb index d426e88a..0e9c174f 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -217,12 +217,23 @@ class Item < ApplicationRecord nc_trade_value.value_text.include?("Permanent Dyeworks") end + # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, + # but only as part of a limited-time event. (Owls tracks this, not us!) + def dyeworks_limited? + return false if nc_trade_value.nil? + nc_trade_value.value_text.include?("Limited Dyeworks") + end + + # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, + # either at any time or as a limited-time event. (Owls tracks this, not us!) + def dyeworks_dyeable? + dyeworks_permanent? || dyeworks_limited? + end + # Whether this is a Dyeworks item whose base item can currently be purchased # in the NC Mall, then dyed via Dyeworks. (Owls tracks this last part!) def dyeworks_buyable? - # TODO: Add support for limited-time Dyeworks items. Does Owls offer this - # info too? (At time of writing, there are no active Dyeworks events.) - dyeworks_base_buyable? && dyeworks_permanent? + dyeworks_base_buyable? && dyeworks_dyeable? end DYEWORKS_NAME_PATTERN = %r{ diff --git a/app/views/items/sources.html.haml b/app/views/items/sources.html.haml index 8cf9b9be..af526ed4 100644 --- a/app/views/items/sources.html.haml +++ b/app/views/items/sources.html.haml @@ -80,6 +80,12 @@ "Dyeworks items are." } (Permanent) + - elsif item.dyeworks_limited? + %span.dyeworks-timeframe{ + title: "This recipe is part of a limited-time Dyeworks " + + "event, and is scheduled to be removed from the NC Mall soon." + } + (Limited-time) %button{onclick: "alert('Todo!')"} = cart_icon alt: "" From 015010345a804c2e5ae23481e2114236bdb5e17f Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 15:21:43 -0700 Subject: [PATCH 03/10] Extract Dyeworks methods into `Item::Dyeworks` module There's just starting to be a lot going on, so I pulled them out into here! I also considered a like, `Item::DyeworksStatus` class, and then you'd go like, `item.dyeworks.buyable?`. But idk, I think it's nice that the current API is simple for callers, and being able to do things like `items.filter(&:dyeworks_buyable?)` is pretty darn convenient. This solution lets us keep the increasing number of Dyeworks methods from polluting the main `item.rb`, while still keeping the API identical! --- app/models/item.rb | 55 +---------------------------------- app/models/item/dyeworks.rb | 57 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 54 deletions(-) create mode 100644 app/models/item/dyeworks.rb diff --git a/app/models/item.rb b/app/models/item.rb index 0e9c174f..83bdbe34 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -3,6 +3,7 @@ require "async/barrier" class Item < ApplicationRecord include PrettyParam + include Item::Dyeworks # We use the `type` column to mean something other than what Rails means! self.inheritance_column = nil @@ -198,60 +199,6 @@ class Item < ApplicationRecord nc_mall_record.present? end - def dyeworks? - dyeworks_base_item.present? - end - - # Whether this is a Dyeworks item whose base item can currently be purchased - # in the NC Mall. It may or may not currently be *dyeable* in the NC Mall, - # because Dyeworks eligibility is often a limited-time event. - def dyeworks_base_buyable? - dyeworks_base_item.present? && dyeworks_base_item.currently_in_mall? - end - - # Whether this is one of the few Dyeworks items that can be dyed in the NC - # Mall at any time, rather than as part of a limited-time event. (Owls tracks - # this, not us!) - def dyeworks_permanent? - return false if nc_trade_value.nil? - nc_trade_value.value_text.include?("Permanent Dyeworks") - end - - # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, - # but only as part of a limited-time event. (Owls tracks this, not us!) - def dyeworks_limited? - return false if nc_trade_value.nil? - nc_trade_value.value_text.include?("Limited Dyeworks") - end - - # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, - # either at any time or as a limited-time event. (Owls tracks this, not us!) - def dyeworks_dyeable? - dyeworks_permanent? || dyeworks_limited? - end - - # Whether this is a Dyeworks item whose base item can currently be purchased - # in the NC Mall, then dyed via Dyeworks. (Owls tracks this last part!) - def dyeworks_buyable? - dyeworks_base_buyable? && dyeworks_dyeable? - end - - DYEWORKS_NAME_PATTERN = %r{ - ^( - # Most Dyeworks items have a colon in the name. - Dyeworks\s+(?.+?:)\s*(?.+) - | - # But sometimes they omit it. If so, assume the first word is the color! - Dyeworks\s+(?\S+)\s*(?.+) - )$ - }x - def inferred_dyeworks_base_item - name_match = name.match(DYEWORKS_NAME_PATTERN) - return nil if name_match.nil? - - Item.find_by_name(name_match["base"]) - end - def source if dyeworks_buyable? :dyeworks diff --git a/app/models/item/dyeworks.rb b/app/models/item/dyeworks.rb new file mode 100644 index 00000000..e873275e --- /dev/null +++ b/app/models/item/dyeworks.rb @@ -0,0 +1,57 @@ +class Item + module Dyeworks + def dyeworks? + dyeworks_base_item.present? + end + + # Whether this is a Dyeworks item whose base item can currently be purchased + # in the NC Mall. It may or may not currently be *dyeable* in the NC Mall, + # because Dyeworks eligibility is often a limited-time event. + def dyeworks_base_buyable? + dyeworks_base_item.present? && dyeworks_base_item.currently_in_mall? + end + + # Whether this is one of the few Dyeworks items that can be dyed in the NC + # Mall at any time, rather than as part of a limited-time event. (Owls tracks + # this, not us!) + def dyeworks_permanent? + return false if nc_trade_value.nil? + nc_trade_value.value_text.include?("Permanent Dyeworks") + end + + # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, + # but only as part of a limited-time event. (Owls tracks this, not us!) + def dyeworks_limited? + return false if nc_trade_value.nil? + nc_trade_value.value_text.include?("Limited Dyeworks") + end + + # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, + # either at any time or as a limited-time event. (Owls tracks this, not us!) + def dyeworks_dyeable? + dyeworks_permanent? || dyeworks_limited? + end + + # Whether this is a Dyeworks item whose base item can currently be purchased + # in the NC Mall, then dyed via Dyeworks. (Owls tracks this last part!) + def dyeworks_buyable? + dyeworks_base_buyable? && dyeworks_dyeable? + end + + DYEWORKS_NAME_PATTERN = %r{ + ^( + # Most Dyeworks items have a colon in the name. + Dyeworks\s+(?.+?:)\s*(?.+) + | + # But sometimes they omit it. If so, assume the first word is the color! + Dyeworks\s+(?\S+)\s*(?.+) + )$ + }x + def inferred_dyeworks_base_item + name_match = name.match(DYEWORKS_NAME_PATTERN) + return nil if name_match.nil? + + Item.find_by_name(name_match["base"]) + end + end +end \ No newline at end of file From 98ecbf029da3537aa73e8bb653586f0d8dccb54d Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 15:20:27 -0700 Subject: [PATCH 04/10] Be case-insensitive when checking Owls values for Dyeworks status Just to be a bit more resilient! I'm not aware of any issues rn but this seems wise! --- app/models/item/dyeworks.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/item/dyeworks.rb b/app/models/item/dyeworks.rb index e873275e..fe971234 100644 --- a/app/models/item/dyeworks.rb +++ b/app/models/item/dyeworks.rb @@ -14,16 +14,18 @@ class Item # Whether this is one of the few Dyeworks items that can be dyed in the NC # Mall at any time, rather than as part of a limited-time event. (Owls tracks # this, not us!) + DYEWORKS_PERMANENT_PATTERN = /Permanent Dyeworks/i def dyeworks_permanent? return false if nc_trade_value.nil? - nc_trade_value.value_text.include?("Permanent Dyeworks") + nc_trade_value.value_text.match?(DYEWORKS_PERMANENT_PATTERN) end # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, # but only as part of a limited-time event. (Owls tracks this, not us!) + DYEWORKS_LIMITED_PATTERN = /Limited Dyeworks/i def dyeworks_limited? return false if nc_trade_value.nil? - nc_trade_value.value_text.include?("Limited Dyeworks") + nc_trade_value.value_text.match?(DYEWORKS_LIMITED_PATTERN) end # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, From c6fbb9b79784d7ce1fe12bceb58ab55c3a3c36bc Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 15:23:39 -0700 Subject: [PATCH 05/10] Reorder `Item::Dyeworks` methods a bit --- app/models/item/dyeworks.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/models/item/dyeworks.rb b/app/models/item/dyeworks.rb index fe971234..d79602bb 100644 --- a/app/models/item/dyeworks.rb +++ b/app/models/item/dyeworks.rb @@ -4,6 +4,12 @@ class Item dyeworks_base_item.present? end + # Whether this is a Dyeworks item whose base item can currently be purchased + # in the NC Mall, then dyed via Dyeworks. (Owls tracks this last part!) + def dyeworks_buyable? + dyeworks_base_buyable? && dyeworks_dyeable? + end + # Whether this is a Dyeworks item whose base item can currently be purchased # in the NC Mall. It may or may not currently be *dyeable* in the NC Mall, # because Dyeworks eligibility is often a limited-time event. @@ -11,6 +17,12 @@ class Item dyeworks_base_item.present? && dyeworks_base_item.currently_in_mall? end + # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, + # either at any time or as a limited-time event. (Owls tracks this, not us!) + def dyeworks_dyeable? + dyeworks_permanent? || dyeworks_limited? + end + # Whether this is one of the few Dyeworks items that can be dyed in the NC # Mall at any time, rather than as part of a limited-time event. (Owls tracks # this, not us!) @@ -28,18 +40,6 @@ class Item nc_trade_value.value_text.match?(DYEWORKS_LIMITED_PATTERN) end - # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, - # either at any time or as a limited-time event. (Owls tracks this, not us!) - def dyeworks_dyeable? - dyeworks_permanent? || dyeworks_limited? - end - - # Whether this is a Dyeworks item whose base item can currently be purchased - # in the NC Mall, then dyed via Dyeworks. (Owls tracks this last part!) - def dyeworks_buyable? - dyeworks_base_buyable? && dyeworks_dyeable? - end - DYEWORKS_NAME_PATTERN = %r{ ^( # Most Dyeworks items have a colon in the name. From 4e2110bf25618cd23c1570fa558d71924dcc90b2 Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 15:26:28 -0700 Subject: [PATCH 06/10] Add comment to `Item#inferred_dyeworks_base_item` --- app/models/item/dyeworks.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/item/dyeworks.rb b/app/models/item/dyeworks.rb index d79602bb..a5ccb9d0 100644 --- a/app/models/item/dyeworks.rb +++ b/app/models/item/dyeworks.rb @@ -40,6 +40,10 @@ class Item nc_trade_value.value_text.match?(DYEWORKS_LIMITED_PATTERN) end + # Infer what base item this Dyeworks item probably relates to, based on + # their names. We only use this when a new item is modeled to initialize + # the `dyeworks_base_item` relationship in the database; after that, we + # just use whatever the database says. (This allows manual overrides!) DYEWORKS_NAME_PATTERN = %r{ ^( # Most Dyeworks items have a colon in the name. From a9f44c0aa62362eee24594471c35d3b7fdd233e1 Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 16:39:37 -0700 Subject: [PATCH 07/10] Configure the application's default time zone to NST (US Pacific) This doesn't affect a lot right now, it was previously defaulting to UTC I think? And I've confirmed that, while timestamps are stored in the database as UTC, they're not interpreted any differently with this setting. (Or, rather, it's loaded as a `DateTime` object for the same moment in time, but in the NST time zone. Good!) But this feels like a more useful default for displaying things for development etc, and moreover, I'm working on some logic for things like "when do limited-time Dyeworks items expire exactly?", and that logic is made *much* simpler if we can just compare dates in NST by default rather than fudge around with the zones on them and figuring out the correct midnight! (Although, as I type this, I think I maybe have thought of an easier way to do it? So maybe this change won't actually be necessary for that, but it still feels like a more sensible default for us regardless!) --- config/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index d2bf815a..74392543 100644 --- a/config/application.rb +++ b/config/application.rb @@ -44,7 +44,7 @@ module OpenneoImpressItems # These settings can be overridden in specific environments using the files # in config/environments, which are processed later. # - # config.time_zone = "Central Time (US & Canada)" + config.time_zone = "Pacific Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") config.i18n.fallbacks = true From ec476f4c658b5995f938d266ff5bf84302dc356d Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 16:47:05 -0700 Subject: [PATCH 08/10] List the specific Dyeworks end date in Item Getting Guide, if we know --- app/models/item/dyeworks.rb | 53 ++++++++++++++++++++++++++--- app/views/items/sources.html.haml | 7 ++++ config/initializers/date_formats.rb | 1 + 3 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 config/initializers/date_formats.rb diff --git a/app/models/item/dyeworks.rb b/app/models/item/dyeworks.rb index a5ccb9d0..7908e582 100644 --- a/app/models/item/dyeworks.rb +++ b/app/models/item/dyeworks.rb @@ -20,26 +20,69 @@ class Item # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, # either at any time or as a limited-time event. (Owls tracks this, not us!) def dyeworks_dyeable? - dyeworks_permanent? || dyeworks_limited? + dyeworks_permanent? || dyeworks_limited_active? end # Whether this is one of the few Dyeworks items that can be dyed in the NC # Mall at any time, rather than as part of a limited-time event. (Owls tracks # this, not us!) - DYEWORKS_PERMANENT_PATTERN = /Permanent Dyeworks/i + DYEWORKS_PERMANENT_PATTERN = /Permanent\s*Dyeworks/i def dyeworks_permanent? return false if nc_trade_value.nil? nc_trade_value.value_text.match?(DYEWORKS_PERMANENT_PATTERN) end - # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, - # but only as part of a limited-time event. (Owls tracks this, not us!) - DYEWORKS_LIMITED_PATTERN = /Limited Dyeworks/i + # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right + # now, as part of a limited-time event. (Owls tracks this, not us!) + # + # If we aren't sure of the final date, this will still return `true`, on + # the assumption it *is* dyeable right now and we just don't understand the + # details of what Owls told us. + def dyeworks_limited_active? + return false unless dyeworks_limited? + return true if dyeworks_limited_final_date.nil? + + # NOTE: The application is configured to NST, so this should be + # equivalent to `Date.today`, but this is clearer and more correct imo! + today_in_nst = Time.find_zone("Pacific Time (US & Canada)").today + + today_in_nst <= dyeworks_limited_final_date + end + + # Whether this is a Dyeworks item that can only be dyed as part of a + # limited-time event. (This may return true even if the end date has + # passed, see `dyeworks_limited_active?`.) (Owls tracks this, not us!) + DYEWORKS_LIMITED_PATTERN = /Limited\s*Dyeworks/i def dyeworks_limited? return false if nc_trade_value.nil? nc_trade_value.value_text.match?(DYEWORKS_LIMITED_PATTERN) end + # If this is a limited-time Dyeworks item, this is the date we think the + # event will end on. Even if `dyeworks_limited?` returns true, this could + # still be `nil`, if we fail to parse this. (Owls tracks this, not us!) + DYEWORKS_LIMITED_FINAL_DATE_PATTERN = + /Dyeable\s*Thru\s*(?[a-z]+)\s*(?[0-9]+)/i + def dyeworks_limited_final_date + return nil unless dyeworks_limited? + + match = nc_trade_value.value_text. + match(DYEWORKS_LIMITED_FINAL_DATE_PATTERN) + return nil if match.nil? + + # Parse this " " date as the *next* such date: parse it as + # this year at first, then add a year if it turns out to be in the past. + # + # NOTE: This could return strange results if the Owls date contains + # something surprising! But the heuristic nature helps with e.g. + # flexibility if they abbreviate months, so let's lean into `Date.parse`. + match => {month:, day:} + date = Date.parse("#{month} #{day}, #{Date.today.year}") + date += 1.year if date < Date.today + + date + end + # Infer what base item this Dyeworks item probably relates to, based on # their names. We only use this when a new item is modeled to initialize # the `dyeworks_base_item` relationship in the database; after that, we diff --git a/app/views/items/sources.html.haml b/app/views/items/sources.html.haml index af526ed4..201a3657 100644 --- a/app/views/items/sources.html.haml +++ b/app/views/items/sources.html.haml @@ -80,6 +80,13 @@ "Dyeworks items are." } (Permanent) + - elsif item.dyeworks_limited_final_date.present? + %span.dyeworks-timeframe{ + title: "This recipe is part of a limited-time Dyeworks " + + "event. The last day you can dye this is " + + "#{item.dyeworks_limited_final_date.to_fs(:month_and_day)}." + } + (Thru #{item.dyeworks_limited_final_date.to_fs(:month_and_day)}) - elsif item.dyeworks_limited? %span.dyeworks-timeframe{ title: "This recipe is part of a limited-time Dyeworks " + diff --git a/config/initializers/date_formats.rb b/config/initializers/date_formats.rb new file mode 100644 index 00000000..bf383589 --- /dev/null +++ b/config/initializers/date_formats.rb @@ -0,0 +1 @@ +Date::DATE_FORMATS[:month_and_day] = "%B %e" From 10301900e29b3e9032a111168bb25fefb9de3c99 Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 16:49:09 -0700 Subject: [PATCH 09/10] Credit the Owls team for Dyeworks extra info in the Item Getting Guide --- app/views/items/sources.html.haml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/items/sources.html.haml b/app/views/items/sources.html.haml index 201a3657..a583cf10 100644 --- a/app/views/items/sources.html.haml +++ b/app/views/items/sources.html.haml @@ -77,20 +77,22 @@ title: "This recipe is NOT currently scheduled to be removed " + "from Dyeworks. It might not stay forever, but it's also " + "not part of a known limited-time event, like most " + - "Dyeworks items are." + "Dyeworks items are. (Thanks Owls team!)" } (Permanent) - elsif item.dyeworks_limited_final_date.present? %span.dyeworks-timeframe{ title: "This recipe is part of a limited-time Dyeworks " + "event. The last day you can dye this is " + - "#{item.dyeworks_limited_final_date.to_fs(:month_and_day)}." + "#{item.dyeworks_limited_final_date.to_fs(:month_and_day)}. " + + "(Thanks Owls team!)" } (Thru #{item.dyeworks_limited_final_date.to_fs(:month_and_day)}) - elsif item.dyeworks_limited? %span.dyeworks-timeframe{ title: "This recipe is part of a limited-time Dyeworks " + - "event, and is scheduled to be removed from the NC Mall soon." + "event, and is scheduled to be removed from the NC Mall " + + "soon. (Thanks Owls team!)" } (Limited-time) From b6bd539fed9ed3be71a7ca0910dccab4a42c7447 Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 18 Jun 2024 16:50:43 -0700 Subject: [PATCH 10/10] Oops, fix indentation in `Item::Dyeworks` Ahh, I started a tabs-y file (as I default to these days), but copied code from a spaces-y file, and didn't notice. (My laptop editor isn't configured to flag this for me, oops!) Fixed! --- app/models/item/dyeworks.rb | 174 ++++++++++++++++++------------------ 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/app/models/item/dyeworks.rb b/app/models/item/dyeworks.rb index 7908e582..5a458580 100644 --- a/app/models/item/dyeworks.rb +++ b/app/models/item/dyeworks.rb @@ -1,106 +1,106 @@ class Item module Dyeworks def dyeworks? - dyeworks_base_item.present? - end + dyeworks_base_item.present? + end - # Whether this is a Dyeworks item whose base item can currently be purchased - # in the NC Mall, then dyed via Dyeworks. (Owls tracks this last part!) - def dyeworks_buyable? - dyeworks_base_buyable? && dyeworks_dyeable? - end + # Whether this is a Dyeworks item whose base item can currently be purchased + # in the NC Mall, then dyed via Dyeworks. (Owls tracks this last part!) + def dyeworks_buyable? + dyeworks_base_buyable? && dyeworks_dyeable? + end - # Whether this is a Dyeworks item whose base item can currently be purchased - # in the NC Mall. It may or may not currently be *dyeable* in the NC Mall, - # because Dyeworks eligibility is often a limited-time event. - def dyeworks_base_buyable? - dyeworks_base_item.present? && dyeworks_base_item.currently_in_mall? - end + # Whether this is a Dyeworks item whose base item can currently be purchased + # in the NC Mall. It may or may not currently be *dyeable* in the NC Mall, + # because Dyeworks eligibility is often a limited-time event. + def dyeworks_base_buyable? + dyeworks_base_item.present? && dyeworks_base_item.currently_in_mall? + end - # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, - # either at any time or as a limited-time event. (Owls tracks this, not us!) - def dyeworks_dyeable? - dyeworks_permanent? || dyeworks_limited_active? - end + # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right now, + # either at any time or as a limited-time event. (Owls tracks this, not us!) + def dyeworks_dyeable? + dyeworks_permanent? || dyeworks_limited_active? + end - # Whether this is one of the few Dyeworks items that can be dyed in the NC - # Mall at any time, rather than as part of a limited-time event. (Owls tracks - # this, not us!) - DYEWORKS_PERMANENT_PATTERN = /Permanent\s*Dyeworks/i - def dyeworks_permanent? - return false if nc_trade_value.nil? - nc_trade_value.value_text.match?(DYEWORKS_PERMANENT_PATTERN) - end + # Whether this is one of the few Dyeworks items that can be dyed in the NC + # Mall at any time, rather than as part of a limited-time event. (Owls tracks + # this, not us!) + DYEWORKS_PERMANENT_PATTERN = /Permanent\s*Dyeworks/i + def dyeworks_permanent? + return false if nc_trade_value.nil? + nc_trade_value.value_text.match?(DYEWORKS_PERMANENT_PATTERN) + end - # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right - # now, as part of a limited-time event. (Owls tracks this, not us!) - # - # If we aren't sure of the final date, this will still return `true`, on - # the assumption it *is* dyeable right now and we just don't understand the - # details of what Owls told us. - def dyeworks_limited_active? - return false unless dyeworks_limited? - return true if dyeworks_limited_final_date.nil? + # Whether this is a Dyeworks item that can be dyed in the NC Mall ~right + # now, as part of a limited-time event. (Owls tracks this, not us!) + # + # If we aren't sure of the final date, this will still return `true`, on + # the assumption it *is* dyeable right now and we just don't understand the + # details of what Owls told us. + def dyeworks_limited_active? + return false unless dyeworks_limited? + return true if dyeworks_limited_final_date.nil? - # NOTE: The application is configured to NST, so this should be - # equivalent to `Date.today`, but this is clearer and more correct imo! - today_in_nst = Time.find_zone("Pacific Time (US & Canada)").today + # NOTE: The application is configured to NST, so this should be + # equivalent to `Date.today`, but this is clearer and more correct imo! + today_in_nst = Time.find_zone("Pacific Time (US & Canada)").today - today_in_nst <= dyeworks_limited_final_date - end + today_in_nst <= dyeworks_limited_final_date + end - # Whether this is a Dyeworks item that can only be dyed as part of a - # limited-time event. (This may return true even if the end date has - # passed, see `dyeworks_limited_active?`.) (Owls tracks this, not us!) - DYEWORKS_LIMITED_PATTERN = /Limited\s*Dyeworks/i - def dyeworks_limited? - return false if nc_trade_value.nil? - nc_trade_value.value_text.match?(DYEWORKS_LIMITED_PATTERN) - end + # Whether this is a Dyeworks item that can only be dyed as part of a + # limited-time event. (This may return true even if the end date has + # passed, see `dyeworks_limited_active?`.) (Owls tracks this, not us!) + DYEWORKS_LIMITED_PATTERN = /Limited\s*Dyeworks/i + def dyeworks_limited? + return false if nc_trade_value.nil? + nc_trade_value.value_text.match?(DYEWORKS_LIMITED_PATTERN) + end - # If this is a limited-time Dyeworks item, this is the date we think the - # event will end on. Even if `dyeworks_limited?` returns true, this could - # still be `nil`, if we fail to parse this. (Owls tracks this, not us!) - DYEWORKS_LIMITED_FINAL_DATE_PATTERN = - /Dyeable\s*Thru\s*(?[a-z]+)\s*(?[0-9]+)/i - def dyeworks_limited_final_date - return nil unless dyeworks_limited? + # If this is a limited-time Dyeworks item, this is the date we think the + # event will end on. Even if `dyeworks_limited?` returns true, this could + # still be `nil`, if we fail to parse this. (Owls tracks this, not us!) + DYEWORKS_LIMITED_FINAL_DATE_PATTERN = + /Dyeable\s*Thru\s*(?[a-z]+)\s*(?[0-9]+)/i + def dyeworks_limited_final_date + return nil unless dyeworks_limited? - match = nc_trade_value.value_text. - match(DYEWORKS_LIMITED_FINAL_DATE_PATTERN) - return nil if match.nil? + match = nc_trade_value.value_text. + match(DYEWORKS_LIMITED_FINAL_DATE_PATTERN) + return nil if match.nil? - # Parse this " " date as the *next* such date: parse it as - # this year at first, then add a year if it turns out to be in the past. - # - # NOTE: This could return strange results if the Owls date contains - # something surprising! But the heuristic nature helps with e.g. - # flexibility if they abbreviate months, so let's lean into `Date.parse`. - match => {month:, day:} - date = Date.parse("#{month} #{day}, #{Date.today.year}") - date += 1.year if date < Date.today + # Parse this " " date as the *next* such date: parse it as + # this year at first, then add a year if it turns out to be in the past. + # + # NOTE: This could return strange results if the Owls date contains + # something surprising! But the heuristic nature helps with e.g. + # flexibility if they abbreviate months, so let's lean into `Date.parse`. + match => {month:, day:} + date = Date.parse("#{month} #{day}, #{Date.today.year}") + date += 1.year if date < Date.today - date - end + date + end - # Infer what base item this Dyeworks item probably relates to, based on - # their names. We only use this when a new item is modeled to initialize - # the `dyeworks_base_item` relationship in the database; after that, we - # just use whatever the database says. (This allows manual overrides!) - DYEWORKS_NAME_PATTERN = %r{ - ^( - # Most Dyeworks items have a colon in the name. - Dyeworks\s+(?.+?:)\s*(?.+) - | - # But sometimes they omit it. If so, assume the first word is the color! - Dyeworks\s+(?\S+)\s*(?.+) - )$ - }x - def inferred_dyeworks_base_item - name_match = name.match(DYEWORKS_NAME_PATTERN) - return nil if name_match.nil? + # Infer what base item this Dyeworks item probably relates to, based on + # their names. We only use this when a new item is modeled to initialize + # the `dyeworks_base_item` relationship in the database; after that, we + # just use whatever the database says. (This allows manual overrides!) + DYEWORKS_NAME_PATTERN = %r{ + ^( + # Most Dyeworks items have a colon in the name. + Dyeworks\s+(?.+?:)\s*(?.+) + | + # But sometimes they omit it. If so, assume the first word is the color! + Dyeworks\s+(?\S+)\s*(?.+) + )$ + }x + def inferred_dyeworks_base_item + name_match = name.match(DYEWORKS_NAME_PATTERN) + return nil if name_match.nil? - Item.find_by_name(name_match["base"]) - end + Item.find_by_name(name_match["base"]) + end end end \ No newline at end of file