Skip to content

Commit a75567d

Browse files
authored
Improve Indexed behaviour with model subclasses (#580)
Improved the `Indexed` mixin with a new `create_validate_and_add!` convenience method and improved behaviour for subclassed models.
2 parents 0677cd7 + a65a4ce commit a75567d

File tree

2 files changed

+73
-6
lines changed

2 files changed

+73
-6
lines changed

dev/lib/product_taxonomy/models/mixins/indexed.rb

+32-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
module ProductTaxonomy
44
# Mixin providing indexing for a model, using hashes to support fast uniqueness checks and lookups by field value.
55
module Indexed
6+
class << self
7+
# Keep track of which model class used "extend Indexed" so that the model can be subclassed while still storing
8+
# model objects in a shared index on the superclass.
9+
def extended(indexed_model_class)
10+
indexed_model_class.instance_variable_set(:@is_indexed, true)
11+
end
12+
end
13+
614
# Validator that checks for uniqueness of a field value across all models in a given index.
715
class UniquenessValidator < ActiveModel::EachValidator
816
def validate_each(record, attribute, value)
@@ -21,6 +29,18 @@ def add(model)
2129
end
2230
end
2331

32+
# Convenience method to create a model, validate it, and add it to the index. If the model is not valid, raise an
33+
# error.
34+
#
35+
# @param args [Array] The arguments to pass to the model's constructor.
36+
# @return [Object] The created model.
37+
def create_validate_and_add!(...)
38+
model = new(...)
39+
model.validate!
40+
add(model)
41+
model
42+
end
43+
2444
# Check if a field value is a duplicate of an existing model. A model is considered to be a duplicate if it was
2545
# added after the first model with that value.
2646
#
@@ -71,6 +91,18 @@ def size
7191
all.size
7292
end
7393

94+
# Get the hash of hash of models, indexed by fields marked unique.
95+
#
96+
# @return [Hash] The hash of hash of models indexed by the fields marked unique.
97+
def hashed_models
98+
# If we're a subclass of the model class that originally extended Indexed, use the parent class' hashed_models.
99+
return superclass.hashed_models unless @is_indexed
100+
101+
@hashed_models ||= unique_fields.each_with_object({}) do |field, hash|
102+
hash[field] = {}
103+
end
104+
end
105+
74106
private
75107

76108
def unique_fields
@@ -80,11 +112,5 @@ def unique_fields
80112
end
81113
end.keys
82114
end
83-
84-
def hashed_models
85-
@hashed_models ||= unique_fields.each_with_object({}) do |field, hash|
86-
hash[field] = {}
87-
end
88-
end
89115
end
90116
end

dev/test/models/mixins/indexed_test.rb

+41
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ def initialize(id:)
2323
end
2424
end
2525

26+
# Subclass of Model to test inheritance behavior with hashed_models
27+
class SubModel < Model
28+
end
29+
2630
setup do
2731
@model = Model.new(id: 1)
2832
Model.add(@model)
@@ -93,5 +97,42 @@ def initialize(id:)
9397
test "all returns all models in the index" do
9498
assert_equal [@model], Model.all
9599
end
100+
101+
test "self.extended sets @is_indexed to true on the extended class" do
102+
assert_equal true, Model.instance_variable_get(:@is_indexed)
103+
end
104+
105+
test "create_validate_and_add! creates, validates, and adds a model to the index" do
106+
Model.reset
107+
model = Model.create_validate_and_add!(id: 2)
108+
109+
assert_equal 2, model.id
110+
assert_equal 1, Model.size
111+
assert_equal model, Model.find_by(id: 2)
112+
end
113+
114+
test "create_validate_and_add! raises an error if the model is not valid" do
115+
assert_raises(ActiveModel::ValidationError) do
116+
Model.create_validate_and_add!(id: 1)
117+
end
118+
end
119+
120+
test "subclass shares hashed_models with parent class" do
121+
parent_model = Model.new(id: 2)
122+
Model.add(parent_model)
123+
124+
sub_model = SubModel.new(id: 3)
125+
SubModel.add(sub_model)
126+
127+
assert_equal 3, Model.size
128+
assert_equal 3, SubModel.size
129+
130+
assert_equal parent_model, Model.find_by(id: 2)
131+
assert_equal sub_model, Model.find_by(id: 3)
132+
assert_equal parent_model, SubModel.find_by(id: 2)
133+
assert_equal sub_model, SubModel.find_by(id: 3)
134+
135+
assert_same Model.hashed_models, SubModel.hashed_models
136+
end
96137
end
97138
end

0 commit comments

Comments
 (0)