Skip to content

Instantly share code, notes, and snippets.

@kaspth
Last active April 6, 2023 20:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kaspth/54c2541309704e054bca2be5ace3ebee to your computer and use it in GitHub Desktop.
Save kaspth/54c2541309704e054bca2be5ace3ebee to your computer and use it in GitHub Desktop.
class Developer
has_object :search_scorer, after_save_commit: :rebuild_later # Forward callback onto context object.
end
# From my https://github.com/kaspth/active_record-associated_object gem.
# app/models/developer/search_scorer.rb
class Developer::SearchScorer < ActiveRecord::AssociatedObject
performs :rebuild # Adds `rebuild_later` to automatically generate a job to run the scoring calculation in.
def rebuild
record.update! search_score: new_score
end
private
def new_score
small, medium, large, x_large = 5, 10, 20, 30
score = []
score << medium if record.scheduling_link?
score << large if record.source_contributor?
score << x_large if record.recently_added?
score << medium if record.bio_length > 500
score << -large if record.bio_length < 50
score << large if record.profile_updated_at&.before? 1.months.ago
score << medium if record.profile_updated_at&.before? 3.months.ago
score << -medium if record.profile_updated_at&.after? 6.months.ago
score << large if record.conversations_count.positive? && record.response_rate >= Developer::HasBadges::HIGH_RESPONSE_RATE_CUTOFF
score << -large if record.conversations_count.positive? && record.response_rate <= Developer::HasBadges::LOW_RESPONSE_RATE_CUTOFF
score.sum
end
end
# app/models/developers/search_score.rb
module Developers::SearchScore
extend ActiveSupport::Concern
included do
small, medium, large, x_large = 5, 10, 20, 30
scores :scheduling_link?, by: medium, if: :present?
scores :source_contributor?, by: large, if: :present?
scores :recently_added?, by: x_large, if: :present?
scores :bio_length, by: medium, if: -> { _1 > 500 }
scores :bio_length, by: -large, if: -> { _1 < 50 }
scores :profile_updated_at, by: large, if: -> { _1&.before? 1.months.ago }
scores :profile_updated_at, by: medium, if: -> { _1&.before? 3.months.ago }
scores :profile_updated_at, by: -medium, if: -> { _1&.after? 6.months.ago }
scores :response_rate, by: large, if: -> { conversations_count.positive? && _1 >= HasBadges::HIGH_RESPONSE_RATE_CUTOFF }
scores :response_rate, by: -large, if: -> { conversations_count.positive? && _1 <= HasBadges::LOW_RESPONSE_RATE_CUTOFF }
before_save :recalculate_search_score
end
class_methods do
attr_reader :scorings
def scores(attribute, by:, **options)
(@scorings ||= []) << -> { by if instance_exec(public_send(attribute), &options.fetch(:if)) } }
end
end
private
def recalculate_search_score
self.search_score = self.class.scorings.filter_map(&:call).sum
end
end
@swanson
Copy link

swanson commented Apr 6, 2023

Careful, if I change my bio it's going to keep adding 10 to my score every time I save the changes :)

@kaspth
Copy link
Author

kaspth commented Apr 6, 2023

@swanson ah yep, that's true

@kaspth
Copy link
Author

kaspth commented Apr 6, 2023

Deleted the linking tweet, since this approach ended up being flawed 👍

@joemasilotti
Copy link

Ah, man! I really like this approach though.

I wonder if the same scores could be a declarative approach to how each score contributes, then it is calculated fresh every time.

@kaspth
Copy link
Author

kaspth commented Apr 6, 2023

I wonder if the same scores could be a declarative approach to how each score contributes, then it is calculated fresh every time.

yeah, good point. I updated it to do that. Also added an alternate just because I was curious to see what it would look like modeled as a domain context object using a gem I've got for that.

@joemasilotti
Copy link

joemasilotti commented Apr 6, 2023

Oh, nice! I might be missing something, but how does this line work?

self.search_score = self.class.scorings.filter_map(&:call).sum

It keeps trying to call, for example, scheduling_link on the class, not the instance.

If I can get this to work with the existing tests then I'd love to merge it in!

@kaspth
Copy link
Author

kaspth commented Apr 6, 2023

Yeah, it's a little finicky unfortunately. I fixed it up here joemasilotti/railsdevs.com#858

@joemasilotti
Copy link

Amazing, thanks @kaspth!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment