Class: Brick::JoinArray
Overview
JoinArray and JoinHash
These JOIN-related collection classes – JoinArray and its related “partner in crime” JoinHash – both interact to more easily build out nested sets of hashes and arrays to be used with ActiveRecord’s .joins() method. For example, if there is an Order, Customer, and Employee model, and Order belongs_to :customer and :employee, then from the perspective of Order all these three could be JOINed together by referencing the two belongs_to association names:
Order.joins([:customer, :employee])
and from the perspective of Employee it would instead use a hash like this, using the has_many :orders association and the :customer belongs_to:
Employee.joins({ orders: :customer })
(in both cases the same three tables are being JOINed, the two approaches differ just based on their starting standpoint.) These utility classes are designed to make building out any goofy linkages like this pretty simple in a few ways: ** if the same association is requested more than once then no duplicates. ** If a bunch of intermediary associations are referenced leading up to a final one then all of them get automatically built
out and added along the way, without any having to previously exist.
** If one reference was made previously and now another neighbouring one is called for, then what used to be a simple symbol
is automatically graduated into an array so that both members can be held. For instance, if with the Order example above
there was also a LineItem model that belongs_to Order, then let's say you start from LineItem and want to now get all 4
related models. You could start by going through :order to :employee like this:
line_item_joins = JoinArray.new line_item_joins = :employee
> { order: :employee }
and then add in the reference to :customer like this:
line_item_joins = :customer
> { order: [:employee, :customer] }
and then carry on incrementally building out more JOINs in whatever sequence makes the best sense. This bundle of nested
stuff can then be used to query ActiveRecord like this:
LineItem.joins(line_item_joins)
Instance Attribute Summary collapse
-
#orig_parent ⇒ Object
readonly
Returns the value of attribute orig_parent.
-
#parent ⇒ Object
readonly
Returns the value of attribute parent.
-
#parent_key ⇒ Object
readonly
Returns the value of attribute parent_key.
Class Method Summary collapse
Instance Method Summary collapse
- #[](*args) ⇒ Object
- #[]=(*args) ⇒ Object
- #_brick_set ⇒ Object
- #add_parts(parts) ⇒ Object
- #set_matching(key, value) ⇒ Object
Instance Attribute Details
#orig_parent ⇒ Object (readonly)
Returns the value of attribute orig_parent.
41 42 43 |
# File 'lib/brick/join_array.rb', line 41 def orig_parent @orig_parent end |
#parent ⇒ Object (readonly)
Returns the value of attribute parent.
41 42 43 |
# File 'lib/brick/join_array.rb', line 41 def parent @parent end |
#parent_key ⇒ Object (readonly)
Returns the value of attribute parent_key.
41 42 43 |
# File 'lib/brick/join_array.rb', line 41 def parent_key @parent_key end |
Class Method Details
.attach_back_to_root(collection, key = nil, value = nil) ⇒ Object
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 |
# File 'lib/brick/join_array.rb', line 80 def self.attach_back_to_root(collection, key = nil, value = nil) # Create a list of layers which start at the root layers = [] layer = collection while layer.parent layers << layer layer = layer.parent end # Go through the layers from root down to child, attaching everything layers.each do |layer| if (prnt = layer.remove_instance_variable(:@parent)) layer.instance_variable_set(:@orig_parent, prnt) end case prnt when ::Brick::JoinHash value = if prnt.key?(layer.parent_key) if layer.is_a?(Hash) layer else ::Brick::JoinArray.new.replace([prnt.fetch(layer.parent_key, nil), layer]) end else layer end # This is as if we did: prnt[layer.parent_key] = value # but calling it that way would attempt to infinitely recurse back onto this overridden version of the []= method, # so we go directly to ._brick_store() instead. prnt._brick_store(layer.parent_key, value) when ::Brick::JoinArray if (key) puts "X1" prnt[layer.parent_key][key] = value else prnt[layer.parent_key] = layer end end end end |
Instance Method Details
#[](*args) ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/brick/join_array.rb', line 44 def [](*args) if !(key = args[0]).is_a?(Symbol) super else idx = -1 # Whenever a JoinHash has a value of a JoinArray with a single member then it is a wrapper, usually for a Symbol matching = find { |x| idx += 1; (x.is_a?(::Brick::JoinArray) && x.first == key) || (x.is_a?(::Brick::JoinHash) && x.key?(key)) || x == key } case matching when ::Brick::JoinHash matching[key] when ::Brick::JoinArray matching.first else ::Brick::JoinHash.new.tap do |child| child.instance_variable_set(:@parent, self) child.instance_variable_set(:@parent_key, key) # %%% Use idx instead of key? end end end end |
#[]=(*args) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/brick/join_array.rb', line 65 def []=(*args) ::Brick::JoinArray.attach_back_to_root(self, args[0], args[1]) if (key = args[0]).is_a?(Symbol) && ((value = args[1]).is_a?(::Brick::JoinHash) || value.is_a?(Symbol) || value.nil?) # %%% This is for the first symbol added to a JoinArray, cleaning out the leftover {} that is temporarily built out # when doing my_join_array[:value1][:value2] = nil. idx = -1 delete_at(idx) if value.nil? && any? { |x| idx += 1; x.is_a?(::Brick::JoinHash) && x.empty? } set_matching(key, value) else super end end |
#_brick_set ⇒ Object
42 |
# File 'lib/brick/join_array.rb', line 42 alias _brick_set []= |
#add_parts(parts) ⇒ Object
169 170 171 172 173 |
# File 'lib/brick/join_array.rb', line 169 def add_parts(parts) s = self parts[0..-3].each { |part| s = s[part.to_sym] } s[parts[-2].to_sym] = nil # unless parts[-2].empty? # Using []= will "hydrate" any missing part(s) in our whole series end |
#set_matching(key, value) ⇒ Object
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 |
# File 'lib/brick/join_array.rb', line 119 def set_matching(key, value) idx = -1 matching = find { |x| idx += 1; (x.is_a?(::Brick::JoinArray) && x.first == key) || (x.is_a?(::Brick::JoinHash) && x.key?(key)) || x == key } case matching when ::Brick::JoinHash matching[key] = value when Symbol if value.nil? # If it already exists then no worries matching else # Not yet there, so we will "graduate" this single value into being a key / value pair found in a JoinHash. The # destination hash to be used will be either an existing one if there is a neighbouring JoinHash available, or a # newly-built one placed in the "new_hash" variable if none yet exists. hash = find { |x| x.is_a?(::Brick::JoinHash) } || (new_hash = ::Brick::JoinHash.new) hash._brick_store(key, ::Brick::JoinArray.new.tap { |val_array| val_array.replace([value]) }) # hash.instance_variable_set(:@parent, matching.parent) if matching.parent # hash.instance_variable_set(:@parent_key, matching.parent_key) if matching.parent_key # When a new JoinHash was created, we place it at the same index where the original lone symbol value was pulled from. # If instead we used an existing JoinHash then since that symbol has now been graduated into a new key / value pair in # the existing JoinHash then we delete the original symbol by its index. new_hash ? _brick_set(idx, new_hash) : delete_at(idx) end when ::Brick::JoinArray # Replace this single thing (usually a Symbol found as a value in a JoinHash) (hash = ::Brick::JoinHash.new)._brick_store(key, value) if matching.parent hash.instance_variable_set(:@parent, matching.parent) hash.instance_variable_set(:@parent_key, matching.parent_key) end _brick_set(idx, hash) else # Doesn't already exist anywhere, so add it to the end of this JoinArray and return the new member if value ::Brick::JoinHash.new.tap do |hash| val_collection = if value.is_a?(::Brick::JoinHash) value else ::Brick::JoinArray.new.tap { |array| array.replace([value]) } end val_collection.instance_variable_set(:@parent, hash) val_collection.instance_variable_set(:@parent_key, key) hash._brick_store(key, val_collection) hash.instance_variable_set(:@parent, self) hash.instance_variable_set(:@parent_key, length) end else key end.tap { |member| push(member) } end end |