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.
Files changed (70) hide show
  1. checksums.yaml +15 -0
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +7 -4
  4. data/Appraisals +9 -0
  5. data/Guardfile +7 -1
  6. data/README.md +5 -1
  7. data/Rakefile +12 -0
  8. data/gemfiles/rails_3.gemfile +8 -0
  9. data/gemfiles/rails_3.gemfile.lock +109 -0
  10. data/gemfiles/rails_4.gemfile +8 -0
  11. data/gemfiles/rails_4.gemfile.lock +117 -0
  12. data/lib/memory_model/base/actions/class_methods.rb +27 -0
  13. data/lib/memory_model/base/actions.rb +75 -0
  14. data/lib/memory_model/base/attributes.rb +87 -0
  15. data/lib/memory_model/base/auto_increment.rb +47 -0
  16. data/lib/memory_model/base/collectible.rb +26 -0
  17. data/lib/memory_model/base/conversion.rb +11 -0
  18. data/lib/memory_model/base/fields/field.rb +57 -0
  19. data/lib/memory_model/base/fields/field_set.rb +87 -0
  20. data/lib/memory_model/base/fields.rb +49 -0
  21. data/lib/memory_model/base/operations/comparisons.rb +25 -0
  22. data/lib/memory_model/base/operations.rb +10 -0
  23. data/lib/memory_model/base/persistence.rb +13 -11
  24. data/lib/memory_model/base.rb +58 -43
  25. data/lib/memory_model/collection/finders.rb +75 -0
  26. data/lib/memory_model/collection/index/multi.rb +61 -0
  27. data/lib/memory_model/collection/index/unique.rb +79 -0
  28. data/lib/memory_model/collection/index.rb +86 -0
  29. data/lib/memory_model/collection/initializers.rb +48 -0
  30. data/lib/memory_model/collection/loader_delegate.rb +63 -0
  31. data/lib/memory_model/collection/marshaled_record.rb +23 -0
  32. data/lib/memory_model/collection/operations.rb +82 -0
  33. data/lib/memory_model/collection.rb +18 -73
  34. data/lib/memory_model/version.rb +1 -1
  35. data/lib/memory_model.rb +7 -7
  36. data/memory_model.gemspec +8 -3
  37. data/spec/benchmark/benchmark.rb +126 -0
  38. data/spec/memory_model/base/{actionable_spec.rb → actions_spec.rb} +34 -103
  39. data/spec/memory_model/base/{attributable_spec.rb → attributes_spec.rb} +4 -6
  40. data/spec/memory_model/base/{collectable_spec.rb → collectible_spec.rb} +1 -1
  41. data/spec/memory_model/base/fieldable/field_set_spec.rb +23 -37
  42. data/spec/memory_model/base/fieldable/field_spec.rb +16 -16
  43. data/spec/memory_model/base/{fieldable_spec.rb → fields_spec.rb} +1 -1
  44. data/spec/memory_model/base/{comparable_spec.rb → operations/comparisons_spec.rb} +4 -4
  45. data/spec/memory_model/base/persistence_spec.rb +2 -2
  46. data/spec/memory_model/base_spec.rb +10 -9
  47. data/spec/memory_model/collection_spec.rb +24 -146
  48. data/spec/spec_helper.rb +11 -0
  49. data/spec/support/delegate_matcher.rb +40 -0
  50. metadata +120 -65
  51. data/.idea/.rakeTasks +0 -7
  52. data/.idea/dictionaries/jwaldrip.xml +0 -3
  53. data/.idea/encodings.xml +0 -5
  54. data/.idea/memory_model.iml +0 -47
  55. data/.idea/misc.xml +0 -8
  56. data/.idea/modules.xml +0 -9
  57. data/.idea/scopes/scope_settings.xml +0 -5
  58. data/.idea/vcs.xml +0 -7
  59. data/.idea/workspace.xml +0 -701
  60. data/lib/memory_model/base/actionable.rb +0 -95
  61. data/lib/memory_model/base/attributable.rb +0 -76
  62. data/lib/memory_model/base/collectable.rb +0 -22
  63. data/lib/memory_model/base/comparable.rb +0 -16
  64. data/lib/memory_model/base/fieldable/field.rb +0 -35
  65. data/lib/memory_model/base/fieldable/field_set.rb +0 -74
  66. data/lib/memory_model/base/fieldable.rb +0 -45
  67. data/lib/memory_model/base/versionable.rb +0 -17
  68. data/lib/memory_model/core_ext/object.rb +0 -5
  69. data/spec/memory_model/base/versionable_spec.rb +0 -31
  70. 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
- class MemoryModel::Collection
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
- class InvalidTypeError < StandardError;
4
- end
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
- def all
19
- unique.reject(&:deleted?)
20
- end
21
-
22
- def deleted
23
- unique.select(&:deleted?)
24
- end
9
+ autoload :Index
10
+ autoload :MarshaledRecord
11
+ autoload :LoaderDelegate
12
+ autoload :Initializers
13
+ autoload :Finders
14
+ autoload :Operations
25
15
 
26
- def find(id, options={ })
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
- def insert(record)
39
- raise InvalidTypeError unless record.is_a? @model
40
- record = record.dup
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
- alias :<< :insert
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
@@ -1,3 +1,3 @@
1
1
  module MemoryModel
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/memory_model.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  require "memory_model/version"
2
- require "memory_model/core_ext/object"
2
+ require "active_support/dependencies/autoload"
3
3
 
4
4
  module MemoryModel
5
- autoload :Collection, 'memory_model/collection'
6
- autoload :Base, 'memory_model/base'
5
+ extend ActiveSupport::Autoload
7
6
 
8
- class InvalidCollectionError < StandardError ; end
9
- class InvalidFieldError < StandardError ; end
10
- class ReadonlyFieldError < StandardError ; end
11
- class RecordNotFoundError < StandardError ; end
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 }