Class: ActiveStash::IndexDSL
- Inherits:
-
Object
- Object
- ActiveStash::IndexDSL
- Defined in:
- lib/active_stash/index_dsl.rb
Overview
Implements the DSL for adding searchable encrypted indexes to ActiveRecord models that include ActiveStash::Search.
stash_index do
auto :email, :first_name, :last_name
unique :email
range :created_at, :updated_at
exact :gender
match :description, filter_term_bits: 512
match_all :first_name, :last_name, :email
index_assoc :patient do
auto :email, :first_name, :last_name
end
end
Instance Method Summary collapse
-
#auto(*fields) ⇒ Object
Automatically defines all applicable index types on one or more fields.
-
#exact(*fields) ⇒ Object
Defines an exact index on one or more fields.
-
#finalize! ⇒ Object
Performs the following checks and operations before returning the final indexes.
-
#index_assoc(association, &block) ⇒ Object
Pulls fields from an association into the index on this model.
-
#initialize(model_class, path = [], reflector = nil) ⇒ IndexDSL
constructor
A new instance of IndexDSL.
- #match(*fields, **opts) ⇒ Object
- #match_all(*fields, **opts) ⇒ Object
- #range(*fields) ⇒ Object
-
#unique(field) ⇒ Object
Defines a unique index on a single field.
Constructor Details
#initialize(model_class, path = [], reflector = nil) ⇒ IndexDSL
Returns a new instance of IndexDSL.
21 22 23 24 25 26 27 28 29 30 |
# File 'lib/active_stash/index_dsl.rb', line 21 def initialize(model_class, path = [], reflector = nil) @model_class = model_class @reflector = reflector || Reflector.new(@model_class) @path = path || [] @is_in_association = path.size > 0 @indexes = [] @associations = [] @unique_fields = [] @callback_registration_handlers = [] end |
Instance Method Details
#auto(*fields) ⇒ Object
Automatically defines all applicable index types on one or more fields.
60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/active_stash/index_dsl.rb', line 60 def auto(*fields) fields.each do |name| if @reflector.fields.include?(name.to_s) field_type = @reflector.fields[name.to_s] Index.applicable_index_types(field_type).each do |index_type| @indexes.push(Index.send(index_type, name.to_s)) end else raise ConfigError, "Attempted to auto index an unknown attribute '#{name}' on model '#{@model_class}'" end end end |
#exact(*fields) ⇒ Object
Defines an exact index on one or more fields.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/active_stash/index_dsl.rb', line 74 def exact(*fields) fields.each do |name| if @reflector.fields.include?(name.to_s) field_type = @reflector.fields[name.to_s] if Index.valid_index_type_for_field_type?(:exact, field_type) @indexes.push(Index.exact(name.to_s)) else raise ConfigError, "Attempted to create an exact index on type that does not support an exact index (attribute '#{name}' of model '#{@model_class})'" end else raise ConfigError, "Attempted to apply an exact index an unknown attribute '#{name}' on model '#{@model_class}'" end end end |
#finalize! ⇒ Object
Performs the following checks and operations before returning the final indexes.
-
Validates that there are not multiple index definitions of the same
type for the same field.
-
Validates that there are not multiple associated indexes defined for
the same association.
-
Processes `unique` indexes by either:
-
Updating existing `range` and `exact` indexes for a field to enforce
uniqueness
-
Creating an `exact` or `range` index for the field if one does not
already exist (`string` and `text` fields generate `exact` indexes, everything else generates a `range` index).
-
50 51 52 53 54 55 56 |
# File 'lib/active_stash/index_dsl.rb', line 50 def finalize! return @finalized_config if @finalized_config validate_no_fields_with_duplicate_index_definitions! validate_no_association_duplicates! process_unique_indexes! @finalized_config = ActiveStash::FinalizedIndexConfig.new(@indexes, @callback_registration_handlers) end |
#index_assoc(association, &block) ⇒ Object
Pulls fields from an association into the index on this model. This is a form of denormalisation.
This mechanism works whether the foreign model includes ActiveStash::Search or not.
Currently, it is only possible to index `has_one` or `belongs_to` associations.
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/active_stash/index_dsl.rb', line 168 def index_assoc(association, &block) unless ActiveStash::ModelReflection.association_names(@model_class).include?(association) raise ConfigError, "No such association '#{association}' on model '#{@model_class}'" end unless @path.size == 0 raise ConfigError, "Nested association indexing is currently not supported" end associated_model = ActiveStash::ModelReflection.associated_model(@model_class, association) path = [*@path, association] dsl = IndexDSL.new(associated_model, @path, Reflector.new(associated_model)) reflection = @model_class.reflect_on_association(association) unless [:has_one, :belongs_to].include?(reflection.macro) raise ConfigError, "Only 1-to-1 associations (belongs_to and has_one) are currently supported" end dsl.instance_eval(&block) association_indexes = dsl.finalize!.indexes @indexes.concat(association_indexes.indexes.map do |idx| idx.tap { |i| i.name = "#{path.join(".")}.#{i.name}" } end) inverse_name = reflection.inverse_of.name @callback_registration_handlers.push(-> { reflection.klass.after_save do |record| record.send(inverse_name).try(:cs_put) end reflection.klass.after_destroy do |record| # This will reindex every stash index associated with the model, not # only the index that needs updating. We can get smarter about this # in the future. record.send(inverse_name).reload record.send(inverse_name).try(:cs_put) end }) end |
#match(*fields, **opts) ⇒ Object
104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/active_stash/index_dsl.rb', line 104 def match(*fields, **opts) fields.each do |name| if @reflector.fields.include?(name.to_s) field_type = @reflector.fields[name.to_s] if Index.valid_index_type_for_field_type?(:match, field_type) @indexes.push(Index.match(name.to_s, **opts)) else raise ConfigError, "Attempted to create a match index on type that does not support an range index (attribute '#{name}' of model '#{@model_class})'" end else raise ConfigError, "Attempted to apply a match index an unknown attribute '#{name}' on model '#{@model_class}'" end end end |
#match_all(*fields, **opts) ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/active_stash/index_dsl.rb', line 119 def match_all(*fields, **opts) raise ConfigError, "No fields specified for match_all" if fields.size == 0 fields.each do |f| field_type = @reflector.fields[f.to_s] unless Index.valid_index_type_for_field_type?(:match, field_type) raise ConfigError, "Only attributes of type string or text can be used in a match_all index (attribute #{f} is of type #{field_type}) in model '#{@model_class}'" end end @indexes.push(Index.match_multi(fields, **opts)) end |
#range(*fields) ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/active_stash/index_dsl.rb', line 89 def range(*fields) fields.each do |name| if @reflector.fields.include?(name.to_s) field_type = @reflector.fields[name.to_s] if Index.valid_index_type_for_field_type?(:range, field_type) @indexes.push(Index.range(name)) else raise ConfigError, "Attempted to create a range index on type that does not support an range index (attribute '#{name}' of model '#{@model_class})'" end else raise ConfigError, "Attempted to apply a range index an unknown attribute '#{name}' on model '#{@model_class}'" end end end |
#unique(field) ⇒ Object
Defines a unique index on a single field.
If no exact or range index already exists on the field, `unique` will create a new exact index with a unique constraint.
All existing exact and range indexes for the field will be modified to enforce a unique constraint, except if the field is a `string` or `text` type, in which case the unique constraint will only be applied to the `exact` index. This is because `range` indexes on strings are lossy and could cause false positive uniqueness checks.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/active_stash/index_dsl.rb', line 143 def unique(field) if @is_in_association raise ConfigError, "Attempted to create a unique constraint on an associated model" end if @reflector.fields.include?(field.to_s) if @unique_fields.include?(field) raise ConfigError, "A unique constraint is already defined on '#{field}' on model '#{@model_class})" else @unique_fields.push(field) end else raise ConfigError, "Attempted to create a unique constraint on unknown attribute '#{field}' on model '#{@model_class})" end end |