memory_model 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|