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