1

I am developing a system where the user inputs several symptoms and receives a list of all illnesses that match those symptoms. I haven't found a way to do so, however.

For example, if the user has "Fever" and "Sneezing" as symptoms, I want the system to find all illnesses that have both fever and sneezing as symptoms simultaneously and list them in no particular order.

I have three tables: illnesses, symptoms and illness_symptoms. Illnesses and Symptoms both have a has_many relationship through illness_symptoms.

Both Illnesses and Symptoms are similarly mapped: Text, description and id fields. illness_symptoms only has id_symptom and id_illness. Symptoms are "saved" to an illness through a form where one illness and one symptom are specified, though this is rudimentary and will be changed later.

Using @illness = symptom.illnesses I can find and list all illnesses that have one certain symptom but I'm not sure how to narrow it down for a second (and any subsequent) symptoms.

4
  • Can you show your Illness and Symptom models? and the schema (to see what attributes are there), specially to see how you save multiple symptoms for a particular illness. Commented Nov 12, 2015 at 5:16
  • Not sure how useful a direct copy-and-paste would be considering some of it is not in English but edited the original post to explain more of the system. Commented Nov 12, 2015 at 5:20
  • "Fever" and "Sneezing" are different types of symptoms, right? How is that saved in the database table? as name or description column in the symptoms table? Just show couple of database records from the illness and symptoms tables Commented Nov 12, 2015 at 5:24
  • Yes, they are two different entries in the Symptoms table. Let's say: id = 1; name = "Fever"; description = "..." and id = 2; name = "Sneezing"; description = "..." Commented Nov 12, 2015 at 5:28

3 Answers 3

2

You can use an aggregate function

symptoms = Symptom.find(1,2)

illnesses = Illness.joins(:illness_symptoms)
                    .where("illness_symptoms.symptom_id in (?)", symptoms)
                    .group("illnesses.id")
                    .having("count(illnesses.id) >= ?", symptoms.length)

To answer your comment - querying the database is best done in the model. I would make a scope in the Illness model:

  scope :with_symptoms, -> (symptoms) { joins(:illness_symptoms)
                                                .where("illness_symptoms.symptom_id in (?)", symptoms)
                                                .group("illnesses.id")
                                                .having("count(illnesses.id) >= ?", symptoms.length) }

And use it wherever needed. E.g. Illness.with_symptoms(Symptom.find(1,2))

Sign up to request clarification or add additional context in comments.

4 Comments

Probably a very simple question (I'm somewhat new to Rails): does this section of code go in the def that I want to use to execute the search?
Thank you for your help. Adding the scope to the Illness model gives a few "unexpected PAREN_ARG" errors in the IDE
This was fixed by removing a space between the arrow and the parenthesis
Nice solution! One thing - you don't need to join through to symptom. Just change the where clause - where("illness_symptoms.symptom_id in (?)", symptoms)
0

You can do an intersect - get the ids of one and limit the results of the other to get the final intersection. Be sure to index illness_symptoms on symptom_id and illness_id.

You can write something to extend this to take an array and hide the details, but here is an example.

# for example
fever = Symptom.find(1)
sneezing = Symptom.find(2)
@illnesses = Illness.with_symptom(fever).where(:id => Illness.with_symptom(sneezing).pluck(:id))

Add a scope to the Illness class

class Illness < ActiveRecord::Base

  has_many :illness_symptoms
  has_many :symptoms, :through => :illness_symptoms

  scope :with_symptom, -> (symptom) { joins(:illness_symptoms).where(:symptom_id => symptom) }

end

5 Comments

This seems to give an "Unexpected tlPAREN_ARG" error that I assume is related to the spacing from what I've looked up but I can't seem to fix it.
Your using Ruby 1.9? If so, try to change the spacing here: scope :with_symptom, ->(symptom) (remove space between arrow and paren)
ruby -vsays version 2.2.1p85 but that did solve it, thank you
The main takeaway here is to get the illness ids from one symptom and use that to limit the next one. There are a few ways to do that, you don't necessarily need a scope.
This seems to have worked properly and looks like it can be expanded to fit more symptoms as needed. Now I must learn how to pass the results of a user form to the method (though this is probably for another, unrelated question)
0

To find illnesses by symptoms, you can use IN in sql by array of text that user inputs several symptoms. You can do this:

Illness.joins(:symptoms).where("symptoms.name IN (?)", ["Fever", "Sneezing"])

I hope this help you.

1 Comment

I'm sorry for the possibly simple question but I'm not sure where I'd add that code. I assume it's inside whatever method I'll use for searching, correct?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.