dm-appengine 0.0.7 → 0.0.8

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/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'date'
5
5
  require 'spec/rake/spectask'
6
6
 
7
7
  GEM = "dm-appengine"
8
- GEM_VERSION = "0.0.7"
8
+ GEM_VERSION = "0.0.8"
9
9
  AUTHOR = "Ryan Brown"
10
10
  EMAIL = "ribrdb@gmail.com"
11
11
  HOMEPAGE = "http://code.google.com/p/appengine-jruby"
@@ -23,13 +23,12 @@ spec = Gem::Specification.new do |s|
23
23
  s.email = EMAIL
24
24
  s.homepage = HOMEPAGE
25
25
 
26
- s.add_dependency("appengine-apis", ["~> 0.0.12"])
26
+ s.add_dependency("appengine-apis", ["~> 0.0.14"])
27
27
  s.add_dependency("dm-core", ["0.10.2"])
28
28
 
29
29
  s.require_path = 'lib'
30
30
  s.autorequire = GEM
31
- s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("spec/**/*") +
32
- %w(lib/appengine_adapter.rb lib/dm-appengine/types.rb)
31
+ s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("spec/*.rb") + Dir.glob("lib/**/*.rb")
33
32
  end
34
33
 
35
34
  task :default => :spec
@@ -17,7 +17,8 @@
17
17
  #
18
18
  # Datamapper adapter for Google App Engine
19
19
 
20
- require 'date'
20
+ autoload :Date, 'date'
21
+ autoload :DateTime, 'date'
21
22
  require 'rubygems'
22
23
  require 'time'
23
24
 
@@ -26,6 +27,8 @@ require 'dm-core'
26
27
  require 'dm-appengine/types'
27
28
 
28
29
  module DataMapper
30
+ autoload(:AppEngineResource, 'dm-appengine/appengine_resource')
31
+
29
32
  module Adapters
30
33
  class AppEngineAdapter < AbstractAdapter
31
34
  Datastore = AppEngine::Datastore
@@ -36,11 +39,6 @@ module DataMapper
36
39
  options = uri_or_options
37
40
  if options['host'] == 'memory'
38
41
  require 'appengine-apis/testing'
39
- begin
40
- AppEngine::ApiProxy.get_app_id
41
- rescue NoMethodError
42
- AppEngine::Testing::install_test_env
43
- end
44
42
  AppEngine::Testing::install_test_datastore
45
43
  end
46
44
  end
@@ -66,13 +64,20 @@ module DataMapper
66
64
  keys = properties.key
67
65
  raise "Multiple keys in #{resource.inspect}" if keys.size > 1
68
66
  if keys.size == 1
69
- key = attributes.delete(keys.first.name)
70
- key = key.to_s if key
67
+ name = keys.first.name
68
+ property = properties[name]
69
+ key = convert_value(property, attributes.delete(name))
71
70
  end
72
- if key && !(key == 0 || keys.first.serial?)
73
- entity = Datastore::Entity.new(kind, key)
74
- else
71
+ if key.nil? || keys.first.serial? || key==0
75
72
  entity = Datastore::Entity.new(kind)
73
+ elsif key.kind_of?(AppEngine::Datastore::Key)
74
+ entity = Datastore::Entity.new(key)
75
+ elsif key.kind_of?(Hash) && property.type == Types::Key
76
+ # AppEngine::Datastore::Key should already have filtered this.
77
+ # Since it didn't, we know it's a serial object with a parent.
78
+ entity = Datastore::Entity.new(kind, key[:parent])
79
+ else
80
+ entity = Datastore::Entity.new(kind, key)
76
81
  end
77
82
 
78
83
  attributes.each do |name, value|
@@ -88,6 +93,8 @@ module DataMapper
88
93
  key = entity.key
89
94
  if id = resource.model.serial(name)
90
95
  id.set!(resource, key.get_id)
96
+ elsif id = resource.model.key(name).find{|k|k.type == Types::Key}
97
+ id.set!(resource, key)
91
98
  end
92
99
  resource.instance_variable_set :@__entity__, entity
93
100
  end
@@ -242,14 +249,7 @@ module DataMapper
242
249
  unless property.key?
243
250
  raise ArgumentError, "#{property_name(property)} is not the key"
244
251
  end
245
- case value
246
- when Integer, String
247
- Datastore::Key.from_path(@kind, value)
248
- when Symbol
249
- Datastore::Key.from_path(@kind, value.to_s)
250
- else
251
- raise ArgumentError, "Unsupported key value #{value.inspect} (a #{value.class})"
252
- end
252
+ Types::Key.typecast(value, property)
253
253
  end
254
254
 
255
255
  def parse_or(or_op)
@@ -318,6 +318,8 @@ module DataMapper
318
318
 
319
319
  if op.kind_of?(InclusionComparison)
320
320
  parse_inclusion(op)
321
+ elsif property.type == Types::AncestorKey
322
+ @query.ancestor = value
321
323
  else
322
324
  if negated
323
325
  filter_op = @@NEGATED_OPERATORS[op.class]
@@ -406,7 +408,13 @@ module DataMapper
406
408
  @dm_query.fields.each do |property|
407
409
  name = property.field
408
410
  if property.key?
409
- hash[name] = key.get_name || key.get_id
411
+ if property.serial?
412
+ hash[name] = key.get_id
413
+ elsif property.type == String
414
+ hash[name] = key.get_name
415
+ else
416
+ hash[name] = key
417
+ end
410
418
  else
411
419
  hash[name] = property.typecast(entity.get_property(name))
412
420
  end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/ruby1.8 -w
2
+ #
3
+ # Copyright:: Copyright 2009 Google Inc.
4
+ # Original Author:: Ryan Brown (mailto:ribrdb@google.com)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # Custom Resource class for App Engine.
19
+ # Adds optimistic transactions and sane defaults for keys.
20
+ # Thin wrapper around is(:entity), but it can be autoloaded.
21
+
22
+ require 'dm-appengine/is_entity'
23
+
24
+ module DataMapper
25
+ module AppEngineResource
26
+
27
+ def self.included(klass)
28
+ klass.class_eval do
29
+ include DataMapper::Resource
30
+ is :entity
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/ruby1.8 -w
2
+ #
3
+ # Copyright:: Copyright 2010 David Masover
4
+ # Original Author:: David Masover (mailto:ninja@slaphack.com)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module DataMapper
19
+ module Is
20
+ module Entity
21
+ DEFAULT_ENTITY_OPTIONS = {
22
+ :transaction => true,
23
+ :key => :id,
24
+ :parent => :parent,
25
+ :ancestor => :ancestor,
26
+ :descendants => :descendants
27
+ }.freeze
28
+ def is_entity(options={})
29
+ extend(ClassMethods)
30
+
31
+ options = DEFAULT_ENTITY_OPTIONS.merge(options)
32
+
33
+ # Override the builtin transactions
34
+ if options[:transaction]
35
+ include Transaction; extend Transaction
36
+ end
37
+
38
+ # Pass :key_name => false if you don't want a key
39
+ if (key_name = options[:key])
40
+ primary_key(key_name)
41
+ end
42
+
43
+ if (parent_name = options[:parent])
44
+ parent_property(parent_name)
45
+ end
46
+
47
+ if (ancestor_name = options[:ancestor])
48
+ ancestor_property(ancestor_name)
49
+ end
50
+
51
+ if (descendants_name = options[:descendants])
52
+ descendants_property(descendants_name)
53
+ end
54
+ end
55
+
56
+ module ClassMethods
57
+ # Being somewhat more rigid here, because this really isn't that complicated (yet).
58
+ # TODO: If possible, a typeless query would be nice here.
59
+ def descendants_property(name)
60
+ define_method(name) do |type|
61
+ type.all(:ancestor => self.key.first)
62
+ end
63
+ end
64
+
65
+ def has_descendants(name, options={})
66
+ model = options.delete(:model) # or leave it nil
67
+ define_method(name) do
68
+ model ||= Extlib::Inflection.constantize(
69
+ Extlib::Inflection.camelize(
70
+ Extlib::Inflection.singularize(name.to_s)
71
+ )
72
+ )
73
+ model.all(options.merge(:ancestor => self.key.first))
74
+ end
75
+ end
76
+
77
+ def ancestor_property(name)
78
+ property name, Types::AncestorKey
79
+ # We don't want to ever set this. It's just for queries.
80
+ undef_method name
81
+ undef_method :"#{name}="
82
+ end
83
+
84
+ def primary_key(name)
85
+ property name, DataMapper::Types::Key, :key => true
86
+ end
87
+
88
+ def parent_property(name)
89
+ define_method("#{name}_id") do
90
+ k = key.first
91
+ k.kind_of?(AppEngine::Datastore::Key) && k.parent
92
+ end
93
+
94
+ belongs_to_entity(name, false)
95
+ end
96
+
97
+ # Polymorphic belongs_to hack.
98
+ # (Datamapper doesn't support polymorphic associations, probably by design.)
99
+ # We already have the type in the key_name anyway.
100
+ # has(n) works at the other end, just set child_key.
101
+ def belongs_to_entity(name, add_id_property=true)
102
+ key_getter = :"#{name}_id"
103
+ key_setter = :"#{key_getter}="
104
+ variable = :"@#{name}"
105
+
106
+ if add_id_property
107
+ property key_getter, Types::Key
108
+ end
109
+
110
+ define_method(name) do
111
+ value = instance_variable_get(variable)
112
+ return value if value
113
+
114
+ key = send(key_getter)
115
+ # All keys are polymorphic
116
+ value = Extlib::Inflection.constantize(Extlib::Inflection.singularize(key.kind)).get(key)
117
+ instance_variable_set(variable, value)
118
+ end
119
+
120
+ define_method(:"#{name}=") do |value|
121
+ send(key_setter, value.key.first)
122
+ instance_variable_set(variable, value)
123
+ end
124
+ end
125
+ end
126
+
127
+ module Transaction
128
+ def transaction(retries=3, &block)
129
+ AppEngine::Datastore.transaction(retries, &block)
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ Model.append_extensions(Is::Entity)
136
+ end
@@ -18,6 +18,7 @@
18
18
  # Custom types for App Engine
19
19
 
20
20
  require 'dm-core/type' unless defined? DataMapper::Type::PROPERTY_OPTIONS
21
+ require 'dm-core/property' unless defined? DataMapper::Property::PRIMITIVES
21
22
 
22
23
  module DataMapper
23
24
  module Types
@@ -113,7 +114,88 @@ module DataMapper
113
114
  end
114
115
  end
115
116
 
116
- IMHandle = GeoPt = Key = User = AppEngineNativeType
117
+ IMHandle = GeoPt = User = AppEngineNativeType
118
+
119
+ class Key < Type
120
+ primitive AppEngine::Datastore::Key
121
+
122
+ def self.dump(value, property)
123
+ property.typecast(value)
124
+ end
125
+
126
+ def self.load(value, property)
127
+ value
128
+ end
129
+
130
+ def self.typecast(value, property)
131
+ case value
132
+ when AppEngine::Datastore::Key, NilClass
133
+ value
134
+ when Integer, String
135
+ AppEngine::Datastore::Key.from_path(kind(property), value)
136
+ when Symbol
137
+ AppEngine::Datastore::Key.from_path(kind(property), value.to_s)
138
+ when Hash
139
+ parent = property.typecast(value[:parent])
140
+ id = value[:id]
141
+ name = value[:name]
142
+ if id
143
+ id_or_name = id.to_i
144
+ elsif name
145
+ id_or_name = name.to_s
146
+ end
147
+ if parent
148
+ if id_or_name || (!property.key?)
149
+ parent.getChild(kind(property), id_or_name)
150
+ else
151
+ # TODO: is it sane to not typecast this?
152
+ value
153
+ end
154
+ else
155
+ property.typecast(id_or_name)
156
+ end
157
+ else
158
+ raise ArgumentError, "Unsupported key value #{value.inspect} (a #{value.class})"
159
+ end
160
+ end
161
+
162
+ def self.kind(property)
163
+ property.model.repository.adapter.kind(property.model)
164
+ end
165
+ end
166
+
167
+ # Hacks for primitive types.
168
+ # AppEngine::Datastore::Key truly IS a primitive,
169
+ # as far as AppEngine is concerned.
170
+ original_primitives = Property::PRIMITIVES
171
+ Property::PRIMITIVES = (original_primitives.dup << AppEngine::Datastore::Key).freeze
172
+
173
+ # Hack to allow a property defined as AppEngine::Datastore::Key to work.
174
+ # This is needed for associations -- child_key tries to define it as
175
+ # the primitive of the parent key type. It then takes that type name and
176
+ # tries to resolve it in DM::Types, so we catch it here.
177
+ module Java
178
+ module ComGoogleAppengineApiDatastore
179
+ end
180
+ end
181
+ Java::ComGoogleAppengineApiDatastore::Key = Key
182
+
183
+ # Should NOT be used directly!
184
+ # Also, should be sharing these better...
185
+ class AncestorKey < Type
186
+ primitive AppEngine::Datastore::Key
187
+ def self.dump(value, property)
188
+ property.typecast(value)
189
+ end
190
+
191
+ def self.load(value, property)
192
+ value
193
+ end
194
+
195
+ def self.typecast(value, property)
196
+ Key.typecast(value, property)
197
+ end
198
+ end
117
199
 
118
200
  # TODO store user as email and id?
119
201
  end
@@ -47,6 +47,8 @@ class TypeTest
47
47
  property :im, IMHandle
48
48
  property :point, GeoPt
49
49
  property :user, User
50
+ # the 'key' method is already defined on DataMapper::Resource.
51
+ property :key_property, Key
50
52
  end
51
53
 
52
54
  class Flower
@@ -60,6 +62,13 @@ class FooBar
60
62
  property :string, String
61
63
  end
62
64
 
65
+ class KeyTest
66
+ include DataMapper::Resource
67
+
68
+ property :appengine_key, Key, :key => true
69
+ property :string, String
70
+ end
71
+
63
72
  describe DataMapper::Adapters::AppEngineAdapter do
64
73
  before :all do
65
74
  AppEngine::Testing.install_test_env
@@ -262,5 +271,151 @@ describe DataMapper::Adapters::AppEngineAdapter do
262
271
  a.point.latitude.should be_close(latitude, 0.1)
263
272
  a.point.longitude.should be_close(longitude, 0.1)
264
273
  end
274
+
275
+ it "should support Key" do
276
+ obj = AppEngine::Datastore::Entity.new('Key Test')
277
+ AppEngine::Datastore.put obj
278
+ key = obj.key
279
+ a = TypeTest.new(:name => 'key', :key_property => key)
280
+ a.save
281
+ a.reload
282
+ a.key_property.should == key
283
+ end
284
+ end
285
+
286
+ describe 'primary keys' do
287
+ before :each do
288
+ @resource = KeyTest.new(:appengine_key => AppEngine::Datastore::Key.from_path('KeyTests', 'Foo'))
289
+ end
290
+
291
+ it 'should support AppEngine::Datastore::Key' do
292
+ @resource.string = 'Make sure it has some data.'
293
+ @resource.save
294
+ @resource.reload
295
+ @resource.string.should == 'Make sure it has some data.'
296
+ end
297
+
298
+ it 'should be an AppEngine::Datastore::Key under the hood' do
299
+ @resource.appengine_key.should be_a(AppEngine::Datastore::Key)
300
+ key = @resource.appengine_key
301
+ @resource.save
302
+ @resource.reload
303
+ @resource.appengine_key.should be_a(AppEngine::Datastore::Key)
304
+ @resource.appengine_key.should == key
305
+ end
306
+
307
+ it "shouldn't save keys as actual entity properties" do
308
+ @resource.save
309
+ entity = AppEngine::Datastore.get(@resource.appengine_key)
310
+ entity.should_not have_property(:appengine_key)
311
+ end
312
+
313
+ it 'should be possible to query by string' do
314
+ @resource.string = 'Querying by string'
315
+ @resource.save
316
+ fetched = KeyTest.get 'Foo'
317
+ fetched.string.should == 'Querying by string'
318
+ end
319
+
320
+ it 'should be possible save by string' do
321
+ @resource.appengine_key = 'Bar'
322
+ @resource.string = 'Saving by string'
323
+ @resource.save
324
+ fetched = KeyTest.get 'Bar'
325
+ fetched.string.should == 'Saving by string'
326
+ fetched.appengine_key.should be_kind_of AppEngine::Datastore::Key
327
+ end
328
+
329
+ it 'should be possible to save and query by id' do
330
+ @resource.appengine_key = 10
331
+ @resource.string = 'Querying by id'
332
+ @resource.save
333
+ fetched = KeyTest.get 10
334
+ fetched.string.should == 'Querying by id'
335
+ fetched.appengine_key.should be_kind_of AppEngine::Datastore::Key
336
+ end
337
+
338
+ describe 'should support creating and fetching resources by Hash' do
339
+ it 'with parents' do
340
+ @resource.save
341
+ child = KeyTest.new
342
+ child.appengine_key = {:parent => @resource.appengine_key, :name => 'Child!'}
343
+ child.string = 'Some more data.'
344
+ child.save
345
+ child.reload
346
+ child = KeyTest.get :parent => @resource.appengine_key, :name => 'Child!'
347
+ child.string.should == 'Some more data.'
348
+ end
349
+
350
+ it 'with ids instead of names' do
351
+ @resource.appengine_key = {:id => 5}
352
+ @resource.string = 'Even more data.'
353
+ @resource.save
354
+ @resource.reload
355
+ @resource.string.should == 'Even more data.'
356
+ end
357
+
358
+ it 'without forgetting about parents' do
359
+ @resource.save
360
+ child = KeyTest.new
361
+ child.appengine_key = {:parent => @resource.appengine_key, :name => 'Child!'}
362
+ child.appengine_key.parent.should == @resource.appengine_key
363
+ child.string = "don't forget"
364
+ child.save
365
+ child.reload
366
+ child.string.should == "don't forget"
367
+ child.appengine_key.parent.should == @resource.appengine_key
368
+ child.appengine_key.parent.should_not be_nil
369
+ end
370
+ end
371
+
372
+ it 'should typecast hash values' do
373
+ @resource.appengine_key = {:id => '5'}
374
+ @resource.string = 'Typecasting hash values.'
375
+ @resource.save
376
+ fetched = KeyTest.get 5
377
+ fetched.string.should == 'Typecasting hash values.'
378
+ @resource.appengine_key.id.should be 5
379
+ end
380
+
381
+ it 'should allow nil (autoincrementing ids), even with parents' do
382
+ @resource.save
383
+ child = KeyTest.new
384
+ child.appengine_key = {:parent => @resource.appengine_key}
385
+ child.string = 'Should allow nil'
386
+ child.save
387
+ child.appengine_key.id.should_not be_nil
388
+ child.reload
389
+ child.string.should == 'Should allow nil'
390
+ end
391
+
392
+ it 'should work when the key is not set at all' do
393
+ a = KeyTest.create(:string => 'Completely nil key')
394
+ a.save
395
+ a.reload
396
+ a.string.should == 'Completely nil key'
397
+ a.appengine_key.should_not be_nil
398
+ a.appengine_key.id.should_not be_nil
399
+ a.appengine_key.id.should be > 0
400
+ end
401
+
402
+ # Probably not comprehensive.
403
+ # The idea is that if neither name nor id is specified,
404
+ # leave it nil and let the key be generated when it's saved.
405
+ it "shouldn't create serial keys until the entity is saved, even with a parent" do
406
+ AppEngine::Datastore::Key.should_not_receive :from_path
407
+ parent = KeyTest.create(:string => 'foo')
408
+ key = parent.appengine_key
409
+ key.should be_kind_of AppEngine::Datastore::Key
410
+ key.should_not_receive :getChild
411
+
412
+ child = KeyTest.create(:appengine_key => {:parent => key})
413
+ child_key = child.appengine_key
414
+ child_key.should be_kind_of AppEngine::Datastore::Key
415
+ child_key.parent.should == key
416
+
417
+ child_key.id.should_not be_nil
418
+ child_key.id.should be > 0
419
+ end
265
420
  end
266
421
  end
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/ruby1.8 -w
2
+ #
3
+ # Copyright:: Copyright 2009 David Masover
4
+ # Original Author:: David Masover (mailto:ninja@slaphack.com)
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require File.dirname(__FILE__) + '/spec_helper'
19
+ require 'dm-appengine/is_entity'
20
+
21
+ class VanillaTest
22
+ include DataMapper::Resource
23
+ is :entity
24
+ property :string, String
25
+ end
26
+
27
+ class AppEngineResourceTest
28
+ include DataMapper::AppEngineResource
29
+ property :string, String
30
+ end
31
+
32
+ class HasManyTest
33
+ include DataMapper::AppEngineResource
34
+ property :string, String
35
+ has n, :belongs_to_tests
36
+ end
37
+
38
+ class BelongsToTest
39
+ include DataMapper::AppEngineResource
40
+ property :string, String
41
+ belongs_to :has_many_test
42
+ end
43
+
44
+ class Post
45
+ include DataMapper::AppEngineResource
46
+ property :title, String
47
+ property :body, Text
48
+ has n, :comments, :child_key => [:commentable_id]
49
+ end
50
+
51
+ class Image
52
+ include DataMapper::AppEngineResource
53
+ property :name, String
54
+ property :url, Link
55
+ has n, :comments, :child_key => [:commentable_id]
56
+ end
57
+
58
+ class Comment
59
+ include DataMapper::AppEngineResource
60
+ property :subject, String
61
+ property :body, Text
62
+ belongs_to_entity :commentable
63
+ end
64
+
65
+ class User
66
+ include DataMapper::AppEngineResource
67
+ property :name, String
68
+
69
+ has_descendants :settings
70
+ end
71
+
72
+ class Setting
73
+ include DataMapper::AppEngineResource
74
+ property :value, String
75
+ end
76
+
77
+ describe 'An is(:entity) model' do
78
+ it 'should work without anything special' do
79
+ a = VanillaTest.create(:string => 'Plain Vanilla Test')
80
+ a.reload
81
+ a.string.should == 'Plain Vanilla Test'
82
+ a.id.should_not be_nil
83
+ a.id.should be_kind_of AppEngine::Datastore::Key
84
+ end
85
+
86
+ describe 'in a relationship' do
87
+ before :each do
88
+ @parent = HasManyTest.create(:string => 'the parent')
89
+ @child = @parent.belongs_to_tests.create(:string => 'the child')
90
+ end
91
+
92
+ it 'should have the parent' do
93
+ @child.reload
94
+ @child.has_many_test.should == @parent
95
+ end
96
+
97
+ it 'should have the parent_id' do
98
+ @child.reload
99
+ @child.has_many_test_id.should == @parent.id
100
+ @child.has_many_test_id.should be_kind_of AppEngine::Datastore::Key
101
+ end
102
+
103
+ it 'should have the child' do
104
+ @parent.belongs_to_tests.create(:string => 'another child')
105
+ parent = HasManyTest.get(@parent.id)
106
+ child_one = parent.belongs_to_tests.all(:string => 'the child')
107
+ child_one.count.should == 1
108
+ child_one.first.string.should == 'the child'
109
+ end
110
+ end
111
+
112
+ describe 'with a polymorphic owner' do
113
+ before :each do
114
+ @post = Post.create(:title => 'I saw an image today', :body => 'and it was ugly.')
115
+ @post_comment = @post.comments.create(:subject => "No, it wasn't!", :body => 'it was beautiful!')
116
+ @image = Image.create(:name => 'Ugly image', :url => 'http://example.com/UGLY.png')
117
+ @image_comment = @image.comments.create(:subject => 'Beautiful', :body => 'I was touched!')
118
+ end
119
+
120
+ it 'should reload properly' do
121
+ @post.reload.title.should == 'I saw an image today'
122
+ @post_comment.reload.subject.should == "No, it wasn't!"
123
+ @image.reload.name.should == 'Ugly image'
124
+ @image_comment.reload.subject.should == 'Beautiful'
125
+ end
126
+
127
+ it 'should have the right children' do
128
+ @post.reload
129
+ @image.reload
130
+
131
+ @post.comments.count.should == 1
132
+ @image.comments.count.should == 1
133
+ @post.comments.first.subject.should == @post_comment.subject
134
+ @image.comments.first.subject.should == @image_comment.subject
135
+ end
136
+
137
+ it 'should have the right parents' do
138
+ @post_comment.reload.commentable.should == @post
139
+ @image_comment.reload.commentable.should == @image
140
+ end
141
+
142
+ # This can happen when you forget to set child_key.
143
+ it "shouldn't have extra keys" do
144
+ [@post_comment, @image_comment].each do |comment|
145
+ properties = comment.send(:properties).map(&:name)
146
+ properties.should include :commentable_id
147
+ properties.should_not include :post_id
148
+ properties.should_not include :image_id
149
+ end
150
+ end
151
+ end
152
+
153
+ describe 'with a parent' do
154
+ before :each do
155
+ @parent = VanillaTest.create(:string => 'Plain vanilla parent')
156
+ @child = VanillaTest.create(:id => {:parent => @parent.id}, :string => 'Plain vanilla child')
157
+ end
158
+
159
+ it 'should work' do
160
+ @parent.reload.string.should == 'Plain vanilla parent'
161
+ @child.reload.string.should == 'Plain vanilla child'
162
+ end
163
+
164
+ it 'should have the parent' do
165
+ @child.reload.parent.should == @parent
166
+ end
167
+
168
+ it 'should be visible with an ancestor query' do
169
+ VanillaTest.all(:ancestor => @parent.id).should include @child
170
+ # Note: "ancestor" queries appear to include self.
171
+ end
172
+
173
+ it 'should be visible with a "descendant" query' do
174
+ @parent.descendants(VanillaTest).should include @child
175
+ end
176
+
177
+ # TODO: break into smaller specs
178
+ it "shouldn't include the whole world as siblings" do
179
+ stranger = VanillaTest.create(:string => 'Strange vanilla child')
180
+ children = VanillaTest.all(:ancestor => @parent.id)
181
+ children.should_not include stranger
182
+ children.none?{|x| x.string == 'Strange vanilla child'}.should be true
183
+
184
+ stranger.update(:string => 'Plain vanilla child')
185
+ plain = VanillaTest.all(:string => 'Plain vanilla child')
186
+ plain.should include stranger
187
+ plain.should include @child
188
+
189
+ our_plain = VanillaTest.all(:ancestor => @parent.id, :string => 'Plain vanilla child')
190
+ our_plain.should_not include stranger
191
+ our_plain.should include @child
192
+ end
193
+ end
194
+
195
+ describe 'with has_descendants' do
196
+ before :each do
197
+ @user = User.create(:name => 'Billy')
198
+ @setting = Setting.create(:id => {:parent => @user.id, :name => 'displayname'}, :value => 'John')
199
+ end
200
+
201
+ it 'should be possible to find the appropriate descendants' do
202
+ @user.settings.count.should == 1
203
+ @user.settings.first.should == @setting
204
+ @user.settings.first.value.should == 'John'
205
+ end
206
+ end
207
+ end
208
+
209
+ describe DataMapper::AppEngineResource do
210
+ it 'should work just like the vanilla test' do
211
+ a = AppEngineResourceTest.create(:string => 'AppEngineResource test')
212
+ a.reload
213
+ a.string.should == 'AppEngineResource test'
214
+ end
215
+ end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-appengine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 8
9
+ version: 0.0.8
5
10
  platform: ruby
6
11
  authors:
7
12
  - Ryan Brown
@@ -9,29 +14,37 @@ autorequire: dm-appengine
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2009-12-22 00:00:00 -08:00
17
+ date: 2010-04-12 00:00:00 -07:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: appengine-apis
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ~>
22
26
  - !ruby/object:Gem::Version
23
- version: 0.0.12
24
- version:
27
+ segments:
28
+ - 0
29
+ - 0
30
+ - 14
31
+ version: 0.0.14
32
+ type: :runtime
33
+ version_requirements: *id001
25
34
  - !ruby/object:Gem::Dependency
26
35
  name: dm-core
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
30
38
  requirements:
31
39
  - - "="
32
40
  - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ - 10
44
+ - 2
33
45
  version: 0.10.2
34
- version:
46
+ type: :runtime
47
+ version_requirements: *id002
35
48
  description: A DataMapper adapter for Google App Engine
36
49
  email: ribrdb@gmail.com
37
50
  executables: []
@@ -46,8 +59,11 @@ files:
46
59
  - README.rdoc
47
60
  - Rakefile
48
61
  - spec/dm-appengine_spec.rb
62
+ - spec/dm-is-entity_spec.rb
49
63
  - spec/spec_helper.rb
50
64
  - lib/appengine_adapter.rb
65
+ - lib/dm-appengine/appengine_resource.rb
66
+ - lib/dm-appengine/is_entity.rb
51
67
  - lib/dm-appengine/types.rb
52
68
  has_rdoc: true
53
69
  homepage: http://code.google.com/p/appengine-jruby
@@ -62,18 +78,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
62
78
  requirements:
63
79
  - - ">="
64
80
  - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
65
83
  version: "0"
66
- version:
67
84
  required_rubygems_version: !ruby/object:Gem::Requirement
68
85
  requirements:
69
86
  - - ">="
70
87
  - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
71
90
  version: "0"
72
- version:
73
91
  requirements: []
74
92
 
75
93
  rubyforge_project:
76
- rubygems_version: 1.3.5
94
+ rubygems_version: 1.3.6
77
95
  signing_key:
78
96
  specification_version: 3
79
97
  summary: A DataMapper adapter for Google App Engine