perpetuity 0.4.4 → 0.4.5

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,24 @@
1
+ require 'spec_helper'
2
+ require 'support/test_classes'
3
+
4
+ describe "deletion" do
5
+ it 'deletes an object' do
6
+ 2.times { Perpetuity[Article].insert Article.new }
7
+ expect { Perpetuity[Article].delete Perpetuity[Article].first }.to change { Perpetuity[Article].count }.by(-1)
8
+ end
9
+
10
+ it 'deletes an object with a given id' do
11
+ article_id = Perpetuity[Article].insert Article.new
12
+ expect {
13
+ Perpetuity[Article].delete article_id
14
+ }.to change { Perpetuity[Article].count }.by(-1)
15
+ end
16
+
17
+ describe "#delete_all" do
18
+ it "should delete all objects of a certain class" do
19
+ Perpetuity[Article].insert Article.new
20
+ Perpetuity[Article].delete_all
21
+ Perpetuity[Article].count.should eq 0
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'indexing' do
4
+ let(:mapper_class) do
5
+ Class.new(Perpetuity::Mapper) do
6
+ map Object
7
+ attribute :name
8
+ index :name, unique: true
9
+ end
10
+ end
11
+ let(:mapper) { mapper_class.new }
12
+ let(:name_index) do
13
+ mapper.indexes.find do |index|
14
+ index.attribute.to_s == :name
15
+ end
16
+ end
17
+
18
+ after { mapper.data_source.drop_collection Object }
19
+
20
+ it 'adds indexes to database collections/tables' do
21
+ name_index.attribute.name.should be == :name
22
+ end
23
+
24
+ it 'verifies that indexes are inactive' do
25
+ name_index.should be_inactive
26
+ end
27
+
28
+ it 'creates indexes' do
29
+ mapper.reindex!
30
+ name_index.should be_active
31
+ mapper.remove_index! name_index
32
+ end
33
+
34
+ it 'specifies uniqueness of the index' do
35
+ name_index.should be_unique
36
+ end
37
+ end
38
+
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'support/test_classes'
3
+
4
+ describe 'pagination' do
5
+ it 'specifies the page we want' do
6
+ Perpetuity[Article].all.should respond_to :page
7
+ end
8
+
9
+ it 'specify the quantity per page' do
10
+ Perpetuity[Article].all.should respond_to :per_page
11
+ end
12
+
13
+ it 'returns an empty set when there is no data for that page' do
14
+ mapper = Perpetuity[Article]
15
+ mapper.delete_all
16
+ data = mapper.all.page(2)
17
+ data.should be_empty
18
+ end
19
+
20
+ it 'specifies per-page quantity' do
21
+ Perpetuity[Article].delete_all
22
+ 5.times { |i| Perpetuity[Article].insert Article.new i }
23
+ data = Perpetuity[Article].all.page(3).per_page(2)
24
+ data.should have(1).item
25
+ end
26
+ end
27
+
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+ require 'support/test_classes'
3
+
4
+ describe 'Persistence' do
5
+ it "persists an object" do
6
+ article = Article.new 'I have a title'
7
+ expect { Perpetuity[Article].insert article }.
8
+ to change { Perpetuity[Article].count }.by 1
9
+ Perpetuity[Article].find(article.id).title.should eq 'I have a title'
10
+ end
11
+
12
+ it 'returns the id of the persisted object' do
13
+ article = Article.new
14
+ Perpetuity[Article].insert(article).should eq article.id
15
+ end
16
+
17
+ it "gives an id to objects" do
18
+ article = Article.new
19
+ Perpetuity[Article].give_id_to article, 1
20
+
21
+ article.id.should eq 1
22
+ end
23
+
24
+ it 'persists referenced objects if they are not persisted' do
25
+ article = Article.new
26
+ article.author = User.new
27
+ Perpetuity[Article].insert article
28
+
29
+ Perpetuity[Article].find(article.id).author.id.should be == article.author.id
30
+ end
31
+
32
+ it 'persists arrays of referenced objects if they are not persisted' do
33
+ authors = [User.new('Dave'), User.new('Andy')]
34
+ book = Book.new
35
+ book.authors = authors
36
+ Perpetuity[Book].insert book
37
+
38
+ Perpetuity[Book].find(book.id).authors.first.id.should be == authors.first.id
39
+ end
40
+
41
+ describe 'id injection' do
42
+ let(:article) { Article.new }
43
+
44
+ it 'assigns an id to the inserted object' do
45
+ Perpetuity[Article].insert article
46
+ article.should respond_to :id
47
+ end
48
+
49
+ it "assigns an id using Mapper.first" do
50
+ Perpetuity[Article].first.should respond_to :id
51
+ end
52
+
53
+ it 'assigns an id using Mapper.all.first' do
54
+ Perpetuity[Article].all.first.should respond_to :id
55
+ end
56
+ end
57
+
58
+ describe 'persisting arrays' do
59
+ let(:article) { Article.new }
60
+
61
+ it 'persists arrays' do
62
+ article.comments << 1 << 2 << 3
63
+ Perpetuity[Article].insert article
64
+ Perpetuity[Article].find(article.id).comments.should eq [1, 2, 3]
65
+ end
66
+
67
+ it 'persists arrays with unserializable objects in them' do
68
+ comment = Comment.new('my comment')
69
+ article.comments << comment
70
+ Perpetuity[Article].insert article
71
+ Perpetuity[Article].find(article.id).comments.first.tap do |persisted_comment|
72
+ persisted_comment.should be_a Comment
73
+ persisted_comment.body.should eq comment.body
74
+ end
75
+ end
76
+ end
77
+
78
+ describe 'persisting hashes' do
79
+ let(:name_hash) { { 'first_name' => 'Jamie', 'last_name' => 'Gaskins' } }
80
+ let(:user) { User.new(name_hash) }
81
+ let(:user_mapper) { Perpetuity[User] }
82
+
83
+ it 'saves and retrieves hashes' do
84
+ user_mapper.insert user
85
+ user_mapper.find(user.id).name.should be == name_hash
86
+ end
87
+ end
88
+
89
+ it "allows mappers to set the id field" do
90
+ noise = Time.now.to_f.to_s.sub('.', '')
91
+ book = Book.new("My Title #{noise}")
92
+
93
+ Perpetuity[Book].insert book
94
+ book.id.should eq "my-title-#{noise}"
95
+ end
96
+ end
97
+
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+ require 'support/test_classes'
3
+
4
+ describe "retrieval" do
5
+ it "gets all the objects of a class" do
6
+ expect { Perpetuity[Article].insert Article.new }.
7
+ to change { Perpetuity[Article].all.count }.by 1
8
+ end
9
+
10
+ it "has an ID when retrieved" do
11
+ Perpetuity[Article].insert Article.new
12
+ Perpetuity[Article].first.should respond_to :id
13
+ end
14
+
15
+ it "gets an item with a specific ID" do
16
+ article = Article.new
17
+ Perpetuity[Article].insert article
18
+ retrieved = Perpetuity[Article].find(article.id)
19
+
20
+ retrieved.id.should eq article.id
21
+ retrieved.title.should eq article.title
22
+ retrieved.body.should eq article.body
23
+ end
24
+
25
+ describe 'sorting' do
26
+ let(:first) { Article.new('First', '', nil, Time.now - 20) }
27
+ let(:second) { Article.new('Second', '', nil, Time.now - 10) }
28
+ let(:third) { Article.new('Third', '', nil, Time.now) }
29
+
30
+ before do
31
+ Perpetuity[Article].delete_all
32
+ [second, third, first].each { |article| Perpetuity[Article].insert article }
33
+ end
34
+
35
+ it 'sorts results' do
36
+ titles = Perpetuity[Article].all.sort(:published_at).map(&:title)
37
+ titles.should be == %w(First Second Third)
38
+ end
39
+
40
+ it 'reverse-sorts results' do
41
+ titles = Perpetuity[Article].all.sort(:published_at).reverse.map(&:title)
42
+ titles.should be == %w(Third Second First)
43
+ end
44
+ end
45
+
46
+ it 'limits result set' do
47
+ 5.times { Perpetuity[Article].insert Article.new }
48
+ Perpetuity[Article].all.limit(4).should have(4).items
49
+ end
50
+
51
+ describe "Array-like syntax" do
52
+ let(:draft) { Article.new 'Draft', 'draft content', nil, Time.now + 30 }
53
+ let(:published) { Article.new 'Published', 'content', nil, Time.now - 30, 3 }
54
+ before do
55
+ Perpetuity[Article].insert draft
56
+ Perpetuity[Article].insert published
57
+ end
58
+
59
+ it 'selects objects using equality' do
60
+ selected = Perpetuity[Article].select { |article| article.title == 'Published' }
61
+ selected.map(&:id).should include published.id
62
+ selected.map(&:id).should_not include draft.id
63
+ end
64
+
65
+ it 'selects objects using greater-than' do
66
+ selected = Perpetuity[Article].select { |article| article.published_at < Time.now }
67
+ ids = selected.map(&:id)
68
+ ids.should include published.id
69
+ ids.should_not include draft.id
70
+ end
71
+
72
+ it 'selects objects using greater-than-or-equal' do
73
+ selected = Perpetuity[Article].select { |article| article.views >= 3 }
74
+ ids = selected.map(&:id)
75
+ ids.should include published.id
76
+ ids.should_not include draft.id
77
+ end
78
+
79
+ it 'selects objects using less-than' do
80
+ selected = Perpetuity[Article].select { |article| article.views < 3 }
81
+ ids = selected.map(&:id)
82
+ ids.should include draft.id
83
+ ids.should_not include published.id
84
+ end
85
+
86
+ it 'selects objects using less-than-or-equal' do
87
+ selected = Perpetuity[Article].select { |article| article.views <= 0 }
88
+ ids = selected.map(&:id)
89
+ ids.should include draft.id
90
+ ids.should_not include published.id
91
+ end
92
+
93
+ it 'selects objects using inequality' do
94
+ selected = Perpetuity[Article].select { |article| article.title.not_equal? 'Draft' }
95
+ ids = selected.map(&:id)
96
+ ids.should_not include draft.id
97
+ ids.should include published.id
98
+ end
99
+
100
+ it 'selects objects using regular expressions' do
101
+ selected = Perpetuity[Article].select { |article| article.title =~ /Pub/ }
102
+ ids = selected.map(&:id)
103
+ ids.should include published.id
104
+ ids.should_not include draft.id
105
+ end
106
+
107
+ it 'selects objects using inclusion' do
108
+ selected = Perpetuity[Article].select { |article| article.title.in %w( Published ) }
109
+ ids = selected.map(&:id)
110
+ ids.should include published.id
111
+ ids.should_not include draft.id
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+ require 'support/test_classes'
3
+
4
+ describe 'serialization' do
5
+ let(:author) { User.new 'username' }
6
+ let(:comment) { Comment.new }
7
+ let(:article) { Article.new }
8
+ let(:mapper) { Perpetuity[Article] }
9
+ let(:serialized_value) do
10
+ {
11
+ 'title' => article.title,
12
+ 'body' => article.body,
13
+ 'author' => {
14
+ '__metadata__' => {
15
+ 'class' => author.class.to_s,
16
+ 'id' => author.id
17
+ }
18
+ },
19
+ 'comments' => [
20
+ {
21
+ '__metadata__' => {
22
+ 'class' => comment.class.to_s
23
+ },
24
+ 'body' => comment.body,
25
+ 'author' => {
26
+ '__metadata__' => {
27
+ 'class' => author.class.to_s,
28
+ 'id' => author.id
29
+ }
30
+ }
31
+ },
32
+ ],
33
+ 'published_at' => article.published_at,
34
+ 'views' => article.views
35
+ }
36
+ end
37
+
38
+ before do
39
+ article.author = author
40
+ article.comments = [comment]
41
+ comment.author = author
42
+
43
+ Perpetuity[User].insert author
44
+ Perpetuity[Article].insert article
45
+ end
46
+
47
+ it 'serializes objects into hashes' do
48
+ mapper.serialize(article).should be == serialized_value
49
+ end
50
+
51
+ it 'deserializes hashes into proper objects' do
52
+ unserialized = mapper.find article.id
53
+ unserialized.should be_a Article
54
+ unserialized.title.should be == article.title
55
+ unserialized.body.should be == article.body
56
+ unserialized.comments.first.tap do |unserialized_comment|
57
+ unserialized_comment.body.should be == comment.body
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'support/test_classes'
3
+
4
+ describe 'updating' do
5
+ let(:article) { Article.new }
6
+ let(:mapper) { Perpetuity[Article] }
7
+ let(:new_title) { 'I has a new title!' }
8
+
9
+ before do
10
+ mapper.insert article
11
+ end
12
+
13
+ it 'updates an object in the database' do
14
+ mapper.update article, title: new_title
15
+ mapper.find(article.id).title.should eq new_title
16
+ end
17
+
18
+ it 'updates the object in memory' do
19
+ mapper.update article, title: new_title
20
+ article.title.should eq new_title
21
+ end
22
+
23
+ it 'resaves the object in the database' do
24
+ article.title = new_title
25
+ mapper.save article
26
+ mapper.find(article.id).title.should eq new_title
27
+ end
28
+ end
29
+
30
+
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'support/test_classes'
3
+
4
+ describe 'validations' do
5
+ let(:car_mapper) { Perpetuity[Car] }
6
+
7
+ it 'raises an exception when inserting an invalid object' do
8
+ car = Car.new
9
+ expect { car_mapper.insert car }.to raise_error
10
+ end
11
+
12
+ it 'does not raise an exception when validations are met' do
13
+ car = Car.new
14
+ car.make = "Volkswagen"
15
+ expect { car_mapper.insert car }.not_to raise_error
16
+ end
17
+ end
@@ -1,5 +1,6 @@
1
1
  require 'perpetuity/mapper_registry'
2
2
  require 'perpetuity/mapper'
3
+ require 'perpetuity/mongodb'
3
4
 
4
5
  module Perpetuity
5
6
  describe Mapper do
@@ -49,7 +50,7 @@ module Perpetuity
49
50
  end
50
51
 
51
52
  describe 'talking to the data source' do
52
- let(:data_source) { double }
53
+ let(:data_source) { MongoDB.new(db: nil) }
53
54
  before do
54
55
  mapper_class.stub(data_source: data_source)
55
56
  mapper_class.map Object, registry
@@ -78,7 +79,7 @@ module Perpetuity
78
79
  end
79
80
 
80
81
  it 'finds an object by ID' do
81
- returned_object = double
82
+ returned_object = double('Retrieved Object')
82
83
  criteria = { id: 1 }
83
84
  options = {:attribute=>nil, :direction=>nil, :limit=>nil, :page=>nil}
84
85
  data_source.should_receive(:retrieve)
@@ -12,11 +12,8 @@ module Perpetuity
12
12
  end
13
13
 
14
14
  it 'connects to its host' do
15
- connection = double('connection')
16
- Mongo::MongoClient.should_receive(:new).and_return connection
17
15
  mongo.connect
18
16
  mongo.should be_connected
19
- mongo.connection.should == connection
20
17
  end
21
18
 
22
19
  it 'connects automatically when accessing the database' do
@@ -85,8 +82,10 @@ module Perpetuity
85
82
  it 'retrieves by id if the id is a string' do
86
83
  time = Time.now.utc
87
84
  id = mongo.insert Object, {inserted: time}
88
- objects = mongo.retrieve(Object, id: id.to_s).to_a
89
- objects.map{|i| i["inserted"].to_f}.first.should be_within(0.001).of time.to_f
85
+
86
+ object = mongo.retrieve(Object, id: id.to_s).first
87
+ retrieved_time = object["inserted"]
88
+ retrieved_time.to_f.should be_within(0.001).of time.to_f
90
89
  end
91
90
 
92
91
  describe 'serializable objects' do
@@ -94,12 +93,16 @@ module Perpetuity
94
93
 
95
94
  it 'can insert serializable values' do
96
95
  serializable_values.each do |value|
97
- mongo.insert(Object, {value: value}).should be_a BSON::ObjectId
96
+ mongo.insert(Object, {value: value}).should be_a Moped::BSON::ObjectId
98
97
  mongo.can_serialize?(value).should be_true
99
98
  end
100
99
  end
101
100
  end
102
101
 
102
+ it 'generates a new query DSL object' do
103
+ mongo.query { |object| object.whatever == 1 }.should respond_to :to_db
104
+ end
105
+
103
106
  describe 'indexing' do
104
107
  let(:collection) { Object }
105
108
  let(:key) { 'object_id' }
@@ -0,0 +1,16 @@
1
+ require 'perpetuity/persisted_object'
2
+
3
+ module Perpetuity
4
+ describe PersistedObject do
5
+ let(:object) { Object.new }
6
+
7
+ before do
8
+ object.instance_variable_set '@id', :fake_id
9
+ object.extend PersistedObject
10
+ end
11
+
12
+ it 'gives an object an ID method' do
13
+ object.id.should be == :fake_id
14
+ end
15
+ end
16
+ end
@@ -3,7 +3,9 @@ require 'perpetuity/retrieval'
3
3
  module Perpetuity
4
4
  describe Retrieval do
5
5
  let(:data_source) { double('data_source') }
6
- let(:retrieval) { Perpetuity::Retrieval.new Object, {}, data_source }
6
+ let(:registry) { double('mapper_registry') }
7
+ let(:mapper) { double(mapped_class: Object, data_source: data_source, mapper_registry: registry) }
8
+ let(:retrieval) { Perpetuity::Retrieval.new mapper, {} }
7
9
  subject { retrieval }
8
10
 
9
11
  it "sorts the results" do
@@ -11,7 +11,7 @@ module Perpetuity
11
11
  let(:authors) { [dave, andy] }
12
12
  let(:book) { Book.new('The Pragmatic Programmer', authors) }
13
13
  let(:mapper_registry) { MapperRegistry.new }
14
- let!(:book_mapper) do
14
+ let!(:book_mapper_class) do
15
15
  registry = mapper_registry
16
16
  Class.new(Perpetuity::Mapper) do
17
17
  map Book, registry
@@ -19,7 +19,7 @@ module Perpetuity
19
19
  attribute :authors
20
20
  end
21
21
  end
22
- let!(:user_mapper) do
22
+ let!(:user_mapper_class) do
23
23
  registry = mapper_registry
24
24
  Class.new(Perpetuity::Mapper) do
25
25
  map User, registry
@@ -27,13 +27,13 @@ module Perpetuity
27
27
  end
28
28
  end
29
29
  let(:data_source) { double('Data Source') }
30
- let(:serializer) { Serializer.new(mapper_registry[Book], mapper_registry) }
30
+ let(:serializer) { Serializer.new(mapper_registry[Book]) }
31
31
 
32
32
  before do
33
- dave.stub(id: 1)
34
- andy.stub(id: 2)
35
- user_mapper.stub(data_source: data_source)
36
- book_mapper.stub(data_source: data_source)
33
+ dave.extend PersistedObject
34
+ andy.extend PersistedObject
35
+ user_mapper_class.stub(data_source: data_source)
36
+ book_mapper_class.stub(data_source: data_source)
37
37
  end
38
38
 
39
39
  it 'serializes an array of non-embedded attributes as references' do
@@ -58,5 +58,29 @@ module Perpetuity
58
58
  ]
59
59
  }
60
60
  end
61
+
62
+ context 'with objects that have hashes as attributes' do
63
+ let(:name_data) { {first_name: 'Jamie', last_name: 'Gaskins'} }
64
+ let(:serialized_data) do
65
+ {
66
+ 'name' => name_data
67
+ }
68
+ end
69
+ let(:user) { User.new(name_data) }
70
+ let(:user_mapper) { mapper_registry[User] }
71
+ let(:user_serializer) { Serializer.new(user_mapper) }
72
+
73
+ before do
74
+ data_source.stub(:can_serialize?).with(name_data) { true }
75
+ end
76
+
77
+ it 'serializes' do
78
+ user_serializer.serialize(user).should be == serialized_data
79
+ end
80
+
81
+ it 'unserializes' do
82
+ user_serializer.unserialize(serialized_data).name.should be == user.name
83
+ end
84
+ end
61
85
  end
62
86
  end