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,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 }
|