0

In my Rails project I need to match Profiles to a certain set of criteria. My current setup is that each profile has a set of Evaluations, where the latest evaluation is the active one. Each Evaluation consists of a set of Properties which may have a value from 0 to 4. I would like to create a custom "filter-evaluation" (namespace is ProfessionProfile in my code) and find all Profiles who's latest evaluation is either better or equal (higher or equal values for each Property) than the filter.

My current solution works but makes a huge amount of requests to the server, thus taking a lot of time, I would like to instead do this in a single request. FYI PostgreSQL is used.

Current solution:

filters_controller.rb:

def filter_by_profession(profiles)
    hits = []
    profession = ProfessionProfile.find(params[:profession])
    Profile.find_each do |p|
        if p.has_better_values(profession)
            hits << p
        end
     end
    profiles = profiles.where(id: hits.map(&:id))
end

profile.rb:

def has_better_values(profession_profile)
    latest_evaluation = property_evaluations.order(:created_at).first
    if latest_evaluation.present?
        latest_evaluation.property_values.each do |value|
            profession_value = profession_profile.property_values.where(property_id: value.property_id)
            return false if profession_value.present? && value.value < profession_value.take!.value
        end
        return true
    else
        return false
    end
end

Instead of doing this I have tried the following solution but it always returns an empty set, even when I should have a match.

def filter_by_profession(profiles)
    profession_profile = ProfessionProfile.find(params[:profession])
    profiles = profiles.joins(:property_values)
    profession_profile.property_values.find_each do |property_value|
      profiles = profiles
                      .where("property_values.property_id = ?", property_value.property_id)
                      .where("property_values.value >= ? ", property_value.value)
    end
    profiles
end

Any tips or help would be appreciated!

I have seen a similar question here but I failed to adapt it to my situation.

Community
  • 1
  • 1
Ozgar
  • 305
  • 2
  • 14
  • In your second solution, you join `profiles` directly with `property_values`. Is this correct? I am asking because in the previous solution you accessed `property_values` via `property_evaluations` of a `profile` and not directly via a `profile`. – larsbe Jan 13 '17 at 13:57
  • I should have specified that profiles `has_many :property_values, through: :property_evaluations` , but you are on to something else, by doing it this way I'm not sure if I'll be able to actually only get the latest evaluation – Ozgar Jan 13 '17 at 14:18

1 Answers1

0

I managed to get it working finally. The solution is not very pretty but it's fast and works well creating only two sever queries. I ended up using the & operator for arrays which returns the intersection of two arrays. I also added a boolean attribute to PropertyEvaluations in order to avoid the need of finding the latest PropertyEvaluation for each Profile (Profile.each.property_evaluations.order(:created_at)) which would require a lot of server communication.

In profile.rb:

def self.matches_profession(profession_profile)
    property_values = profession_profile.property_values.pluck(:property_id, :value) 
    ids = Profile.ids
    property_values.each do |p|
       ids = ids & PropertyEvaluation.where(current: true)
          .joins(:property_values)
          .where("property_values.property_id = ?", p[0])
          .where("property_values.value >= ?", p[1]).pluck(:profile_id)
    end
    Profile.where(id: ids)
end

The reason I first converted to arrays of ids is that the merge() function in Rails returns an array, thus it's only usable for getting the intersection of two ActiveRecords, whereas in my situation I need to intersect over at least 11.

If anyone should come up with a "railsier" solution please share!

Ozgar
  • 305
  • 2
  • 14