perpetuity 0.7.3 → 1.0.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile +3 -0
- data/README.md +18 -8
- data/lib/perpetuity.rb +4 -4
- data/lib/perpetuity/attribute.rb +9 -0
- data/lib/perpetuity/attribute_set.rb +4 -6
- data/lib/perpetuity/data_injectable.rb +1 -1
- data/lib/perpetuity/duplicator.rb +27 -0
- data/lib/perpetuity/mapper.rb +9 -14
- data/lib/perpetuity/version.rb +1 -1
- data/perpetuity.gemspec +0 -1
- data/spec/integration/associations_spec.rb +2 -6
- data/spec/integration/indexing_spec.rb +1 -1
- data/spec/integration/persistence_spec.rb +3 -2
- data/spec/integration/retrieval_spec.rb +5 -13
- data/spec/perpetuity/attribute_set_spec.rb +1 -1
- data/spec/perpetuity/attribute_spec.rb +6 -1
- data/spec/perpetuity/duplicator_spec.rb +35 -0
- data/spec/perpetuity/mapper_spec.rb +21 -2
- data/spec/perpetuity_spec.rb +2 -2
- data/spec/spec_helper.rb +8 -2
- data/spec/support/test_classes.rb +22 -26
- data/spec/support/test_classes/topic.rb +5 -0
- data/spec/support/test_classes/user.rb +1 -1
- metadata +7 -55
- data/lib/perpetuity/mongodb.rb +0 -230
- data/lib/perpetuity/mongodb/index.rb +0 -52
- data/lib/perpetuity/mongodb/nil_query.rb +0 -11
- data/lib/perpetuity/mongodb/query.rb +0 -33
- data/lib/perpetuity/mongodb/query_attribute.rb +0 -66
- data/lib/perpetuity/mongodb/query_expression.rb +0 -94
- data/lib/perpetuity/mongodb/query_intersection.rb +0 -16
- data/lib/perpetuity/mongodb/query_union.rb +0 -16
- data/lib/perpetuity/mongodb/serializer.rb +0 -174
- data/lib/perpetuity/validations.rb +0 -1
- data/lib/perpetuity/validations/length.rb +0 -36
- data/lib/perpetuity/validations/presence.rb +0 -14
- data/lib/perpetuity/validations/validation_set.rb +0 -28
- data/spec/integration/mongodb_spec.rb +0 -218
- data/spec/integration/validations_spec.rb +0 -17
- data/spec/perpetuity/mongodb/index_spec.rb +0 -44
- data/spec/perpetuity/mongodb/query_attribute_spec.rb +0 -58
- data/spec/perpetuity/mongodb/query_expression_spec.rb +0 -67
- data/spec/perpetuity/mongodb/query_intersection_spec.rb +0 -16
- data/spec/perpetuity/mongodb/query_spec.rb +0 -79
- data/spec/perpetuity/mongodb/query_union_spec.rb +0 -16
- data/spec/perpetuity/mongodb/serializer_spec.rb +0 -212
- data/spec/perpetuity/validations/length_spec.rb +0 -53
- data/spec/perpetuity/validations/presence_spec.rb +0 -30
- data/spec/perpetuity/validations_spec.rb +0 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e6705adfb6aafad45d73afe6fd8fc8da9ecb7d0
|
4
|
+
data.tar.gz: c7bb3bc720c641e4b5951188591279b4fda33baa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94d4440cfb7076313402de2f8055d81fc7151504dacc1bc3e7004549ab60605f04f3cf6342dcd56893dd85e4714d39327ffe240b31fd51ebe9cb2342261c4941
|
7
|
+
data.tar.gz: 8d868e8b3ba8a5fdc63b9a8ac2e32ff24bcac8b09275cadff37262be8e7ef1e39586b7c067e50627247131249a3692f467e635855332cf1350a617192d03bbf6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## Version 1.0.0.beta
|
2
|
+
|
3
|
+
- Duplicate objects properly for the identity map. The identity map holds references to objects pulled out of the DB. We need to duplicate these references on insertion and retrieval from the identity map to make sure that modifications to an object do not pollute the version in the identity map.
|
4
|
+
- Add support for the [Amazon DynamoDB adapter](https://github.com/cardspring/perpetuity-dynamodb) by [Cardspring](https://github.com/cardspring). This adapter is still in very early development.
|
5
|
+
- Allow passing in the type of the `id` attribute for databases that enforce column/field types. This allows us to say `id(String) { ... }`, telling the database that `id` needs to be a String.
|
6
|
+
- Use hashes for AttributeSets to reduce serialization time.
|
7
|
+
- Extract all database-specific logic to their own gems. The existing MongoDB adapter has been moved to the `perpetuity-mongodb` gem and there is a PostgreSQL adapter that lives under `perpetuity-postgres`.
|
8
|
+
- Allow specs to be run on top of `perpetuity-postgres`
|
9
|
+
- Remove validations.
|
10
|
+
|
1
11
|
## Version 0.7.3
|
2
12
|
|
3
13
|
- Only save attributes which have changed
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Perpetuity is a simple Ruby object persistence layer that attempts to follow Martin Fowler's Data Mapper pattern, allowing you to use plain-old Ruby objects in your Ruby apps in order to decouple your domain logic from the database as well as speed up your tests. There is no need for your model classes to inherit from another class or even include a mix-in.
|
4
4
|
|
5
|
-
Your objects will hopefully eventually be able to be persisted into whichever database you like. Right now, only MongoDB is supported. Other persistence solutions will come later.
|
5
|
+
Your objects will hopefully eventually be able to be persisted into whichever database you like. Right now, only MongoDB is supported. There is also a [PostgreSQL adapter](https://github.com/jgaskins/perpetuity-postgres) under heavy development (nearly up-to-date with the [MongoDB adapter](https://github.com/jgaskins/perpetuity-mongodb)). Other persistence solutions will come later.
|
6
6
|
|
7
7
|
## How it works
|
8
8
|
|
@@ -16,15 +16,25 @@ Add the following to your Gemfile and run `bundle` to install it.
|
|
16
16
|
gem 'perpetuity'
|
17
17
|
```
|
18
18
|
|
19
|
+
**NOTE:** In version 1.0, you will need to install the Perpetuity database adapter, which will pull in the core Perpetuity gem:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'perpetuity/mongodb' # if using MongoDB
|
23
|
+
gem 'perpetuity/postgres' # if using Postgres
|
24
|
+
```
|
25
|
+
|
19
26
|
## Configuration
|
20
27
|
|
21
|
-
The only currently supported persistence method is MongoDB. Other schemaless solutions can probably be implemented easily. The simplest configuration is with the following line:
|
28
|
+
The only currently supported persistence method is MongoDB, but stay tuned for the [Postgres adapter](https://github.com/jgaskins/perpetuity-postgres). Other schemaless solutions can probably be implemented easily. The simplest configuration is with the following line:
|
22
29
|
|
23
30
|
```ruby
|
24
|
-
Perpetuity.data_source :mongodb, '
|
31
|
+
Perpetuity.data_source :mongodb, 'my_mongo_database'
|
32
|
+
Perpetuity.data_source :postgres, 'my_pg_database'
|
25
33
|
```
|
26
34
|
|
27
|
-
|
35
|
+
*Note:* You cannot use different databases in the same app like that. At least, not yet. :-)
|
36
|
+
|
37
|
+
If your database is on another server/port or you need authentication, you can specify those as options:
|
28
38
|
|
29
39
|
```ruby
|
30
40
|
Perpetuity.data_source :mongodb, 'my_database', host: 'mongo.example.com',
|
@@ -129,7 +139,7 @@ Perpetuity.generate_mapper_for Comment do
|
|
129
139
|
end
|
130
140
|
```
|
131
141
|
|
132
|
-
In this case, the article has an array of `Comment` objects, which the serializer knows that
|
142
|
+
In this case, the article has an array of `Comment` objects, which the serializer knows that the data source cannot serialize. It will then tell the `Comment` mapper to serialize it and it stores that within the array.
|
133
143
|
|
134
144
|
If some of the comments aren't objects of class `Comment`, it will adapt and serialize them according to their class. This works very well for objects that can have attributes of various types, such as a `User` having a profile attribute that can be either a `UserProfile` or `AdminProfile` object. You don't need to declare anything different for this case, just store the appropriate type of object into the `User`'s `profile` attribute and the mapper will take care of the details.
|
135
145
|
|
@@ -187,7 +197,7 @@ Perpetuity.generate_mapper_for Article do
|
|
187
197
|
end
|
188
198
|
```
|
189
199
|
|
190
|
-
Also,
|
200
|
+
Also, some 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.
|
191
201
|
|
192
202
|
```ruby
|
193
203
|
Perpetuity.generate_mapper_for Article do
|
@@ -197,7 +207,7 @@ end
|
|
197
207
|
|
198
208
|
### Applying indexes
|
199
209
|
|
200
|
-
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.
|
210
|
+
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. Additionally, if a unique index fails to apply, you would not be able to start your app.
|
201
211
|
|
202
212
|
In order to apply indexes to the database, you must send `reindex!` to the mapper. For example:
|
203
213
|
|
@@ -221,7 +231,7 @@ Let's face it, most Ruby apps run on Rails, so we need to be able to support it.
|
|
221
231
|
|
222
232
|
Previous versions of Perpetuity would break when Rails reloaded your models in development mode due to class objects being different. It now reloads mappers dynamically based on whether the class has been reloaded.
|
223
233
|
|
224
|
-
In order for this to work, your mapper files need to be named `*_mapper.rb` and be stored anywhere inside your project's `app` directory.
|
234
|
+
In order for this to work, your mapper files need to be named `*_mapper.rb` and be stored anywhere inside your project's `app` directory. Usually, this would be `app/mappers`, but this is not enforced.
|
225
235
|
|
226
236
|
### ActiveModel-compliant API
|
227
237
|
|
data/lib/perpetuity.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require "perpetuity/version"
|
2
|
-
require "perpetuity/mongodb"
|
3
2
|
require "perpetuity/config"
|
4
3
|
require "perpetuity/mapper"
|
5
4
|
require "perpetuity/mapper_registry"
|
@@ -9,7 +8,7 @@ module Perpetuity
|
|
9
8
|
detect_rails
|
10
9
|
configuration.instance_exec(&block)
|
11
10
|
end
|
12
|
-
|
11
|
+
|
13
12
|
def self.configuration
|
14
13
|
@configuration ||= Configuration.new
|
15
14
|
end
|
@@ -29,9 +28,10 @@ module Perpetuity
|
|
29
28
|
end
|
30
29
|
|
31
30
|
def self.data_source adapter, db_name, options={}
|
32
|
-
adapters = { mongodb: MongoDB }
|
31
|
+
adapters = { dynamodb: 'DynamoDB', mongodb: 'MongoDB', postgres: 'Postgres' }
|
32
|
+
adapter_class = const_get(adapters[adapter])
|
33
33
|
|
34
|
-
configure { data_source
|
34
|
+
configure { data_source adapter_class.new(options.merge(db: db_name)) }
|
35
35
|
end
|
36
36
|
|
37
37
|
# Necessary to be able to check whether Rails is loaded and initialized
|
data/lib/perpetuity/attribute.rb
CHANGED
@@ -5,11 +5,20 @@ module Perpetuity
|
|
5
5
|
@name = name
|
6
6
|
@type = type
|
7
7
|
|
8
|
+
@_options = options.dup
|
8
9
|
options.each do |option, value|
|
9
10
|
instance_variable_set "@#{option}", value
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
14
|
+
def options option=nil
|
15
|
+
if option
|
16
|
+
instance_variable_get("@#{option}")
|
17
|
+
else
|
18
|
+
@_options
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
13
22
|
def embedded?
|
14
23
|
@embedded ||= false
|
15
24
|
end
|
@@ -1,23 +1,21 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
1
|
module Perpetuity
|
4
2
|
class AttributeSet
|
5
3
|
include Enumerable
|
6
4
|
|
7
5
|
def initialize
|
8
|
-
@attributes =
|
6
|
+
@attributes = {}
|
9
7
|
end
|
10
8
|
|
11
9
|
def << attribute
|
12
|
-
@attributes
|
10
|
+
@attributes[attribute.name] = attribute
|
13
11
|
end
|
14
12
|
|
15
13
|
def [] name
|
16
|
-
@attributes
|
14
|
+
@attributes[name]
|
17
15
|
end
|
18
16
|
|
19
17
|
def each &block
|
20
|
-
@attributes.
|
18
|
+
@attributes.each_value(&block)
|
21
19
|
end
|
22
20
|
end
|
23
21
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Perpetuity
|
2
|
+
class Duplicator
|
3
|
+
attr_reader :object
|
4
|
+
def initialize object
|
5
|
+
if object.is_a? Array
|
6
|
+
@object = object.map { |i| Duplicator.new(i).object }
|
7
|
+
else
|
8
|
+
@object = object.dup rescue object
|
9
|
+
end
|
10
|
+
@object.instance_variables.each do |ivar|
|
11
|
+
duplicate_attribute ivar
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def attribute ivar
|
16
|
+
object.instance_variable_get ivar
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_attribute ivar, value
|
20
|
+
object.instance_variable_set ivar, value
|
21
|
+
end
|
22
|
+
|
23
|
+
def duplicate_attribute ivar
|
24
|
+
set_attribute ivar, Duplicator.new(attribute(ivar)).object
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/perpetuity/mapper.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'perpetuity/attribute_set'
|
2
2
|
require 'perpetuity/attribute'
|
3
|
-
require 'perpetuity/validations'
|
4
3
|
require 'perpetuity/data_injectable'
|
5
4
|
require 'perpetuity/dereferencer'
|
6
5
|
require 'perpetuity/retrieval'
|
6
|
+
require 'perpetuity/duplicator'
|
7
7
|
|
8
8
|
module Perpetuity
|
9
9
|
class Mapper
|
@@ -71,11 +71,10 @@ module Perpetuity
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def insert object
|
74
|
-
raise "#{object} is invalid and cannot be persisted." unless self.class.validations.valid?(object)
|
75
74
|
objects = Array(object)
|
76
75
|
serialized_objects = objects.map { |obj| serialize(obj) }
|
77
76
|
|
78
|
-
new_ids = data_source.insert(mapped_class, serialized_objects)
|
77
|
+
new_ids = data_source.insert(mapped_class, serialized_objects, attribute_set)
|
79
78
|
objects.each_with_index do |obj, index|
|
80
79
|
give_id_to obj, new_ids[index]
|
81
80
|
end
|
@@ -139,7 +138,7 @@ module Perpetuity
|
|
139
138
|
result = select { |object| object.id == id }.first
|
140
139
|
|
141
140
|
if cache_result and !result.nil?
|
142
|
-
identity_map << result
|
141
|
+
identity_map << Duplicator.new(result).object
|
143
142
|
end
|
144
143
|
|
145
144
|
result
|
@@ -174,9 +173,13 @@ module Perpetuity
|
|
174
173
|
end
|
175
174
|
end
|
176
175
|
|
177
|
-
def self.id &block
|
176
|
+
def self.id type=nil, &block
|
178
177
|
if block_given?
|
179
178
|
@id = block
|
179
|
+
if type
|
180
|
+
attribute :id, type: type
|
181
|
+
end
|
182
|
+
nil
|
180
183
|
else
|
181
184
|
@id ||= -> { nil }
|
182
185
|
end
|
@@ -222,14 +225,6 @@ module Perpetuity
|
|
222
225
|
object.instance_variable_get(:@id) if persisted?(object)
|
223
226
|
end
|
224
227
|
|
225
|
-
def self.validate &block
|
226
|
-
validations.instance_exec(&block)
|
227
|
-
end
|
228
|
-
|
229
|
-
def self.validations
|
230
|
-
@validations ||= ValidationSet.new
|
231
|
-
end
|
232
|
-
|
233
228
|
def data_source
|
234
229
|
self.class.data_source
|
235
230
|
end
|
@@ -237,7 +232,7 @@ module Perpetuity
|
|
237
232
|
def serialize object
|
238
233
|
attributes = data_source.serialize(object, self)
|
239
234
|
if o_id = generate_id_for(object)
|
240
|
-
attributes[
|
235
|
+
attributes['id'] = o_id
|
241
236
|
end
|
242
237
|
|
243
238
|
attributes
|
data/lib/perpetuity/version.rb
CHANGED
data/perpetuity.gemspec
CHANGED
@@ -2,16 +2,12 @@ require 'spec_helper'
|
|
2
2
|
require 'support/test_classes'
|
3
3
|
|
4
4
|
describe 'associations with other objects' do
|
5
|
-
let(:user) { User.new }
|
6
|
-
let(:topic) { Topic.new }
|
5
|
+
let(:user) { User.new('Flump') }
|
6
|
+
let(:topic) { Topic.new('Title', user) }
|
7
7
|
let(:user_mapper) { Perpetuity[User] }
|
8
8
|
let(:topic_mapper) { Perpetuity[Topic] }
|
9
9
|
|
10
10
|
before do
|
11
|
-
user.name = 'Flump'
|
12
|
-
topic.creator = user
|
13
|
-
topic.title = 'Title'
|
14
|
-
|
15
11
|
user_mapper.insert user
|
16
12
|
topic_mapper.insert topic
|
17
13
|
end
|
@@ -24,7 +24,7 @@ describe 'indexing' do
|
|
24
24
|
let(:db_name) { Perpetuity.configuration.data_source.db }
|
25
25
|
|
26
26
|
before do
|
27
|
-
|
27
|
+
load './spec/spec_helper.rb'
|
28
28
|
mapper.data_source.drop_collection Object
|
29
29
|
end
|
30
30
|
after { mapper.data_source.drop_collection Object }
|
@@ -1,13 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'support/test_classes'
|
3
|
+
require 'securerandom'
|
3
4
|
|
4
5
|
describe 'Persistence' do
|
5
6
|
let(:mapper) { Perpetuity[Article] }
|
6
7
|
|
7
8
|
it "persists an object" do
|
8
9
|
article = Article.new 'I have a title'
|
9
|
-
|
10
|
-
|
10
|
+
mapper.serialize article
|
11
|
+
expect { mapper.insert article }.to change { mapper.count }.by 1
|
11
12
|
mapper.find(mapper.id_for(article)).title.should eq 'I have a title'
|
12
13
|
end
|
13
14
|
|
@@ -195,22 +195,14 @@ describe "retrieval" do
|
|
195
195
|
end
|
196
196
|
end
|
197
197
|
|
198
|
-
it 'selects objects with nested data' do
|
199
|
-
user = User.new(first_name: 'foo', last_name: 'bar')
|
200
|
-
mapper = Perpetuity[User]
|
201
|
-
mapper.insert user
|
202
|
-
users = mapper.select { |u| u.name.first_name == 'foo' }
|
203
|
-
ids = users.map { |retrieved_user| mapper.id_for(retrieved_user) }
|
204
|
-
ids.should include mapper.id_for(user)
|
205
|
-
end
|
206
|
-
|
207
198
|
it 'skips a specified number of objects' do
|
208
|
-
|
209
|
-
articles = 3.times.map { Article.new(
|
199
|
+
title = SecureRandom.hex
|
200
|
+
articles = 3.times.map { Article.new(title) }.sort_by { |article| mapper.id_for(article) }
|
210
201
|
articles.each { |article| mapper.insert article }
|
211
202
|
|
212
|
-
ret = mapper.select { |article| article.
|
213
|
-
ret.should
|
203
|
+
ret = mapper.select { |article| article.title == title }.drop(2).sort(:id).to_a
|
204
|
+
ret.should have(1).items
|
205
|
+
ret.first.should == articles.last
|
214
206
|
end
|
215
207
|
|
216
208
|
describe 'selecting random objects' do
|
@@ -2,7 +2,7 @@ require 'perpetuity/attribute'
|
|
2
2
|
|
3
3
|
module Perpetuity
|
4
4
|
describe Attribute do
|
5
|
-
let(:attribute) { Attribute.new :article, Object }
|
5
|
+
let(:attribute) { Attribute.new :article, Object, default: 1 }
|
6
6
|
subject { attribute }
|
7
7
|
|
8
8
|
it 'has a name' do
|
@@ -13,6 +13,11 @@ module Perpetuity
|
|
13
13
|
subject.type.should == Object
|
14
14
|
end
|
15
15
|
|
16
|
+
it 'can get extra options' do
|
17
|
+
attribute.options.should == { default: 1 }
|
18
|
+
attribute.options(:default).should == 1
|
19
|
+
end
|
20
|
+
|
16
21
|
it 'can be embedded' do
|
17
22
|
attribute = Attribute.new :article, Object, embedded: true
|
18
23
|
attribute.should be_embedded
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'perpetuity/duplicator'
|
2
|
+
require 'support/test_classes/book'
|
3
|
+
require 'support/test_classes/user'
|
4
|
+
|
5
|
+
module Perpetuity
|
6
|
+
describe Duplicator do
|
7
|
+
let(:authors) { [User.new('Dave'), User.new('Andy')] }
|
8
|
+
let(:book) { Book.new('Title', authors) }
|
9
|
+
let(:duper) { Duplicator.new(book) }
|
10
|
+
|
11
|
+
it 'duplicates an object' do
|
12
|
+
duper.object.should be_a Book
|
13
|
+
duper.object.should_not be book
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'duplicates attributes inside an object' do
|
17
|
+
duped_book = duper.object
|
18
|
+
duped_book.title.should be_a String
|
19
|
+
duped_book.title.should == book.title
|
20
|
+
duped_book.title.should_not be book.title
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'does not duplicate non-duplicable attributes' do
|
24
|
+
# Symbols cannot be duped
|
25
|
+
book = Book.new(:foo)
|
26
|
+
duper = Duplicator.new(book)
|
27
|
+
duper.object.title.should be :foo
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'duplicates objects contained within array attributes' do
|
31
|
+
duper.object.authors.first.should be_a User
|
32
|
+
duper.object.authors.first.should_not be authors.first
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|