Module: Refine::TracksPendingRelationshipSubqueries
- Included in:
- Filter
- Defined in:
- app/models/refine/tracks_pending_relationship_subqueries.rb
Instance Method Summary collapse
- #add_pending_joins_relationship_subquery(subquery:) ⇒ Object
- #add_pending_relationship_subquery(subquery:, primary_key:, secondary_key: nil, inverse_clause: false) ⇒ Object
-
#allow_pending_relationship_to_collapse ⇒ Object
Allow filter refinements to be collapsable in order to slot them in a the appropriate depth.
- #commit_pending_relationship_subqueries ⇒ Object
- #commit_subset(subset:, query: nil) ⇒ Object
- #get_current_relationship ⇒ Object
- #get_pending_relationship_instance ⇒ Object
- #get_pending_relationship_item(key) ⇒ Object
- #get_pending_relationship_subquery ⇒ Object
- #pending_relationship_subqueries ⇒ Object
- #pending_relationship_subquery_depth ⇒ Object
- #relationship_supports_collapsing(instance) ⇒ Object
-
#release_pending_relationship ⇒ Object
This method can be challenging to understand.
- #set_pending_relationship(relation, instance) ⇒ Object
- #set_pending_relationship_subquery_wrapper(callback) ⇒ Object
- #use_multiple_databases?(current_model, parent_model) ⇒ Boolean
Instance Method Details
#add_pending_joins_relationship_subquery(subquery:) ⇒ Object
28 29 30 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 28 def add_pending_joins_relationship_subquery(subquery:) add_pending_relationship_subquery(subquery: subquery, primary_key: JOINS) end |
#add_pending_relationship_subquery(subquery:, primary_key:, secondary_key: nil, inverse_clause: false) ⇒ Object
32 33 34 35 36 37 38 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 32 def add_pending_relationship_subquery(subquery:, primary_key:, secondary_key: nil, inverse_clause: false) # Add key, query, and secondary keys at the correct depth pending_relationship_subqueries.dig(*get_current_relationship)[:key] = primary_key pending_relationship_subqueries.dig(*get_current_relationship)[:query] = subquery pending_relationship_subqueries.dig(*get_current_relationship)[:secondary] = secondary_key pending_relationship_subqueries.dig(*get_current_relationship)[:inverse_clause] = inverse_clause end |
#allow_pending_relationship_to_collapse ⇒ Object
Allow filter refinements to be collapsable in order to slot them in a the appropriate depth
14 15 16 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 14 def allow_pending_relationship_to_collapse pending_relationship_subqueries.dig(*get_current_relationship)[:collapsible] = true end |
#commit_pending_relationship_subqueries ⇒ Object
107 108 109 110 111 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 107 def commit_pending_relationship_subqueries applied_query = commit_subset(subset: pending_relationship_subqueries) @pending_relationship_subqueries = Hash.new { |h, k| h[k] = h.dup.clear } applied_query end |
#commit_subset(subset:, query: nil) ⇒ Object
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 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 113 def commit_subset(subset:, query: nil) # Turn pending relationship subqueries into nodes to apply # Subquery below is the hash value which has a query key. subset.each do |relation, subquery| # relation = table # subquery.keys = [:instance, :query, :key, :secondary] possibly (:children) if subquery.dig(:children).present? # Send in the values at children as the subset hash and remove from existing subquery child_nodes = subquery.delete(:children) # If there are children, we recursively call this method again # to build up the inner (child) queries first. This allows us # to intelligently nest multiple levels of relationships. commit_subset(query: subquery[:query], subset: child_nodes) end # If the subquery has a wrapper proc we are dealing with a compare to 0 situation if subquery.dig(:wrapper).respond_to? :call subquery[:query] = subquery[:wrapper].call(subquery[:query], subquery[:key], subquery[:secondary]) end parent_table = subquery[:instance].active_record.arel_table linking_key = subquery[:key] inner_query = subquery[:query] # Compare database connections for inner and outer query. Refer to ActiveRecord::Reflection # Example: # class Company < ActiveRecord::Base # has_many :clients # end # Company.reflect_on_association(:clients).klass # # => Client # If the query needs to be flipped because it's a negative do that here connecting_method = subquery[:inverse_clause] ? :not_in : :in current_model = subquery[:instance]&.klass parent_model = subquery[:instance]&.active_record use_multiple_databases = (inner_query.is_a? Arel::SelectManager) && use_multiple_databases?(current_model, parent_model) if query.present? # If query exists and is a Select Manager we are deeply nested and need to build the query # with a WHERE statement if query.is_a? Arel::SelectManager if use_multiple_databases array_of_ids = current_model.connection.exec_query(inner_query.to_sql).rows.flatten query.where(parent_table[linking_key.to_s].in(array_of_ids)) else # Same DB, don’t decompose. Note: Where’s called on AREL select managers modify the object in place query.where(parent_table[linking_key.to_s].in(inner_query)) end else # Otherwise we are joining nodes, which requires an AND statement (ORs are immediately commited) # query can be a `Arel::Nodes::In` class (See HMMT test for example) # The group() in front of query is required for nested relationship attributes. if use_multiple_databases array_of_ids = current_model.connection.exec_query(inner_query.to_sql).rows.flatten query = group(query).and(group(parent_table[linking_key.to_s].in(array_of_ids))) else query = group(query).and(group(parent_table[linking_key.to_s].in(inner_query))) end # query = group(query).and(group(parent_table[linking_key.to_s].in(inner_query))) end else # No existing query, top level of stack if use_multiple_databases array_of_ids = current_model.connection.exec_query(inner_query.to_sql).rows.flatten query = parent_table[linking_key.to_s].send(connecting_method, array_of_ids.uniq) else query = parent_table[linking_key.to_s].send(connecting_method, inner_query) end end end query end |
#get_current_relationship ⇒ Object
24 25 26 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 24 def get_current_relationship pending_relationship_subquery_depth.join(".children.").split(".").map(&:to_sym) end |
#get_pending_relationship_instance ⇒ Object
40 41 42 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 40 def get_pending_relationship_instance get_pending_relationship_item("instance") end |
#get_pending_relationship_item(key) ⇒ Object
44 45 46 47 48 49 50 51 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 44 def get_pending_relationship_item(key) # Digging for a particular key will create a {} value, must check presence. if pending_relationship_subqueries.dig(*get_current_relationship, key.to_sym).present? pending_relationship_subqueries.dig(*get_current_relationship, key.to_sym) else nil end end |
#get_pending_relationship_subquery ⇒ Object
193 194 195 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 193 def get_pending_relationship_subquery get_pending_relationship_item("query") end |
#pending_relationship_subqueries ⇒ Object
7 8 9 10 11 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 7 def pending_relationship_subqueries # The Hash.new block will dynamically created multi-level nested keys # Note: Will create a value of {} when accessed with `dig` @pending_relationship_subqueries ||= Hash.new { |h, k| h[k] = h.dup.clear } end |
#pending_relationship_subquery_depth ⇒ Object
3 4 5 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 3 def pending_relationship_subquery_depth @pending_relationship_subquery_depth ||= [] end |
#relationship_supports_collapsing(instance) ⇒ Object
57 58 59 60 61 62 63 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 57 def relationship_supports_collapsing(instance) if get_current_relationship.present? return true if pending_relationship_subqueries.dig(*get_current_relationship)[:collapsible] == true end (instance.is_a? ActiveRecord::Reflection::BelongsToReflection) || (instance.is_a? ActiveRecord::Reflection::HasOneReflection) end |
#release_pending_relationship ⇒ Object
This method can be challenging to understand. Walk through the values with the can handle nested relationships test.
The query will eventually return the following SQL:
SELECT "btt_phones".* FROM "btt_phones"
WHERE ("btt_phones"."btt_user_id" IN
(SELECT "btt_users"."id" FROM "btt_users" WHERE "btt_users"."id" IN
(SELECT "btt_notes"."btt_user_id" FROM "btt_notes" WHERE ("btt_notes"."body" LIKE '%foo%'))))
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 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 72 def release_pending_relationship # instance is the ActiveRecord::Reflection::HasManyReflection between btt_notes and BttUser instance = get_pending_relationship_instance # Pop off the last key (last relationship) # popped = :btt_notes popped = pending_relationship_subquery_depth.pop.to_sym return if relationship_supports_collapsing(instance) # current = [:btt_user] current = get_current_relationship if current.blank? @immediately_commit_pending_relationship_subqueries = true return end # Grab the query one level higher than the current stack (removed during pop) # query is the pending_relationship_subqueries[:btt_user][:query] = "SELECT `btt_users`.`id` FROM `btt_users`" query = pending_relationship_subqueries.dig(*current)[:query] # Build hash to send to commit_subset with popped -> value at popped subset = {} # subset is a hash with key [:btt_notes] (the popped relationship) = value at popped. # subset[:btt_notes].keys = [:instance, :query, :key, :secondary, :inverse_clause] # subset[:btt_notes][:query].to_sql # "SELECT `btt_notes`.`btt_user_id` FROM `btt_notes` WHERE (`btt_notes`.`body` LIKE '%foo%')" subset[popped] = pending_relationship_subqueries.dig(*current)[:children][popped] # Remove popped from pending relationships subqueries -> handled in commit_subset # We are removing the "child" relationship we just set to subset (in this case :btt_notes) # pending_relationship_subqueries[:btt_user][:children] = {} # And assigning the value at [:btt_user][:query] to what is returned in commit_subset pending_relationship_subqueries.dig(*current)[:children].except!(popped) # query is the high level linking query = "SELECT `btt_users`.`id` FROM `btt_users`" # and subset is the hash of the child relationship we just removed from pending_relationship_subqueries # subset[:btt_notes].keys = [:instance, :query, :key, :secondary, :inverse_clause] pending_relationship_subqueries.dig(*current)[:query] = commit_subset(subset: subset, query: query) end |
#set_pending_relationship(relation, instance) ⇒ Object
18 19 20 21 22 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 18 def set_pending_relationship(relation, instance) pending_relationship_subquery_depth << relation.to_sym # this populates modelA[:children][:modelB] pending_relationship_subqueries.dig(*get_current_relationship)[:instance] = instance end |
#set_pending_relationship_subquery_wrapper(callback) ⇒ Object
53 54 55 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 53 def set_pending_relationship_subquery_wrapper(callback) pending_relationship_subqueries.dig(*get_current_relationship)[:wrapper] = callback end |
#use_multiple_databases?(current_model, parent_model) ⇒ Boolean
188 189 190 191 |
# File 'app/models/refine/tracks_pending_relationship_subqueries.rb', line 188 def use_multiple_databases?(current_model, parent_model) # Are the queries on different databases? parent_model.connection_db_config.configuration_hash != current_model.connection_db_config.configuration_hash end |