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

Join affiliates with owners #118

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open

Conversation

huyserp
Copy link
Collaborator

@huyserp huyserp commented May 17, 2021

  • Seeded my local db with 7 affiliates and 20 owners

    • 9 owners get an affiliate code string in owners.affiliate column.
    • 3 owners get an affiliate code that doesn’t correspond to an affiliate instance
      • To test the hypothetical that some owners may have content in the affiliate string but that content doesn't match an instance of an affiliate.
  • Ran migration - change column name in owners table from affiliate to affiliate_code

    • After next migration to add a foreign key, owner.affiliate would refer to both the old string in the owners table AND the newly established association to an Affiliate object, which is problematic...
  • Ran migration - add foreign key affiliates to owners table.

    • Also update associations in owner.rb and affiliate.rb
      • Owner belongs to affiliate (optional: true)
      • Affiliate has_many owners
  • Ran script to join data in tables. Used rails runner (this script is in /db/scripts/join_affiliate_to_owner.rb)

    • Filter Owner.where(affiliate_code == true)
    • Iterate over filtered owners
      • If the affiliate_code value corresponds to an affiliate instance (by affiliate.code) set the Owner.affiliate to this instance of affiliate.
  • Add class method to owner.rb to quickly identify owners that have a string in affiliate_code which doesn’t correspond to an affiliate instances.

    • Test: Owner.non_associated_affiliates.each { |owner| puts owner.affiliate_code } (there should be 3, each string begins with 'a' based on my seed.rb)
  • Review models to update any code that gets an affiliate with find_by

    • Changes:
      • Owner - use new association to get owner.affiliate
        • #affiliate_logo
        • #stripe_price_id

Testing was done in rails console.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Member

@bonflintstone bonflintstone left a comment

Choose a reason for hiding this comment

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

We need to make sure that for new owners we set the affiliate on creation. This probably needs to happen in the owners_controller, so that based on the affiliate code we associate the right model directly.
We should also consider how to handle this in the frontend. I think it might actually be nice if the backend returns an error if an affiliate code is sent be the frontend that does not exist in the database. But in that case we should make sure that the frontend handles that error gracefully and displays a helpful error message to the user, so that they can double check the affiliate link or simply create an account without affiliate link.

@@ -50,8 +51,7 @@ def blocked?

def affiliate_logo
return unless affiliate

Affiliate.find_by(code: affiliate)&.logo_url
affiliate.logo_url
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
affiliate.logo_url
affiliate&.logo_url

In case you have not seen before, it's called the safe navigation operator and makes sure we do not get an exception when affiliate is nil here :)

@@ -74,8 +74,7 @@ def stripe_price_id
main_price_id = ENV['STRIPE_SUBSCRIPTION_PRICE_ID']

raise "ENV['STRIPE_SUBSCRIPTION_PRICE_ID'] is empty" unless main_price_id.present?

Affiliate.find_by(code: affiliate)&.stripe_price_id_monthly || main_price_id
affiliate.stripe_price_id_monthly || main_price_id
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
affiliate.stripe_price_id_monthly || main_price_id
affiliate&.stripe_price_id_monthly || main_price_id

Elsewise this will also crash for empty affiliates

@@ -106,4 +105,9 @@ def trial_end
# There is not trial if the trial is blank or has already been passed, else it has to be at least two days in the future
trial_ends_at? && trial_ends_at.future? ? [trial_ends_at, 50.hours.from_now].max.to_i : nil
end

def self.non_associated_affiliates
Copy link
Member

Choose a reason for hiding this comment

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

This should be a scope instead of a class method, then one can also chain this, e.g.:

Owner.where('created_at > ?', 1.week.ago).non_associated_affiliates

Perhaps a better name for this scope would then also be "with_missing_affiliate" or similar


def self.non_associated_affiliates
# To identify owners that have used an affiliate thats not properly registered in the affiliates table.
Owner.where(affiliate: nil).where.not(affiliate_code: nil)
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 actually leave the Owner., since where is also defined as an instance method. This would also enable chaining.

@@ -0,0 +1,9 @@
owners_with_affiliate = Owner.all.select { |owner| owner.affiliate_code }
Copy link
Member

Choose a reason for hiding this comment

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

This should be a rake task :)

@@ -0,0 +1,9 @@
owners_with_affiliate = Owner.all.select { |owner| owner.affiliate_code }
Copy link
Member

Choose a reason for hiding this comment

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

This can be written as a db query. Right now you first load all owners from the database to an array, and then filter it. This operation can be done way more efficiently by the database using the active record query interface. In this case one might use:
Owner.where.not(affiliate_code: nil)

Comment on lines +1 to +27
Affiliate.destroy_all
Owner.destroy_all

7.times do
FactoryBot.create(:affiliate)
end
affiliates = Affiliate.all
affiliates.each { |affiliate| puts "#{affiliate.name} #{affiliate.code}" }

20.times do
FactoryBot.create(:owner)
end
owners = Owner.all
owners.each { |owner| puts owner.name }


# give 9 owners an affilate
owners_with_affiliates = owners[0..8]
owners_with_affiliates.each do |owner|
owner.update!(affiliate_code: affiliates.sample.code)
end

# give 3 owners affiliates which are not yet entities in the db
owners_with_other_affiliates = owners[9..11]
owners_with_other_affiliates.each do |owner|
owner.update!(affiliate_code: affiliates.sample.code.prepend('a').chop)
end
Copy link
Member

Choose a reason for hiding this comment

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

This should go into a spec (probably spec/modles/owner_spec.rb)

validates :password,
allow_nil: true,
length: { in: Devise.password_length },
confirmation: true,
on: :update

Copy link
Member

Choose a reason for hiding this comment

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

Right now, when migrating, we are going through three stages:

  1. The app as is right now, owner.affiliate will return a string
  2. The apps db is migrated, but the data migration script has not been run yet. Since the code is updated, the app should expect .affiliate to return a model, but it's always empty. If payments are executed in this period, owners with affiliates might pay the full price, or similar weird behaviour might occur
  3. The data migration has run, now .affilaite will return the affilaite model

So I think for the transition it might make sense to overwrite the owner.affiliate method so it always returns the affiliate model. Something like

Suggested change
def affiliate
return super if super.present? # make sure the association is used after the data migration
Affiliate.find_by(code: affiliate_code) # before the data migration is run, we try to find the affiliate "the old fashioned way"
end

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.

None yet

3 participants