dm-mongo-adapter 0.2.0.pre1
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.
- data/.gitignore +9 -0
- data/LICENSE +20 -0
- data/README.rdoc +130 -0
- data/Rakefile +36 -0
- data/TODO +33 -0
- data/VERSION.yml +5 -0
- data/bin/console +31 -0
- data/dm-mongo-adapter.gemspec +154 -0
- data/lib/mongo_adapter.rb +71 -0
- data/lib/mongo_adapter/adapter.rb +255 -0
- data/lib/mongo_adapter/aggregates.rb +21 -0
- data/lib/mongo_adapter/conditions.rb +100 -0
- data/lib/mongo_adapter/embedded_model.rb +187 -0
- data/lib/mongo_adapter/embedded_resource.rb +134 -0
- data/lib/mongo_adapter/embedments/one_to_many.rb +139 -0
- data/lib/mongo_adapter/embedments/one_to_one.rb +53 -0
- data/lib/mongo_adapter/embedments/relationship.rb +258 -0
- data/lib/mongo_adapter/migrations.rb +45 -0
- data/lib/mongo_adapter/model.rb +89 -0
- data/lib/mongo_adapter/model/embedment.rb +215 -0
- data/lib/mongo_adapter/modifier.rb +85 -0
- data/lib/mongo_adapter/query.rb +252 -0
- data/lib/mongo_adapter/query/java_script.rb +145 -0
- data/lib/mongo_adapter/resource.rb +147 -0
- data/lib/mongo_adapter/types/date.rb +28 -0
- data/lib/mongo_adapter/types/date_time.rb +28 -0
- data/lib/mongo_adapter/types/db_ref.rb +65 -0
- data/lib/mongo_adapter/types/discriminator.rb +32 -0
- data/lib/mongo_adapter/types/object_id.rb +72 -0
- data/lib/mongo_adapter/types/objects.rb +31 -0
- data/script/performance.rb +260 -0
- data/spec/legacy/README +6 -0
- data/spec/legacy/adapter_shared_spec.rb +299 -0
- data/spec/legacy/adapter_spec.rb +174 -0
- data/spec/legacy/associations_spec.rb +140 -0
- data/spec/legacy/embedded_resource_spec.rb +157 -0
- data/spec/legacy/embedments_spec.rb +177 -0
- data/spec/legacy/modifier_spec.rb +81 -0
- data/spec/legacy/property_spec.rb +51 -0
- data/spec/legacy/spec_helper.rb +3 -0
- data/spec/legacy/sti_spec.rb +53 -0
- data/spec/lib/cleanup_models.rb +32 -0
- data/spec/lib/raw_connections.rb +11 -0
- data/spec/public/embedded_collection_spec.rb +61 -0
- data/spec/public/embedded_resource_spec.rb +220 -0
- data/spec/public/model/embedment_spec.rb +186 -0
- data/spec/public/model_spec.rb +37 -0
- data/spec/public/resource_spec.rb +564 -0
- data/spec/public/shared/model_embedments_spec.rb +338 -0
- data/spec/public/shared/object_id_shared_spec.rb +56 -0
- data/spec/public/types/df_ref_spec.rb +6 -0
- data/spec/public/types/discriminator_spec.rb +118 -0
- data/spec/public/types/embedded_array_spec.rb +55 -0
- data/spec/public/types/embedded_hash_spec.rb +83 -0
- data/spec/public/types/object_id_spec.rb +6 -0
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/embedded_model_spec.rb +43 -0
- data/spec/semipublic/model/embedment_spec.rb +42 -0
- data/spec/semipublic/resource_spec.rb +70 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +45 -0
- data/tasks/spec.rake +37 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +21 -0
- metadata +215 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Types
|
4
|
+
# Each object (document) stored in Mongo DB has an _id field as its
|
5
|
+
# first attribute. This is an object id. It must be unique for each
|
6
|
+
# member of a collection (this is enforced if the collection has an _id
|
7
|
+
# index, which is the case by default).
|
8
|
+
#
|
9
|
+
# The database will assign an _id if an object being inserted into a
|
10
|
+
# collection does not have one.
|
11
|
+
#
|
12
|
+
# The _id may be of any type as long as it is a unique value.
|
13
|
+
#
|
14
|
+
# @see http://www.mongodb.org/display/DOCS/Object+IDs
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
class ObjectID < DataMapper::Type
|
18
|
+
primitive ::Object
|
19
|
+
key true
|
20
|
+
field "_id"
|
21
|
+
required false
|
22
|
+
|
23
|
+
# Returns the ObjectID as a string; suitable for use in a Resource
|
24
|
+
#
|
25
|
+
# @return [String]
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
def self.load(value, property)
|
29
|
+
typecast(value, property)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the ObjectID as a Mongo::ObjectID; suitable to be passed to
|
33
|
+
# the Mongo library
|
34
|
+
#
|
35
|
+
# @return [Mongo::ObjectID] The dumped ID.
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def self.dump(value, property)
|
39
|
+
case value
|
40
|
+
when NilClass
|
41
|
+
nil
|
42
|
+
when String
|
43
|
+
::Mongo::ObjectID.from_string(value)
|
44
|
+
when ::Mongo::ObjectID
|
45
|
+
value
|
46
|
+
else
|
47
|
+
raise ArgumentError.new('+value+ must be nil, String or ObjectID')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the ObjectID as a string
|
52
|
+
#
|
53
|
+
# @return [String]
|
54
|
+
#
|
55
|
+
# @api semipublic
|
56
|
+
def self.typecast(value, property)
|
57
|
+
case value
|
58
|
+
when NilClass
|
59
|
+
nil
|
60
|
+
when String
|
61
|
+
value
|
62
|
+
when ::Mongo::ObjectID
|
63
|
+
value.to_s
|
64
|
+
else
|
65
|
+
raise ArgumentError.new('+value+ must be nil, String or ObjectID')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end # ObjectID
|
70
|
+
end # Types
|
71
|
+
end # Mongo
|
72
|
+
end # DataMapper
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Mongo
|
3
|
+
module Types
|
4
|
+
class EmbeddedArray < DataMapper::Type
|
5
|
+
primitive Object
|
6
|
+
end
|
7
|
+
|
8
|
+
class EmbeddedHash < DataMapper::Type
|
9
|
+
primitive Object
|
10
|
+
|
11
|
+
# @api public
|
12
|
+
def self.load(value, property)
|
13
|
+
typecast(value, property)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @api semipublic
|
17
|
+
def self.typecast(value, property)
|
18
|
+
case value
|
19
|
+
when NilClass
|
20
|
+
nil
|
21
|
+
when Hash
|
22
|
+
value.to_mash.symbolize_keys
|
23
|
+
when Array
|
24
|
+
value.empty? ? Mash.new : [value].to_mash.symbolize_keys
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end #EmbeddedHash
|
28
|
+
|
29
|
+
end # Types
|
30
|
+
end # Mongo
|
31
|
+
end # DataMapper
|
@@ -0,0 +1,260 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
gem 'addressable', '~>2.0'
|
7
|
+
gem 'faker', '~>0.3.1'
|
8
|
+
gem 'rbench', '~>0.2.3'
|
9
|
+
|
10
|
+
require 'addressable/uri'
|
11
|
+
require 'faker'
|
12
|
+
require 'rbench'
|
13
|
+
require 'dm-core'
|
14
|
+
|
15
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'mongo_adapter'))
|
16
|
+
|
17
|
+
TIMES = ENV.key?('x') ? ENV['x'].to_i : 10_000
|
18
|
+
|
19
|
+
configuration = { :hostname => 'localhost', :database => 'dm_core_test', :username => 'root', :password => '' }
|
20
|
+
|
21
|
+
DataMapper.setup(:default, configuration.merge(:adapter => "mongo").except(:username, :password))
|
22
|
+
DataMapper.setup(:mysql, configuration.merge(:adapter => "mysql"))
|
23
|
+
|
24
|
+
class User
|
25
|
+
include DataMapper::Resource
|
26
|
+
|
27
|
+
def self.default_repository_name; :mysql; end
|
28
|
+
|
29
|
+
property :id, Serial
|
30
|
+
property :name, String
|
31
|
+
property :email, String
|
32
|
+
property :about, Text, :lazy => false
|
33
|
+
property :created_on, Time
|
34
|
+
end
|
35
|
+
|
36
|
+
class Exhibit
|
37
|
+
include DataMapper::Resource
|
38
|
+
|
39
|
+
def self.default_repository_name; :mysql; end
|
40
|
+
|
41
|
+
property :id, Serial
|
42
|
+
property :name, String
|
43
|
+
property :user_id, Integer
|
44
|
+
property :notes, Text, :lazy => false
|
45
|
+
property :created_on, Time
|
46
|
+
end
|
47
|
+
|
48
|
+
User.has User.n, :exhibits
|
49
|
+
Exhibit.belongs_to :user
|
50
|
+
|
51
|
+
module Mongo
|
52
|
+
class User
|
53
|
+
include DataMapper::Mongo::Resource
|
54
|
+
|
55
|
+
def self.default_storage_name; 'user'; end
|
56
|
+
|
57
|
+
property :id, DataMapper::Mongo::Types::ObjectID
|
58
|
+
property :name, String
|
59
|
+
property :email, String
|
60
|
+
property :about, Text, :lazy => false
|
61
|
+
property :created_on, Time
|
62
|
+
end
|
63
|
+
|
64
|
+
class Exhibit
|
65
|
+
include DataMapper::Mongo::Resource
|
66
|
+
|
67
|
+
def self.default_storage_name; 'exhibit'; end
|
68
|
+
|
69
|
+
property :id, DataMapper::Mongo::Types::ObjectID
|
70
|
+
property :name, String
|
71
|
+
property :user_id, Integer
|
72
|
+
property :notes, Text, :lazy => false
|
73
|
+
property :created_on, Time
|
74
|
+
|
75
|
+
belongs_to :user
|
76
|
+
end
|
77
|
+
|
78
|
+
User.has User.n, :exhibits
|
79
|
+
Exhibit.belongs_to :user
|
80
|
+
end
|
81
|
+
|
82
|
+
def touch_attributes(*exhibits)
|
83
|
+
exhibits.flatten.each do |exhibit|
|
84
|
+
exhibit.id
|
85
|
+
exhibit.name
|
86
|
+
exhibit.created_on
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def touch_relationships(*exhibits)
|
91
|
+
exhibits.flatten.each do |exhibit|
|
92
|
+
exhibit.id
|
93
|
+
exhibit.name
|
94
|
+
exhibit.created_on
|
95
|
+
exhibit.user
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
root = File.dirname(__FILE__)
|
100
|
+
|
101
|
+
dump_dir = root / 'dumps'
|
102
|
+
|
103
|
+
dump_dir_mysql = dump_dir / 'mysql'
|
104
|
+
dump_dir_mongo = dump_dir / 'mongo'
|
105
|
+
|
106
|
+
FileUtils.mkdir_p(dump_dir_mysql) unless File.exist?(dump_dir_mysql)
|
107
|
+
FileUtils.mkdir_p(dump_dir_mongo) unless File.exist?(dump_dir_mongo)
|
108
|
+
|
109
|
+
dump_sql = dump_dir / 'mysql' / 'dump.sql'
|
110
|
+
dump_mongo_users = dump_dir_mongo / configuration[:database] / 'users.bson'
|
111
|
+
dump_mongo_exhibits = dump_dir_mongo / configuration[:database] / 'exhibits.bson'
|
112
|
+
|
113
|
+
mongo_user_ids = []
|
114
|
+
mongo_exhibit_ids = []
|
115
|
+
|
116
|
+
mysql_user_ids = []
|
117
|
+
mysql_exhibit_ids = []
|
118
|
+
|
119
|
+
puts 'Clearing databases...'
|
120
|
+
|
121
|
+
User.auto_migrate!
|
122
|
+
Exhibit.auto_migrate!
|
123
|
+
|
124
|
+
Mongo::User.auto_migrate!
|
125
|
+
Mongo::Exhibit.auto_migrate!
|
126
|
+
|
127
|
+
# pre-compute the insert statements and fake data compilation,
|
128
|
+
# so the benchmmysqlks below show the actual runtime for the execute
|
129
|
+
# method, minus the setup steps
|
130
|
+
|
131
|
+
# Using the same paragraphs for all exhibits because it is very slow
|
132
|
+
# to generate unique paragraphs for all exhibits.
|
133
|
+
notes = Faker::Lorem.paragraphs.join($/)
|
134
|
+
today = Time.now
|
135
|
+
|
136
|
+
puts 'Generating data for benchmarking...'
|
137
|
+
|
138
|
+
c = configuration
|
139
|
+
|
140
|
+
if File.exist?(dump_sql) && File.exist?(dump_mongo_users) && File.exist?(dump_mongo_exhibits)
|
141
|
+
puts "Found db dumps, importing..."
|
142
|
+
|
143
|
+
`mysql -u #{c[:username]} #{"-p#{ c[:password]}" unless c[:password].blank?} --database #{c[:database]} < #{dump_sql}`
|
144
|
+
`mongorestore -d #{c[:database]} -c users --dir #{dump_mongo_users}`
|
145
|
+
`mongorestore -d #{c[:database]} -c exhibits --dir #{dump_mongo_exhibits}`
|
146
|
+
|
147
|
+
mysql_user_ids = User.all.map(&:id)
|
148
|
+
mysql_exhibit_ids = Exhibit.all.map(&:id)
|
149
|
+
|
150
|
+
mongo_user_ids = Mongo::User.all.map(&:id)
|
151
|
+
mongo_exhibit_ids = Mongo::Exhibit.all.map(&:id)
|
152
|
+
else
|
153
|
+
FileUtils.rm(dump_sql) if File.exist?(dump_sql)
|
154
|
+
FileUtils.rm_r(dump_mongo_users) if File.exist?(dump_mongo_users)
|
155
|
+
FileUtils.rm_r(dump_mongo_exhibits) if File.exist?(dump_mongo_exhibits)
|
156
|
+
|
157
|
+
puts 'Inserting 10,000 users and exhibits...'
|
158
|
+
10_000.times do
|
159
|
+
user_properties = {
|
160
|
+
:created_on => today,
|
161
|
+
:name => Faker::Name.name,
|
162
|
+
:email => Faker::Internet.email }
|
163
|
+
|
164
|
+
exhibit_properties = {
|
165
|
+
:created_on => today,
|
166
|
+
:name => Faker::Company.name,
|
167
|
+
:notes => notes
|
168
|
+
}
|
169
|
+
|
170
|
+
mongo_user = Mongo::User.create(user_properties)
|
171
|
+
mongo_exhibit = Mongo::Exhibit.create(exhibit_properties.merge(:user_id => mongo_user.id))
|
172
|
+
|
173
|
+
mongo_user_ids << mongo_user.id.to_s
|
174
|
+
mongo_exhibit_ids << mongo_exhibit.id.to_s
|
175
|
+
|
176
|
+
mysql_user = User.new(user_properties)
|
177
|
+
mysql_user.save!
|
178
|
+
mysql_exhibit = Exhibit.new(exhibit_properties.merge(:user_id => mysql_user.id))
|
179
|
+
mysql_exhibit.save!
|
180
|
+
|
181
|
+
mysql_user_ids << mysql_user.id
|
182
|
+
mysql_exhibit_ids << mysql_exhibit.id
|
183
|
+
end
|
184
|
+
|
185
|
+
# mysql dump
|
186
|
+
`mysqldump -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} exhibits users > #{dump_sql}`
|
187
|
+
# mongo dump
|
188
|
+
`mongodump -d #{c[:database]} -c users -o #{dump_dir_mongo}`
|
189
|
+
`mongodump -d #{c[:database]} -c exhibits -o #{dump_dir_mongo}`
|
190
|
+
end
|
191
|
+
|
192
|
+
RBench.run(TIMES) do
|
193
|
+
column :times
|
194
|
+
column :mysql, :title => 'MySQL Adapter'
|
195
|
+
column :mongo, :title => "Mongo Adapter"
|
196
|
+
column :diff, :compare => [:mysql, :mongo]
|
197
|
+
|
198
|
+
report 'Model#id', (TIMES * 100).ceil do
|
199
|
+
mysql_obj = Exhibit.get(mysql_exhibit_ids.first)
|
200
|
+
mongo_obj = Mongo::Exhibit.get(mongo_exhibit_ids.first)
|
201
|
+
|
202
|
+
mysql { mysql_obj.id }
|
203
|
+
mongo { mongo_obj.id }
|
204
|
+
end
|
205
|
+
|
206
|
+
report 'Model.get specific (not cached)' do
|
207
|
+
mysql { touch_attributes(Exhibit.get(mysql_exhibit_ids.first)) }
|
208
|
+
mongo { touch_attributes(Mongo::Exhibit.get(mongo_exhibit_ids.first)) }
|
209
|
+
end
|
210
|
+
|
211
|
+
report 'Model.get specific (cached)' do
|
212
|
+
Exhibit.repository(:mysql) { mysql { touch_attributes(Exhibit.get(mysql_exhibit_ids.first)) } }
|
213
|
+
Mongo::Exhibit.repository(:default) { mongo { touch_attributes(Mongo::Exhibit.get(mongo_exhibit_ids.first)) } }
|
214
|
+
end
|
215
|
+
|
216
|
+
report 'Model.first' do
|
217
|
+
mysql { touch_attributes(Exhibit.first) }
|
218
|
+
mongo { touch_attributes(Mongo::Exhibit.first) }
|
219
|
+
end
|
220
|
+
|
221
|
+
report 'Model.all limit(100)', (TIMES / 100).ceil do
|
222
|
+
mysql { touch_attributes(Exhibit.all(:limit => 100)) }
|
223
|
+
mongo { touch_attributes(Mongo::Exhibit.all(:limit => 100)) }
|
224
|
+
end
|
225
|
+
|
226
|
+
report 'Model.all limit(10,000)', (TIMES / 1000).ceil do
|
227
|
+
mysql { touch_attributes(Exhibit.all(:limit => 10_000)) }
|
228
|
+
mongo { touch_attributes(Mongo::Exhibit.all(:limit => 10_000)) }
|
229
|
+
end
|
230
|
+
|
231
|
+
exhibit = {
|
232
|
+
:name => Faker::Company.name,
|
233
|
+
:notes => Faker::Lorem.paragraphs.join($/),
|
234
|
+
:created_on => today
|
235
|
+
}
|
236
|
+
|
237
|
+
report 'Model.create' do
|
238
|
+
mysql { Exhibit.create(exhibit) }
|
239
|
+
mongo { Mongo::Exhibit.create(exhibit) }
|
240
|
+
end
|
241
|
+
|
242
|
+
report 'Resource#attributes=' do
|
243
|
+
attrs_first = { :name => 'sam', :notes => 'foo bar' }
|
244
|
+
attrs_second = { :name => 'tom', :notes => 'foo bar' }
|
245
|
+
mysql { exhibit = Exhibit.new(attrs_first); exhibit.attributes = attrs_second }
|
246
|
+
mongo { exhibit = Mongo::Exhibit.new(attrs_first); exhibit.attributes = attrs_second }
|
247
|
+
end
|
248
|
+
|
249
|
+
report 'Resource#update' do
|
250
|
+
mysql { Exhibit.get(mysql_user_ids.first).update(:name => 'bob') }
|
251
|
+
mongo { Mongo::Exhibit.get(mongo_exhibit_ids.first).update(:name => 'bob') }
|
252
|
+
end
|
253
|
+
|
254
|
+
report 'Resource#destroy' do
|
255
|
+
mysql { Exhibit.first.destroy }
|
256
|
+
mongo { Mongo::Exhibit.first.destroy }
|
257
|
+
end
|
258
|
+
|
259
|
+
summary 'Total'
|
260
|
+
end
|
data/spec/legacy/README
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
This directory contains the old specs, written for 0.1.0. They are retained
|
2
|
+
here while the specs are separated into spec/{public,semipublic}.
|
3
|
+
|
4
|
+
Examples marked as @done indicate those for which there are equivalents in
|
5
|
+
spec/{public,semipublic}. Once all specs are moved (or rewritten, as
|
6
|
+
appropriate), the legacy specs will be removed.
|
@@ -0,0 +1,299 @@
|
|
1
|
+
share_examples_for 'An Adapter' do
|
2
|
+
|
3
|
+
def self.adapter_supports?(*methods)
|
4
|
+
methods.all? do |method|
|
5
|
+
# TODO: figure out a way to see if the instance method is only inherited
|
6
|
+
# from the Abstract Adapter, and not defined in it's class. If that is
|
7
|
+
# the case return false
|
8
|
+
|
9
|
+
# CRUD methods can be inherited from parent class
|
10
|
+
described_type.instance_methods.any? { |instance_method| method.to_s == instance_method.to_s }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
before :all do
|
15
|
+
# create all tables and constraints before each spec
|
16
|
+
if @repository.respond_to?(:auto_migrate!)
|
17
|
+
Heffalump.auto_migrate!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if adapter_supports?(:create)
|
22
|
+
describe '#create' do
|
23
|
+
it 'should not raise any errors' do
|
24
|
+
lambda {
|
25
|
+
Heffalump.create(:color => 'peach')
|
26
|
+
}.should_not raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should set the identity field for the resource' do
|
30
|
+
heffalump = Heffalump.new(:color => 'peach')
|
31
|
+
heffalump.id.should be_nil
|
32
|
+
heffalump.save
|
33
|
+
heffalump.id.should_not be_nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
else
|
37
|
+
it 'needs to support #create'
|
38
|
+
end
|
39
|
+
|
40
|
+
if adapter_supports?(:read)
|
41
|
+
describe '#read' do
|
42
|
+
before :all do
|
43
|
+
@heffalump = Heffalump.create(:color => 'brownish hue')
|
44
|
+
#just going to borrow this, so I can check the return values
|
45
|
+
@query = Heffalump.all.query
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should not raise any errors' do
|
49
|
+
lambda {
|
50
|
+
Heffalump.all()
|
51
|
+
}.should_not raise_error
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should return stuff' do
|
55
|
+
Heffalump.all.should be_include(@heffalump)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
else
|
59
|
+
it 'needs to support #read'
|
60
|
+
end
|
61
|
+
|
62
|
+
if adapter_supports?(:update)
|
63
|
+
describe '#update' do
|
64
|
+
before do
|
65
|
+
@heffalump = Heffalump.create(:color => 'indigo')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should not raise any errors' do
|
69
|
+
lambda {
|
70
|
+
@heffalump.color = 'violet'
|
71
|
+
@heffalump.save
|
72
|
+
}.should_not raise_error
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should not alter the identity field' do
|
76
|
+
id = @heffalump.id
|
77
|
+
@heffalump.color = 'violet'
|
78
|
+
@heffalump.save
|
79
|
+
@heffalump.id.should == id
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should update altered fields' do
|
83
|
+
@heffalump.color = 'violet'
|
84
|
+
@heffalump.save
|
85
|
+
Heffalump.get(*@heffalump.key).color.should == 'violet'
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should not alter other fields' do
|
89
|
+
color = @heffalump.color
|
90
|
+
@heffalump.num_spots = 3
|
91
|
+
@heffalump.save
|
92
|
+
Heffalump.get(*@heffalump.key).color.should == color
|
93
|
+
end
|
94
|
+
end
|
95
|
+
else
|
96
|
+
it 'needs to support #update'
|
97
|
+
end
|
98
|
+
|
99
|
+
if adapter_supports?(:delete)
|
100
|
+
describe '#delete' do
|
101
|
+
before do
|
102
|
+
@heffalump = Heffalump.create(:color => 'forest green')
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should not raise any errors' do
|
106
|
+
lambda {
|
107
|
+
@heffalump.destroy
|
108
|
+
}.should_not raise_error
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should delete the requested resource' do
|
112
|
+
id = @heffalump.id
|
113
|
+
@heffalump.destroy
|
114
|
+
Heffalump.get(id).should be_nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
else
|
118
|
+
it 'needs to support #delete'
|
119
|
+
end
|
120
|
+
|
121
|
+
if adapter_supports?(:read, :create)
|
122
|
+
describe 'query matching' do
|
123
|
+
before :all do
|
124
|
+
@red = Heffalump.create(:color => 'red')
|
125
|
+
@two = Heffalump.create(:num_spots => 2)
|
126
|
+
@five = Heffalump.create(:num_spots => 5)
|
127
|
+
end
|
128
|
+
|
129
|
+
describe 'conditions' do
|
130
|
+
describe 'eql' do
|
131
|
+
it 'should be able to search for objects included in an inclusive range of values' do
|
132
|
+
Heffalump.all(:num_spots => 1..5).should be_include(@five)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should be able to search for objects included in an exclusive range of values' do
|
136
|
+
Heffalump.all(:num_spots => 1...6).should be_include(@five)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should not be able to search for values not included in an inclusive range of values' do
|
140
|
+
Heffalump.all(:num_spots => 1..4).should_not be_include(@five)
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should not be able to search for values not included in an exclusive range of values' do
|
144
|
+
Heffalump.all(:num_spots => 1...5).should_not be_include(@five)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'not' do
|
149
|
+
it 'should be able to search for objects with not equal value' do
|
150
|
+
Heffalump.all(:color.not => 'red').should_not be_include(@red)
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should include objects that are not like the value' do
|
154
|
+
Heffalump.all(:color.not => 'black').should be_include(@red)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should be able to search for objects with not nil value' do
|
158
|
+
Heffalump.all(:color.not => nil).should be_include(@red)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should not include objects with a nil value' do
|
162
|
+
Heffalump.all(:color.not => nil).should_not be_include(@two)
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'should be able to search for object with a nil value using required properties' do
|
166
|
+
Heffalump.all(:id.not => nil).should == [ @red, @two, @five ]
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should be able to search for objects not in an empty list (match all)' do
|
170
|
+
Heffalump.all(:color.not => []).should == [ @red, @two, @five ]
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should be able to search for objects in an empty list and another OR condition (match none on the empty list)' do
|
174
|
+
Heffalump.all(:conditions => DataMapper::Query::Conditions::Operation.new(
|
175
|
+
:or,
|
176
|
+
DataMapper::Query::Conditions::Comparison.new(:in, Heffalump.properties[:color], []),
|
177
|
+
DataMapper::Query::Conditions::Comparison.new(:in, Heffalump.properties[:num_spots], [5]))).should == [ @five ]
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should be able to search for objects not included in an array of values' do
|
181
|
+
Heffalump.all(:num_spots.not => [ 1, 3, 5, 7 ]).should be_include(@two)
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should be able to search for objects not included in an array of values' do
|
185
|
+
Heffalump.all(:num_spots.not => [ 1, 3, 5, 7 ]).should_not be_include(@five)
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'should be able to search for objects not included in an inclusive range of values' do
|
189
|
+
Heffalump.all(:num_spots.not => 1..4).should be_include(@five)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should be able to search for objects not included in an exclusive range of values' do
|
193
|
+
Heffalump.all(:num_spots.not => 1...5).should be_include(@five)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'should not be able to search for values not included in an inclusive range of values' do
|
197
|
+
Heffalump.all(:num_spots.not => 1..5).should_not be_include(@five)
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'should not be able to search for values not included in an exclusive range of values' do
|
201
|
+
Heffalump.all(:num_spots.not => 1...6).should_not be_include(@five)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe 'like' do
|
206
|
+
it 'should be able to search for objects that match value' do
|
207
|
+
Heffalump.all(:color.like => '%ed').should be_include(@red)
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should not search for objects that do not match the value' do
|
211
|
+
Heffalump.all(:color.like => '%blak%').should_not be_include(@red)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe 'regexp' do
|
216
|
+
before do
|
217
|
+
if (defined?(DataMapper::Adapters::Sqlite3Adapter) && @adapter.kind_of?(DataMapper::Adapters::Sqlite3Adapter) ||
|
218
|
+
defined?(DataMapper::Adapters::SqlserverAdapter) && @adapter.kind_of?(DataMapper::Adapters::SqlserverAdapter))
|
219
|
+
pending 'delegate regexp matches to same system that the InMemory and YAML adapters use'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'should be able to search for objects that match value' do
|
224
|
+
Heffalump.all(:color => /ed/).should be_include(@red)
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should not be able to search for objects that do not match the value' do
|
228
|
+
Heffalump.all(:color => /blak/).should_not be_include(@red)
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'should be able to do a negated search for objects that match value' do
|
232
|
+
Heffalump.all(:color.not => /blak/).should be_include(@red)
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'should not be able to do a negated search for objects that do not match value' do
|
236
|
+
Heffalump.all(:color.not => /ed/).should_not be_include(@red)
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
describe 'gt' do
|
242
|
+
it 'should be able to search for objects with value greater than' do
|
243
|
+
Heffalump.all(:num_spots.gt => 1).should be_include(@two)
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should not find objects with a value less than' do
|
247
|
+
Heffalump.all(:num_spots.gt => 3).should_not be_include(@two)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
describe 'gte' do
|
252
|
+
it 'should be able to search for objects with value greater than' do
|
253
|
+
Heffalump.all(:num_spots.gte => 1).should be_include(@two)
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'should be able to search for objects with values equal to' do
|
257
|
+
Heffalump.all(:num_spots.gte => 2).should be_include(@two)
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'should not find objects with a value less than' do
|
261
|
+
Heffalump.all(:num_spots.gte => 3).should_not be_include(@two)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe 'lt' do
|
266
|
+
it 'should be able to search for objects with value less than' do
|
267
|
+
Heffalump.all(:num_spots.lt => 3).should be_include(@two)
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'should not find objects with a value less than' do
|
271
|
+
Heffalump.all(:num_spots.gt => 2).should_not be_include(@two)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe 'lte' do
|
276
|
+
it 'should be able to search for objects with value less than' do
|
277
|
+
Heffalump.all(:num_spots.lte => 3).should be_include(@two)
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'should be able to search for objects with values equal to' do
|
281
|
+
Heffalump.all(:num_spots.lte => 2).should be_include(@two)
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'should not find objects with a value less than' do
|
285
|
+
Heffalump.all(:num_spots.lte => 1).should_not be_include(@two)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe 'limits' do
|
291
|
+
it 'should be able to limit the objects' do
|
292
|
+
Heffalump.all(:limit => 2).length.should == 2
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
else
|
297
|
+
it 'needs to support #read and #create to test query matching'
|
298
|
+
end
|
299
|
+
end
|