Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store country band details in database #10476

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

danieljames-dj
Copy link
Member

This PR aims at removing some hard-coding related to dues details. This will help to make the changes quickly in case we need to make some change in dues in future.

@danieljames-dj danieljames-dj force-pushed the country_band_details branch 15 times, most recently from d02c82a to 9c50e1a Compare January 4, 2025 10:05
app/controllers/country_bands_controller.rb Outdated Show resolved Hide resolved
db/migrate/20241225031242_populate_country_band_details.rb Outdated Show resolved Hide resolved
lib/dues_calculator.rb Outdated Show resolved Hide resolved
lib/dues_calculator.rb Outdated Show resolved Hide resolved
@danieljames-dj danieljames-dj force-pushed the country_band_details branch 4 times, most recently from 91c0294 to e28d6cc Compare January 9, 2025 12:23
Comment on lines 22 to 23
country_band = CountryBand.find_by(iso2: country_iso2)&.number
country_band_detail = CountryBandDetail.find_by(number: country_band)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're completely ignoring your new belongs_to association here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, changed it.


[registration_fee_dues_us_dollars, country_band_dues_us_dollars_money].max
# Calculation of 'country band dues'
country_band_dues = Money.new(country_band_detail&.due_amount_per_competitor_in_cents || 0, "USD")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too much happening in one line

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned it a bit

# times 100 because Money require lowest currency subunit, which is cents for USD
country_band_dues_us_dollars_money = Money.new(country_band_dues_us_dollars * 100, "USD")
# Calculation of 'registration fee dues'
registration_fee_dues = Money.new(registration_fees * (country_band_detail&.due_percent_registration_fee.to_f || 0) / 100, "USD")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too much happening in one line

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned it a bit

belongs_to :country, foreign_key: :iso2, primary_key: :iso2
belongs_to :country_band_detail, foreign_key: :number, primary_key: :number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand the idea behind this CountryBandDetails model. It has a start date and an end date, but every CountryBand (which is perpetual/static) contains a column which points it to exactly one CountryBandDetail.

That means, you could point Band 5 to a CountryBandDetail from 3 years ago, which isn't even valid anymore.
I know you will probably tell me that "WFC will prevent that" but I want to challenge you to see whether you can come up with some better database design first to avoid these problems from happening in the first place

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I didn't think of that case. I've now moved country_band_detail into a function, and I think this is solving the issue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or in other words, I'll not have tight coupling between CountryBand and CountryBandDetail, instead CountryBand table will represent the band number of a country (maybe in future we can introduce start_date and end_date as well if we need the history of bands of a country). From the CountryBandDetails table, we can get the active details of a country band.

<%= t("country_bands.band_title", number: number) %>
(<%= t("country_bands.due_value", value: data[:value]) %>)
<%= t("country_bands.band_title", number: country_band_detail.number) %>
(<%= t("country_bands.due_value", value: country_band_detail.due_amount_per_competitor_in_cents.to_f / 100) %>)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm 60% sure you don't need the to_f

@@ -0,0 +1,6 @@
# frozen_string_literal: true

class CountryBandDetail < ApplicationRecord
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can specify a belongs_to :country_band association by providing primary_key: :number and foreign_key: :number I think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't confuse these with SQL PRIMARY KEY and FOREIGN KEY. The options I'm talking about here are (easily misunderstandable) Rails AR vocabulary, which means "the key [i.e. column] where I look up information in the foreign [i.e. pointing to] model, or in myself [i.e. primary]".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay done

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, removed it for now. But looks like the association is not used anywhere, is it still needed to have this association here?

Comment on lines 11 to 13
def active_country_band_detail
CountryBandDetail.active.find_by(number: self.number)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per my other comment in CountryBandDetail, I think you can solve this via a has_many relation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

t.integer "number", null: false
t.date "start_date", null: false
t.date "end_date"
t.integer "due_amount_per_competitor_in_cents", null: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe name this column due_amount_per_competitor_us_cents to make clear that the currency is pre-defined?


# Calculation of 'registration fee dues'
due_percent_registration_fee = country_band_detail&.due_percent_registration_fee.to_f || 0
registration_fee_dues = Money.new(registration_fees * due_percent_registration_fee / 100, "USD")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can do calculations with the money object directly:

my_fee = Money.new(2000, "USD")
some_percentage = my_fee * 0.8

registration_fees = Money.new(base_entry_fee_lowest_denomination, currency_code).exchange_to("USD")

# Calculation of 'registration fee dues'
due_percent_registration_fee = country_band_detail&.due_percent_registration_fee.to_f || 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I think you don't need the to_f. Ruby is very flexible with numbers.

belongs_to :country, foreign_key: :iso2, primary_key: :iso2
validates_inclusion_of :iso2, in: Country::WCA_COUNTRY_ISO_CODES
validates_inclusion_of :number, in: BANDS.keys.freeze
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you hard-code the range from 0 to 5 instead of just completely deleting the line?
I know hard-coding is not very pretty, but effectively it was already hard-coded before (through an intermediate step which was getting the keys from the hard-coded hash)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay done.

@@ -15,6 +15,6 @@

it "invalidates band with invalid band id" do
cb = CountryBand.new(number: 6, iso2: "HELLO")
expect(cb).to be_invalid_with_errors(number: ["is not included in the list"])
expect(cb).to be_invalid_with_errors(country: ["must exist"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding my other comment above, feel free to re-add the previous "number not included in the list" validation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why did the country validation that you're testing now not exist/fail before?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added back that error. I just realized the country validation test was already there in the unit test just before this.

Copy link
Member Author

@danieljames-dj danieljames-dj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gregorbg Thanks for your review, I've partially attended your comments. But since some changes are clashing with changes in #10767, I'll wait for that PR to get merged before attending the remaining.

belongs_to :country, foreign_key: :iso2, primary_key: :iso2
validates_inclusion_of :iso2, in: Country::WCA_COUNTRY_ISO_CODES
validates_inclusion_of :number, in: BANDS.keys.freeze
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay done.

@@ -0,0 +1,6 @@
# frozen_string_literal: true

class CountryBandDetail < ApplicationRecord
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay done

Comment on lines 11 to 13
def active_country_band_detail
CountryBandDetail.active.find_by(number: self.number)
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -0,0 +1,6 @@
# frozen_string_literal: true

class CountryBandDetail < ApplicationRecord
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, removed it for now. But looks like the association is not used anywhere, is it still needed to have this association here?

@@ -15,6 +15,6 @@

it "invalidates band with invalid band id" do
cb = CountryBand.new(number: 6, iso2: "HELLO")
expect(cb).to be_invalid_with_errors(number: ["is not included in the list"])
expect(cb).to be_invalid_with_errors(country: ["must exist"])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added back that error. I just realized the country validation test was already there in the unit test just before this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants