mirah_model 0.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ Mirah Model
2
+ ================
3
+
4
+ Mirah Model is a Mirah ORM library for using AppEngine's datastore. It is inspired by ActiveRecord and DataMapper--particularly DataMapper. It's used by Dubious, a Rails-ish web framework.
5
+
6
+ Code Examples
7
+ ------
8
+
9
+ If you are familar with DataMapper's property methods this should look pretty familiar. You can define properties using #property, passing the property name and a type.
10
+
11
+ import com.google.appengine.api.datastore.*
12
+
13
+ class Shout < Model
14
+ property 'title', String
15
+ property 'body', Text
16
+ end
17
+
18
+ You can set instances by updating their attrs individually,
19
+
20
+ shout = Shout.new
21
+ shout.title = 'foo'
22
+ shout.body = 'bar'
23
+ shout.save
24
+
25
+ or by using the update method and passing a hash
26
+
27
+ shout = Shout.new.update title: 'foo', body: 'bar'
28
+ shout.save
29
+
30
+ Development
31
+ ------------
32
+
33
+ You can build the jar by running:
34
+
35
+ rake jar
data/Rakefile ADDED
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package_task'
3
+ require 'ant'
4
+ require 'appengine-sdk'
5
+ require 'mirah_task'
6
+
7
+
8
+ Gem::PackageTask.new Gem::Specification.load('mirah_model.gemspec') do |pkg|
9
+ pkg.need_zip = true
10
+ pkg.need_tar = true
11
+ end
12
+
13
+ task :gem => :jar
14
+ #require 'maven/junit/junit'
15
+ # -- will work after maven support --JUNIT_JAR = Gem.find_files('maven/junit/junit.jar').first
16
+ JUNIT_JAR = 'javalib/junit.jar'
17
+ TESTING_JARS = [AppEngine::SDK::API_JAR, AppEngine::SDK::LABS_JAR, JUNIT_JAR
18
+ ] +
19
+ AppEngine::SDK::RUNTIME_JARS.reject {|j| j =~ /appengine-local-runtime/}
20
+ TESTING_JARS.each {|jar| $CLASSPATH << jar}
21
+
22
+ # Mirah.compiler_options = ['-V']
23
+
24
+ task :default => :test
25
+
26
+ task :init do
27
+ mkdir_p 'dist'
28
+ mkdir_p 'build'
29
+ end
30
+
31
+ task :clean do
32
+ ant.delete :quiet => true, :dir => 'build'
33
+ ant.delete :quiet => true, :dir => 'dist'
34
+ end
35
+
36
+ task :compile => :init do
37
+ # build the Duby sources
38
+ puts "Compiling Duby sources"
39
+ mirahc '.', :dir => 'src', :dest => 'build'
40
+ end
41
+
42
+ desc "run tests"
43
+ task :compile_test => :jar do
44
+ puts "Compiling Duby tests"
45
+ mirahc '.', :dir => 'test', :dest => 'test',
46
+ :options => ['--classpath', Dir.pwd + "/dist/dubydatastore.jar"]
47
+ end
48
+
49
+ desc "build jar"
50
+ task :jar => :compile do
51
+ ant.jar :jarfile => 'dist/dubydatastore.jar' do
52
+ fileset :dir => 'lib'
53
+ fileset :dir => 'build'
54
+ end
55
+ end
56
+
57
+ task :test => :compile_test do
58
+ ant.junit :haltonfailure => 'true', :fork => 'true' do
59
+ classpath :path => (TESTING_JARS + ['build', 'test']).join(":")
60
+ batchtest do
61
+ fileset :dir => "test" do
62
+ include :name => '**/*Test.class'
63
+ end
64
+ formatter :type => 'plain', :usefile => 'false'
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,375 @@
1
+ module AppEngine
2
+ class DubyDatastorePlugin
3
+ @models = {}
4
+
5
+ TypeMap = {
6
+ 'Category' => 'String',
7
+ 'Email' => 'String',
8
+ 'Link' => 'String',
9
+ 'PhoneNumber' => 'String',
10
+ 'PostalAddress' => 'String',
11
+ 'Text' => 'String',
12
+ 'Blob' => 'byte[]',
13
+ 'ShortBlob' => 'byte[]',
14
+ 'Rating' => 'int',
15
+ 'Long' => 'long',
16
+ 'Double' => 'double',
17
+ 'Boolean' => 'boolean'
18
+ }
19
+
20
+ Primitives = ['Long', 'Double', 'Boolean', 'Rating']
21
+
22
+ Boxes = {
23
+ 'int' => 'Integer',
24
+ 'boolean' => 'Boolean',
25
+ 'long' => 'Long',
26
+ 'double' => 'Double',
27
+ }
28
+
29
+ Defaults = {
30
+ 'Rating' => '0',
31
+ 'Long' => 'long(0)',
32
+ 'Double' => '0.0',
33
+ 'Boolean' => 'false',
34
+ 'Blob' => 'byte[].cast(nil)',
35
+ 'ShortBlob' => 'byte[].cast(nil)',
36
+ }
37
+
38
+ Conversions = {
39
+ 'Category' => 'getCategory',
40
+ 'Email' => 'getEmail',
41
+ 'Link' => 'getValue',
42
+ 'PhoneNumber' => 'getNumber',
43
+ 'PostalAddress' => 'getAddress',
44
+ 'Text' => 'getValue',
45
+ 'Blob' => 'getBytes',
46
+ 'ShortBlob' => 'getBytes',
47
+ 'Long' => 'longValue',
48
+ 'Double' => 'doubleValue',
49
+ 'Boolean' => 'booleanValue',
50
+ 'Rating' => 'getRating',
51
+ }
52
+
53
+ class ModelState
54
+ include Duby::AST
55
+ attr_reader :kind, :query, :read, :save, :transformer
56
+
57
+ def initialize(transformer, klass, parent, position, ast)
58
+ @transformer = transformer
59
+ @kind = klass.name.split('.')[-1]
60
+ init_query(klass.name, parent, position, ast)
61
+ init_static(parent, ast)
62
+ init_read(parent, position, ast)
63
+ init_save(parent, position, ast)
64
+ end
65
+
66
+ def init_query(classname, parent, position, ast)
67
+ name = "#{classname}$Query"
68
+ @query = ClassDefinition.new(parent, position, name) do |classdef|
69
+ queryinit = <<-EOF
70
+ def initialize; end
71
+
72
+ def kind
73
+ "#{kind}"
74
+ end
75
+
76
+ def first
77
+ it = _prepare.asIterator
78
+ if it.hasNext
79
+ e = Entity(it.next)
80
+ m = #{kind}.new
81
+ m._read_from(e)
82
+ m
83
+ else
84
+ #{kind}(nil)
85
+ end
86
+ end
87
+
88
+ def run
89
+ entities = _prepare.asList(_options)
90
+ models = #{kind}[entities.size]
91
+ it = entities.iterator
92
+ i = 0
93
+ while (it.hasNext)
94
+ e = Entity(it.next)
95
+ m = #{kind}.new
96
+ m._read_from(e)
97
+ models[i] = m
98
+ i += 1
99
+ end
100
+ models
101
+ end
102
+
103
+ def sort(name:String)
104
+ sort(name, false)
105
+ end
106
+
107
+ def sort(name:String, descending:boolean)
108
+ _sort(name, descending)
109
+ self
110
+ end
111
+ EOF
112
+ [Duby::AST.type(nil, 'com.google.appengine.ext.duby.db.DQuery'),
113
+ eval(classdef, queryinit)]
114
+ end
115
+ ast << @query
116
+ end
117
+
118
+ def init_read(parent, position, ast)
119
+ @read = eval(parent, <<-EOF)
120
+ def _read_from(e:Entity)
121
+ self.entity = e
122
+ nil
123
+ end
124
+ EOF
125
+ ast << @read
126
+ get_properties = eval(parent, <<-EOF)
127
+ def properties
128
+ result = super()
129
+ nil
130
+ result
131
+ end
132
+ EOF
133
+ @get_properties = get_properties.body.children[1] =
134
+ Duby::AST::Body.new(get_properties.body, position)
135
+ ast << get_properties
136
+ update = eval(parent, <<-EOF)
137
+ def update(properties:Map)
138
+ nil
139
+ self
140
+ end
141
+ EOF
142
+ @update = update.body.children[0] = Body.new(update.body, position) {[]}
143
+ ast << update
144
+ end
145
+
146
+ def init_save(parent, position, ast)
147
+ @save = eval(parent, <<-EOF)
148
+ def _save_to(e:Entity)
149
+ end
150
+ EOF
151
+ @save.body = Body.new(@save, position) {[]}
152
+ ast << @save
153
+ end
154
+
155
+ def init_static(parent, ast)
156
+ # TODO These imports don't work any more. Figure out how to fix that.
157
+ scope = ast.static_scope
158
+ package = "#{scope.package}." unless scope.package.empty?
159
+ scope.import('java.util.Map', 'Map')
160
+ scope.import("#{package}#{kind}$Query", "#{kind}__Query__")
161
+ %w( Entity Blob Category Email GeoPt IMHandle Key
162
+ Link PhoneNumber PostalAddress Rating ShortBlob
163
+ Text KeyFactory EntityNotFoundException ).each do |name|
164
+ scope.import("com.google.appengine.api.datastore.#{name}", name)
165
+ end
166
+ ast << eval(parent, <<-EOF)
167
+ def initialize
168
+ super
169
+ end
170
+
171
+ def initialize(key_name:String)
172
+ super
173
+ end
174
+
175
+ def initialize(parent:Model)
176
+ super
177
+ end
178
+
179
+ def initialize(parent:Key)
180
+ super
181
+ end
182
+
183
+ def initialize(parent:Model, key_name:String)
184
+ super
185
+ end
186
+
187
+ def initialize(parent:Key, key_name:String)
188
+ super
189
+ end
190
+
191
+ def self.get(key:Key)
192
+ begin
193
+ m = #{kind}.new
194
+ m._read_from(Model._datastore.get(key))
195
+ m
196
+ rescue EntityNotFoundException
197
+ nil
198
+ end
199
+ end
200
+
201
+ def self.get(key_name:String)
202
+ get(KeyFactory.createKey("#{kind}", key_name))
203
+ end
204
+
205
+ def self.get(id:long)
206
+ get(KeyFactory.createKey("#{kind}", id))
207
+ end
208
+
209
+ def self.get(parent:Key, key_name:String)
210
+ get(KeyFactory.createKey(parent, "#{kind}", key_name))
211
+ end
212
+
213
+ def self.get(parent:Key, id:long)
214
+ get(KeyFactory.createKey(parent, "#{kind}", id))
215
+ end
216
+
217
+ def self.get(parent:Model, key_name:String)
218
+ get(KeyFactory.createKey(parent.key, "#{kind}", key_name))
219
+ end
220
+
221
+ def self.get(parent:Model, id:long)
222
+ get(KeyFactory.createKey(parent.key, "#{kind}", id))
223
+ end
224
+
225
+ def self.all
226
+ #{kind}__Query__.new
227
+ end
228
+ EOF
229
+ end
230
+
231
+ def eval(parent, src)
232
+ transformer.eval(src, __FILE__, parent)
233
+ end
234
+
235
+ def extend_query(code)
236
+ query.body << eval(query.body, code)
237
+ end
238
+
239
+ def extend_update(code)
240
+ @update << eval(@update, code)
241
+ end
242
+
243
+ def extend_get_properties(code)
244
+ @get_properties << eval(@get_properties, code)
245
+ end
246
+
247
+ def extend_read(code)
248
+ code = 'e=nil;' + code
249
+ eval(read.body, code).children[1..-1].each do |node|
250
+ read.body << node
251
+ end
252
+ end
253
+
254
+ def extend_save(code)
255
+ code = 'e=nil;' + code
256
+ eval(save.body, code).children[1..-1].each do |node|
257
+ save.body << node
258
+ end
259
+ end
260
+ end
261
+
262
+ def self.find_class(node)
263
+ node = node.parent until Duby::AST::ClassDefinition === node
264
+ node
265
+ end
266
+
267
+ def self.to_datastore(type, value)
268
+ if Primitives.include?(type)
269
+ "#{type}.new(#{value})"
270
+ elsif TypeMap.include?(type)
271
+ "(#{value} ? #{type}.new(#{value}) : nil)"
272
+ else
273
+ value
274
+ end
275
+ end
276
+
277
+ def self.from_datastore(type, value)
278
+ duby_type = TypeMap[type]
279
+ if duby_type
280
+ default = Defaults.fetch(type, "#{duby_type}(nil)")
281
+ conversion = Conversions[type]
282
+ "(#{value} ? #{type}(#{value}).#{conversion} : #{default})"
283
+ else
284
+ "#{type}(#{value})"
285
+ end
286
+ end
287
+
288
+
289
+ def self.add_property(name, type, transformer, fcall)
290
+ if transformer.state != @state
291
+ reset
292
+ @state = transformer.state
293
+ end
294
+ parent = fcall.parent
295
+ name = name.literal
296
+ type = type.name
297
+ type = 'Long' if type == 'Integer'
298
+
299
+ result = Duby::AST::ScopedBody.new(parent, fcall.position) {[]}
300
+ result.static_scope = fcall.scope.static_scope
301
+ klass = find_class(parent)
302
+ unless @models[klass]
303
+ @models[klass] = ModelState.new(
304
+ transformer, klass, parent, fcall.position, result)
305
+ end
306
+ model = @models[klass]
307
+
308
+ duby_type = TypeMap.fetch(type, type)
309
+ coercion = "coerce_" + duby_type.downcase.sub("[]", "s")
310
+
311
+ if duby_type == 'List'
312
+ model.extend_query(<<-EOF)
313
+ def #{name}(value:Object)
314
+ returns :void
315
+ _query.addFilter("#{name}", _eq_op, value)
316
+ end
317
+ EOF
318
+ else
319
+ model.extend_query(<<-EOF)
320
+ def #{name}(value:#{duby_type})
321
+ returns :void
322
+ _query.addFilter("#{name}", _eq_op, #{to_datastore(type, 'value')})
323
+ end
324
+ EOF
325
+ end
326
+ temp = transformer.tmp
327
+
328
+ model.extend_read(<<-EOF)
329
+ #{temp} = e.getProperty("#{name}")
330
+ @#{name} = #{from_datastore(type, temp)}
331
+ EOF
332
+
333
+ model.extend_save(<<-EOF)
334
+ e.setProperty("#{name}", #{to_datastore(type, '@' + name)})
335
+ EOF
336
+
337
+ model.extend_update(<<-EOF)
338
+ self.#{name} = properties.get("#{name}") if properties.containsKey("#{name}")
339
+ EOF
340
+
341
+ model.extend_get_properties(<<-EOF)
342
+ result.put("#{name}", #{maybe_box(duby_type, 'self.' + name)})
343
+ EOF
344
+
345
+ result << model.eval(parent, <<-EOF)
346
+ def #{name}
347
+ @#{name}
348
+ end
349
+
350
+ def #{name}=(value:#{duby_type})
351
+ @#{name} = value
352
+ end
353
+
354
+ def #{name}=(value:Object)
355
+ self.#{name} = #{coercion}(value)
356
+ end
357
+ EOF
358
+
359
+ result
360
+ end
361
+
362
+ def self.maybe_box(duby_type, value)
363
+ if Boxes.include?(duby_type)
364
+ "#{Boxes[duby_type]}.valueOf(#{value})"
365
+ else
366
+ value
367
+ end
368
+ end
369
+
370
+ def self.reset
371
+ @models = {}
372
+ end
373
+ end
374
+ ::Duby.plugins << DubyDatastorePlugin
375
+ end
@@ -0,0 +1,115 @@
1
+ package com.google.appengine.ext.duby.db
2
+
3
+ import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig
4
+ import com.google.appengine.tools.development.testing.LocalServiceTestConfig
5
+ import com.google.appengine.tools.development.testing.LocalServiceTestHelper
6
+
7
+ import org.junit.After
8
+ import org.junit.Before
9
+ import org.junit.Test
10
+ import org.junit.Assert
11
+
12
+ import com.google.appengine.api.datastore.Blob
13
+ import com.google.appengine.api.datastore.Category
14
+ import com.google.appengine.api.datastore.Email
15
+ import com.google.appengine.api.datastore.GeoPt
16
+ import com.google.appengine.api.datastore.IMHandle
17
+ import com.google.appengine.api.datastore.Key
18
+ import com.google.appengine.api.datastore.Link
19
+ import com.google.appengine.api.datastore.PhoneNumber
20
+ import com.google.appengine.api.datastore.PostalAddress
21
+ import com.google.appengine.api.datastore.Rating
22
+ import com.google.appengine.api.datastore.ShortBlob
23
+ import com.google.appengine.api.datastore.Text
24
+ import java.util.Date
25
+ import java.util.HashMap
26
+ import java.util.List
27
+ import java.util.ArrayList
28
+ import com.google.appengine.api.users.User
29
+
30
+ class TestModel < Model
31
+ property 'date', Date
32
+ property 'akey', Key
33
+ property 'blob', Blob
34
+ property 'shortblob', ShortBlob
35
+ property 'category', Category
36
+ property 'email', Email
37
+ property 'geopt', GeoPt
38
+ property 'imhandle', IMHandle
39
+ property 'link', Link
40
+ property 'phonenumber', PhoneNumber
41
+ property 'address', PostalAddress
42
+ property 'rating', Rating
43
+ property 'text', Text
44
+ property 'string', String
45
+ property 'integer', Long
46
+ property 'afloat', Double
47
+ property 'user', User
48
+ property 'list', List
49
+ end
50
+
51
+ class ModelTest
52
+ include Assert
53
+
54
+ def helper
55
+ @helper ||= begin
56
+ configs = LocalServiceTestConfig[1]
57
+ configs[0] = LocalDatastoreServiceTestConfig.new
58
+ LocalServiceTestHelper.new(configs)
59
+ end
60
+ end
61
+
62
+ $Before
63
+ def setup
64
+ returns void
65
+ helper.setUp
66
+ end
67
+
68
+ $After
69
+ def teardown
70
+ returns void
71
+ helper.tearDown
72
+ end
73
+
74
+ $Test
75
+ def test_get_and_put
76
+ returns void
77
+ e = TestModel.new
78
+ e.string = "Hello"
79
+ e.save
80
+ e2 = TestModel.get(e.key)
81
+ assertEquals("Hello", e2.string)
82
+ assertEquals(e.key, e2.key)
83
+ end
84
+
85
+ $Test
86
+ def test_sort
87
+ returns void
88
+ # just make sure it compiles
89
+ TestModel.all.sort('link').run
90
+ end
91
+
92
+ $Test
93
+ def test_properties
94
+ returns void
95
+ e = TestModel.new
96
+ e.string = "Hi"
97
+ e.rating = 10
98
+ properties = e.properties
99
+ assertEquals("Hi", properties.get("string"))
100
+ assertEquals(Integer.valueOf(10), properties.get("rating"))
101
+ assertTrue(properties.containsKey("blob"))
102
+ assertEquals(nil, properties.get("blob"))
103
+ end
104
+
105
+ $Test
106
+ def test_query_list
107
+ returns void
108
+ e = TestModel.new
109
+ e.list = ["a", "b", "c"]
110
+ e.save
111
+ e2 = TestModel.all.list("b").first
112
+ assertEquals(e.key, e2.key)
113
+ end
114
+ # TODO more tests.
115
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mirah_model
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: java
11
+ authors:
12
+ - Ryan Brown
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-15 08:47:11.863000 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mirah
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 0
30
+ - 4
31
+ version: 0.0.4
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: appengine-sdk
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 4
44
+ - 0
45
+ version: 1.4.0
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: Mirah Model is a ORM library for App Engine's datastore. It is inspired by ActiveRecord and DataMapper. It's used by Dubious, a Rails-ish web framework.
49
+ email:
50
+ - ribrdb@google.com
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files:
56
+ - README.md
57
+ files:
58
+ - lib/mirah_model/datastore.rb
59
+ - test/model_test.mirah
60
+ - test/com/google/appengine/ext/duby/db/ModelTest.class
61
+ - test/com/google/appengine/ext/duby/db/TestModel$Query.class
62
+ - test/com/google/appengine/ext/duby/db/TestModel.class
63
+ - README.md
64
+ - Rakefile
65
+ has_rdoc: true
66
+ homepage: http://www.mirah.org/
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options:
71
+ - --main
72
+ - README.md
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.3.6
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Mirah Model is a ORM library for App Engine's datastore.
96
+ test_files: []
97
+