mementus 0.0.1 → 0.1.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.
- checksums.yaml +8 -8
- data/.travis.yml +10 -0
- data/README.md +60 -2
- data/lib/mementus/model.rb +105 -0
- data/lib/mementus/version.rb +1 -1
- data/lib/mementus.rb +4 -4
- data/mementus.gemspec +4 -0
- data/spec/attribute_spec.rb +40 -0
- data/spec/collection_spec.rb +32 -0
- data/spec/mapping_spec.rb +35 -0
- data/spec/spec_helper.rb +6 -0
- metadata +55 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
Mzg4MDg1OTYxZjllZGJiMWM1OGFjODc5MTcyNDQ1YzIyYmU3NzgwMA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YjcyZWZiNGE0ZTllMWU2MzI1Nzc1Yjg1ZDlkMGUzZGQxMTkyOWJhNg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YjIyNDEyNTQyZWQwYzgzMzhlMzEzYTI5MzQyZDQ2YjE3MGQwYTZmMTljY2Vm
|
10
|
+
MTIxZGVhMDJmYjQ2NWE3MTFjYWU0YjQxYWUyYTgyZTAxODEwM2UwNmIyNzM5
|
11
|
+
YzI3MzI2ZmY4NjU5YmEyMTA4OTg3ZDBlNDM3MzFjZGY3MTVmZTg=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NjZjOGRkZWZhMjE0MDVhM2NhZjk1NDg3NDE3N2JiYzBmMGVmMGY4NDQ5M2Zj
|
14
|
+
N2Y0ZWQ5Y2QyNGRmZWY4YzdhOTJkOWIxNzA5OTYyY2JmOGZmZTJhNWM0ZGU0
|
15
|
+
ZGI3N2NlYzYxYWQwZDUyMDY2NTU2ZjRkMmQwMmIxZTNkNTlhYzc=
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,6 +1,21 @@
|
|
1
1
|
# Mementus
|
2
2
|
|
3
|
-
|
3
|
+
[](https://travis-ci.org/maetl/mementus)
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
# Proof of concept for a toy ORM that combines some aspects of the
|
7
|
+
# ActiveRecord API with an in-memory query model based on the Axiom
|
8
|
+
# relational algebra API.
|
9
|
+
#
|
10
|
+
# The weirdest trick is that the data stored in the relational model
|
11
|
+
# is a read-only index that never gets re-materialised back into the
|
12
|
+
# model objects themselves (though Axiom does seem to be capable of
|
13
|
+
# doing this).
|
14
|
+
#
|
15
|
+
# Instead of returning mapped data from queries, the Ruby object_id
|
16
|
+
# is used as a reference to point to the existing instance in the
|
17
|
+
# runtime object space.
|
18
|
+
```
|
4
19
|
|
5
20
|
## Installation
|
6
21
|
|
@@ -18,7 +33,50 @@ Or install it yourself as:
|
|
18
33
|
|
19
34
|
## Usage
|
20
35
|
|
21
|
-
|
36
|
+
To define model classes, inherit from the `Mementus::Model` base class and add attributes using the [Virtus API](https://github.com/solnic/virtus):
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class Book < Mementus::Model
|
40
|
+
attribute :title, String
|
41
|
+
attribute :author, String
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Create new instances by passing data through the constructor or assigning attributes directly:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
book1 = Book.new(
|
49
|
+
:title => "Gravity's Rainbow",
|
50
|
+
:author => "Thomas Pynchon"
|
51
|
+
)
|
52
|
+
|
53
|
+
book2 = Book.new
|
54
|
+
book2.title = "The Golden Notebook"
|
55
|
+
book2.author = "Doris Lessing"
|
56
|
+
```
|
57
|
+
|
58
|
+
To write objects to the in-memory index, call their `create` method:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
book3 = Book.new(
|
62
|
+
:title => "Crash",
|
63
|
+
:author => "J.G. Ballard"
|
64
|
+
)
|
65
|
+
|
66
|
+
book3.create
|
67
|
+
```
|
68
|
+
|
69
|
+
The query API is currently being worked on. Right now to test it out, you can access the relational collection for each model through the `collection` method, and execute basic matching queries using the `where` method:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
Book.collection.count
|
73
|
+
|
74
|
+
Book.collection.first
|
75
|
+
|
76
|
+
Book.where(author: "Doris Lessing")
|
77
|
+
|
78
|
+
Book.where(title: "Crash")
|
79
|
+
```
|
22
80
|
|
23
81
|
## Contributing
|
24
82
|
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
require 'axiom-memory-adapter'
|
3
|
+
|
4
|
+
module Mementus
|
5
|
+
|
6
|
+
# Proof of concept for a toy ORM that combines some aspects of the
|
7
|
+
# ActiveRecord API with an in-memory query model based on the Axiom
|
8
|
+
# relational algebra API.
|
9
|
+
#
|
10
|
+
# The weirdest trick is that the data stored in the relational model
|
11
|
+
# is a read-only index that never gets re-materialised back into the
|
12
|
+
# model objects themselves (though Axiom does seem to be capable of
|
13
|
+
# doing this).
|
14
|
+
#
|
15
|
+
# Instead of returning mapped data from queries, the Ruby object_id
|
16
|
+
# is used as a reference to point to the existing instance in the
|
17
|
+
# runtime object space.
|
18
|
+
#
|
19
|
+
class Model
|
20
|
+
|
21
|
+
@@local_storage = nil
|
22
|
+
@@model_registry = {}
|
23
|
+
|
24
|
+
def self.name_to_sym
|
25
|
+
name_without_namespace = name.split("::").last
|
26
|
+
name_without_namespace.gsub(/([^\^])([A-Z])/,'\1_\2').downcase.to_sym
|
27
|
+
end
|
28
|
+
|
29
|
+
# TODO: this mapping could be based on declared indexes,
|
30
|
+
# rather than dumping the entire attribute schema here.
|
31
|
+
#
|
32
|
+
def schema_tuple
|
33
|
+
tuple = [[:__object_id, String]]
|
34
|
+
attribute_set.each do |attribute_type|
|
35
|
+
tuple << [attribute_type.name.to_sym, attribute_type.primitive]
|
36
|
+
end
|
37
|
+
tuple
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: this mapping could be based on declared indexes,
|
41
|
+
# rather than dumping the entire attribute data set here.
|
42
|
+
#
|
43
|
+
def values_tuple
|
44
|
+
tuple = [object_id.to_s]
|
45
|
+
attributes.each do |_, attribute_value|
|
46
|
+
tuple << attribute_value
|
47
|
+
end
|
48
|
+
tuple
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.inherited(concrete_model)
|
52
|
+
concrete_model.send(:include, Virtus.model)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Create only writes to memory. It's required in order to
|
56
|
+
# index the object's attributes in the query model.
|
57
|
+
#
|
58
|
+
def create
|
59
|
+
ensure_registered(self)
|
60
|
+
local_storage[self.class.name_to_sym].insert([values_tuple])
|
61
|
+
end
|
62
|
+
|
63
|
+
def ensure_registered(model)
|
64
|
+
register(model) unless registered?(model)
|
65
|
+
end
|
66
|
+
|
67
|
+
def registered?(model)
|
68
|
+
model_registry.has_key? model.class.name_to_sym
|
69
|
+
end
|
70
|
+
|
71
|
+
def register(model)
|
72
|
+
model_id = model.class.name_to_sym
|
73
|
+
model_registry[model_id] = true
|
74
|
+
local_storage[model_id] = Axiom::Relation.new(model.schema_tuple)
|
75
|
+
end
|
76
|
+
|
77
|
+
def model_registry
|
78
|
+
@@model_registry
|
79
|
+
end
|
80
|
+
|
81
|
+
def local_storage
|
82
|
+
unless @@local_storage
|
83
|
+
@@local_storage = Axiom::Adapter::Memory.new
|
84
|
+
end
|
85
|
+
@@local_storage
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.collection
|
89
|
+
@@local_storage[name_to_sym]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Stub implementation that demonstrates the ObjectSpace
|
93
|
+
# reference grabbing.
|
94
|
+
#
|
95
|
+
# Currently just splats to an array instead of handing back
|
96
|
+
# a chainable scope object.
|
97
|
+
#
|
98
|
+
def self.where(constraints)
|
99
|
+
self.collection.restrict(constraints).inject([]) do |list, relation|
|
100
|
+
list << ObjectSpace._id2ref(relation[:__object_id].to_i)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
data/lib/mementus/version.rb
CHANGED
data/lib/mementus.rb
CHANGED
data/mementus.gemspec
CHANGED
@@ -18,6 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_runtime_dependency "virtus"
|
22
|
+
spec.add_runtime_dependency "axiom-memory-adapter"
|
23
|
+
|
21
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
25
|
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
23
27
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mementus::Model do
|
4
|
+
|
5
|
+
describe "Attributes" do
|
6
|
+
|
7
|
+
class PrimaryAttributes < Mementus::Model
|
8
|
+
attribute :name, String
|
9
|
+
attribute :count, Integer
|
10
|
+
attribute :switch, Boolean
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:model) {
|
14
|
+
PrimaryAttributes.new(
|
15
|
+
:name => "a string",
|
16
|
+
:count => 500,
|
17
|
+
:switch => false
|
18
|
+
)
|
19
|
+
}
|
20
|
+
|
21
|
+
it "reads attributes" do
|
22
|
+
expect(model.name).to eq "a string"
|
23
|
+
expect(model.count).to eq 500
|
24
|
+
expect(model.switch).to eq false
|
25
|
+
end
|
26
|
+
|
27
|
+
it "writes attributes" do
|
28
|
+
model.name = "stringstringstring"
|
29
|
+
model.count = 488
|
30
|
+
model.switch = true
|
31
|
+
|
32
|
+
expect(model.name).to eq "stringstringstring"
|
33
|
+
expect(model.count).to eq 488
|
34
|
+
expect(model.switch).to eq true
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mementus::Model do
|
4
|
+
|
5
|
+
describe "Collection" do
|
6
|
+
|
7
|
+
class Item < Mementus::Model
|
8
|
+
attribute :name, String
|
9
|
+
attribute :order, Integer
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:all) do
|
13
|
+
20.times do |i|
|
14
|
+
item = Item.new
|
15
|
+
item.name = "Item: #{i.to_s}"
|
16
|
+
item.order = i + 1
|
17
|
+
item.create
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "counts created items" do
|
22
|
+
expect(Item.collection.count).to eq 20
|
23
|
+
end
|
24
|
+
|
25
|
+
it "finds item by matching attribute" do
|
26
|
+
expect(Item.where(order: 10).count).to eq 1
|
27
|
+
expect(Item.where(order: 10).first.name).to eq "Item: 9"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mementus::Model do
|
4
|
+
|
5
|
+
describe "Mapping" do
|
6
|
+
|
7
|
+
class Mapping < Mementus::Model
|
8
|
+
attribute :description, String
|
9
|
+
attribute :number, Integer
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:mapping) {
|
13
|
+
Mapping.new(
|
14
|
+
:description => "Hello world.",
|
15
|
+
:number => 2014
|
16
|
+
)
|
17
|
+
}
|
18
|
+
|
19
|
+
let(:object_id) {
|
20
|
+
mapping.object_id.to_s
|
21
|
+
}
|
22
|
+
|
23
|
+
it "maps attribute schema to tuple" do
|
24
|
+
schema = [[:__object_id, String], [:description, String], [:number, Integer]]
|
25
|
+
expect(mapping.schema_tuple).to eq schema
|
26
|
+
end
|
27
|
+
|
28
|
+
it "maps attribute values to tuple" do
|
29
|
+
values = [object_id, "Hello world.", 2014]
|
30
|
+
expect(mapping.values_tuple).to eq values
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mementus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- maetl
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06
|
11
|
+
date: 2014-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: virtus
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: axiom-memory-adapter
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: bundler
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +66,20 @@ dependencies:
|
|
38
66
|
- - ! '>='
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
41
83
|
description: In-memory data model
|
42
84
|
email:
|
43
85
|
- me@maetl.net
|
@@ -46,13 +88,19 @@ extensions: []
|
|
46
88
|
extra_rdoc_files: []
|
47
89
|
files:
|
48
90
|
- .gitignore
|
91
|
+
- .travis.yml
|
49
92
|
- Gemfile
|
50
93
|
- LICENSE.txt
|
51
94
|
- README.md
|
52
95
|
- Rakefile
|
53
96
|
- lib/mementus.rb
|
97
|
+
- lib/mementus/model.rb
|
54
98
|
- lib/mementus/version.rb
|
55
99
|
- mementus.gemspec
|
100
|
+
- spec/attribute_spec.rb
|
101
|
+
- spec/collection_spec.rb
|
102
|
+
- spec/mapping_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
56
104
|
homepage: ''
|
57
105
|
licenses:
|
58
106
|
- MIT
|
@@ -77,4 +125,8 @@ rubygems_version: 2.1.8
|
|
77
125
|
signing_key:
|
78
126
|
specification_version: 4
|
79
127
|
summary: In-memory data model
|
80
|
-
test_files:
|
128
|
+
test_files:
|
129
|
+
- spec/attribute_spec.rb
|
130
|
+
- spec/collection_spec.rb
|
131
|
+
- spec/mapping_spec.rb
|
132
|
+
- spec/spec_helper.rb
|