Class: PaperTrailDiff::ScopedRootSelection
- Inherits:
-
Object
- Object
- PaperTrailDiff::ScopedRootSelection
- Defined in:
- lib/paper_trail_diff/scoped_root_selection.rb,
sig/generated/paper_trail_diff/scoped_root_selection.rbs
Overview
Chooses the roots a batch will analyze from a relation instead of from an array the caller assembled, so a reporting page does not reimplement the root selection this gem already performs.
Two populations are deliberately kept apart, because conflating them would make the report wrong in a way nothing announces:
- A root whose live row does not satisfy the relation is filtered out. That is what the caller asked for, and it needs no reporting.
- A root whose live row is gone cannot be filtered at all. The relation's conditions read the live table, and a destroyed root has nothing there to read -- even though its history is intact, and the state it held when it was destroyed may well have satisfied those conditions. Reifying every candidate to find out would cost the batched query plan this class exists to provide.
So the second population is returned by name rather than dropped. A caller that does not care can ignore it; one auditing deletions is told where to look instead of silently coming up short.
Note that a relation filters on current state, not on state during the
window. where(status: 'published') selects what is published now, which is
not the same set as what was published while the window was open.
Defined Under Namespace
Classes: Result
Instance Method Summary collapse
-
#base_class ⇒ Object
: () -> untyped.
-
#call ⇒ Result
: () -> Result.
-
#identity(id) ⇒ Array[String]
: (String) -> Array.
-
#initialize(scope, time_range:, limit:) ⇒ ScopedRootSelection
constructor
: (untyped, time_range: TimeRange?, limit: Integer) -> void.
-
#item_type ⇒ String
: () -> String.
-
#live_ids(candidates) ⇒ Array[String]
Which of those candidates still have a row, asked without the relation's conditions so that "filtered out" and "no longer exists" stay separable.
-
#normalize_scope(scope) ⇒ Object
Accepts a model class as readily as a relation:
ArticleandArticle.where(...)both name a population, andallis what makes them the same kind of thing. -
#primary_key ⇒ Object
: () -> untyped.
-
#selected_records(live) ⇒ Array[untyped]
Loading one past the limit is what turns an oversized page into an error rather than a silently truncated report.
-
#validate_limit(limit) ⇒ Integer
: (Integer) -> Integer.
-
#version_class ⇒ Object
: () -> untyped.
-
#versioned_ids ⇒ Array[String]
Every root whose history moved inside the window, destroyed ones included.
Constructor Details
#initialize(scope, time_range:, limit:) ⇒ ScopedRootSelection
: (untyped, time_range: TimeRange?, limit: Integer) -> void
44 45 46 47 48 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 44 def initialize(scope, time_range:, limit:) @scope = normalize_scope(scope) @time_range = time_range @limit = validate_limit(limit) end |
Instance Method Details
#base_class ⇒ Object
: () -> untyped
134 135 136 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 134 def base_class @scope.model.base_class end |
#call ⇒ Result
: () -> Result
51 52 53 54 55 56 57 58 59 60 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 51 def call candidates = versioned_ids return Result.new(records: [], unreachable: []) if candidates.empty? live = live_ids(candidates) Result.new( records: selected_records(live), unreachable: (candidates - live).map { |id| identity(id) }.freeze ) end |
#identity(id) ⇒ Array[String]
: (String) -> Array
124 125 126 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 124 def identity(id) [item_type, id].freeze end |
#item_type ⇒ String
: () -> String
129 130 131 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 129 def item_type base_class.name.to_s end |
#live_ids(candidates) ⇒ Array[String]
105 106 107 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 105 def live_ids(candidates) base_class.unscoped.where(primary_key => candidates).pluck(primary_key).map(&:to_s) end |
#normalize_scope(scope) ⇒ Object
Accepts a model class as readily as a relation: Article and
Article.where(...) both name a population, and all is what makes them
the same kind of thing.
: (untyped) -> untyped
72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 72 def normalize_scope(scope) unless scope.respond_to?(:all) && scope.respond_to?(:where) raise ConfigurationError, 'scope: must be an ActiveRecord relation or model class' end relation = scope.all return relation if Support.versioned?(relation.model) raise UnversionedAssociationError, "scope: #{relation.model.name} is not versioned, so it has no history to select from" end |
#primary_key ⇒ Object
: () -> untyped
139 140 141 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 139 def primary_key @scope.model.primary_key end |
#selected_records(live) ⇒ Array[untyped]
112 113 114 115 116 117 118 119 120 121 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 112 def selected_records(live) return [] if live.empty? records = @scope.where(primary_key => live).limit(@limit + 1).to_a return records.freeze unless records.length > @limit raise BatchLimitExceededError, "scope: selected more than #{@limit} roots; narrow the window or the " \ 'relation, or raise limit: to the page size you intend to analyze' end |
#validate_limit(limit) ⇒ Integer
: (Integer) -> Integer
85 86 87 88 89 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 85 def validate_limit(limit) return limit if limit.is_a?(Integer) && limit.positive? raise ConfigurationError, 'limit: must be a positive Integer' end |
#version_class ⇒ Object
: () -> untyped
144 145 146 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 144 def version_class @scope.model.paper_trail.version_class end |
#versioned_ids ⇒ Array[String]
Every root whose history moved inside the window, destroyed ones included. This is the gem's own notion of "which roots changed", answered from the version table alone so that it does not depend on rows still existing. : () -> Array
95 96 97 98 99 100 |
# File 'lib/paper_trail_diff/scoped_root_selection.rb', line 95 def versioned_ids relation = version_class.where(item_type: item_type) range = @time_range relation = range.scope(relation) if range relation.distinct.pluck(:item_id).map(&:to_s).uniq end |