Module: LowCardTables::ActiveRecord::Scoping::ClassMethods
- Defined in:
- lib/low_card_tables/active_record/scoping.rb
Instance Method Summary collapse
-
#scope(name, scope_options = {}, &block) ⇒ Object
Overrides #scope to check for statically-defined scopes against low-card attributes, as discussed in the comment for LowCardTables::ActiveRecord::Scoping.
Instance Method Details
#scope(name, scope_options = {}, &block) ⇒ Object
Overrides #scope to check for statically-defined scopes against low-card attributes, as discussed in the comment for LowCardTables::ActiveRecord::Scoping.
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 |
# File 'lib/low_card_tables/active_record/scoping.rb', line 31 def scope(name, = {}, &block) # First, go invoke the superclass method. out = super(name, , &block) # We're safe if it's not a statically-defined scope. return out if block # We're also safe if this class doesn't refer to any low-card tables, because then it could not possibly # have been constraining on any low-card columns. return out if (! self.has_any_low_card_tables?) # If you defined a scope that isn't an actual ::ActiveRecord::Relation, you're fine. return out unless .kind_of?(::ActiveRecord::Relation) # ::ActiveRecord::Relation#where_values gets you a list of the 'where clauses' applied in the relation. used_associations = .where_values.map do |where_value| # Let's grab the SQL... sql = if where_value.respond_to?(:to_sql) where_value.to_sql elsif where_value.kind_of?(String) where_value end # ...and just search it for the foreign-key name. Is this foolproof? No; it's possible that you'll get some # false positives. Is this a big deal? No -- because changing a static scope to dynamic really has no # drawbacks at all, so there's a trivial fix for any false positives. self._low_card_associations_manager.associations.select do |association| foreign_key = association.foreign_key_column_name sql =~ /#{foreign_key}/i end end.flatten # Here's where we check for our problem and blow up if it's there. if used_associations.length > 0 raise LowCardTables::Errors::LowCardStaticScopeError, %{You defined a named scope, #{name.inspect}, on model #{self.name}. This scope appears to constrain on the following foreign keys, which point to low-card tables. Because this scope is defined statically (e.g., 'scope :foo, where(...)' rather than 'scope :foo { where(...) }'), these conditions will only be evaluated a single time, at startup. This means that if additional low-card rows get created that match the criteria for this scope, they will never be picked up no matter what (as the WHERE clause is frozen in time forever), and you will miss critical data. The fix for this is simple: define this scope dynamically (i.e., enclose the call to #where in a block). This will cause the conditions to be evaluated every time you use it, thus updating the set of IDs used on every call, properly. The foreign keys you appear to be constraining on are: #{used_associations.map(&:foreign_key_column_name).sort.join(", ")}} end out end |