Class: Descriptor::Gene
- Inherits:
-
Descriptor
- Object
- ActiveRecord::Base
- ApplicationRecord
- Descriptor
- Descriptor::Gene
- Defined in:
- app/models/descriptor/gene.rb
Overview
A Descriptor::Gene defines a set of sequences, i.e. column in a “matrix” whose cells contain Sequences with a specific set of attributes (e.g. forward and reverse primers), as defined by GeneAttributes.
A user may define the set of sequences returned by the descriptor via a logical expression. For example show me all sequences with this set of forward primers and that set of reverse primers. The logic can be expanded as extensively as needed, up to a maximum 52 attributes.
@!attribute cached_gene_attribute_sql
@return [String]
An automatically composed SQL fragment that corresponds to #gene_attribute_logic. Used in #sequences.
Constant Summary
Constants inherited from Descriptor
Constants included from SoftValidation
SoftValidation::ANCESTORS_WITH_SOFT_VALIDATIONS
Instance Attribute Summary collapse
-
#base_on_sequence ⇒ Object
A Sequence, if provided clone that sequence description to this Descriptor::Gene.
-
#gene_attribute_logic ⇒ String
A logical expression describing how the gene attributes (e.g. primers) should be intepretted when return sequences.
Class Method Summary collapse
-
.gene_attribute_pairs(target_gene_attributes = GeneAttribute.none) ⇒ Array
Of Arrays, like [[sequence_id, sequence_relationship_type], [sequence_id, sequence_relationship_type]].
-
.sequences_for_gene_attributes(object_sequence_id = nil, target_attributes = [], table_alias = nil) ⇒ Scope
Sequences using AND for the supplied target attributes.
Instance Method Summary collapse
- #add_gene_attributes ⇒ Object protected
- #append_gene_attribute_logic(gene_attribute, logic = :and) ⇒ Object protected
- #attributes_from_or_queries(queries) ⇒ Array
- #build_gene_attribute_logic_sql ⇒ Object protected
- #cache_gene_attribute_logic_sql ⇒ Object protected
-
#compress_logic ⇒ String
Translates each key/value (SequenceRelationshipType.SequenceID) term into a single letter term and translates ‘AND’ to ‘+’ and ‘OR’ to ‘.’.
-
#contains_logic_for?(gene_attribute) ⇒ Boolean
True if the current logic statement contains the gene_attribute.
-
#extend_gene_attribute_logic(gene_attribute, logic = :and) ⇒ Object
See use in GeneAttribute.
- #gene_attribute_logic_compresses ⇒ Object protected
- #gene_attribute_logic_matches_gene_attributes ⇒ Object protected
- #gene_attribute_logic_parses ⇒ Object protected
-
#gene_attribute_pairs ⇒ Array
Of Arrays, like [[sequence_id, sequence_relationship_type], [sequence_id, sequence_relationship_type]].
-
#gene_attribute_term_index ⇒ Hash
A lookup linking key/value terms to their single letter representation note that.
- #sequence_query_set ⇒ Array
-
#sequences ⇒ Scope
Sequences as determined by #gene_attribute_logic, or if that is nil, #sequences_matching_any_gene_attributes.
-
#sequences_matching_any_gene_attributes ⇒ Scope
Sequences matching any #gene_attributes !! This ignores logic in gene_attribute_logic !!.
-
#strict_and_sequences ⇒ Scope
A Sequence scope that matches ALL, and only ALL gene attributes AHA from stackoverflow.com/questions/28568205/rails-4-arel-join-on-subquery.
Methods inherited from Descriptor
#gene?, human_name, #observation_type, #presence_absence?, #qualitative?, #short_name_is_shorter, #sv_short_name_is_short, #target_name, #type_is_subclassed
Methods included from SoftValidation
#clear_soft_validations, #fix_for, #fix_soft_validations, #soft_fixed?, #soft_valid?, #soft_validate, #soft_validated?, #soft_validations, #soft_validators
Methods included from Shared::IsData
#errors_excepting, #full_error_messages_excepting, #identical, #is_community?, #is_destroyable?, #is_editable?, #is_in_use?, #is_in_users_projects?, #metamorphosize, #similar
Methods included from Shared::Documentation
#document_array=, #documented?, #reject_documentation, #reject_documents
Methods included from Shared::Confidences
Methods included from Shared::AlternateValues
#all_values_for, #alternate_valued?
Methods included from Shared::DataAttributes
#import_attributes, #internal_attributes, #keyword_value_hash, #reject_data_attributes
Methods included from Shared::Depictions
#has_depictions?, #image_array=, #reject_depictions, #reject_images
Methods included from Shared::Notes
#concatenated_notes_string, #reject_notes
Methods included from Shared::Tags
#reject_tags, #tag_with, #tagged?, #tagged_with?
Methods included from Shared::Identifiers
#dwc_occurrence_id, #identified?, #next_by_identifier, #previous_by_identifier, #reject_identifiers, #uri, #uuid
Methods included from Shared::Citations
#cited?, #mark_citations_for_destruction, #nomenclature_date, #origin_citation_source_id, #reject_citations, #requires_citation?, #sources_by_topic_id
Methods included from Housekeeping
#has_polymorphic_relationship?
Methods inherited from ApplicationRecord
Instance Attribute Details
#base_on_sequence ⇒ Object
A Sequence, if provided clone that sequence description to this Descriptor::Gene
25 26 27 |
# File 'app/models/descriptor/gene.rb', line 25 def base_on_sequence @base_on_sequence end |
#gene_attribute_logic ⇒ String
A logical expression describing how the gene attributes (e.g. primers) should be intepretted when return sequences. Call @gene_attribute.to_logic_literal for the format of individual gene attribute references. Use parenthesis, ‘ AND ` and ` OR ` to compose the statements. For example:
(SequenceRelationship::ForwardPrimer.2 OR SequenceRelationship::ForwardPrimer.3) AND SequenceRelationship::ReversePrimer.4
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'app/models/descriptor/gene.rb', line 20 class Descriptor::Gene < Descriptor has_many :gene_attributes, inverse_of: :descriptor, foreign_key: :descriptor_id # A Sequence, if provided clone that sequence description to this Descriptor::Gene attr_accessor :base_on_sequence before_validation :add_gene_attributes, if: -> {base_on_sequence.present?} validate :gene_attribute_logic_compresses, if: :gene_attribute_logic_changed? validate :gene_attribute_logic_parses, if: -> { gene_attribute_logic_changed? && !errors.any? } validate :gene_attribute_logic_matches_gene_attributes, if: :gene_attribute_logic_changed? # TODO: use common cache/set_cache pattern after_save :cache_gene_attribute_logic_sql, if: -> { saved_change_to_gene_attribute_logic? && valid? } accepts_nested_attributes_for :gene_attributes, allow_destroy: true # @return [Scope] # Sequences using AND for the supplied target attributes # # @param :target_attributes # [[], [] ...] an array as generated from #sequence_query_set def self.sequences_for_gene_attributes(object_sequence_id = nil, target_attributes = [], table_alias = nil) return Sequence.none if target_attributes.empty? s = Sequence.arel_table sr = SequenceRelationship.arel_table a = s.alias("a_#{table_alias}") b = s.project(a[Arel.star]).from(a) .join(sr) .on(sr['object_sequence_id'].eq(a['id'])) i = 0 target_attributes.each do |sequence_type, id| sr_a = sr.alias("#{table_alias}_#{i}") b = b.join(sr_a).on( sr_a['object_sequence_id'].eq(a['id']), sr_a['type'].eq(sequence_type), sr_a['subject_sequence_id'].eq(id) ) i += 1 end b = b.group(a['id']).having(sr['object_sequence_id'].count.gteq(target_attributes.count)) b = b.as("z_#{table_alias}") Sequence.joins(Arel::Nodes::InnerJoin.new(b, Arel::Nodes::On.new(b['id'].eq(s['id'])))) end # @return [Array] # of Arrays, like [[sequence_id, sequence_relationship_type], [sequence_id, sequence_relationship_type]] def self.gene_attribute_pairs(target_gene_attributes = GeneAttribute.none) target_gene_attributes.pluck(:sequence_id, :sequence_relationship_type) end # @return [Scope] # Sequences as determined by #gene_attribute_logic, or # if that is nil, #sequences_matching_any_gene_attributes def sequences return Sequence.none if !gene_attributes.all.any? return sequences_matching_any_gene_attributes if gene_attribute_logic.blank? Sequence.from("(#{cached_gene_attribute_sql}) as sequences").distinct end # @return [Scope] # a Sequence scope that matches ALL, and only ALL gene attributes # AHA from http://stackoverflow.com/questions/28568205/rails-4-arel-join-on-subquery def strict_and_sequences return Sequence.none if !gene_attributes.all.any? data = gene_attribute_pairs s = Sequence.arel_table sr = SequenceRelationship.arel_table j = s.alias('j') # required for group/having purposes b = s.project(j[Arel.star]).from(j) .join(sr) .on(sr['object_sequence_id'].eq(j['id'])) # Build an aliased join for each set of attributes data.each do |id, type| sr_a = sr.alias("b#{id}") b = b.join(sr_a).on( sr_a['object_sequence_id'].eq(j['id']), sr_a['type'].eq(type), sr_a['subject_sequence_id'].eq(id) ) end # match only those sequences with exactly these attributes, no more, no less b = b.group(j['id']).having(sr['object_sequence_id'].count.eq(data.count)) b = b.as('join_alias') Sequence.joins(Arel::Nodes::InnerJoin.new(b, Arel::Nodes::On.new(b['id'].eq(s['id'])))) end # @return [Scope] # Sequences matching any #gene_attributes # !! This ignores logic in gene_attribute_logic !! def sequences_matching_any_gene_attributes return Sequence.none if !gene_attributes.all.any? sr = SequenceRelationship.arel_table clauses = gene_attribute_pairs.collect {|subject_sequence_id, type| sr[:subject_sequence_id].eq(subject_sequence_id) .and(sr[:type].eq(type)) } q = clauses.shift clauses.each do |c| q = q.or(c) end Sequence.joins(:related_sequence_relationships).where(q.to_sql).references(:sequence_relationships).distinct end # @return [Array] # of Arrays, like [[sequence_id, sequence_relationship_type], [sequence_id, sequence_relationship_type]] def gene_attribute_pairs Descriptor::Gene.gene_attribute_pairs(gene_attributes.all) end # @return [Boolean] # true if the current logic statement contains the gene_attribute def contains_logic_for?(gene_attribute) gene_attribute_logic =~ /#{gene_attribute.to_logic_literal}/ ? true : false end # @return [Array] def sequence_query_set attributes_from_or_queries( Utilities::Logic.or_queries(compress_logic) ) end # @return [Array] def attributes_from_or_queries(queries) translate = gene_attribute_term_index.invert a = [] queries.each do |v| b = [] v.split(//).each do |axiom| b.push translate[axiom].split(/\./) end a.push b end a end # @return [Hash] # a lookup linking key/value terms to their single letter representation # note that def gene_attribute_term_index h = {} return h if gene_attribute_logic.blank? symbols = ('a'..'z').to_a + ('A'..'Z').to_a matches = gene_attribute_logic.scan(/([A-Za-z:]+\.[\d]+)/).flatten matches.each_with_index do |m, i| h[m] = symbols[i] end h end # @return [String] # translates each key/value (SequenceRelationshipType.SequenceID) term into a single letter term # and translates 'AND' to '+' and 'OR' to '.' def compress_logic return '' if gene_attribute_logic.blank? a = gene_attribute_logic.dup b = gene_attribute_term_index b.each do |k, v| # Match whole words, ABC should only match ABC NOT AB. # Uses lookahead to acomplish this by checking if the # next character is a space, closing parentheses, or # is the end of the string a.gsub!(/#{k}(?=[\s)]|$)/, v) end a.gsub!(/\s+OR\s+/, '+') a.gsub!(/\s+AND\s+/, '.') a.gsub!(/\s+/, '') a end # See use in GeneAttribute def extend_gene_attribute_logic(gene_attribute, logic = :and) logic.downcase!.to_sym! unless logic.kind_of?(Symbol) raise if ![:and, :or].include?(logic) append_gene_attribute_logic(gene_attribute, logic) cache_gene_attribute_logic_sql end protected def gene_attribute_logic_compresses if compress_logic.match?(/[^a-zA-Z\(\)\.\+]/) errors.add(:gene_attribute_logic, 'is invalidly formed (likely a bad sequence_relationship_type)') end end def gene_attribute_logic_parses begin Utilities::Logic.parse_logic(compress_logic).to_s.split('+') rescue Parslet::ParseFailed => e errors.add(:gene_attribute_logic, "is invalidly formed: #{e}") end end def gene_attribute_logic_matches_gene_attributes a = gene_attribute_term_index.keys b = gene_attributes.select{|ga| !ga.marked_for_destruction? }.collect{|l| l.to_logic_literal} c = a - b d = b - a errors.add(:gene_attribute_logic, "provided logic without matching gene attribute: #{c.join(';')}") if !c.empty? errors.add(:gene_attribute_logic, "gene attribute (#{d.join(';')}) not referenced in provided logic") if !d.empty? !errors.any? end def add_gene_attributes base_on_sequence..each do |sa| gene_attributes.build(sequence: sa.sequence, type: sa.type) end end def append_gene_attribute_logic(gene_attribute, logic = :and) v = [gene_attribute_logic, gene_attribute.to_logic_literal].compact.join(' AND ') update_column(:gene_attribute_logic, v) end def build_gene_attribute_logic_sql queries = [] sequence_query_set.each_with_index do |target_attributes, i| queries.push Descriptor::Gene.sequences_for_gene_attributes(id, target_attributes, "uq#{i}") end queries.collect{|q| "(#{q.to_sql})"}.join(' UNION ') end def cache_gene_attribute_logic_sql update_column(:cached_gene_attribute_sql, build_gene_attribute_logic_sql) end end |
Class Method Details
.gene_attribute_pairs(target_gene_attributes = GeneAttribute.none) ⇒ Array
Returns of Arrays, like [[sequence_id, sequence_relationship_type], [sequence_id, sequence_relationship_type]].
80 81 82 |
# File 'app/models/descriptor/gene.rb', line 80 def self.gene_attribute_pairs(target_gene_attributes = GeneAttribute.none) target_gene_attributes.pluck(:sequence_id, :sequence_relationship_type) end |
.sequences_for_gene_attributes(object_sequence_id = nil, target_attributes = [], table_alias = nil) ⇒ Scope
Returns Sequences using AND for the supplied target attributes.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'app/models/descriptor/gene.rb', line 49 def self.sequences_for_gene_attributes(object_sequence_id = nil, target_attributes = [], table_alias = nil) return Sequence.none if target_attributes.empty? s = Sequence.arel_table sr = SequenceRelationship.arel_table a = s.alias("a_#{table_alias}") b = s.project(a[Arel.star]).from(a) .join(sr) .on(sr['object_sequence_id'].eq(a['id'])) i = 0 target_attributes.each do |sequence_type, id| sr_a = sr.alias("#{table_alias}_#{i}") b = b.join(sr_a).on( sr_a['object_sequence_id'].eq(a['id']), sr_a['type'].eq(sequence_type), sr_a['subject_sequence_id'].eq(id) ) i += 1 end b = b.group(a['id']).having(sr['object_sequence_id'].count.gteq(target_attributes.count)) b = b.as("z_#{table_alias}") Sequence.joins(Arel::Nodes::InnerJoin.new(b, Arel::Nodes::On.new(b['id'].eq(s['id'])))) end |
Instance Method Details
#add_gene_attributes ⇒ Object (protected)
256 257 258 259 260 |
# File 'app/models/descriptor/gene.rb', line 256 def add_gene_attributes base_on_sequence..each do |sa| gene_attributes.build(sequence: sa.sequence, type: sa.type) end end |
#append_gene_attribute_logic(gene_attribute, logic = :and) ⇒ Object (protected)
262 263 264 265 |
# File 'app/models/descriptor/gene.rb', line 262 def append_gene_attribute_logic(gene_attribute, logic = :and) v = [gene_attribute_logic, gene_attribute.to_logic_literal].compact.join(' AND ') update_column(:gene_attribute_logic, v) end |
#attributes_from_or_queries(queries) ⇒ Array
169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'app/models/descriptor/gene.rb', line 169 def attributes_from_or_queries(queries) translate = gene_attribute_term_index.invert a = [] queries.each do |v| b = [] v.split(//).each do |axiom| b.push translate[axiom].split(/\./) end a.push b end a end |
#build_gene_attribute_logic_sql ⇒ Object (protected)
267 268 269 270 271 272 273 274 |
# File 'app/models/descriptor/gene.rb', line 267 def build_gene_attribute_logic_sql queries = [] sequence_query_set.each_with_index do |target_attributes, i| queries.push Descriptor::Gene.sequences_for_gene_attributes(id, target_attributes, "uq#{i}") end queries.collect{|q| "(#{q.to_sql})"}.join(' UNION ') end |
#cache_gene_attribute_logic_sql ⇒ Object (protected)
276 277 278 |
# File 'app/models/descriptor/gene.rb', line 276 def cache_gene_attribute_logic_sql update_column(:cached_gene_attribute_sql, build_gene_attribute_logic_sql) end |
#compress_logic ⇒ String
Returns translates each key/value (SequenceRelationshipType.SequenceID) term into a single letter term and translates ‘AND’ to ‘+’ and ‘OR’ to ‘.’.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'app/models/descriptor/gene.rb', line 201 def compress_logic return '' if gene_attribute_logic.blank? a = gene_attribute_logic.dup b = gene_attribute_term_index b.each do |k, v| # Match whole words, ABC should only match ABC NOT AB. # Uses lookahead to acomplish this by checking if the # next character is a space, closing parentheses, or # is the end of the string a.gsub!(/#{k}(?=[\s)]|$)/, v) end a.gsub!(/\s+OR\s+/, '+') a.gsub!(/\s+AND\s+/, '.') a.gsub!(/\s+/, '') a end |
#contains_logic_for?(gene_attribute) ⇒ Boolean
Returns true if the current logic statement contains the gene_attribute.
157 158 159 |
# File 'app/models/descriptor/gene.rb', line 157 def contains_logic_for?(gene_attribute) gene_attribute_logic =~ /#{gene_attribute.to_logic_literal}/ ? true : false end |
#extend_gene_attribute_logic(gene_attribute, logic = :and) ⇒ Object
See use in GeneAttribute
221 222 223 224 225 226 227 |
# File 'app/models/descriptor/gene.rb', line 221 def extend_gene_attribute_logic(gene_attribute, logic = :and) logic.downcase!.to_sym! unless logic.kind_of?(Symbol) raise if ![:and, :or].include?(logic) append_gene_attribute_logic(gene_attribute, logic) cache_gene_attribute_logic_sql end |
#gene_attribute_logic_compresses ⇒ Object (protected)
231 232 233 234 235 |
# File 'app/models/descriptor/gene.rb', line 231 def gene_attribute_logic_compresses if compress_logic.match?(/[^a-zA-Z\(\)\.\+]/) errors.add(:gene_attribute_logic, 'is invalidly formed (likely a bad sequence_relationship_type)') end end |
#gene_attribute_logic_matches_gene_attributes ⇒ Object (protected)
245 246 247 248 249 250 251 252 253 254 |
# File 'app/models/descriptor/gene.rb', line 245 def gene_attribute_logic_matches_gene_attributes a = gene_attribute_term_index.keys b = gene_attributes.select{|ga| !ga.marked_for_destruction? }.collect{|l| l.to_logic_literal} c = a - b d = b - a errors.add(:gene_attribute_logic, "provided logic without matching gene attribute: #{c.join(';')}") if !c.empty? errors.add(:gene_attribute_logic, "gene attribute (#{d.join(';')}) not referenced in provided logic") if !d.empty? !errors.any? end |
#gene_attribute_logic_parses ⇒ Object (protected)
237 238 239 240 241 242 243 |
# File 'app/models/descriptor/gene.rb', line 237 def gene_attribute_logic_parses begin Utilities::Logic.parse_logic(compress_logic).to_s.split('+') rescue Parslet::ParseFailed => e errors.add(:gene_attribute_logic, "is invalidly formed: #{e}") end end |
#gene_attribute_pairs ⇒ Array
Returns of Arrays, like [[sequence_id, sequence_relationship_type], [sequence_id, sequence_relationship_type]].
151 152 153 |
# File 'app/models/descriptor/gene.rb', line 151 def gene_attribute_pairs Descriptor::Gene.gene_attribute_pairs(gene_attributes.all) end |
#gene_attribute_term_index ⇒ Hash
Returns a lookup linking key/value terms to their single letter representation note that.
185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'app/models/descriptor/gene.rb', line 185 def gene_attribute_term_index h = {} return h if gene_attribute_logic.blank? symbols = ('a'..'z').to_a + ('A'..'Z').to_a matches = gene_attribute_logic.scan(/([A-Za-z:]+\.[\d]+)/).flatten matches.each_with_index do |m, i| h[m] = symbols[i] end h end |
#sequence_query_set ⇒ Array
162 163 164 165 166 |
# File 'app/models/descriptor/gene.rb', line 162 def sequence_query_set attributes_from_or_queries( Utilities::Logic.or_queries(compress_logic) ) end |
#sequences ⇒ Scope
Returns Sequences as determined by #gene_attribute_logic, or if that is nil, #sequences_matching_any_gene_attributes.
87 88 89 90 91 |
# File 'app/models/descriptor/gene.rb', line 87 def sequences return Sequence.none if !gene_attributes.all.any? return sequences_matching_any_gene_attributes if gene_attribute_logic.blank? Sequence.from("(#{cached_gene_attribute_sql}) as sequences").distinct end |
#sequences_matching_any_gene_attributes ⇒ Scope
Returns Sequences matching any #gene_attributes !! This ignores logic in gene_attribute_logic !!.
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'app/models/descriptor/gene.rb', line 131 def sequences_matching_any_gene_attributes return Sequence.none if !gene_attributes.all.any? sr = SequenceRelationship.arel_table clauses = gene_attribute_pairs.collect {|subject_sequence_id, type| sr[:subject_sequence_id].eq(subject_sequence_id) .and(sr[:type].eq(type)) } q = clauses.shift clauses.each do |c| q = q.or(c) end Sequence.joins(:related_sequence_relationships).where(q.to_sql).references(:sequence_relationships).distinct end |
#strict_and_sequences ⇒ Scope
Returns a Sequence scope that matches ALL, and only ALL gene attributes AHA from stackoverflow.com/questions/28568205/rails-4-arel-join-on-subquery.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'app/models/descriptor/gene.rb', line 96 def strict_and_sequences return Sequence.none if !gene_attributes.all.any? data = gene_attribute_pairs s = Sequence.arel_table sr = SequenceRelationship.arel_table j = s.alias('j') # required for group/having purposes b = s.project(j[Arel.star]).from(j) .join(sr) .on(sr['object_sequence_id'].eq(j['id'])) # Build an aliased join for each set of attributes data.each do |id, type| sr_a = sr.alias("b#{id}") b = b.join(sr_a).on( sr_a['object_sequence_id'].eq(j['id']), sr_a['type'].eq(type), sr_a['subject_sequence_id'].eq(id) ) end # match only those sequences with exactly these attributes, no more, no less b = b.group(j['id']).having(sr['object_sequence_id'].count.eq(data.count)) b = b.as('join_alias') Sequence.joins(Arel::Nodes::InnerJoin.new(b, Arel::Nodes::On.new(b['id'].eq(s['id'])))) end |