perpetuity-mongodb 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,94 @@
1
+ require 'perpetuity/mongodb/query_union'
2
+ require 'perpetuity/mongodb/query_intersection'
3
+
4
+ module Perpetuity
5
+ class MongoDB
6
+ class QueryExpression
7
+ attr_accessor :attribute, :comparator, :negated, :value
8
+
9
+ def initialize attribute, comparator, value
10
+ @attribute = attribute
11
+ @comparator = comparator
12
+ @value = value
13
+ @negated = false
14
+
15
+ @attribute = @attribute.to_sym if @attribute.respond_to? :to_sym
16
+ end
17
+
18
+ def to_db
19
+ public_send @comparator
20
+ end
21
+
22
+ def equals
23
+ if @negated
24
+ { @attribute => { '$ne' => @value } }
25
+ else
26
+ { @attribute => @value }
27
+ end
28
+ end
29
+
30
+ def function func
31
+ f = { func => @value }
32
+
33
+ if @negated
34
+ { @attribute => { '$not' => f } }
35
+ else
36
+ { @attribute => f }
37
+ end
38
+ end
39
+
40
+ def less_than
41
+ function '$lt'
42
+ end
43
+
44
+ def lte
45
+ function '$lte'
46
+ end
47
+
48
+ def greater_than
49
+ function '$gt'
50
+ end
51
+
52
+ def gte
53
+ function '$gte'
54
+ end
55
+
56
+ def not_equal
57
+ function '$ne'
58
+ end
59
+
60
+ def in
61
+ function '$in'
62
+ end
63
+
64
+ def matches
65
+ if @negated
66
+ { @attribute => { '$not' => @value } }
67
+ else
68
+ { @attribute => @value }
69
+ end
70
+ end
71
+
72
+ def | other
73
+ QueryUnion.new(self, other)
74
+ end
75
+
76
+ def & other
77
+ QueryIntersection.new(self, other)
78
+ end
79
+
80
+ def negate
81
+ expr = dup
82
+ expr.negated = true
83
+ expr
84
+ end
85
+
86
+ def == other
87
+ attribute == other.attribute &&
88
+ comparator == other.comparator &&
89
+ value == other.value &&
90
+ negated == other.negated
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,16 @@
1
+ module Perpetuity
2
+ class MongoDB
3
+ class QueryIntersection
4
+ attr_reader :lhs, :rhs
5
+
6
+ def initialize lhs, rhs
7
+ @lhs = lhs
8
+ @rhs = rhs
9
+ end
10
+
11
+ def to_db
12
+ { '$and' => [lhs.to_db, rhs.to_db] }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Perpetuity
2
+ class MongoDB
3
+ class QueryUnion
4
+ attr_reader :lhs, :rhs
5
+
6
+ def initialize lhs, rhs
7
+ @lhs = lhs
8
+ @rhs = rhs
9
+ end
10
+
11
+ def to_db
12
+ { '$or' => [lhs.to_db, rhs.to_db] }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,174 @@
1
+ require 'perpetuity/data_injectable'
2
+ require 'perpetuity/reference'
3
+
4
+ module Perpetuity
5
+ class MongoDB
6
+ class Serializer
7
+ include DataInjectable
8
+
9
+ attr_reader :mapper, :mapper_registry
10
+
11
+ def initialize(mapper)
12
+ @mapper = mapper
13
+ @class = mapper.mapped_class
14
+ @mapper_registry = mapper.mapper_registry
15
+ end
16
+
17
+ def attribute_for object, attribute_name
18
+ object.instance_variable_get("@#{attribute_name}")
19
+ end
20
+
21
+ def has_attribute? object, attribute_name
22
+ object.instance_variable_defined? "@#{attribute_name}"
23
+ end
24
+
25
+ def serialize object
26
+ attrs = mapper.attribute_set.map do |attrib|
27
+ next unless has_attribute? object, attrib.name
28
+
29
+ value = attribute_for object, attrib.name
30
+
31
+ serialized_value = if value.is_a? Reference
32
+ serialize_reference value
33
+ elsif value.is_a? Array
34
+ serialize_array(value, attrib.embedded?)
35
+ elsif mapper.data_source.can_serialize? value
36
+ value
37
+ elsif mapper_registry.has_mapper?(value.class)
38
+ serialize_with_foreign_mapper(value, attrib.embedded?)
39
+ else
40
+ marshal(value)
41
+ end
42
+
43
+ [attrib.name.to_s, serialized_value]
44
+ end
45
+
46
+ Hash[attrs.compact]
47
+ end
48
+
49
+ def serialize_changes changed, original
50
+ Hash[Array(serialize(changed)) - Array(serialize(original))]
51
+ end
52
+
53
+ def unserialize data
54
+ if data.is_a? Array
55
+ unserialize_object_array data
56
+ else
57
+ object = unserialize_object(data)
58
+ give_id_to object
59
+ object
60
+ end
61
+ end
62
+
63
+ def unserialize_object data, klass=@class
64
+ if data.is_a? Hash
65
+ object = klass.allocate
66
+ data.each do |attr, value|
67
+ inject_attribute object, attr, unserialize_attribute(value)
68
+ end
69
+ object
70
+ else
71
+ unserialize_attribute data
72
+ end
73
+ end
74
+
75
+ def unserialize_object_array objects
76
+ objects.map do |data|
77
+ object = unserialize_object data
78
+ give_id_to object
79
+ object
80
+ end
81
+ end
82
+
83
+ def unserialize_attribute data
84
+ return data.map { |i| unserialize_attribute i } if data.is_a? Array
85
+ return data unless data.is_a? Hash
86
+ metadata = data.fetch("__metadata__", {})
87
+ marshaled = data.fetch("__marshaled__", false)
88
+
89
+ if marshaled
90
+ value = data.fetch("value")
91
+ return unmarshal(value)
92
+ end
93
+
94
+ if metadata.any?
95
+ klass = metadata['class'].split('::').inject(Kernel) do |scope, const_name|
96
+ scope.const_get(const_name)
97
+ end
98
+ id = metadata['id']
99
+
100
+ if id
101
+ Reference.new(klass, id)
102
+ else
103
+ unserialize_object(data, klass)
104
+ end
105
+ else
106
+ data
107
+ end
108
+ end
109
+
110
+ def serialize_with_foreign_mapper value, embedded = false
111
+ if embedded
112
+ value_mapper = mapper_registry[value.class]
113
+ value_serializer = Serializer.new(value_mapper)
114
+ attr = value_serializer.serialize(value)
115
+ attr.merge '__metadata__' => { 'class' => value.class }
116
+ else
117
+ serialize_reference(value)
118
+ end
119
+ end
120
+
121
+ def serialize_array enum, embedded
122
+ enum.map do |value|
123
+ if value.is_a? Reference
124
+ serialize_reference value
125
+ elsif value.is_a? Array
126
+ serialize_array(value)
127
+ elsif mapper.data_source.can_serialize? value
128
+ value
129
+ elsif mapper_registry.has_mapper?(value.class)
130
+ if embedded
131
+ {
132
+ '__metadata__' => {
133
+ 'class' => value.class.to_s
134
+ }
135
+ }.merge mapper_registry[value.class].serialize(value)
136
+ else
137
+ serialize_reference value
138
+ end
139
+ else
140
+ marshal value
141
+ end
142
+ end
143
+ end
144
+
145
+ def serialize_reference value
146
+ if value.is_a? Reference
147
+ reference = value
148
+ else
149
+ unless mapper.persisted? value
150
+ mapper_registry[value.class].insert value
151
+ end
152
+ reference = Reference.new(value.class.to_s, mapper.id_for(value))
153
+ end
154
+ {
155
+ '__metadata__' => {
156
+ 'class' => reference.klass.to_s,
157
+ 'id' => reference.id
158
+ }
159
+ }
160
+ end
161
+
162
+ def marshal value
163
+ {
164
+ '__marshaled__' => true,
165
+ 'value' => Marshal.dump(value)
166
+ }
167
+ end
168
+
169
+ def unmarshal value
170
+ Marshal.load(value)
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,5 @@
1
+ module Perpetuity
2
+ module Mongodb
3
+ VERSION = "1.0.0.beta"
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'perpetuity/mongodb/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "perpetuity-mongodb"
8
+ spec.version = Perpetuity::Mongodb::VERSION
9
+ spec.authors = ["Jamie Gaskins"]
10
+ spec.email = ["jgaskins@gmail.com"]
11
+ spec.description = %q{MongoDB adapter for Perpetuity}
12
+ spec.summary = %q{MongoDB adapter for Perpetuity}
13
+ spec.homepage = "https://github.com/jgaskins/perpetuity-mongodb"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_runtime_dependency "perpetuity", "~> 1.0.0.beta"
25
+ spec.add_runtime_dependency "moped"
26
+ end
@@ -0,0 +1,44 @@
1
+ require 'perpetuity/mongodb/index'
2
+
3
+ module Perpetuity
4
+ class MongoDB
5
+ describe Index do
6
+ let(:attribute) { double(name: 'name') }
7
+ let(:index) { Index.new(Object, attribute) }
8
+
9
+ it 'is not active by default' do
10
+ index.should_not be_active
11
+ end
12
+
13
+ it 'can be activated' do
14
+ index.activate!
15
+ index.should be_active
16
+ end
17
+
18
+ it 'can be unique' do
19
+ index = Index.new(Object, attribute, unique: true)
20
+ index.should be_unique
21
+ end
22
+
23
+ it 'is not unique by default' do
24
+ index.should_not be_unique
25
+ end
26
+
27
+ describe 'index ordering' do
28
+ it 'can be ordered in ascending order' do
29
+ index = Index.new(Object, attribute, order: :ascending)
30
+ index.order.should be :ascending
31
+ end
32
+
33
+ it 'is ordered ascending by default' do
34
+ index.order.should be :ascending
35
+ end
36
+
37
+ it 'can be ordered in descending order' do
38
+ index = Index.new(Object, attribute, order: :descending)
39
+ index.order.should be :descending
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,58 @@
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 'allows checking subattributes' do
11
+ attribute.title.name.should == :'attribute_name.title'
12
+ end
13
+
14
+ it 'wraps .id subattribute in metadata' do
15
+ attribute.id.name.should == :'attribute_name.__metadata__.id'
16
+ end
17
+
18
+ it 'wraps .klass subattribute in metadata' do
19
+ attribute.klass.name.should == :'attribute_name.__metadata__.class'
20
+ end
21
+
22
+ it 'checks for equality' do
23
+ (attribute == 1).should be_a MongoDB::QueryExpression
24
+ end
25
+
26
+ it 'checks for less than' do
27
+ (attribute < 1).should be_a MongoDB::QueryExpression
28
+ end
29
+
30
+ it 'checks for <=' do
31
+ (attribute <= 1).should be_a MongoDB::QueryExpression
32
+ end
33
+
34
+ it 'checks for greater than' do
35
+ (attribute > 1).should be_a MongoDB::QueryExpression
36
+ end
37
+
38
+ it 'checks for >=' do
39
+ (attribute >= 1).should be_a MongoDB::QueryExpression
40
+ end
41
+
42
+ it 'checks for inequality' do
43
+ (attribute != 1).should be_a MongoDB::QueryExpression
44
+ end
45
+
46
+ it 'checks for regexp matches' do
47
+ (attribute =~ /value/).should be_a MongoDB::QueryExpression
48
+ end
49
+
50
+ it 'checks for inclusion' do
51
+ (attribute.in [1, 2, 3]).should be_a MongoDB::QueryExpression
52
+ end
53
+
54
+ it 'checks for its own truthiness' do
55
+ attribute.to_db.should == ((attribute != false) & (attribute != nil)).to_db
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,67 @@
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
+
49
+ describe 'unions' do
50
+ let(:lhs) { MongoDB::QueryExpression.new :first, :equals, :one }
51
+ let(:rhs) { MongoDB::QueryExpression.new :second, :equals, :two }
52
+
53
+ it 'converts | to an $or query' do
54
+ (lhs | rhs).to_db.should == { '$or' => [{first: :one}, {second: :two}] }
55
+ end
56
+ end
57
+
58
+ describe 'intersections' do
59
+ let(:lhs) { MongoDB::QueryExpression.new :first, :equals, :one }
60
+ let(:rhs) { MongoDB::QueryExpression.new :second, :equals, :two }
61
+
62
+ it 'converts & to an $and query' do
63
+ (lhs & rhs).to_db.should == { '$and' => [{first: :one}, {second: :two}] }
64
+ end
65
+ end
66
+ end
67
+ end