ruby-on-railsnamed-scope

Rails 4 scope joins to find either/or relationships


I have a rails 4.2.8 application with a number of relationships. My Interaction model has and belongs to many genes (specifically an interaction can have 2 genes while a gene can belong to any number of interactions) and the Gene model in turn has many drugs and diseases.

I am trying to allow users to filter Interactions based on whether one or the other of its genes has a drug and / or disease associated with it. The code below works as required if either the drug or disease 'filter' is selected in that all interactions with at least one gene with at least one of those associations are displayed.

However when both filters are checked I am only displaying interactions where one or both of the genes have at least one drug and at least one disease associated. What I would like is to also show interactions where one gene has drug/s but no disease and the other gene has disease/s but no drugs associated.

Models

class Interaction < ActiveRecord::Base
    has_and_belongs_to_many :genes
    ...

    scope :disease_associated, -> { joins(genes: :diseases) }
    scope :drug_target, -> { joins(genes: :drugs) }
    ...
end

class Gene < ActiveRecord::Base
    has_and_belongs_to_many :interactions
    has_and_belongs_to_many :drugs
    ...
end

class Drug < ActiveRecord::Base
    has_and_belongs_to_many :genes
end

class Disease < ActiveRecord::Base
    has_and_belongs_to_many :genes
end

Interaction Controller

class InteractionsController < ApplicationController
    ...
    @interactions = @interactions.disease_associated() if params[:filter_disease].present?
    @interactions = @interactions.drug_target() if params[:filter_druggable].present?
    ...

I haven't found any approaches / questions that obviously address this though that may be a result of me not being able to find the words to address the problem concisely enough to search effectively.

Thanks in advance!


Solution

  • In Rails 4 I can propose two options:

    1. ID list

    ids = []
    ids |= Interaction.disease_associated.pluck(:id) if params[:filter_disease].present?
    ids |= Interaction.drug_target.pluck(:id) if params[:filter_druggable].present?
    
    interactions = Interaction.where(id: ids)
    

    Clear, but not good if there are many Interactions in database.

    2. UNION

    Gem active_record_union gem provides SQL UNION support for Rails.

    interactions = Interaction.none
    interactions = interactions.union(Interaction.disease_associated) if params[:filter_disease].present?
    interactions = interactions.union(Interaction.drug_target) if params[:filter_druggable].present?
    

    P.S. Common Advise: Design your DB by common queries not by Domain.