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,63 @@
|
|
1
|
+
require 'active_support/cache'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
|
4
|
+
module MemoryModel
|
5
|
+
class Collection
|
6
|
+
class LoaderDelegate < BasicObject
|
7
|
+
include ::Enumerable
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def delegate_and_load(*methods)
|
11
|
+
methods.each do |method|
|
12
|
+
define_method method do |*args, &block|
|
13
|
+
@records.send(method, *args, &block).try(:load)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def cache
|
19
|
+
@cache ||= ::ActiveSupport::Cache.lookup_store :memory_store
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
delegate_and_load :first, :last, :sample
|
24
|
+
delegate :count, :size, :length, :present?, :blank?, to: :@records
|
25
|
+
delegate :to_s, :pretty_inspect, :inspect, to: :loaded_records
|
26
|
+
|
27
|
+
def initialize(records)
|
28
|
+
@records = records
|
29
|
+
end
|
30
|
+
|
31
|
+
def each(&block)
|
32
|
+
loaded_records.each(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def kind_of?(klass)
|
36
|
+
klass == self.class
|
37
|
+
end
|
38
|
+
|
39
|
+
alias is_a? kind_of?
|
40
|
+
|
41
|
+
def methods
|
42
|
+
LoaderDelegate.ancestors.map(&:instance_methods).flatten.uniq
|
43
|
+
end
|
44
|
+
|
45
|
+
def class
|
46
|
+
LoaderDelegate
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def loaded_records
|
52
|
+
LoaderDelegate.cache.fetch(records_digest) do
|
53
|
+
@records.map(&:load)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def records_digest
|
58
|
+
::Digest::MD5.hexdigest @records.map(&:string).join
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MemoryModel
|
2
|
+
class Collection
|
3
|
+
class MarshaledRecord
|
4
|
+
|
5
|
+
attr_reader :uuid, :string
|
6
|
+
|
7
|
+
def initialize(record)
|
8
|
+
@uuid = record._uuid_
|
9
|
+
@string = Marshal.dump record
|
10
|
+
freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def load
|
14
|
+
Marshal.load @string
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other_object)
|
18
|
+
uuid == other_object.try(:uuid)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module MemoryModel
|
2
|
+
class Collection
|
3
|
+
module Operations
|
4
|
+
|
5
|
+
def clear
|
6
|
+
indexes.each { |name, index| index.clear }
|
7
|
+
GC.start
|
8
|
+
all
|
9
|
+
end
|
10
|
+
|
11
|
+
def read_all(*ids)
|
12
|
+
return [] if ids.blank?
|
13
|
+
indexes[primary_key].values_at(*ids).compact
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_all(*uuids)
|
17
|
+
return [] if uuids.blank?
|
18
|
+
indexes[:_uuid_].values_at(*uuids).compact.map(&:load)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(item)
|
22
|
+
item._uuid_ = SecureRandom.uuid
|
23
|
+
transact item, operation: :create, rollback_with: :delete
|
24
|
+
end
|
25
|
+
|
26
|
+
def read(key)
|
27
|
+
indexes[primary_key].read(key)
|
28
|
+
end
|
29
|
+
|
30
|
+
def update(item)
|
31
|
+
transact item, operation: :update, rollback_with: :rollback
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(item)
|
35
|
+
transact item, operation: :delete
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def transact(record, options={})
|
41
|
+
# Set up the index
|
42
|
+
successful_indexes = []
|
43
|
+
|
44
|
+
# Fetch the options
|
45
|
+
operation = options[:operation] || :undefined
|
46
|
+
indexes = options[:indexes] || self.indexes.values
|
47
|
+
rollback_operation = options[:rollback_with]
|
48
|
+
|
49
|
+
# Marshal the object
|
50
|
+
marshaled_record = MarshaledRecord.new(record)
|
51
|
+
|
52
|
+
# Do the transaction
|
53
|
+
indexes.map do |index|
|
54
|
+
send("#{operation}_with_index", index, record, marshaled_record).tap do
|
55
|
+
successful_indexes << index
|
56
|
+
end
|
57
|
+
end
|
58
|
+
rescue Exception => e
|
59
|
+
transact(record, operation: rollback_operation, indexes: successful_indexes) if rollback_operation
|
60
|
+
raise e
|
61
|
+
end
|
62
|
+
|
63
|
+
## Transactors
|
64
|
+
def create_with_index(index, record, marshaled_record)
|
65
|
+
index.create record.read_attribute(index.name), marshaled_record
|
66
|
+
end
|
67
|
+
|
68
|
+
def update_with_index(index, record, marshaled_record)
|
69
|
+
index.update record.read_attribute(index.name), marshaled_record
|
70
|
+
end
|
71
|
+
|
72
|
+
def rollback_with_index(index, record, marshaled_record)
|
73
|
+
index.update record.changed_attributes[index.name], marshaled_record
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete_with_index(index, record, marshaled_record)
|
77
|
+
index.delete marshaled_record.uuid
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -1,80 +1,25 @@
|
|
1
|
-
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'active_support/hash_with_indifferent_access'
|
3
|
+
require 'active_support/dependencies/autoload'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
class << self
|
7
|
-
attr_accessor :all
|
8
|
-
end
|
9
|
-
|
10
|
-
self.all = []
|
11
|
-
|
12
|
-
def initialize(model=Class.new)
|
13
|
-
@model = model
|
14
|
-
@records = []
|
15
|
-
self.class.all << self
|
16
|
-
end
|
5
|
+
module MemoryModel
|
6
|
+
class Collection
|
7
|
+
extend ActiveSupport::Autoload
|
17
8
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
9
|
+
autoload :Index
|
10
|
+
autoload :MarshaledRecord
|
11
|
+
autoload :LoaderDelegate
|
12
|
+
autoload :Initializers
|
13
|
+
autoload :Finders
|
14
|
+
autoload :Operations
|
25
15
|
|
26
|
-
|
27
|
-
version = options[:version] || 0
|
28
|
-
return_deleted = !!options[:deleted]
|
29
|
-
record = sorted.select { |r| r.id == id }[version]
|
30
|
-
return nil unless record
|
31
|
-
if !record.deleted? || (return_deleted && record.deleted?)
|
32
|
-
record
|
33
|
-
else
|
34
|
-
raise MemoryModel::RecordNotFoundError
|
35
|
-
end
|
36
|
-
end
|
16
|
+
attr_reader :primary_key
|
37
17
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
record.freeze unless record.frozen?
|
42
|
-
@records << record
|
43
|
-
self
|
44
|
-
end
|
18
|
+
include Finders
|
19
|
+
include Initializers
|
20
|
+
include Operations
|
45
21
|
|
46
|
-
|
22
|
+
delegate *(LoaderDelegate.public_instance_methods - self.instance_methods), :size, :length, :inspect, to: :all
|
47
23
|
|
48
|
-
def inspect
|
49
|
-
self.all.inspect
|
50
24
|
end
|
51
|
-
|
52
|
-
def records(dup = true)
|
53
|
-
if dup
|
54
|
-
@records.map do |record|
|
55
|
-
record.deleted? ? record : record.dup
|
56
|
-
end
|
57
|
-
else
|
58
|
-
@records
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
def method_missing(m, *args, &block)
|
65
|
-
all.respond_to?(m) ? all.send(m, *args, &block) : super
|
66
|
-
end
|
67
|
-
|
68
|
-
def respond_to_missing?(m, include_private=false)
|
69
|
-
all.respond_to?(m, include_private)
|
70
|
-
end
|
71
|
-
|
72
|
-
def sorted(records=self.records)
|
73
|
-
records.sort { |b, a| a.timestamp <=> b.timestamp }
|
74
|
-
end
|
75
|
-
|
76
|
-
def unique(records=self.records)
|
77
|
-
sorted(records).uniq(&:id)
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
25
|
+
end
|
data/lib/memory_model/version.rb
CHANGED
data/lib/memory_model.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require "memory_model/version"
|
2
|
-
require "
|
2
|
+
require "active_support/dependencies/autoload"
|
3
3
|
|
4
4
|
module MemoryModel
|
5
|
-
|
6
|
-
autoload :Base, 'memory_model/base'
|
5
|
+
extend ActiveSupport::Autoload
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
class
|
7
|
+
autoload :Collection
|
8
|
+
autoload :Base
|
9
|
+
|
10
|
+
class Error < StandardError ; end
|
11
|
+
class ReadonlyFieldError < Error ; end
|
12
12
|
|
13
13
|
end
|
data/memory_model.gemspec
CHANGED
@@ -17,16 +17,21 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = %w{lib}
|
19
19
|
|
20
|
-
gem.add_dependency 'activemodel'
|
21
|
-
gem.add_dependency 'activesupport'
|
20
|
+
gem.add_dependency 'activemodel', '>= 3.2'
|
21
|
+
gem.add_dependency 'activesupport', '>= 3.2'
|
22
22
|
gem.add_dependency 'concerned_inheritance'
|
23
|
-
gem.add_dependency 'ice_nine'
|
24
23
|
|
24
|
+
gem.add_development_dependency 'appraisal'
|
25
25
|
gem.add_development_dependency 'guard-rspec'
|
26
|
+
gem.add_development_dependency 'guard-bundler'
|
26
27
|
gem.add_development_dependency 'rb-fsevent'
|
27
28
|
gem.add_development_dependency 'rspec'
|
28
29
|
gem.add_development_dependency 'simplecov'
|
29
30
|
gem.add_development_dependency 'terminal-notifier-guard'
|
30
31
|
gem.add_development_dependency 'test-unit'
|
32
|
+
gem.add_development_dependency 'coveralls'
|
33
|
+
gem.add_development_dependency 'pry'
|
34
|
+
gem.add_development_dependency 'colorize'
|
35
|
+
gem.add_development_dependency 'faker'
|
31
36
|
|
32
37
|
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup(:default)
|
3
|
+
require 'memory_model'
|
4
|
+
require 'colorize'
|
5
|
+
require 'faker'
|
6
|
+
|
7
|
+
class Foo < MemoryModel::Base
|
8
|
+
|
9
|
+
set_primary_key :id
|
10
|
+
|
11
|
+
field :first_name
|
12
|
+
field :last_name
|
13
|
+
field :email
|
14
|
+
field :age
|
15
|
+
|
16
|
+
add_index :last_name
|
17
|
+
add_index :first_name
|
18
|
+
add_index :email, unique: true, allow_nil: true
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def benchmark_average(count, name = nil, options={}, &block)
|
23
|
+
|
24
|
+
nominal_range = options[:range] || (1.5..4)
|
25
|
+
|
26
|
+
puts '',
|
27
|
+
"Starting benchmark of #{name} on #{count} records",
|
28
|
+
'---------',
|
29
|
+
'| '.green + "< #{nominal_range.min}ms",
|
30
|
+
'| '.yellow + "#{nominal_range.to_s}ms",
|
31
|
+
'| '.red + "> #{nominal_range.max}ms",
|
32
|
+
'---------'
|
33
|
+
times = count.times.map do
|
34
|
+
options[:before_each].call if options[:before_each].is_a? Proc
|
35
|
+
st = Time.now
|
36
|
+
block.call
|
37
|
+
(Time.now - st).tap do |seconds|
|
38
|
+
milliseconds = seconds * 1000
|
39
|
+
color = case milliseconds
|
40
|
+
when 0..nominal_range.min
|
41
|
+
:green
|
42
|
+
when nominal_range
|
43
|
+
:yellow
|
44
|
+
else
|
45
|
+
:red
|
46
|
+
end
|
47
|
+
print '|'.colorize(color)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
options[:after_all].call if options[:after_all].is_a? Proc
|
51
|
+
print "\n"
|
52
|
+
total = times.reduce(:+)
|
53
|
+
shortest_milliseconds, shortest_index = times.map { |t| t * 1000 }.each_with_index.sort { |(timea, ia), (timeb, ib)| timea <=> timeb }.first
|
54
|
+
longest_milliseconds, longest_index = times.map { |t| t * 1000 }.each_with_index.sort { |(timea, ia), (timeb, ib)| timea <=> timeb }.reverse.first
|
55
|
+
milliseconds_avg = (total / count) * 1000
|
56
|
+
|
57
|
+
color = case milliseconds_avg
|
58
|
+
when 0..nominal_range.min
|
59
|
+
:green
|
60
|
+
when nominal_range
|
61
|
+
:yellow
|
62
|
+
else
|
63
|
+
:red
|
64
|
+
end
|
65
|
+
|
66
|
+
puts '',
|
67
|
+
'------------------------------ BENCHMARK RESULTS ------------------------------',
|
68
|
+
'',
|
69
|
+
"when executing #{name}",
|
70
|
+
"#{count} times",
|
71
|
+
"executions took an average of #{milliseconds_avg} milliseconds".colorize(color),
|
72
|
+
"and a total of #{total.round(2)} seconds",
|
73
|
+
"shortest".green + " was execution ##{shortest_index} lasting #{shortest_milliseconds} milliseconds",
|
74
|
+
"longest".red + " was execution ##{longest_index} lasting #{longest_milliseconds} milliseconds",
|
75
|
+
'',
|
76
|
+
'###############################################################################'
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
def gather_attributes!
|
81
|
+
$attributes = { first_name: Faker::Name.first_name,
|
82
|
+
last_name: Faker::Name.last_name,
|
83
|
+
age: Random.rand(0..100),
|
84
|
+
email: "#{Faker::Lorem.word}_#{SecureRandom.hex(12)}@example.org"
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_record!
|
89
|
+
Foo.create gather_attributes!
|
90
|
+
end
|
91
|
+
|
92
|
+
def clear!
|
93
|
+
Foo.clear
|
94
|
+
end
|
95
|
+
|
96
|
+
n = 2500
|
97
|
+
|
98
|
+
## Creates
|
99
|
+
|
100
|
+
# Benchmark Create
|
101
|
+
benchmark_average(n, '.create', before_each: -> { gather_attributes! }, after_all: -> { clear! }) { Foo.create $attributes }
|
102
|
+
|
103
|
+
## Reads
|
104
|
+
|
105
|
+
# Benchmark Find
|
106
|
+
require 'pry'
|
107
|
+
benchmark_average(n, '.find', before_each: -> { create_record!; $id = Foo.sample.id }, after_all: -> { clear! }) { Foo.find($id) }
|
108
|
+
|
109
|
+
# Benchmark Sample
|
110
|
+
benchmark_average(n, '.sample', before_each: -> { create_record! }, after_all: -> { clear! }) { Foo.sample }
|
111
|
+
|
112
|
+
# Benchmark Where (using index)
|
113
|
+
benchmark_average(n, '.where (using index)', before_each: -> { create_record! }, after_all: -> { clear! }) { Foo.where(first_name: Faker::Name.first_name) }
|
114
|
+
|
115
|
+
# Benchmark Where (using loading)
|
116
|
+
benchmark_average(1000, '.where (using loading)', before_each: -> { create_record! }, after_all: -> { clear! }) { Foo.where(age: Random.rand(0..100)) }
|
117
|
+
|
118
|
+
## Updates
|
119
|
+
|
120
|
+
# Benchmark Update
|
121
|
+
benchmark_average(n, '.update', before_each: -> { $record = create_record! }, after_all: -> { clear! }) { $record.save }
|
122
|
+
|
123
|
+
## Deletes
|
124
|
+
|
125
|
+
# Benchmark Delete
|
126
|
+
benchmark_average(n, '.delete', before_each: -> { 2.times { create_record! } ; $record = Foo.sample }, after_all: -> { clear! }) { $record.delete }
|