memorydb 0.0.1

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.
@@ -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: []