perpetuity 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/Guardfile +24 -0
- data/README.md +142 -0
- data/Rakefile +6 -0
- data/lib/perpetuity/attribute.rb +17 -0
- data/lib/perpetuity/attribute_set.rb +23 -0
- data/lib/perpetuity/config.rb +11 -0
- data/lib/perpetuity/data_injectable.rb +21 -0
- data/lib/perpetuity/mapper.rb +195 -0
- data/lib/perpetuity/mongodb/query.rb +19 -0
- data/lib/perpetuity/mongodb/query_attribute.rb +49 -0
- data/lib/perpetuity/mongodb/query_expression.rb +55 -0
- data/lib/perpetuity/mongodb.rb +102 -0
- data/lib/perpetuity/retrieval.rb +82 -0
- data/lib/perpetuity/validations/length.rb +36 -0
- data/lib/perpetuity/validations/presence.rb +14 -0
- data/lib/perpetuity/validations/validation_set.rb +27 -0
- data/lib/perpetuity/validations.rb +1 -0
- data/lib/perpetuity/version.rb +3 -0
- data/lib/perpetuity.rb +23 -0
- data/perpetuity.gemspec +25 -0
- data/spec/perpetuity/attribute_set_spec.rb +19 -0
- data/spec/perpetuity/attribute_spec.rb +19 -0
- data/spec/perpetuity/config_spec.rb +13 -0
- data/spec/perpetuity/data_injectable_spec.rb +26 -0
- data/spec/perpetuity/mapper_spec.rb +51 -0
- data/spec/perpetuity/mongodb/query_attribute_spec.rb +42 -0
- data/spec/perpetuity/mongodb/query_expression_spec.rb +49 -0
- data/spec/perpetuity/mongodb/query_spec.rb +37 -0
- data/spec/perpetuity/mongodb_spec.rb +91 -0
- data/spec/perpetuity/retrieval_spec.rb +58 -0
- data/spec/perpetuity/validations/length_spec.rb +53 -0
- data/spec/perpetuity/validations/presence_spec.rb +30 -0
- data/spec/perpetuity/validations_spec.rb +87 -0
- data/spec/perpetuity_spec.rb +293 -0
- data/spec/test_classes.rb +93 -0
- metadata +181 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'perpetuity/data_injectable'
|
2
|
+
require 'perpetuity/mapper'
|
3
|
+
|
4
|
+
module Perpetuity
|
5
|
+
class Retrieval
|
6
|
+
include DataInjectable
|
7
|
+
include Enumerable
|
8
|
+
attr_accessor :sort_attribute, :sort_direction, :result_limit, :result_page, :quantity_per_page
|
9
|
+
|
10
|
+
def initialize klass, criteria, data_source = Perpetuity.configuration.data_source
|
11
|
+
@class = klass
|
12
|
+
@criteria = criteria
|
13
|
+
@data_source = data_source
|
14
|
+
end
|
15
|
+
|
16
|
+
def sort attribute=:name
|
17
|
+
retrieval = clone
|
18
|
+
retrieval.sort_attribute = attribute
|
19
|
+
retrieval.sort_direction = :ascending
|
20
|
+
|
21
|
+
retrieval
|
22
|
+
end
|
23
|
+
|
24
|
+
def reverse
|
25
|
+
retrieval = clone
|
26
|
+
retrieval.sort_direction = retrieval.sort_direction == :descending ? :ascending : :descending
|
27
|
+
|
28
|
+
retrieval
|
29
|
+
end
|
30
|
+
|
31
|
+
def page page
|
32
|
+
retrieval = clone
|
33
|
+
retrieval.result_page = page
|
34
|
+
retrieval.quantity_per_page = 20
|
35
|
+
retrieval
|
36
|
+
end
|
37
|
+
|
38
|
+
def per_page per
|
39
|
+
retrieval = clone
|
40
|
+
retrieval.quantity_per_page = per
|
41
|
+
retrieval
|
42
|
+
end
|
43
|
+
|
44
|
+
def each &block
|
45
|
+
to_a.each(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_a
|
49
|
+
options = {
|
50
|
+
attribute: sort_attribute,
|
51
|
+
direction: sort_direction,
|
52
|
+
limit: result_limit || quantity_per_page,
|
53
|
+
page: result_page
|
54
|
+
}
|
55
|
+
results = @data_source.retrieve(@class, @criteria, options)
|
56
|
+
objects = []
|
57
|
+
results.each do |result|
|
58
|
+
object = @class.new
|
59
|
+
inject_data object, Mapper.new.unserialize(result)
|
60
|
+
|
61
|
+
objects << object
|
62
|
+
end
|
63
|
+
|
64
|
+
objects
|
65
|
+
end
|
66
|
+
|
67
|
+
def [] index
|
68
|
+
to_a[index]
|
69
|
+
end
|
70
|
+
|
71
|
+
def empty?
|
72
|
+
to_a.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
def limit lim
|
76
|
+
retrieval = clone
|
77
|
+
retrieval.result_limit = lim
|
78
|
+
|
79
|
+
retrieval
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Perpetuity
|
2
|
+
module Validations
|
3
|
+
class Length
|
4
|
+
def initialize attribute, options
|
5
|
+
@attribute = attribute
|
6
|
+
@at_least = nil
|
7
|
+
@at_most = nil
|
8
|
+
options.each do |option, value|
|
9
|
+
send option, value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def pass? object
|
14
|
+
length = object.send(@attribute).length
|
15
|
+
|
16
|
+
return false unless @at_least.nil? or @at_least <= length
|
17
|
+
return false unless @at_most.nil? or @at_most >= length
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def at_least value
|
23
|
+
@at_least = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def at_most value
|
27
|
+
@at_most = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def between range
|
31
|
+
at_least range.min
|
32
|
+
at_most range.max
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'perpetuity/validations/length'
|
2
|
+
require 'perpetuity/validations/presence'
|
3
|
+
|
4
|
+
module Perpetuity
|
5
|
+
class ValidationSet < Set
|
6
|
+
|
7
|
+
def valid? object
|
8
|
+
each do |validation|
|
9
|
+
return false unless validation.pass?(object)
|
10
|
+
end
|
11
|
+
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def invalid? object
|
16
|
+
!valid? object
|
17
|
+
end
|
18
|
+
|
19
|
+
def present attribute
|
20
|
+
self << Perpetuity::Validations::Presence.new(attribute)
|
21
|
+
end
|
22
|
+
|
23
|
+
def length attribute, options = {}
|
24
|
+
self << Perpetuity::Validations::Length.new(attribute, options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'perpetuity/validations/validation_set'
|
data/lib/perpetuity.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "perpetuity/version"
|
2
|
+
require "perpetuity/retrieval"
|
3
|
+
require "perpetuity/mongodb"
|
4
|
+
require "perpetuity/config"
|
5
|
+
require "perpetuity/mapper"
|
6
|
+
|
7
|
+
module Perpetuity
|
8
|
+
def self.configure &block
|
9
|
+
configuration.instance_exec(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.configuration
|
13
|
+
@@configuration ||= Configuration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.generate_mapper_for klass, &block
|
17
|
+
Mapper.generate_for klass, &block
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.[] klass
|
21
|
+
Mapper[klass]
|
22
|
+
end
|
23
|
+
end
|
data/perpetuity.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "perpetuity/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "perpetuity"
|
7
|
+
s.version = Perpetuity::VERSION
|
8
|
+
s.authors = ["Jamie Gaskins"]
|
9
|
+
s.email = ["jgaskins@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/jgaskins/perpetuity.git"
|
11
|
+
s.summary = %q{Persistence library allowing persistence of Ruby objects}
|
12
|
+
s.description = %q{Persistence library allowing persistence of Ruby objects without adding persistence concerns to domain objects.}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
# specify any dependencies here; for example:
|
20
|
+
s.add_development_dependency "rake"
|
21
|
+
s.add_development_dependency "rspec", "~> 2.8.0"
|
22
|
+
s.add_development_dependency "guard-rspec"
|
23
|
+
s.add_runtime_dependency "mongo"
|
24
|
+
s.add_runtime_dependency "bson_ext"
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'perpetuity/attribute_set'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe AttributeSet do
|
5
|
+
it 'contains attributes' do
|
6
|
+
attribute = double('Attribute')
|
7
|
+
subject << attribute
|
8
|
+
|
9
|
+
subject.first.should eq attribute
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can access attributes by name' do
|
13
|
+
user_attribute = double('Attribute', name: :user)
|
14
|
+
subject << user_attribute
|
15
|
+
|
16
|
+
subject[:user].should eq user_attribute
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'perpetuity/attribute'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe Attribute do
|
5
|
+
subject { Attribute.new :article, Object }
|
6
|
+
it 'has a name' do
|
7
|
+
subject.name.should == :article
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'has a type' do
|
11
|
+
subject.type.should == Object
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can be embedded' do
|
15
|
+
attribute = Attribute.new :article, Object, embedded: true
|
16
|
+
attribute.should be_embedded
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'perpetuity/data_injectable'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe DataInjectable do
|
5
|
+
let(:klass) { Class.new }
|
6
|
+
let(:object) { klass.new }
|
7
|
+
|
8
|
+
before { klass.extend DataInjectable }
|
9
|
+
|
10
|
+
it 'injects data into an object' do
|
11
|
+
klass.inject_data object, { a: 1, b: 2 }
|
12
|
+
object.instance_variable_get(:@a).should eq 1
|
13
|
+
object.instance_variable_get(:@b).should eq 2
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'injects an id' do
|
17
|
+
klass.inject_data object, { id: 1 }
|
18
|
+
object.id.should eq 1
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'injects a specified id' do
|
22
|
+
klass.give_id_to object, 2
|
23
|
+
object.id.should eq 2
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'perpetuity/mapper'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe Mapper do
|
5
|
+
let(:mapper) do
|
6
|
+
Mapper.new do
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { mapper }
|
11
|
+
|
12
|
+
it { should be_a Mapper }
|
13
|
+
|
14
|
+
it 'has correct attributes' do
|
15
|
+
Mapper.new { attribute :name, String }.attributes.should eq [:name]
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns an empty attribute list when no attributes have been assigned' do
|
19
|
+
Mapper.new.attributes.should be_empty
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can have embedded attributes' do
|
23
|
+
mapper_with_embedded_attrs = Mapper.new { attribute :comments, Array, embedded: true }
|
24
|
+
mapper_with_embedded_attrs.attribute_set[:comments].should be_embedded
|
25
|
+
end
|
26
|
+
|
27
|
+
its(:mapped_class) { should eq Object }
|
28
|
+
|
29
|
+
context 'with unserializable attributes' do
|
30
|
+
let(:unserializable_object) { 1.to_c }
|
31
|
+
let(:serialized_attrs) do
|
32
|
+
[ Marshal.dump(unserializable_object) ]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'serializes attributes' do
|
36
|
+
object = Object.new
|
37
|
+
object.stub(sub_objects: [unserializable_object])
|
38
|
+
mapper.attribute :sub_objects, Array, embedded: true
|
39
|
+
mapper.attributes_for(object)[:sub_objects].should eq serialized_attrs
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'unserializes attributes' do
|
43
|
+
let(:comments) { mapper.unserialize(serialized_attrs) }
|
44
|
+
subject { comments.first }
|
45
|
+
|
46
|
+
it { should be_a Complex }
|
47
|
+
it { should eq unserializable_object }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'perpetuity/mongodb/query_attribute'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe MongoDB::QueryAttribute do
|
5
|
+
let(:attribute) { MongoDB::QueryAttribute.new :attribute_name }
|
6
|
+
subject { attribute }
|
7
|
+
|
8
|
+
its(:name) { should == :attribute_name }
|
9
|
+
|
10
|
+
it 'checks for equality' do
|
11
|
+
(attribute == 1).should be_a MongoDB::QueryExpression
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'checks for less than' do
|
15
|
+
(attribute < 1).should be_a MongoDB::QueryExpression
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'checks for <=' do
|
19
|
+
(attribute <= 1).should be_a MongoDB::QueryExpression
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'checks for greater than' do
|
23
|
+
(attribute > 1).should be_a MongoDB::QueryExpression
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'checks for >=' do
|
27
|
+
(attribute >= 1).should be_a MongoDB::QueryExpression
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'checks for inequality' do
|
31
|
+
attribute.not_equal?(1).should be_a MongoDB::QueryExpression
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'checks for regexp matches' do
|
35
|
+
(attribute =~ /value/).should be_a MongoDB::QueryExpression
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'checks for inclusion' do
|
39
|
+
(attribute.in [1, 2, 3]).should be_a MongoDB::QueryExpression
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'perpetuity/mongodb/query_expression'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe MongoDB::QueryExpression do
|
5
|
+
let(:expression) { MongoDB::QueryExpression.new :attribute, :equals, :value }
|
6
|
+
subject { expression }
|
7
|
+
|
8
|
+
describe 'translation to Mongo expressions' do
|
9
|
+
it 'equality expression' do
|
10
|
+
expression.to_db.should == { attribute: :value }
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'less-than expression' do
|
14
|
+
expression.comparator = :less_than
|
15
|
+
expression.to_db.should == { attribute: { '$lt' => :value } }
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'less-than-or-equal-to expression' do
|
19
|
+
expression.comparator = :lte
|
20
|
+
expression.to_db.should == { attribute: { '$lte' => :value } }
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'greater-than expression' do
|
24
|
+
expression.comparator = :greater_than
|
25
|
+
expression.to_db.should == { attribute: { '$gt' => :value } }
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'greater-than-or-equal-to expression' do
|
29
|
+
expression.comparator = :gte
|
30
|
+
expression.to_db.should == { attribute: { '$gte' => :value } }
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'not-equal' do
|
34
|
+
expression.comparator = :not_equal
|
35
|
+
expression.to_db.should == { attribute: { '$ne' => :value } }
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'checks for inclusion' do
|
39
|
+
expression.comparator = :in
|
40
|
+
expression.to_db.should == { attribute: { '$in' => :value } }
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'checks for regexp matching' do
|
44
|
+
expression.comparator = :matches
|
45
|
+
expression.to_db.should == { attribute: :value }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'perpetuity/mongodb/query'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe MongoDB::Query do
|
5
|
+
let(:query) { MongoDB::Query }
|
6
|
+
|
7
|
+
it 'generates Mongo equality expressions' do
|
8
|
+
query.new{name == 'Jamie'}.to_db.should == {name: 'Jamie'}
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'generates Mongo less-than expressions' do
|
12
|
+
query.new{quantity < 10}.to_db.should == {quantity: { '$lt' => 10}}
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'generates Mongo less-than-or-equal expressions' do
|
16
|
+
query.new{quantity <= 10}.to_db.should == {quantity: { '$lte' => 10}}
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'generates Mongo greater-than expressions' do
|
20
|
+
query.new{quantity > 10}.to_db.should == {quantity: { '$gt' => 10}}
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'generates Mongo greater-than-or-equal expressions' do
|
24
|
+
query.new{quantity >= 10}.to_db.should == {quantity: { '$gte' => 10}}
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'generates Mongo inequality expressions' do
|
28
|
+
query.new{name.not_equal? 'Jamie'}.to_db.should == {
|
29
|
+
name: {'$ne' => 'Jamie'}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'generates Mongo regexp expressions' do
|
34
|
+
query.new{name =~ /Jamie/}.to_db.should == {name: /Jamie/}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'perpetuity/mongodb'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe MongoDB do
|
5
|
+
let(:mongo) { MongoDB.new db: 'perpetuity_gem_test' }
|
6
|
+
let(:klass) { String }
|
7
|
+
subject { mongo }
|
8
|
+
|
9
|
+
it 'is not connected when instantiated' do
|
10
|
+
mongo.should_not be_connected
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'connects to its host' do
|
14
|
+
connection = double('connection')
|
15
|
+
Mongo::Connection.stub(new: connection)
|
16
|
+
mongo.connect
|
17
|
+
mongo.should be_connected
|
18
|
+
mongo.connection.should == connection
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'connects automatically when accessing the database' do
|
22
|
+
mongo.database
|
23
|
+
mongo.should be_connected
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'initialization params' do
|
27
|
+
let(:host) { double('host') }
|
28
|
+
let(:port) { double('port') }
|
29
|
+
let(:db) { double('db') }
|
30
|
+
let(:pool_size) { double('pool size') }
|
31
|
+
let(:username) { double('username') }
|
32
|
+
let(:password) { double('password') }
|
33
|
+
let(:mongo) do
|
34
|
+
MongoDB.new(
|
35
|
+
host: host,
|
36
|
+
port: port,
|
37
|
+
db: db,
|
38
|
+
pool_size: pool_size,
|
39
|
+
username: username,
|
40
|
+
password: password
|
41
|
+
)
|
42
|
+
end
|
43
|
+
subject { mongo }
|
44
|
+
|
45
|
+
its(:host) { should == host }
|
46
|
+
its(:port) { should == port }
|
47
|
+
its(:db) { should == db }
|
48
|
+
its(:pool_size) { should == pool_size }
|
49
|
+
its(:username) { should == username }
|
50
|
+
its(:password) { should == password }
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'uses the selected database' do
|
54
|
+
mongo.database.name.should == 'perpetuity_gem_test'
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'removes all documents from a collection' do
|
58
|
+
mongo.insert klass, {}
|
59
|
+
mongo.delete_all klass
|
60
|
+
mongo.count(klass).should == 0
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'counts the documents in a collection' do
|
64
|
+
mongo.delete_all klass
|
65
|
+
3.times do
|
66
|
+
mongo.insert klass, {}
|
67
|
+
end
|
68
|
+
mongo.count(klass).should == 3
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'gets the first document in a collection' do
|
72
|
+
value = {value: 1}
|
73
|
+
mongo.insert klass, value
|
74
|
+
mongo.first(klass)[:hypothetical_value].should == value['value']
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'gets all of the documents in a collection' do
|
78
|
+
values = [{value: 1}, {value: 2}]
|
79
|
+
mongo.should_receive(:retrieve).with(Object, {}, {})
|
80
|
+
.and_return(values)
|
81
|
+
mongo.all(Object).should == values
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'retrieves by id if the id is a string' do
|
85
|
+
time = Time.now.utc
|
86
|
+
id = mongo.insert Article, {inserted: time}
|
87
|
+
objects = mongo.retrieve(Article, id: id.to_s).to_a
|
88
|
+
objects.map{|i| i["inserted"].to_f}.first.should be_within(0.001).of time.to_f
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'perpetuity/retrieval'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
describe Retrieval do
|
5
|
+
let(:data_source) { double('data_source') }
|
6
|
+
let(:retrieval) { Perpetuity::Retrieval.new Object, {}, data_source }
|
7
|
+
subject { retrieval }
|
8
|
+
|
9
|
+
it "sorts the results" do
|
10
|
+
sorted = retrieval.sort(:name)
|
11
|
+
sorted.sort_attribute.should == :name
|
12
|
+
end
|
13
|
+
|
14
|
+
it "reverses the sort order of the results" do
|
15
|
+
sorted = retrieval.sort(:name).reverse
|
16
|
+
sorted.sort_direction.should == :descending
|
17
|
+
end
|
18
|
+
|
19
|
+
it "limits the result set" do
|
20
|
+
retrieval.limit(1).result_limit.should == 1
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'indicates whether it includes a specific item' do
|
24
|
+
subject.stub(to_a: [1])
|
25
|
+
subject.should include 1
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can be empty' do
|
29
|
+
retrieval.stub(to_a: [])
|
30
|
+
retrieval.should be_empty
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'pagination' do
|
34
|
+
let(:paginated) { retrieval.page(2) }
|
35
|
+
it 'paginates data' do
|
36
|
+
paginated.result_page.should == 2
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'defaults to 20 items per page' do
|
40
|
+
paginated.quantity_per_page.should == 20
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'sets the number of items per page' do
|
44
|
+
paginated.per_page(50).quantity_per_page.should == 50
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'retrieves data from the data source' do
|
49
|
+
return_data = { id: 0, a: 1, b: 2 }
|
50
|
+
options = { attribute: nil, direction: nil, limit: nil, page: nil }
|
51
|
+
data_source.should_receive(:retrieve).with(Object, {}, options).
|
52
|
+
and_return([return_data])
|
53
|
+
results = retrieval.to_a
|
54
|
+
|
55
|
+
results.map(&:id).should == [0]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'perpetuity/validations/length'
|
2
|
+
|
3
|
+
module Perpetuity
|
4
|
+
module Validations
|
5
|
+
describe Length do
|
6
|
+
let(:length) { Length.new :to_s, {}}
|
7
|
+
|
8
|
+
describe 'minimum length' do
|
9
|
+
before { length.at_least 4 }
|
10
|
+
|
11
|
+
it 'invalidates' do
|
12
|
+
length.pass?('abc').should be false
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'validates' do
|
16
|
+
length.pass?('abcd').should be true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'maximum length' do
|
21
|
+
before { length.at_most 4 }
|
22
|
+
|
23
|
+
it 'validates' do
|
24
|
+
length.pass?('abcd').should be true
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'invalidates' do
|
28
|
+
length.pass?('abcde').should be false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'ranges' do
|
33
|
+
before { length.between 4..5 }
|
34
|
+
|
35
|
+
it 'invalidates values too short' do
|
36
|
+
length.pass?('abc').should be false
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'validates lengths at the low end' do
|
40
|
+
length.pass?('abcd').should be true
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'validates lengths at the high end' do
|
44
|
+
length.pass?('abcde').should be true
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'invalidates values too long' do
|
48
|
+
length.pass?('abcdef').should be false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|