perpetuity 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +16 -0
- data/LICENSE +7 -0
- data/README.md +50 -8
- data/lib/perpetuity/attribute.rb +5 -1
- data/lib/perpetuity/attribute_set.rb +1 -1
- data/lib/perpetuity/mapper.rb +53 -71
- data/lib/perpetuity/mapper_registry.rb +17 -0
- data/lib/perpetuity/mongodb/index.rb +48 -0
- data/lib/perpetuity/mongodb/query_expression.rb +11 -0
- data/lib/perpetuity/mongodb/query_intersection.rb +16 -0
- data/lib/perpetuity/mongodb/query_union.rb +16 -0
- data/lib/perpetuity/mongodb.rb +44 -7
- data/lib/perpetuity/serializer.rb +75 -0
- data/lib/perpetuity/version.rb +1 -1
- data/lib/perpetuity.rb +2 -2
- data/perpetuity.gemspec +2 -3
- data/spec/perpetuity/mapper_registry_spec.rb +15 -0
- data/spec/perpetuity/mapper_spec.rb +17 -11
- data/spec/perpetuity/mongodb/index_spec.rb +44 -0
- data/spec/perpetuity/mongodb/query_expression_spec.rb +18 -0
- data/spec/perpetuity/mongodb/query_intersection_spec.rb +16 -0
- data/spec/perpetuity/mongodb/query_union_spec.rb +16 -0
- data/spec/perpetuity/mongodb_spec.rb +28 -1
- data/spec/perpetuity/serializer_spec.rb +6 -0
- data/spec/perpetuity_spec.rb +138 -59
- data/spec/test_classes.rb +26 -17
- metadata +22 -23
- data/.rspec +0 -1
- data/Guardfile +0 -24
data/CHANGELOG.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
## Version 0.3
|
2
|
+
|
3
|
+
- Add `Mapper#save` to update an object's current state in the DB
|
4
|
+
- Fix `select` calls using `id` as criteria
|
5
|
+
- Switch from `Mongo::Connection` to `Mongo::MongoClient`
|
6
|
+
- This makes MongoDB reads and writes fail fast
|
7
|
+
- Add indexing interface
|
8
|
+
- Silence warnings
|
9
|
+
- Raise when calling Mapper[] with unmapped class
|
10
|
+
- Add unions and intersections to select queries for MongoDB adapter
|
11
|
+
- This allows for queries like `Perpetuity[Article].select { (created_at < Time.now) & (published == true) }`
|
12
|
+
- Update object in memory when calling `Mapper#update`
|
13
|
+
- Allow subclassing of `Perpetuity::Mapper` with map macro
|
14
|
+
- Use `Perpetuity[]` instead of `Perpetuity::Mapper[]` to get mapper instances
|
15
|
+
|
16
|
+
*Version 0.2 and 0.1 have no changelog because I am a terrible developer*
|
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (C) Jamie Gaskins
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -49,8 +49,8 @@ class Article
|
|
49
49
|
end
|
50
50
|
|
51
51
|
Perpetuity.generate_mapper_for Article do
|
52
|
-
attribute :title
|
53
|
-
attribute :body
|
52
|
+
attribute :title
|
53
|
+
attribute :body
|
54
54
|
end
|
55
55
|
|
56
56
|
article = Article.new
|
@@ -94,7 +94,9 @@ end
|
|
94
94
|
|
95
95
|
## Associations with Other Objects
|
96
96
|
|
97
|
-
If an object references another object (such as an article referencing its author),
|
97
|
+
The database can natively serialize some objects. For example, MongoDB can serialize `String`, `Numeric`, `Array`, `Hash`, `Time`, `nil`, `true`, `false`, and a few others. If an object references another type of object (such as an article referencing its author, a `User` object), the association is declared just as any other attribute. No special treatment is required.
|
98
|
+
|
99
|
+
If the associated object's class has a mapper defined, it will be used by the parent object's mapper for serialization. Otherwise, the object will be `Marshal.dump`ed. If the object cannot be marshaled, the object cannot be serialized and an exception will be raised.
|
98
100
|
|
99
101
|
```ruby
|
100
102
|
class User
|
@@ -102,17 +104,13 @@ end
|
|
102
104
|
|
103
105
|
class Article
|
104
106
|
attr_accessor :author
|
105
|
-
|
106
|
-
def initialize(author)
|
107
|
-
self.author = author
|
108
|
-
end
|
109
107
|
end
|
110
108
|
|
111
109
|
Perpetuity.generate_mapper_for User do
|
112
110
|
end
|
113
111
|
|
114
112
|
Perpetuity.generate_mapper_for Article do
|
115
|
-
attribute :author
|
113
|
+
attribute :author
|
116
114
|
end
|
117
115
|
```
|
118
116
|
|
@@ -125,6 +123,8 @@ article_mapper.load_association! article, :author
|
|
125
123
|
user = article.author
|
126
124
|
```
|
127
125
|
|
126
|
+
All loading of associated objects is explicit so that we don't load an entire object graph unnecessarily.
|
127
|
+
|
128
128
|
## Customizing persistence
|
129
129
|
|
130
130
|
Setting the ID of a record to a custom value rather than using the DB default.
|
@@ -135,6 +135,48 @@ Perpetuity.generate_mapper_for Article do
|
|
135
135
|
end
|
136
136
|
```
|
137
137
|
|
138
|
+
## Indexing
|
139
|
+
|
140
|
+
Indexes are declared with the `index` method. The simplest way to create an index is just to pass the attribute to be indexed as a parameter:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
Perpetuity.generate_mapper_for Article do
|
144
|
+
index :title
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
The following will generate a unique index on an `Article` class so that two articles cannot be added to the database with the same title. This eliminates the need for uniqueness validations (like ActiveRecord has) that check for existence of that value. Uniqueness validations have race conditions and don't protect you at the database level. Using unique indexes is a superior way to do this.
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
Perpetuity.generate_mapper_for Article do
|
152
|
+
index :title, unique: true
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
Also, MongoDB, as well as some other databases, provide the ability to specify an order for the index. For example, if you want to query your blog with articles in descending order, you can specify a descending-order index on the timestamp for increased query performance.
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
Perpetuity.generate_mapper_for Article do
|
160
|
+
index :timestamp, order: :descending
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
### Applying indexes
|
165
|
+
|
166
|
+
It's very important to keep in mind that specifying an index does not create it on the database immediately. If you did this, you could potentially introduce downtime every time you specify a new index and deploy your application.
|
167
|
+
|
168
|
+
In order to apply indexes to the database, you must send `reindex!` to the mapper. For example:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class ArticleMapper < Perpetuity::Mapper
|
172
|
+
map Article
|
173
|
+
attribute :title
|
174
|
+
index :title, unique: true
|
175
|
+
end
|
176
|
+
|
177
|
+
Perpetuity[Article].reindex!
|
178
|
+
```
|
179
|
+
|
138
180
|
## Contributing
|
139
181
|
|
140
182
|
Right now, this code is pretty bare and there are possibly some design decisions that need some more refinement. You can help. If you have ideas to build on this, send some love in the form of pull requests or issues or tweets or e-mails and I'll do what I can for them.
|
data/lib/perpetuity/attribute.rb
CHANGED
data/lib/perpetuity/mapper.rb
CHANGED
@@ -2,37 +2,50 @@ require 'perpetuity/attribute_set'
|
|
2
2
|
require 'perpetuity/attribute'
|
3
3
|
require 'perpetuity/validations'
|
4
4
|
require 'perpetuity/data_injectable'
|
5
|
-
require 'perpetuity/
|
5
|
+
require 'perpetuity/mapper_registry'
|
6
|
+
require 'perpetuity/serializer'
|
6
7
|
|
7
8
|
module Perpetuity
|
8
9
|
class Mapper
|
9
10
|
include DataInjectable
|
10
|
-
attr_accessor :object, :original_object
|
11
11
|
|
12
|
-
def
|
12
|
+
def self.generate_for(klass, &block)
|
13
|
+
mapper = Class.new(base_class, &block)
|
14
|
+
mapper.map klass
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.map klass
|
18
|
+
MapperRegistry[klass] = self
|
13
19
|
@mapped_class = klass
|
14
|
-
instance_exec &block if block_given?
|
15
20
|
end
|
16
21
|
|
17
|
-
def self.
|
18
|
-
|
19
|
-
mappers[klass] = mapper
|
22
|
+
def self.attribute_set
|
23
|
+
@attribute_set ||= AttributeSet.new
|
20
24
|
end
|
21
25
|
|
22
|
-
def self.
|
23
|
-
|
26
|
+
def self.attribute name, options = {}
|
27
|
+
type = options.fetch(:type) { nil }
|
28
|
+
attribute_set << Attribute.new(name, type, options)
|
24
29
|
end
|
25
30
|
|
26
|
-
def
|
27
|
-
|
31
|
+
def self.attributes
|
32
|
+
attribute_set.map(&:name)
|
28
33
|
end
|
29
34
|
|
30
|
-
def attribute
|
31
|
-
|
35
|
+
def self.index attribute
|
36
|
+
data_source.index mapped_class, attribute_set[attribute]
|
37
|
+
end
|
38
|
+
|
39
|
+
def indexes
|
40
|
+
data_source.indexes(mapped_class)
|
41
|
+
end
|
42
|
+
|
43
|
+
def reindex!
|
44
|
+
indexes.each { |index| data_source.activate_index! index }
|
32
45
|
end
|
33
46
|
|
34
47
|
def attributes
|
35
|
-
|
48
|
+
self.class.attributes
|
36
49
|
end
|
37
50
|
|
38
51
|
def delete_all
|
@@ -40,9 +53,9 @@ module Perpetuity
|
|
40
53
|
end
|
41
54
|
|
42
55
|
def insert object
|
43
|
-
raise "#{object} is invalid and cannot be persisted." unless validations.valid?(object)
|
56
|
+
raise "#{object} is invalid and cannot be persisted." unless self.class.validations.valid?(object)
|
44
57
|
serializable_attributes = serialize(object)
|
45
|
-
if o_id = object.instance_exec(&id)
|
58
|
+
if o_id = object.instance_exec(&self.class.id)
|
46
59
|
serializable_attributes[:id] = o_id
|
47
60
|
end
|
48
61
|
|
@@ -52,70 +65,29 @@ module Perpetuity
|
|
52
65
|
end
|
53
66
|
|
54
67
|
def serialize object
|
55
|
-
|
56
|
-
attribute_set.each do |attrib|
|
57
|
-
value = object.send(attrib.name)
|
58
|
-
attrib_name = attrib.name.to_s
|
59
|
-
|
60
|
-
if value.respond_to? :each
|
61
|
-
attrs[attrib_name] = serialize_enumerable(value)
|
62
|
-
elsif data_source.can_serialize? value
|
63
|
-
attrs[attrib_name] = value
|
64
|
-
elsif Mapper[value.class]
|
65
|
-
if attrib.embedded?
|
66
|
-
attrs[attrib_name] = Mapper[value.class].serialize(value).merge '__metadata__' => { 'class' => value.class }
|
67
|
-
else
|
68
|
-
attrs[attrib_name] = {
|
69
|
-
'__metadata__' => {
|
70
|
-
'class' => value.class.to_s,
|
71
|
-
'id' => value.id
|
72
|
-
}
|
73
|
-
}
|
74
|
-
end
|
75
|
-
else
|
76
|
-
if attrib.embedded?
|
77
|
-
attrs[attrib_name] = Marshal.dump(value)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
attrs
|
83
|
-
end
|
84
|
-
|
85
|
-
def serialize_enumerable enum
|
86
|
-
enum.map do |value|
|
87
|
-
if value.respond_to? :each
|
88
|
-
serialize_enumerable(value)
|
89
|
-
elsif data_source.can_serialize? value
|
90
|
-
value
|
91
|
-
elsif Mapper[value.class]
|
92
|
-
{
|
93
|
-
'__metadata__' => {
|
94
|
-
'class' => value.class.to_s
|
95
|
-
}
|
96
|
-
}.merge Mapper[value.class].serialize(value)
|
97
|
-
else
|
98
|
-
Marshal.dump(value)
|
99
|
-
end
|
100
|
-
end
|
68
|
+
Serializer.new(self).serialize(object)
|
101
69
|
end
|
102
70
|
|
103
|
-
def self.
|
104
|
-
|
71
|
+
def self.data_source
|
72
|
+
Perpetuity.configuration.data_source
|
105
73
|
end
|
106
74
|
|
107
75
|
def data_source
|
108
|
-
|
76
|
+
self.class.data_source
|
109
77
|
end
|
110
78
|
|
111
79
|
def count
|
112
80
|
data_source.count mapped_class
|
113
81
|
end
|
114
82
|
|
115
|
-
def mapped_class
|
83
|
+
def self.mapped_class
|
116
84
|
@mapped_class
|
117
85
|
end
|
118
86
|
|
87
|
+
def mapped_class
|
88
|
+
self.class.mapped_class
|
89
|
+
end
|
90
|
+
|
119
91
|
def first
|
120
92
|
data = data_source.first mapped_class
|
121
93
|
object = mapped_class.new
|
@@ -159,10 +131,10 @@ module Perpetuity
|
|
159
131
|
klass = reference.klass
|
160
132
|
id = reference.id
|
161
133
|
|
162
|
-
inject_attribute object, attribute,
|
134
|
+
inject_attribute object, attribute, MapperRegistry[klass].find(id)
|
163
135
|
end
|
164
136
|
|
165
|
-
def id &block
|
137
|
+
def self.id &block
|
166
138
|
if block_given?
|
167
139
|
@id = block
|
168
140
|
else
|
@@ -170,21 +142,31 @@ module Perpetuity
|
|
170
142
|
end
|
171
143
|
end
|
172
144
|
|
173
|
-
def update object, new_data
|
145
|
+
def update object, new_data, update_in_memory = true
|
174
146
|
id = object.is_a?(mapped_class) ? object.id : object
|
175
147
|
|
148
|
+
inject_data object, new_data if update_in_memory
|
176
149
|
data_source.update mapped_class, id, new_data
|
177
150
|
end
|
178
151
|
|
179
|
-
def
|
152
|
+
def save object
|
153
|
+
update object, serialize(object), false
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.validate &block
|
180
157
|
@validations ||= ValidationSet.new
|
181
158
|
|
182
159
|
validations.instance_exec(&block)
|
183
160
|
end
|
184
161
|
|
185
|
-
def validations
|
162
|
+
def self.validations
|
186
163
|
@validations ||= ValidationSet.new
|
187
164
|
end
|
165
|
+
|
166
|
+
private
|
167
|
+
def self.base_class
|
168
|
+
Mapper
|
169
|
+
end
|
188
170
|
end
|
189
171
|
end
|
190
172
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Perpetuity
|
2
|
+
class MapperRegistry
|
3
|
+
@mappers = Hash.new { |_, klass| raise KeyError, "No mapper for #{klass}" }
|
4
|
+
|
5
|
+
def self.has_mapper? klass
|
6
|
+
@mappers.has_key? klass
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.[] klass
|
10
|
+
@mappers[klass].new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.[]= klass, mapper
|
14
|
+
@mappers[klass] = mapper
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Perpetuity
|
2
|
+
class MongoDB
|
3
|
+
class Index
|
4
|
+
KEY_ORDERS = { 1 => :ascending, -1 => :descending }
|
5
|
+
attr_reader :collection, :attribute
|
6
|
+
|
7
|
+
def initialize klass, attribute, options={}
|
8
|
+
@collection = klass
|
9
|
+
@attribute = attribute
|
10
|
+
@unique = options.fetch(:unique) { false }
|
11
|
+
@order = options.fetch(:order) { :ascending }
|
12
|
+
@activated = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def active?
|
16
|
+
@activated
|
17
|
+
end
|
18
|
+
|
19
|
+
def inactive?
|
20
|
+
!active?
|
21
|
+
end
|
22
|
+
|
23
|
+
def activate!
|
24
|
+
@activated = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def unique?
|
28
|
+
@unique
|
29
|
+
end
|
30
|
+
|
31
|
+
def order
|
32
|
+
@order
|
33
|
+
end
|
34
|
+
|
35
|
+
def == other
|
36
|
+
hash == other.hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def eql? other
|
40
|
+
self == other
|
41
|
+
end
|
42
|
+
|
43
|
+
def hash
|
44
|
+
"#{collection}/#{attribute}:#{unique?}:#{order}".hash
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'perpetuity/mongodb/query_union'
|
2
|
+
require 'perpetuity/mongodb/query_intersection'
|
3
|
+
|
1
4
|
module Perpetuity
|
2
5
|
class MongoDB
|
3
6
|
class QueryExpression
|
@@ -50,6 +53,14 @@ module Perpetuity
|
|
50
53
|
def matches
|
51
54
|
{ @attribute => @value }
|
52
55
|
end
|
56
|
+
|
57
|
+
def | other
|
58
|
+
QueryUnion.new(self, other)
|
59
|
+
end
|
60
|
+
|
61
|
+
def & other
|
62
|
+
QueryIntersection.new(self, other)
|
63
|
+
end
|
53
64
|
end
|
54
65
|
end
|
55
66
|
end
|
data/lib/perpetuity/mongodb.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'mongo'
|
2
|
+
require 'perpetuity/mongodb/query'
|
3
|
+
require 'perpetuity/mongodb/index'
|
2
4
|
|
3
5
|
module Perpetuity
|
4
6
|
class MongoDB
|
@@ -12,11 +14,12 @@ module Perpetuity
|
|
12
14
|
@username = options[:username]
|
13
15
|
@password = options[:password]
|
14
16
|
@connection = nil
|
17
|
+
@indexes = Hash.new { |hash, key| hash[key] = active_indexes(key) }
|
15
18
|
end
|
16
19
|
|
17
20
|
def connect
|
18
21
|
database.authenticate(@username, @password) if @username and @password
|
19
|
-
@connection ||= Mongo::
|
22
|
+
@connection ||= Mongo::MongoClient.new @host, @port, pool_size: @pool_size
|
20
23
|
end
|
21
24
|
|
22
25
|
def connected?
|
@@ -61,12 +64,11 @@ module Perpetuity
|
|
61
64
|
|
62
65
|
# MongoDB uses '_id' as its ID field.
|
63
66
|
if criteria.has_key?(:id)
|
64
|
-
criteria
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
}
|
67
|
+
if criteria[:id].is_a? String
|
68
|
+
criteria = { _id: BSON::ObjectId.from_string(criteria[:id].to_s) }
|
69
|
+
else
|
70
|
+
criteria[:_id] = criteria.delete(:id)
|
71
|
+
end
|
70
72
|
end
|
71
73
|
|
72
74
|
sort_field = options[:attribute]
|
@@ -103,6 +105,41 @@ module Perpetuity
|
|
103
105
|
serializable_types.include? value.class
|
104
106
|
end
|
105
107
|
|
108
|
+
def drop_collection to_be_dropped
|
109
|
+
collection(to_be_dropped).drop
|
110
|
+
end
|
111
|
+
|
112
|
+
def index klass, attribute, options={}
|
113
|
+
@indexes[klass] ||= Set.new
|
114
|
+
|
115
|
+
index = Index.new(klass, attribute, options)
|
116
|
+
@indexes[klass] << index
|
117
|
+
index
|
118
|
+
end
|
119
|
+
|
120
|
+
def indexes klass
|
121
|
+
@indexes[klass]
|
122
|
+
end
|
123
|
+
|
124
|
+
def active_indexes klass
|
125
|
+
indexes = collection(klass).index_information
|
126
|
+
indexes.map do |name, index|
|
127
|
+
key = index['key'].keys.first
|
128
|
+
direction = index['key'][key]
|
129
|
+
unique = index['unique']
|
130
|
+
Index.new(klass, key, order: Index::KEY_ORDERS[direction], unique: unique)
|
131
|
+
end.to_set
|
132
|
+
end
|
133
|
+
|
134
|
+
def activate_index! index
|
135
|
+
attribute = index.attribute.to_s
|
136
|
+
order = index.order == :ascending ? 1 : -1
|
137
|
+
unique = index.unique?
|
138
|
+
|
139
|
+
collection(index.collection).create_index [[attribute, order]], unique: unique
|
140
|
+
index.activate!
|
141
|
+
end
|
142
|
+
|
106
143
|
private
|
107
144
|
def serializable_types
|
108
145
|
@serializable_types ||= [NilClass, TrueClass, FalseClass, Fixnum, Float, String, Array, Hash, Time]
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'perpetuity/mapper_registry'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
class Serializer
|
5
|
+
attr_reader :mapper
|
6
|
+
|
7
|
+
def initialize(mapper)
|
8
|
+
@mapper = mapper
|
9
|
+
end
|
10
|
+
|
11
|
+
def attribute_for object, attribute_name
|
12
|
+
if object.respond_to? attribute_name
|
13
|
+
object.send(attribute_name)
|
14
|
+
else
|
15
|
+
object.instance_variable_get("@#{attribute_name}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize object
|
20
|
+
attrs = mapper.class.attribute_set.map do |attrib|
|
21
|
+
value = attribute_for object, attrib.name
|
22
|
+
|
23
|
+
serialized_value = if value.is_a? Array
|
24
|
+
serialize_array(value)
|
25
|
+
elsif mapper.data_source.can_serialize? value
|
26
|
+
value
|
27
|
+
elsif MapperRegistry.has_mapper?(value.class)
|
28
|
+
serialize_with_foreign_mapper(value, attrib.embedded?)
|
29
|
+
else
|
30
|
+
if attrib.embedded?
|
31
|
+
Marshal.dump(value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
[attrib.name.to_s, serialized_value]
|
36
|
+
end
|
37
|
+
|
38
|
+
Hash[attrs]
|
39
|
+
end
|
40
|
+
|
41
|
+
def serialize_with_foreign_mapper value, embedded = false
|
42
|
+
if embedded
|
43
|
+
value_mapper = MapperRegistry[value.class]
|
44
|
+
value_serializer = Serializer.new(value_mapper)
|
45
|
+
attr = value_serializer.serialize(value)
|
46
|
+
attr.merge '__metadata__' => { 'class' => value.class }
|
47
|
+
else
|
48
|
+
{
|
49
|
+
'__metadata__' => {
|
50
|
+
'class' => value.class.to_s,
|
51
|
+
'id' => value.id
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def serialize_array enum
|
58
|
+
enum.map do |value|
|
59
|
+
if value.is_a? Array
|
60
|
+
serialize_array(value)
|
61
|
+
elsif mapper.data_source.can_serialize? value
|
62
|
+
value
|
63
|
+
elsif MapperRegistry.has_mapper?(value.class)
|
64
|
+
{
|
65
|
+
'__metadata__' => {
|
66
|
+
'class' => value.class.to_s
|
67
|
+
}
|
68
|
+
}.merge MapperRegistry[value.class].serialize(value)
|
69
|
+
else
|
70
|
+
Marshal.dump(value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/perpetuity/version.rb
CHANGED
data/lib/perpetuity.rb
CHANGED
@@ -10,7 +10,7 @@ module Perpetuity
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.configuration
|
13
|
-
|
13
|
+
@configuration ||= Configuration.new
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.generate_mapper_for klass, &block
|
@@ -18,6 +18,6 @@ module Perpetuity
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.[] klass
|
21
|
-
|
21
|
+
MapperRegistry[klass]
|
22
22
|
end
|
23
23
|
end
|
data/perpetuity.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.email = ["jgaskins@gmail.com"]
|
10
10
|
s.homepage = "https://github.com/jgaskins/perpetuity.git"
|
11
11
|
s.summary = %q{Persistence library allowing serialization of Ruby objects}
|
12
|
-
s.description = %q{Persistence layer Ruby objects}
|
12
|
+
s.description = %q{Persistence layer for Ruby objects}
|
13
13
|
|
14
14
|
s.files = `git ls-files`.split("\n")
|
15
15
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -19,7 +19,6 @@ Gem::Specification.new do |s|
|
|
19
19
|
# specify any dependencies here; for example:
|
20
20
|
s.add_development_dependency "rake"
|
21
21
|
s.add_development_dependency "rspec", "~> 2.8.0"
|
22
|
-
s.
|
23
|
-
s.add_runtime_dependency "mongo"
|
22
|
+
s.add_runtime_dependency "mongo", ">= 1.8.0"
|
24
23
|
s.add_runtime_dependency "bson_ext"
|
25
24
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'perpetuity/mapper_registry'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe MapperRegistry do
|
5
|
+
subject { described_class }
|
6
|
+
let(:mapper) { Class.new }
|
7
|
+
|
8
|
+
before { MapperRegistry[Object] = mapper }
|
9
|
+
|
10
|
+
it { should have_mapper Object }
|
11
|
+
it 'maps classes to instances of their mappers' do
|
12
|
+
MapperRegistry[Object].should be_a mapper
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|