memorydb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ require 'memorydb/db'
2
+
3
+ module MemoryDb
4
+
5
+ def self.new(model_klass, options={})
6
+ Db.new(model_klass, options)
7
+ end
8
+
9
+ end
@@ -0,0 +1,55 @@
1
+ require 'time'
2
+ require 'memorydb/cursor'
3
+ require 'memorydb/filter_factory'
4
+
5
+ module MemoryDb
6
+ class Base
7
+
8
+ def find
9
+ cursor
10
+ end
11
+
12
+ def filter
13
+ @filter_factory ||= FilterFactory.new
14
+ end
15
+
16
+ def remove_by_id!(id)
17
+ if model = find_by_id(id)
18
+ remove_model!(model)
19
+ else
20
+ raise ArgumentError.new("Could not remove record with id: #{id} because it does not exist")
21
+ end
22
+ end
23
+
24
+ def remove!(model=nil)
25
+ if model
26
+ remove_by_id!(model.send(primary_key))
27
+ else
28
+ cursor.remove!
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :model_klass
35
+
36
+ def cursor
37
+ Cursor.new(self)
38
+ end
39
+
40
+ def model_or_hash_as_attrs(model_or_hash)
41
+ if model_or_hash
42
+ if model_or_hash.is_a?(Hash)
43
+ model_or_hash
44
+ elsif model_or_hash.is_a?(model_klass)
45
+ model_or_hash.attributes
46
+ else
47
+ raise ArgumentError.new("A hash or a #{model_klass} must be given to create a record")
48
+ end
49
+ else
50
+ {}
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,168 @@
1
+ require 'memorydb/filter'
2
+ require 'memorydb/sort'
3
+ require 'memorydb/filter_factory'
4
+
5
+ module MemoryDb
6
+ class Cursor
7
+
8
+ def initialize(query_executor)
9
+ @query_executor = query_executor
10
+ @filters = []
11
+ @sorts = []
12
+ end
13
+
14
+ def eq(field, value)
15
+ @filters << filter_factory.eq(field, value)
16
+ self
17
+ end
18
+
19
+ def not_eq(field, value)
20
+ @filters << filter_factory.not_eq(field, value)
21
+ self
22
+ end
23
+
24
+ def lt(field, value)
25
+ @filters << filter_factory.lt(field, value)
26
+ self
27
+ end
28
+
29
+ def lte(field, value)
30
+ @filters << filter_factory.lte(field, value)
31
+ self
32
+ end
33
+
34
+ def gt(field, value)
35
+ @filters << filter_factory.gt(field, value)
36
+ self
37
+ end
38
+
39
+ def gte(field, value)
40
+ @filters << filter_factory.gte(field, value)
41
+ self
42
+ end
43
+
44
+ def in(field, value)
45
+ @filters << filter_factory.in(field, value)
46
+ self
47
+ end
48
+
49
+ def not_in(field, value)
50
+ @filters << filter_factory.not_in(field, value)
51
+ self
52
+ end
53
+
54
+ def like(field, value)
55
+ @filters << filter_factory.like(field, value)
56
+ self
57
+ end
58
+
59
+ def or(*filters)
60
+ @filters << filter_factory.or(*filters)
61
+ self
62
+ end
63
+
64
+ def sort(field, order)
65
+ assert_field!(field)
66
+ assert_order!(order)
67
+ @sorts << Sort.new(field, order.to_sym)
68
+ self
69
+ end
70
+
71
+ def limit(limit)
72
+ if limit
73
+ assert_int!('Limit', limit)
74
+ @limit = limit.to_i
75
+ end
76
+ self
77
+ end
78
+
79
+ def offset(offset)
80
+ if offset
81
+ assert_int!('Offset', offset)
82
+ @offset = offset.to_i
83
+ end
84
+ self
85
+ end
86
+
87
+ def count
88
+ query_executor.execute_count(query)
89
+ end
90
+
91
+ def all
92
+ query_executor.execute_find(query)
93
+ end
94
+
95
+ def first
96
+ query_executor.execute_find(
97
+ query.merge(
98
+ sorts: sorts_for_first,
99
+ limit: 1
100
+ )).first
101
+ end
102
+
103
+ def last
104
+ query_executor.execute_find(
105
+ query.merge(
106
+ sorts: sorts_for_last,
107
+ limit: 1
108
+ )).first
109
+ end
110
+
111
+ def remove!
112
+ query_executor.execute_remove!(query)
113
+ end
114
+
115
+ def first
116
+ query_executor.execute_find(
117
+ query.merge(
118
+ limit: 1
119
+ )).first
120
+ end
121
+
122
+ def query
123
+ {
124
+ :type => @type,
125
+ :filters => @filters,
126
+ :sorts => @sorts,
127
+ :offset => @offset,
128
+ :limit => @limit
129
+ }
130
+ end
131
+
132
+ private
133
+
134
+ attr_reader :query_executor
135
+
136
+ def filter_factory
137
+ @filter_factory ||= FilterFactory.new
138
+ end
139
+
140
+ def sorts_for_first
141
+ @sorts
142
+ end
143
+
144
+ def sorts_for_last
145
+ sorts_for_first.map do |sort|
146
+ Sort.new(sort.field, sort.order == :asc ? :desc : :asc)
147
+ end
148
+ end
149
+
150
+ def assert_field!(field)
151
+ unless field.is_a?(String) || field.is_a?(Symbol)
152
+ raise ArgumentError.new "Field name must be a String or Symbol but you gave #{PP.pp(field, '')}"
153
+ end
154
+ end
155
+
156
+ def assert_order!(order)
157
+ unless [:asc, :desc, 'asc', 'desc'].include?(order)
158
+ raise ArgumentError.new "Sort order must be 'asc' or 'desc' but you gave #{PP.pp(order, '')}"
159
+ end
160
+ end
161
+
162
+ def assert_int!(name, num)
163
+ unless num.is_a?(Integer) || (num.is_a?(String) && num =~ /\d+/)
164
+ raise ArgumentError.new "#{name} must be an integer but you gave #{PP.pp(num, '')}"
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,206 @@
1
+ require 'multi_json'
2
+ require 'memorydb/base'
3
+ require 'memorydb/cursor'
4
+
5
+ # for sorting on booleans
6
+ class FalseClass
7
+ def <(other)
8
+ other
9
+ end
10
+
11
+ def >(other)
12
+ false
13
+ end
14
+ end
15
+
16
+ class TrueClass
17
+ def <(other)
18
+ other
19
+ end
20
+
21
+ def >(other)
22
+ true
23
+ end
24
+ end
25
+
26
+ module MemoryDb
27
+ class Db < Base
28
+
29
+ def initialize(model_klass, options={})
30
+ @model_klass = model_klass
31
+ @id = 0
32
+ @store = {}
33
+ @primary_key = options[:primary_key]
34
+ end
35
+
36
+ def create!(attrs={})
37
+ _id = id
38
+ attrs = model_or_hash_as_attrs(attrs)
39
+ verify_attributes!(attrs)
40
+ attributes = attrs.merge(primary_key => _id)
41
+ store!(_id, attributes)
42
+ model_klass.new(attributes)
43
+ end
44
+
45
+ def update!(model_or_id, attributes={})
46
+ attributes ||= {}
47
+ model = case
48
+ when model_or_id.is_a?(model_klass)
49
+ if model = find_by_id(model_or_id.send(primary_key))
50
+ model_or_id
51
+ else
52
+ raise ArgumentError.new("Could not update record with id: #{model_or_id.send(primary_key)} because it does not exist") unless model
53
+ end
54
+
55
+ else
56
+ if model = find_by_id(model_or_id)
57
+ model
58
+ else
59
+ raise ArgumentError.new("Could not update record with id: #{model_or_id} because it does not exist") unless model
60
+ end
61
+ end
62
+ updated_attrs = model.attributes.merge(attributes_without_pkey(attributes))
63
+ store!(model.send(primary_key), updated_attrs)
64
+ model_klass.new(updated_attrs)
65
+ end
66
+
67
+ def find_by_id(id)
68
+ find.eq(primary_key, id).first
69
+ end
70
+
71
+ ###########################
72
+ # These methods are called by the cursor. Do not call them directly. Or call them. Whatever. Either one.
73
+
74
+ def raw_find(query)
75
+ models = all_models
76
+ models = apply_transforms models, query[:transforms]
77
+ models = filter_models models, query[:filters]
78
+ models = sort_models models, query[:sorts]
79
+ models = offset_models models, query[:offset]
80
+ models = limit_models models, query[:limit]
81
+ end
82
+
83
+ def execute_find(query)
84
+ models = raw_find(query)
85
+ models.map { |h| model_klass.new(h) }
86
+ end
87
+
88
+ def execute_count(query)
89
+ execute_find(query).size
90
+ end
91
+
92
+ def execute_remove!(query)
93
+ execute_find(query).each do |model|
94
+ remove_model!(model)
95
+ end
96
+ end
97
+
98
+ ###########################
99
+
100
+ private
101
+
102
+ attr_reader :primary_key
103
+
104
+ def verify_attributes!(attrs)
105
+ null_model = model_klass.new
106
+ allowed_attributes = null_model.attributes.keys.map(&:to_sym)
107
+ attrs.each do |key, value|
108
+ unless allowed_attributes.include?(key.to_sym)
109
+ raise ArgumentError.new("Unknown attribute: #{key}")
110
+ end
111
+ end
112
+ end
113
+
114
+ def attributes_without_pkey(attributes)
115
+ attributes.reject { |k, v| k == primary_key }
116
+ end
117
+
118
+ def apply_transforms(models, transforms)
119
+ models.map do |model|
120
+ (transforms || []).reduce(model) do |model, transform|
121
+ transform.call(model)
122
+ end
123
+ end
124
+ end
125
+
126
+ def filter_models(models, filters)
127
+ models.select do |model|
128
+ (filters || []).all? do |filter|
129
+ filter_matches?(filter, model)
130
+ end
131
+ end
132
+ end
133
+
134
+ def filter_matches?(filter, model)
135
+ value = model[filter.field]
136
+ case filter.operator
137
+ when '='; value == filter.value
138
+ when '!='; value != filter.value
139
+ when '<'; value && value < filter.value
140
+ when '<='; value && value <= filter.value
141
+ when '>'; value && value > filter.value
142
+ when '>='; value && value >= filter.value
143
+ when 'in'; filter.value.include?(value)
144
+ when '!in'; !filter.value.include?(value)
145
+ when 'or'; filter.value.any? do |sub_filter|
146
+ filter_matches?(sub_filter, model)
147
+ end
148
+ when 'like'; value =~ /#{filter.value}/i
149
+ end
150
+ end
151
+
152
+ def sort_models(models, sorts)
153
+ models.sort { |model1, model2| compare_models(model1, model2, sorts) }
154
+ end
155
+
156
+ def compare_models(model1, model2, sorts)
157
+ sorts.each do |sort|
158
+ result = compare_model(model1, model2, sort)
159
+ return result if result
160
+ end
161
+ 0
162
+ end
163
+
164
+ def compare_model(model1, model2, sort)
165
+ field1, field2 = model1[sort.field], model2[sort.field]
166
+ field1 == field2 ? nil :
167
+ field1 < field2 && sort.order == :asc ? -1 :
168
+ field1 > field2 && sort.order == :desc ? -1 : 1
169
+ end
170
+
171
+ def limit_models(models, limit)
172
+ if limit
173
+ models.take(limit)
174
+ else
175
+ models
176
+ end
177
+ end
178
+
179
+ def offset_models(models, offset)
180
+ if offset
181
+ models.drop(offset)
182
+ else
183
+ models
184
+ end
185
+ end
186
+
187
+ def all_models
188
+ @store.values.map do |raw_record|
189
+ model_klass.new(MultiJson.load(raw_record)).attributes
190
+ end
191
+ end
192
+
193
+ def store!(id, attributes)
194
+ @store[id] = MultiJson.dump(attributes)
195
+ end
196
+
197
+ def remove_model!(model)
198
+ @store.delete(model.send(primary_key))
199
+ nil
200
+ end
201
+
202
+ def id
203
+ @id += 1
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,13 @@
1
+ module MemoryDb
2
+ class Filter
3
+
4
+ attr_reader :field, :operator, :value
5
+
6
+ def initialize(field, operator, value)
7
+ @field = field
8
+ @operator = operator
9
+ @value = value
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ require 'memorydb/filter'
2
+
3
+ module MemoryDb
4
+ class FilterFactory
5
+
6
+ def eq(field, value)
7
+ assert_field!(field)
8
+ Filter.new(field, '=', value)
9
+ end
10
+
11
+ def not_eq(field, value)
12
+ assert_field!(field)
13
+ Filter.new(field, '!=', value)
14
+ end
15
+
16
+ def lt(field, value)
17
+ assert_field!(field)
18
+ assert_not_nil!('Less than', value)
19
+ Filter.new(field, '<', value)
20
+ end
21
+
22
+ def lte(field, value)
23
+ assert_field!(field)
24
+ assert_not_nil!('Less than or equal to', value)
25
+ Filter.new(field, '<=', value)
26
+ end
27
+
28
+ def gt(field, value)
29
+ assert_field!(field)
30
+ assert_not_nil!('Greater than', value)
31
+ Filter.new(field, '>', value)
32
+ end
33
+
34
+ def gte(field, value)
35
+ assert_field!(field)
36
+ assert_not_nil!('Greater than or equal to', value)
37
+ Filter.new(field, '>=', value)
38
+ end
39
+
40
+ def in(field, value)
41
+ assert_field!(field)
42
+ assert_to_a!('Inclusion', value)
43
+ Filter.new(field, 'in', value.to_a)
44
+ end
45
+
46
+ def not_in(field, value)
47
+ assert_field!(field)
48
+ assert_to_a!('Exclusion', value)
49
+ Filter.new(field, '!in', value.to_a)
50
+ end
51
+
52
+ def like(field, value)
53
+ assert_field!(field)
54
+ assert_str_or_sym!(value, 'Value')
55
+ Filter.new(field, 'like', value)
56
+ end
57
+
58
+ def or(*filters)
59
+ Filter.new(nil, 'or', filters)
60
+ end
61
+
62
+ private
63
+
64
+ def assert_to_a!(name, value)
65
+ unless value.respond_to?(:to_a)
66
+ raise ArgumentError.new "#{name} filter value must respond to to_a but #{PP.pp(value, '')} does not"
67
+ end
68
+ end
69
+
70
+ def assert_not_nil!(name, value)
71
+ if value.nil?
72
+ raise ArgumentError.new "#{name} filter value cannot be nil"
73
+ end
74
+ end
75
+
76
+ def assert_field!(field)
77
+ assert_str_or_sym!(field, 'Field name')
78
+ end
79
+
80
+ def assert_str_or_sym!(value, name)
81
+ unless value.is_a?(String) || value.is_a?(Symbol)
82
+ raise ArgumentError.new "#{name} must be a String or Symbol but you gave #{PP.pp(value, '')}"
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,12 @@
1
+ module MemoryDb
2
+ class Sort
3
+
4
+ attr_reader :field, :order
5
+
6
+ def initialize(field, order)
7
+ @field = field
8
+ @order = order
9
+ end
10
+
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memorydb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Myles Megyesi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: multi_json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.7.6
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.7.6
30
+ - !ruby/object:Gem::Dependency
31
+ name: multi_json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.5.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.5.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 10.0.3
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 10.0.3
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.12.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.12.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: virtus
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.5.3
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.5.3
94
+ description: An in memory database
95
+ email:
96
+ - myles.megyesi@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - lib/memorydb/db.rb
102
+ - lib/memorydb/base.rb
103
+ - lib/memorydb/filter_factory.rb
104
+ - lib/memorydb/sort.rb
105
+ - lib/memorydb/filter.rb
106
+ - lib/memorydb/cursor.rb
107
+ - lib/memorydb.rb
108
+ homepage:
109
+ licenses: []
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 1.8.24
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: An in memory database
132
+ test_files: []