mirah_model 0.0.1-java

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/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
+