Module: Refine::Conditions::UsesAttributes
- Included in:
- Condition, FilterCondition, OptionCondition
- Defined in:
- app/models/refine/conditions/uses_attributes.rb
Instance Method Summary collapse
- #apply_relationship_attribute(input:, query:) ⇒ Object
- #can_use_where_in_relationship_subquery?(instance) ⇒ Boolean
- #create_pending_has_many_through_subquery(input:, relation:, instance:, query:) ⇒ Object
- #create_pending_wherein_subquery(input:, relation:, instance:, query:) ⇒ Object
- #get_reflection_object(query, relation) ⇒ Object
- #group(nodes) ⇒ Object
- #is_relationship_attribute? ⇒ Boolean
- #key_1(instance) ⇒ Object
- #key_2(instance) ⇒ Object
- #raw_attribute(attribute) ⇒ Object
-
#should_inverse_clause?(instance, clause) ⇒ Boolean
Determine if the clause should be flipped.
- #with_attribute(value) ⇒ Object
Instance Method Details
#apply_relationship_attribute(input:, query:) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 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 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 9 def apply_relationship_attribute(input:, query:) # Split on first . decompose_attribute = @attribute.split(".", 2) # Attribute now is the back half of the initial attribute @attribute = decompose_attribute[1] if !@attribute.include?(".") # No more .s, deepest level @on_deepest_relationship = true end # Relation to be handled relation = decompose_attribute[0] # Get the Reflection object which defines the relationship between query and relation # First iteration pull relationship using base query which responds to model. instance = get_reflection_object(query, relation) unless instance raise "Relationship does not exist for #{relation}." end filter.set_pending_relationship(relation, instance) # If the current condition is a refinement (filter refinement) set collapsible to true if is_refinement filter.allow_pending_relationship_to_collapse end if can_use_where_in_relationship_subquery?(instance) create_pending_wherein_subquery(input: input, relation: relation, instance: instance, query: query) else create_pending_has_many_through_subquery(input: input, relation: relation, instance: instance, query: query) end filter.release_pending_relationship # We want the method to return nil for relationship attributes # The purpose of this method is to populate pending relationship subqueries nil end |
#can_use_where_in_relationship_subquery?(instance) ⇒ Boolean
175 176 177 178 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 175 def can_use_where_in_relationship_subquery?(instance) # Where in only works for belongs to, has one, or has many (instance.is_a? ActiveRecord::Reflection::BelongsToReflection) || (instance.is_a? ActiveRecord::Reflection::HasManyReflection) || (instance.is_a? ActiveRecord::Reflection::HasOneReflection) end |
#create_pending_has_many_through_subquery(input:, relation:, instance:, query:) ⇒ Object
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 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 120 def create_pending_has_many_through_subquery(input:, relation:, instance:, query:) # In a has_many relationship the negative has to be flipped to positive. inverse_clause = should_inverse_clause?(instance, input[:clause]) # Ex: A country has many posts through hmtt_users. # Use AR to properly join the relation to the base query provided # Convert to AREL to use with nodes subquery_path = query.model.select(key_1(instance)).joins(relation.to_sym).arel relation_table_being_queried = instance.klass.arel_table relation_class = instance.klass if self.through_id_relationship if instance.is_a? ActiveRecord::Reflection::ThroughReflection through_reflection = instance.through_reflection if(through_reflection.is_a?(ActiveRecord::Reflection::BelongsToReflection)) through_reflection = instance.source_reflection.through_reflection end parent_foreign_key = through_reflection.foreign_key child_foreign_key = instance.source_reflection.foreign_key relation_table_being_queried = through_reflection.klass.arel_table relation_class = through_reflection.klass if(through_reflection.chain.count > 1) # We need to shortcut the outer inner joins but allow the others to go as normal parent_foreign_key = through_reflection.chain.last.foreign_key parent_through_reflection = through_reflection.chain.last join_sym = through_reflection.source_reflection_name subquery_path = parent_through_reflection.klass.select(parent_foreign_key).joins(join_sym) if(through_reflection.scope && through_reflection.scope.is_a?(Proc)) subquery_path = subquery_path.instance_exec(&through_reflection.scope) end subquery_path = subquery_path.arel else # Does not need any inner joins subquery_path = through_reflection.klass.select(parent_foreign_key).arel end node_to_apply = apply(input, relation_table_being_queried, relation_class, inverse_clause, child_foreign_key) else raise Refine::Conditions::Errors::RelationshipError.new("with_through_id must be used with a has_many :through relationship") end else node_to_apply = apply(input, relation_table_being_queried, relation_class, inverse_clause) end if self.forced_index mysql_from_segment = "`#{through_reflection.table_name}` FORCE INDEX(`#{self.forced_index}`)" # Ensuring input is sanitized subquery_path = subquery_path.from(Arel.sql(mysql_from_segment)) end complete_subquery = subquery_path.where(node_to_apply) subquery = filter.get_pending_relationship_subquery || complete_subquery filter.add_pending_relationship_subquery(subquery: subquery, primary_key: key_1(instance), secondary_key: nil, inverse_clause: inverse_clause) end |
#create_pending_wherein_subquery(input:, relation:, instance:, query:) ⇒ Object
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 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 76 def create_pending_wherein_subquery(input:, relation:, instance:, query:) # This method builds out the linking keys between the provided query model and the relation # and saves it to pending relationship subqueries # Class of the relation as held in the AR::Relation object relation_class = instance.klass # Pull what's already in the tracker at this depth if already traversed subquery = filter.get_pending_relationship_subquery || relation_class.select([key_2(instance)]).arel # Primary/secondary keys keep track of how to link tables # If depth has been added (i.e. filter.pending_relationship_subquery_depth = [:btt_user, :btt_notes]) # This will add the [:children] key to the pending_relationship_subqueries tracker under the parent key [:btt_user] filter.add_pending_relationship_subquery(subquery: subquery, primary_key: key_1(instance), secondary_key: key_2(instance)) # Apply the condition. If a nested relationship, this apply is adding the children key (with values) to the pending_relationship_subqueries tracker # due to the recursive nature of the apply method. This is critical because it get is then "rolled up" in release_pending_relationship node = apply(input, relation_class.arel_table, relation_class, false) # If node is an AREL::SELECT manager we are allowing the apply condition to return a fully formed subquery - we replace # the linking keys in the tracker with the fully formed select query # Has not been tested more than one level deep if node.is_a? Arel::SelectManager filter.add_pending_relationship_subquery(subquery: node, primary_key: key_1(instance), secondary_key: key_2(instance)) elsif node # This modifies subquery *in* the pending_relationship_subqueries tracker. subquery.where(node) end end |
#get_reflection_object(query, relation) ⇒ Object
66 67 68 69 70 71 72 73 74 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 66 def get_reflection_object(query, relation) if query.respond_to? :model query.model.reflect_on_association(relation.to_sym) else # When query is sent in as subquery (recursive) the query object is the model class pulled from the # previous instance value query.reflect_on_association(relation.to_sym) end end |
#group(nodes) ⇒ Object
105 106 107 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 105 def group(nodes) Arel::Nodes::Grouping.new(nodes) end |
#is_relationship_attribute? ⇒ Boolean
180 181 182 183 184 185 186 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 180 def is_relationship_attribute? # TODO: Allow user to decide attribute is not a relationship # If we are on the deepest relationship, it's no longer a relationship attribute return false if @on_deepest_relationship # If the attribute includes a ., it's a relationship attribute @attribute.include?(".") end |
#key_1(instance) ⇒ Object
49 50 51 52 53 54 55 56 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 49 def key_1(instance) # Foreign key on belongs to, primary key on HasMany if instance.is_a? ActiveRecord::Reflection::BelongsToReflection instance.foreign_key.to_sym else instance.active_record_primary_key.to_sym end end |
#key_2(instance) ⇒ Object
58 59 60 61 62 63 64 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 58 def key_2(instance) if instance.is_a? ActiveRecord::Reflection::BelongsToReflection instance.active_record_primary_key.to_sym else instance.foreign_key.to_sym end end |
#raw_attribute(attribute) ⇒ Object
188 189 190 191 192 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 188 def raw_attribute(attribute) @attribute = Arel.sql(attribute) self end |
#should_inverse_clause?(instance, clause) ⇒ Boolean
Determine if the clause should be flipped. For example, "not_eq" => "eq". Must also change "in" to "not in" upstream
113 114 115 116 117 118 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 113 def should_inverse_clause?(instance, clause) is_through_reflection = instance.is_a?(ActiveRecord::Reflection::ThroughReflection) is_inverse_clause_flippable = Clauses::FLIPPABLE.include?(clause) is_through_reflection && is_inverse_clause_flippable end |
#with_attribute(value) ⇒ Object
4 5 6 7 |
# File 'app/models/refine/conditions/uses_attributes.rb', line 4 def with_attribute(value) @attribute = value self end |