mementus 0.0.1 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/maetl/mementus.svg?branch=master)](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
|