Module: Immunio::QueryingHooks
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/immunio/plugins/active_record_relation.rb
Class Attribute Summary collapse
-
.methods_to_wrap ⇒ Object
Returns the value of attribute methods_to_wrap.
Class Method Summary collapse
-
.wrap_method(method) ⇒ Object
Wrap ActiveRecord::Relation methods to add API calls to the context data for a query.
Class Attribute Details
.methods_to_wrap ⇒ Object
Returns the value of attribute methods_to_wrap.
89 90 91 |
# File 'lib/immunio/plugins/active_record_relation.rb', line 89 def methods_to_wrap @methods_to_wrap end |
Class Method Details
.wrap_method(method) ⇒ Object
Wrap ActiveRecord::Relation methods to add API calls to the context data for a query. Some additional methods are wrapped because they execute queries. In those cases, we must associate the connection to the relation so we can grab the context info when we see the query being executed.
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 |
# File 'lib/immunio/plugins/active_record_relation.rb', line 98 def self.wrap_method(method) method_with_immunio = "#{method}_with_immunio".to_sym method_without_immunio = "#{method}_without_immunio".to_sym define_method method_with_immunio do |*args, &block| Request.time "plugin", "Immunio::RelationTracking" do # Construct the context data. The name method returns the name of the # class of self, which is the name of the model. This allows us to # differentiate between scopes from the same lines of code but on # different models. # # In Ruby 2 and up, we can ask for specific frames and the bits we # need from them. This doesn't impact performance much. But in earlier # versions of Ruby we would need to gather the entire stack trace and # parse the strings for the frames we need. As that is too much of a # performance hit, we don't include caller information in the # additional context data. if defined? caller_locations stack = caller_locations(4..5) # If the method was called on a relation directly, the real caller # is four frames up. But if the method was called on the model # class, like `User.where`, then there is an extra frame for # delegating the method call to the result of the `all` method. We # look for the telltale sign of the caller being in the querying.rb # file of ActiveRecord and with the same name as the method. if stack[0].path.end_with?('querying.rb') && stack[0].label == method.to_s frame = stack[1] else frame = stack[0] end caller_method = frame.label caller_line = frame.lineno checksum = Immunio::Context::FILE_CHECKSUM_CACHE[frame.path] data = "Relation for #{name}, method called: #{method}, caller: #{caller_method}:#{caller_line}" data << ", checksum: #{checksum}" unless checksum.blank? else data = "Relation for #{name}, method called: #{method}" end # In Rails 3, #where and #having clone the relation and return it with # the conditions added, but use the original relation to actually # build the conditions. We have to add a hack to push the last cloned # relation onto the connection's relation stack so parameters are # associated with the cloned relation and not the original relation. relation = if method == :build_where && Rails::VERSION::MAJOR == 3 QueryTracker.instance.last_spawned_relation connection else self end # Push the current relation onto the stack of relations for the # connection. The top relation on the stack at query execution time is # used for contextual data. QueryTracker.instance.push_relation relation begin # Call original method ret = Request.pause "plugin", "Immunio::RelationTracking" do send method_without_immunio, *args, &block end # These two methods create clones of the original without actually # calling #clone. Copy relation data over in this special case. if method == :except || method == :only QueryTracker.instance.spawn_relation self, ret end # If ret is a relation, such as when #where is called, we need to # add data about the API call to the relation. Otherwise, the API # call is returning actual data from a query execution, like #count. # We don't need to add context data about calls that execute queries # because the stack trace at query execution time will include this # call. if ret.is_a? ActiveRecord::Relation QueryTracker.instance.add_relation_data ret, data end ensure QueryTracker.instance.pop_relation relation end ret end end end |