dm-appengine 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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