memory_model 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -4
- data/Appraisals +9 -0
- data/Guardfile +7 -1
- data/README.md +5 -1
- data/Rakefile +12 -0
- data/gemfiles/rails_3.gemfile +8 -0
- data/gemfiles/rails_3.gemfile.lock +109 -0
- data/gemfiles/rails_4.gemfile +8 -0
- data/gemfiles/rails_4.gemfile.lock +117 -0
- data/lib/memory_model/base/actions/class_methods.rb +27 -0
- data/lib/memory_model/base/actions.rb +75 -0
- data/lib/memory_model/base/attributes.rb +87 -0
- data/lib/memory_model/base/auto_increment.rb +47 -0
- data/lib/memory_model/base/collectible.rb +26 -0
- data/lib/memory_model/base/conversion.rb +11 -0
- data/lib/memory_model/base/fields/field.rb +57 -0
- data/lib/memory_model/base/fields/field_set.rb +87 -0
- data/lib/memory_model/base/fields.rb +49 -0
- data/lib/memory_model/base/operations/comparisons.rb +25 -0
- data/lib/memory_model/base/operations.rb +10 -0
- data/lib/memory_model/base/persistence.rb +13 -11
- data/lib/memory_model/base.rb +58 -43
- data/lib/memory_model/collection/finders.rb +75 -0
- data/lib/memory_model/collection/index/multi.rb +61 -0
- data/lib/memory_model/collection/index/unique.rb +79 -0
- data/lib/memory_model/collection/index.rb +86 -0
- data/lib/memory_model/collection/initializers.rb +48 -0
- data/lib/memory_model/collection/loader_delegate.rb +63 -0
- data/lib/memory_model/collection/marshaled_record.rb +23 -0
- data/lib/memory_model/collection/operations.rb +82 -0
- data/lib/memory_model/collection.rb +18 -73
- data/lib/memory_model/version.rb +1 -1
- data/lib/memory_model.rb +7 -7
- data/memory_model.gemspec +8 -3
- data/spec/benchmark/benchmark.rb +126 -0
- data/spec/memory_model/base/{actionable_spec.rb → actions_spec.rb} +34 -103
- data/spec/memory_model/base/{attributable_spec.rb → attributes_spec.rb} +4 -6
- data/spec/memory_model/base/{collectable_spec.rb → collectible_spec.rb} +1 -1
- data/spec/memory_model/base/fieldable/field_set_spec.rb +23 -37
- data/spec/memory_model/base/fieldable/field_spec.rb +16 -16
- data/spec/memory_model/base/{fieldable_spec.rb → fields_spec.rb} +1 -1
- data/spec/memory_model/base/{comparable_spec.rb → operations/comparisons_spec.rb} +4 -4
- data/spec/memory_model/base/persistence_spec.rb +2 -2
- data/spec/memory_model/base_spec.rb +10 -9
- data/spec/memory_model/collection_spec.rb +24 -146
- data/spec/spec_helper.rb +11 -0
- data/spec/support/delegate_matcher.rb +40 -0
- metadata +120 -65
- data/.idea/.rakeTasks +0 -7
- data/.idea/dictionaries/jwaldrip.xml +0 -3
- data/.idea/encodings.xml +0 -5
- data/.idea/memory_model.iml +0 -47
- data/.idea/misc.xml +0 -8
- data/.idea/modules.xml +0 -9
- data/.idea/scopes/scope_settings.xml +0 -5
- data/.idea/vcs.xml +0 -7
- data/.idea/workspace.xml +0 -701
- data/lib/memory_model/base/actionable.rb +0 -95
- data/lib/memory_model/base/attributable.rb +0 -76
- data/lib/memory_model/base/collectable.rb +0 -22
- data/lib/memory_model/base/comparable.rb +0 -16
- data/lib/memory_model/base/fieldable/field.rb +0 -35
- data/lib/memory_model/base/fieldable/field_set.rb +0 -74
- data/lib/memory_model/base/fieldable.rb +0 -45
- data/lib/memory_model/base/versionable.rb +0 -17
- data/lib/memory_model/core_ext/object.rb +0 -5
- data/spec/memory_model/base/versionable_spec.rb +0 -31
- data/spec/memory_model/core_ext/object_spec.rb +0 -12
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'active_support/hash_with_indifferent_access'
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
module MemoryModel
|
7
|
+
class Base
|
8
|
+
module Fields
|
9
|
+
class FieldSet < Set
|
10
|
+
|
11
|
+
def [](name)
|
12
|
+
find { |f| f.name == name.to_sym }
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(name, options={})
|
16
|
+
delete_if { |f| f == name }
|
17
|
+
self << Field.new(name, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def include?(name)
|
21
|
+
self[name].present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def comparable
|
25
|
+
select(&:comparable?).map(&:to_sym)
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_default_values(model, attributes={})
|
29
|
+
model.attributes = self.map(&:name).reduce(attributes.with_indifferent_access) do |hash, field|
|
30
|
+
hash[field] ||= fetch_default_value(model, field)
|
31
|
+
hash
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_default_value(model, field)
|
36
|
+
model.write_attribute field, fetch_default_value(model, field)
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch_default_value(model, field)
|
40
|
+
default = self[field].default
|
41
|
+
send("fetch_value_using_#{default.class.name.underscore}", model, default)
|
42
|
+
rescue NoMethodError => e
|
43
|
+
raise ArgumentError, "#{default} must be a string, symbol, lambda or proc"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def fetch_value_using_proc(model, proc)
|
49
|
+
raise TypeError, 'value must be a Proc' unless proc.is_a? Proc
|
50
|
+
if proc.lambda? && proc.arity == 0
|
51
|
+
proc.call
|
52
|
+
elsif proc.arity < 1
|
53
|
+
model.instance_eval(&proc)
|
54
|
+
elsif proc.arity == 1
|
55
|
+
proc.yield model
|
56
|
+
else
|
57
|
+
raise ArgumentError, "#{proc} must have an arity of 0..1, got #{proc.arity}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def fetch_value_using_string(model, string)
|
62
|
+
raise TypeError, 'value must be a String' unless string.is_a? String
|
63
|
+
string
|
64
|
+
end
|
65
|
+
|
66
|
+
def fetch_value_using_symbol(model, symbol)
|
67
|
+
raise TypeError, 'value must be a Symbol' unless symbol.is_a? Symbol
|
68
|
+
model.instance_eval(&symbol)
|
69
|
+
end
|
70
|
+
|
71
|
+
def fetch_value_using_nil_class(model, nil_object)
|
72
|
+
raise TypeError, 'value must be a NilClass' unless nil_object.is_a? NilClass
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def method_missing(m, *args, &block)
|
77
|
+
if to_a.respond_to? m
|
78
|
+
to_a.send m, *args, &block
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/dependencies/autoload'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module MemoryModel
|
6
|
+
class Base
|
7
|
+
module Fields
|
8
|
+
|
9
|
+
extend ConcernedInheritance
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
extend ActiveSupport::Autoload
|
12
|
+
include ActiveModel::AttributeMethods
|
13
|
+
|
14
|
+
autoload :FieldSet
|
15
|
+
autoload :Field
|
16
|
+
|
17
|
+
inherited do
|
18
|
+
instance_variable_set :@fields, baseclass.fields
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def field(attr, options={})
|
23
|
+
define_attribute_method attr unless instance_method_already_implemented? attr
|
24
|
+
fields.add(attr.to_sym, options)
|
25
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
26
|
+
def #{attr}
|
27
|
+
read_attribute :#{attr}
|
28
|
+
end
|
29
|
+
|
30
|
+
def #{attr}=(value)
|
31
|
+
write_attribute :#{attr}, value
|
32
|
+
end
|
33
|
+
RUBY
|
34
|
+
end
|
35
|
+
|
36
|
+
def fields
|
37
|
+
return nil if self == MemoryModel::Base
|
38
|
+
@fields ||= FieldSet.new
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def fields
|
44
|
+
self.class.fields
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'active_support/hash_with_indifferent_access'
|
3
|
+
|
4
|
+
module MemoryModel
|
5
|
+
class Base
|
6
|
+
module Operations
|
7
|
+
module Comparisons
|
8
|
+
|
9
|
+
def ==(other_object)
|
10
|
+
attributes.slice(*fields.comparable) ==
|
11
|
+
other_object.to_hash.with_indifferent_access.slice(*fields.comparable)
|
12
|
+
end
|
13
|
+
|
14
|
+
def !=(other_object)
|
15
|
+
!(self == other_object)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ===(other_object)
|
19
|
+
other_object.kind_of?(self.class) && self == other_object
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,15 +1,17 @@
|
|
1
|
-
module MemoryModel
|
1
|
+
module MemoryModel
|
2
|
+
class Base
|
3
|
+
module Persistence
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
false
|
7
|
-
end
|
5
|
+
def persisted?
|
6
|
+
!!self.class.find_by(_uuid_: self._uuid_)
|
7
|
+
end
|
8
8
|
|
9
|
-
|
9
|
+
alias :exists? :persisted?
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def new_record?
|
12
|
+
!persisted?
|
13
|
+
end
|
14
14
|
|
15
|
-
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/memory_model/base.rb
CHANGED
@@ -1,51 +1,66 @@
|
|
1
|
-
require
|
1
|
+
require 'concerned_inheritance'
|
2
2
|
require 'active_support/core_ext/object'
|
3
3
|
require 'active_support/core_ext/hash'
|
4
4
|
require 'active_support/dependencies/autoload'
|
5
|
-
require 'active_support/core_ext/hash/indifferent_access'
|
6
5
|
require 'active_model'
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# Active Model Callbacks
|
40
|
-
define_model_callbacks :initialize, only: [:after]
|
41
|
-
|
42
|
-
def initialize(attributes={ })
|
43
|
-
unless self.class.collection.is_a? MemoryModel::Collection
|
44
|
-
raise MemoryModel::InvalidCollectionError, "#{self.class} does not have an assigned collection"
|
7
|
+
module MemoryModel
|
8
|
+
|
9
|
+
class InvalidCollectionError < Error ; end
|
10
|
+
|
11
|
+
class Base
|
12
|
+
extend ActiveSupport::Autoload
|
13
|
+
extend ConcernedInheritance
|
14
|
+
|
15
|
+
autoload :Fields
|
16
|
+
autoload :Collectible
|
17
|
+
autoload :Comparison
|
18
|
+
autoload :Actions
|
19
|
+
autoload :Attributes
|
20
|
+
autoload :Persistence
|
21
|
+
autoload :Operations
|
22
|
+
autoload :Conversion
|
23
|
+
autoload :AutoIncrement
|
24
|
+
|
25
|
+
# Active Model Additions
|
26
|
+
extend ActiveModel::Callbacks
|
27
|
+
extend ActiveModel::Naming
|
28
|
+
extend ActiveModel::Translation
|
29
|
+
include ActiveModel::Conversion
|
30
|
+
include ActiveModel::Serialization
|
31
|
+
include ActiveModel::Validations
|
32
|
+
|
33
|
+
# 3.2 Only Active Model Additions
|
34
|
+
if ActiveModel::VERSION::MAJOR < 4 || (ActiveModel::VERSION::MAJOR == 3 && ActiveModel::VERSION::MINOR > 2)
|
35
|
+
include ActiveModel::MassAssignmentSecurity
|
36
|
+
include ActiveModel::Observing
|
45
37
|
end
|
46
|
-
@attributes = fields.default_values(self, attributes).with_indifferent_access
|
47
|
-
@deleted = false
|
48
|
-
run_callbacks :initialize
|
49
|
-
end
|
50
38
|
|
39
|
+
# Memory Model Additions
|
40
|
+
include Fields
|
41
|
+
include Collectible
|
42
|
+
include Operations::Comparisons
|
43
|
+
include Actions
|
44
|
+
include Attributes
|
45
|
+
include Persistence
|
46
|
+
include Conversion
|
47
|
+
include AutoIncrement
|
48
|
+
|
49
|
+
# Active Model Callbacks
|
50
|
+
define_model_callbacks :initialize, only: [:after]
|
51
|
+
|
52
|
+
def initialize(attributes={})
|
53
|
+
unless self.class.collection.is_a? MemoryModel::Collection
|
54
|
+
raise MemoryModel::InvalidCollectionError, "#{self.class} does not have an assigned collection"
|
55
|
+
end
|
56
|
+
fields.set_default_values(self, attributes)
|
57
|
+
run_callbacks :initialize
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize_dup(other)
|
61
|
+
@attributes = other.attributes.dup
|
62
|
+
reset_incremented_fields!
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
51
66
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module MemoryModel
|
2
|
+
|
3
|
+
class RecordNotFoundError < Error;
|
4
|
+
end
|
5
|
+
|
6
|
+
class Collection
|
7
|
+
module Finders
|
8
|
+
|
9
|
+
def all
|
10
|
+
LoaderDelegate.new records
|
11
|
+
end
|
12
|
+
|
13
|
+
def count
|
14
|
+
_uuids_.count
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(key)
|
18
|
+
read(key).load
|
19
|
+
rescue NoMethodError
|
20
|
+
raise RecordNotFoundError
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_all(*ids)
|
24
|
+
read_all(*ids).map(&:load)
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_by(hash)
|
28
|
+
where(hash).first
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_or_initialize_by(hash)
|
32
|
+
find_by(hash) || model.new(hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_or_create_by(hash)
|
36
|
+
find_by(hash) || model.create(hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_or_create_by!(hash)
|
40
|
+
find_by(hash) || model.create!(hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
def where(hash)
|
44
|
+
matched_ids = hash.symbolize_keys.reduce(_uuids_) do |array, (attr, value)|
|
45
|
+
records = if indexes.has_key?(attr)
|
46
|
+
where_in_index(attr, value).compact.map(&:uuid)
|
47
|
+
else
|
48
|
+
where_in_all(attr, value).map(&:_uuid_)
|
49
|
+
end
|
50
|
+
array & records
|
51
|
+
end
|
52
|
+
load_all(*matched_ids)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def _uuids_
|
58
|
+
indexes[:_uuid_].keys
|
59
|
+
end
|
60
|
+
|
61
|
+
def records
|
62
|
+
indexes[:_uuid_].values
|
63
|
+
end
|
64
|
+
|
65
|
+
def where_in_all(attr, value)
|
66
|
+
all.select { |record| record.read_attribute(attr) == value }
|
67
|
+
end
|
68
|
+
|
69
|
+
def where_in_index(attr, value)
|
70
|
+
indexes[attr].where value
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module MemoryModel
|
2
|
+
class Collection
|
3
|
+
class Index
|
4
|
+
class Multi < MemoryModel::Collection::Index
|
5
|
+
|
6
|
+
# `create` should implement creating a new record, raising an error if an item with the matching storage id already
|
7
|
+
# exists in the index.
|
8
|
+
def create(key, item)
|
9
|
+
insert_into_key(key, item)
|
10
|
+
end
|
11
|
+
|
12
|
+
# `update` should find a record in the collection by its storage_id, remove it, and add with the new value.
|
13
|
+
def update(key, item)
|
14
|
+
raise(RecordNotInIndexError, [self, item]) unless exists? item
|
15
|
+
delete(item)
|
16
|
+
create(key, item)
|
17
|
+
end
|
18
|
+
|
19
|
+
# `read` should find a record in the collection by its indexed_value, remove it, and add with the new value.
|
20
|
+
def read(key)
|
21
|
+
index[key].first
|
22
|
+
end
|
23
|
+
|
24
|
+
# `delete` should find a record in the collection by its indexed_value, and remove it.
|
25
|
+
def delete(key)
|
26
|
+
index.values.map { |refs| refs.delete key }
|
27
|
+
end
|
28
|
+
|
29
|
+
# `exists?` return whether or not an item with the given storage id exists.
|
30
|
+
def exists?(item)
|
31
|
+
index.values.any? { |refs| refs.has_key? item.uuid }
|
32
|
+
end
|
33
|
+
|
34
|
+
# `values` should return the values of the index
|
35
|
+
def values
|
36
|
+
index.values.map(&:values)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def where_using_default(matcher)
|
42
|
+
Array.wrap index[matcher].try(:values)
|
43
|
+
end
|
44
|
+
|
45
|
+
def where_using_proc(matcher)
|
46
|
+
index.slice(*index.keys.select(&matcher)).values.map(&:values).flatten
|
47
|
+
end
|
48
|
+
|
49
|
+
def where_using_regexp(matcher)
|
50
|
+
where_using_proc ->(key) { key =~ matcher }
|
51
|
+
end
|
52
|
+
|
53
|
+
def insert_into_key(key, item)
|
54
|
+
index[key] ||= {}
|
55
|
+
index[key][item.uuid] = item
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module MemoryModel
|
2
|
+
|
3
|
+
class NilValueError < IndexError
|
4
|
+
|
5
|
+
def initialize(index)
|
6
|
+
super "`#{index.name}` cannot be nil"
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
class RecordNotUniqueError < IndexError
|
12
|
+
|
13
|
+
def initialize(args)
|
14
|
+
index, key = args
|
15
|
+
super "`#{index.name}` with `#{key}` already exists"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class Collection
|
21
|
+
class Index
|
22
|
+
class MemoryModel::Collection::Index::Unique < MemoryModel::Collection::Index
|
23
|
+
|
24
|
+
delegate :values_at, to: :index
|
25
|
+
|
26
|
+
# `create` should implement creating a new record, raising an error if an item with the matching storage id already
|
27
|
+
# exists in the index.
|
28
|
+
def create(key, item)
|
29
|
+
raise(NilValueError, self) if key.nil? && !options[:allow_nil]
|
30
|
+
raise(RecordNotUniqueError, [self, key]) if index.has_key?(key)
|
31
|
+
return if key.nil?
|
32
|
+
index[key] = item
|
33
|
+
end
|
34
|
+
|
35
|
+
# `update` should find a record in the collection by its storage_id, remove it, and add with the new value.
|
36
|
+
def update(key, item)
|
37
|
+
raise(RecordNotInIndexError, [self, item]) unless exists? item
|
38
|
+
delete(item.uuid)
|
39
|
+
create(key, item)
|
40
|
+
end
|
41
|
+
|
42
|
+
# `read` should find a record in the collection by its indexed_value, remove it, and add with the new value.
|
43
|
+
def read(key)
|
44
|
+
index[key]
|
45
|
+
end
|
46
|
+
|
47
|
+
# `delete` should find a record in the collection by its indexed_value, and remove it.
|
48
|
+
def delete(key)
|
49
|
+
index.delete_if { |k, value| key == value.uuid }
|
50
|
+
end
|
51
|
+
|
52
|
+
# `exists?` return whether or not an item with the given storage id exists.
|
53
|
+
def exists?(item)
|
54
|
+
index.any? { |key, value| item.uuid == value.uuid }
|
55
|
+
end
|
56
|
+
|
57
|
+
# `values` should return the values of the index
|
58
|
+
def values
|
59
|
+
index.values
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def where_using_default(matcher)
|
65
|
+
[read(matcher)]
|
66
|
+
end
|
67
|
+
|
68
|
+
def where_using_proc(matcher)
|
69
|
+
index.values_at *index.keys.select(&matcher)
|
70
|
+
end
|
71
|
+
|
72
|
+
def where_using_regexp(matcher)
|
73
|
+
where_using_proc ->(key) { key =~ matcher }
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module MemoryModel
|
2
|
+
|
3
|
+
class IndexError < Error
|
4
|
+
end
|
5
|
+
|
6
|
+
class InvalidWhereQuery < IndexError
|
7
|
+
|
8
|
+
def initialize(matcher_class)
|
9
|
+
super "Unable to perform a where with #{matcher_class}"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class RecordNotInIndexError < IndexError
|
15
|
+
|
16
|
+
def initialize(args)
|
17
|
+
item, index = args
|
18
|
+
super "record `#{item.uuid}` is missing from index `#{index.name}`"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class Collection
|
24
|
+
class Index
|
25
|
+
extend ActiveSupport::Autoload
|
26
|
+
|
27
|
+
autoload :Unique
|
28
|
+
autoload :Multi
|
29
|
+
|
30
|
+
attr_reader :name, :options, :index
|
31
|
+
|
32
|
+
delegate :clear, :keys, to: :index
|
33
|
+
delegate :count, to: :values
|
34
|
+
|
35
|
+
def initialize(name, options)
|
36
|
+
@name = name
|
37
|
+
@options = options
|
38
|
+
@index = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
# This is the base index, each method below must be implemented on each subclass
|
42
|
+
|
43
|
+
# `create` should implement creating a new record, raising an error if an item with the matching storage id already
|
44
|
+
# exists in the index.
|
45
|
+
def create(key, item)
|
46
|
+
raise NotImplementedError, "#{__method__} has not been implemented for this #{name} index"
|
47
|
+
end
|
48
|
+
|
49
|
+
# `update` should find a record in the collection by its storage_id, remove it, and add with the new value.
|
50
|
+
def update(key, item)
|
51
|
+
raise NotImplementedError, "#{__method__} has not been implemented for this #{name} index"
|
52
|
+
end
|
53
|
+
|
54
|
+
# `read` should find a record in the collection by its indexed_value, remove it, and add with the new value.
|
55
|
+
def read(key)
|
56
|
+
raise NotImplementedError, "#{__method__} has not been implemented for this #{name} index"
|
57
|
+
end
|
58
|
+
|
59
|
+
# `delete` should find a record in the collection by its indexed_value, and remove it.
|
60
|
+
def delete(key)
|
61
|
+
raise NotImplementedError, "#{__method__} has not been implemented for this #{name} index"
|
62
|
+
end
|
63
|
+
|
64
|
+
# `exists?` return whether or not an item with the given storage id exists.
|
65
|
+
def exists?(item)
|
66
|
+
raise NotImplementedError, "#{__method__} has not been implemented for this #{name} index"
|
67
|
+
end
|
68
|
+
|
69
|
+
# `where` should allow me to specify complex arguments and return an array
|
70
|
+
def where(matcher)
|
71
|
+
matcher_class = matcher.class.name.underscore
|
72
|
+
send("where_using_#{matcher_class}", matcher)
|
73
|
+
rescue NoMethodError
|
74
|
+
respond_to?(:where_using_default, true) ? where_using_default(matcher) :
|
75
|
+
raise(InvalidWhereQuery, matcher_class)
|
76
|
+
end
|
77
|
+
|
78
|
+
# `values` should return the values of the index
|
79
|
+
def values
|
80
|
+
raise NotImplementedError, "#{__method__} has not been implemented for this #{name} index"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MemoryModel
|
2
|
+
class Collection
|
3
|
+
module Initializers
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def initialize(model)
|
7
|
+
@model = model
|
8
|
+
set_primary_key :_uuid_, default: nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_index(name, options={})
|
12
|
+
type = :unique if options.delete(:unique)
|
13
|
+
type ||= options.delete(:type) || :multi
|
14
|
+
indexes[name] = Index.const_get(type.to_s.camelize).new(name, options)
|
15
|
+
rescue NameError => e
|
16
|
+
raise TypeError, "#{type.inspect} is not a valid index"
|
17
|
+
end
|
18
|
+
|
19
|
+
def indexes
|
20
|
+
@indexes ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def index_names
|
24
|
+
indexes.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_primary_key(key, options={})
|
28
|
+
if options[:auto_increment] != false && !options.has_key?(:default)
|
29
|
+
options[:auto_increment] = true
|
30
|
+
end
|
31
|
+
options[:comparable] ||= false
|
32
|
+
@model.field key, options
|
33
|
+
add_index key, type: :unique
|
34
|
+
@primary_key = key
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
|
39
|
+
def all
|
40
|
+
MemoryModel::Collection.instance_variable_get(:@all) ||
|
41
|
+
MemoryModel::Collection.instance_variable_set(:@all, [])
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|