tiny_ds 0.0.2 → 0.0.3.pre
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.rdoc +18 -15
- data/Rakefile +1 -1
- data/lib/tiny_ds/base.rb +28 -25
- data/lib/tiny_ds/base_tx.rb +4 -4
- data/lib/tiny_ds/low_ds.rb +14 -4
- data/lib/tiny_ds/property_definition.rb +14 -0
- data/lib/tiny_ds/query.rb +23 -1
- data/lib/tiny_ds/transaction.rb +37 -0
- data/lib/tiny_ds/version.rb +1 -1
- data/lib/tiny_ds.rb +25 -3
- data/spec/basic_spec.rb +212 -13
- data/spec/gae_spec.rb +86 -0
- data/spec/tx_spec.rb +82 -0
- metadata +7 -13
- data/lib/tiny_ds/base.rb~ +0 -115
- data/lib/tiny_ds/base_tx.rb~ +0 -180
- data/lib/tiny_ds/low_ds.rb~ +0 -80
- data/lib/tiny_ds/property_definition.rb~ +0 -54
- data/lib/tiny_ds/query.rb~ +0 -68
- data/lib/tiny_ds/validations.rb~ +0 -9
- data/lib/tiny_ds.rb~ +0 -2
- data/lib/tinyds.rb~ +0 -2
- data/spec/spec_helper.rb~ +0 -0
data/README.rdoc
CHANGED
@@ -1,25 +1,28 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
= TinyDS -- tiny datastore library for GAE/JRuby
|
2
|
+
github: http://github.com/takeru/tiny_ds
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
- CRUD like a ActiveRecord or DataMapepr
|
5
|
+
- set parent, key, id, name, and find by these, YES entity-group-transaction!!
|
6
|
+
- BASE transaction : http://blog.notdot.net/2009/9/Distributed-Transactions-on-App-Engine
|
7
|
+
- query : very thin wrapper of low level API.
|
8
|
+
- Shout demo app. (sinatra)
|
9
|
+
- some specs..
|
10
10
|
|
11
|
-
|
11
|
+
== run example
|
12
|
+
% examples/basic
|
12
13
|
% dev_appserver.rb .
|
13
14
|
|
14
|
-
|
15
|
+
== how to run specs
|
15
16
|
install jruby 1.4.0
|
16
|
-
%
|
17
|
+
% cd tiny_ds
|
18
|
+
% source examples/basic/set_classpath_for_jruby.sh
|
19
|
+
(set classpath, please read spec/spec_helper.rb)
|
17
20
|
% jruby -S spec -c -b spec/basic_spec.rb
|
18
|
-
or
|
21
|
+
or
|
19
22
|
% jruby -e "ENV['RSPEC']='true'; ENV['AUTOTEST']='true'; system('jruby -S autotest', *ARGV)"
|
20
23
|
|
21
|
-
|
24
|
+
== demo site
|
22
25
|
Rubyist Social Graph : http://rubyist-sg.appspot.com/ is running with GAE/JRuby+Rails2.3.5+TinyDS.
|
23
26
|
|
24
|
-
|
25
|
-
|
27
|
+
== author
|
28
|
+
takeru: sasaki.takeru@gmail.com, twitter:@urekat (almost japanese!), blog:http://d.hatena.ne.jp/urekat
|
data/Rakefile
CHANGED
@@ -28,7 +28,7 @@ spec = Gem::Specification.new do |s|
|
|
28
28
|
s.homepage = "http://github.com/takeru/tiny_ds"
|
29
29
|
s.require_path = 'lib'
|
30
30
|
s.files = %w(LICENSE README.rdoc Rakefile) +
|
31
|
-
Dir.glob("spec
|
31
|
+
Dir.glob("spec/**/*.rb") + Dir.glob("lib/**/*.rb")
|
32
32
|
s.add_dependency('appengine-apis')
|
33
33
|
end
|
34
34
|
|
data/lib/tiny_ds/base.rb
CHANGED
@@ -1,31 +1,10 @@
|
|
1
|
-
# TODO
|
2
|
-
# callback before/after
|
3
|
-
# validations
|
4
|
-
# associations
|
5
|
-
#x default value
|
6
|
-
#x property list
|
7
|
-
# property Key
|
8
|
-
# property others
|
9
|
-
# nil=>false
|
10
|
-
# index=>false setUnindexedProperty
|
11
|
-
# version
|
12
|
-
# schema_version
|
13
|
-
#x new_record?
|
14
|
-
# find_by_xxx
|
15
|
-
#x get_by_id(id) get_by_name(name) : root entity only!!
|
16
|
-
#x get_by_id(parent, id) get_by_name(parent, name) : ancestor+Kind => key get
|
17
|
-
#x Foo.query.filter(:key=>123)
|
18
|
-
# Foo.filter(:key=>123)
|
19
|
-
# logger
|
20
|
-
# logging low level API calls.
|
21
|
-
|
22
1
|
require "time"
|
23
2
|
|
24
3
|
module TinyDS
|
25
4
|
class Base
|
26
5
|
class << self; attr_accessor :_property_definitions; end
|
27
6
|
RESERVED_PROPERTY_NAME = [:id, :name, :key, :entity, :parent_key, :parent]
|
28
|
-
VALID_PROPERTY_TYPE = [:string, :integer, :text, :time, :list]
|
7
|
+
VALID_PROPERTY_TYPE = [:string, :integer, :float, :boolean, :text, :time, :list]
|
29
8
|
def self.property(pname, ptype, opts={})
|
30
9
|
pname = pname.to_sym
|
31
10
|
if RESERVED_PROPERTY_NAME.include?(pname)
|
@@ -42,6 +21,10 @@ class Base
|
|
42
21
|
property_definitions[name.to_sym] or raise "unknown property='#{name}'"
|
43
22
|
end
|
44
23
|
|
24
|
+
def self.has_property?(name)
|
25
|
+
property_definitions.has_key?(name.to_sym)
|
26
|
+
end
|
27
|
+
|
45
28
|
def self.default_attrs
|
46
29
|
attrs = {}
|
47
30
|
property_definitions.each do |pname,pdef|
|
@@ -95,6 +78,22 @@ class Base
|
|
95
78
|
end
|
96
79
|
end
|
97
80
|
|
81
|
+
# KeyRange allocateIds(java.lang.String kind, long num)
|
82
|
+
def self.allocate_ids(num)
|
83
|
+
AppEngine::Datastore.allocate_ids(kind, num) # KeyRange
|
84
|
+
end
|
85
|
+
def self.allocate_id
|
86
|
+
allocate_ids(1).start # Key
|
87
|
+
end
|
88
|
+
|
89
|
+
# KeyRange allocateIds(Key parent, java.lang.String kind, long num)
|
90
|
+
# def allocate_ids(kind, num)
|
91
|
+
# todo
|
92
|
+
# end
|
93
|
+
# def allocate_id(kind)
|
94
|
+
# todo
|
95
|
+
# end
|
96
|
+
|
98
97
|
# Foo.create({:title=>"hello",...}, :parent=>aaa, :id=>bbb, :name=>ccc, :key=>...)
|
99
98
|
def self.create(attrs={}, opts={})
|
100
99
|
m = new(attrs, opts)
|
@@ -104,8 +103,7 @@ class Base
|
|
104
103
|
|
105
104
|
# Foo.new
|
106
105
|
def initialize(attrs={}, opts={})
|
107
|
-
|
108
|
-
unless @entity
|
106
|
+
unless opts.has_key?(:entity)
|
109
107
|
if opts[:parent] && opts[:parent].kind_of?(Base)
|
110
108
|
opts = opts.dup
|
111
109
|
opts[:parent] = opts[:parent].entity
|
@@ -114,6 +112,7 @@ class Base
|
|
114
112
|
self.attributes = self.class.default_attrs.merge(attrs || {})
|
115
113
|
@new_record = true
|
116
114
|
else
|
115
|
+
@entity = opts.delete(:entity) or raise "opts[:entity] is nil."
|
117
116
|
@new_record = false
|
118
117
|
end
|
119
118
|
end
|
@@ -257,7 +256,11 @@ class Base
|
|
257
256
|
if ds_v.nil?
|
258
257
|
@entity.removeProperty(k)
|
259
258
|
else
|
260
|
-
|
259
|
+
if prop_def.index?
|
260
|
+
@entity[k] = ds_v
|
261
|
+
else
|
262
|
+
@entity.setUnindexedProperty(k, ds_v)
|
263
|
+
end
|
261
264
|
end
|
262
265
|
# todo cache value read/write
|
263
266
|
end
|
data/lib/tiny_ds/base_tx.rb
CHANGED
@@ -72,7 +72,7 @@ class BaseTx
|
|
72
72
|
# TODO TxIDを指定できるようにする。TxSrc#key.nameにTxIDを指定して重複実行防止
|
73
73
|
def create_tx(src, dest, args)
|
74
74
|
tx_src = nil
|
75
|
-
TinyDS.tx(tx_retries){
|
75
|
+
TinyDS.tx(:force_begin=>true, :retries=>tx_retries){
|
76
76
|
src = src.class.get(src.key)
|
77
77
|
src_phase(src, args)
|
78
78
|
src.save!
|
@@ -102,7 +102,7 @@ class BaseTx
|
|
102
102
|
def roll_forward
|
103
103
|
tx_src = TxSrc.get(@tx_key)
|
104
104
|
|
105
|
-
TinyDS.tx(tx_retries){
|
105
|
+
TinyDS.tx(:force_begin=>true, :retries=>tx_retries){
|
106
106
|
dest_key = LowDS::KeyFactory.stringToKey(tx_src.dest_key)
|
107
107
|
dest = dest_key.kind.constantize.get(dest_key)
|
108
108
|
done_name = "TxDone_#{@tx_key.to_s}"
|
@@ -122,7 +122,7 @@ class BaseTx
|
|
122
122
|
}
|
123
123
|
|
124
124
|
# TxSrc#statusをdoneに
|
125
|
-
TinyDS.tx(tx_retries){
|
125
|
+
TinyDS.tx(:force_begin=>true, :retries=>tx_retries){
|
126
126
|
tx_src = TxSrc.get!(@tx_key)
|
127
127
|
if tx_src.status=="pending"
|
128
128
|
tx_src.status = "done"
|
@@ -133,7 +133,7 @@ class BaseTx
|
|
133
133
|
return true
|
134
134
|
rescue => e
|
135
135
|
puts e.inspect
|
136
|
-
TinyDS.tx(tx_retries){
|
136
|
+
TinyDS.tx(:force_begin=>true, :retries=>tx_retries){
|
137
137
|
tx_src = TxSrc.get!(@tx_key)
|
138
138
|
tx_src.roll_forward_failed_count += 1
|
139
139
|
if roll_forward_retries_limit < tx_src.roll_forward_failed_count
|
data/lib/tiny_ds/low_ds.rb
CHANGED
@@ -57,11 +57,21 @@ module LowDS
|
|
57
57
|
when String; KeyFactory.stringToKey(key)
|
58
58
|
else raise "invalid key type key.class=[#{key.class}] key.inspect=[#{key.inspect}]"
|
59
59
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
|
61
|
+
retries = opts[:retries] || 20
|
62
|
+
while 0<=retries
|
63
|
+
retries -= 1
|
64
|
+
begin
|
65
|
+
ent = AppEngine::Datastore.get(key)
|
66
|
+
if opts[:kind]
|
67
|
+
raise "kind missmatch. #{ent.kind}!=#{opts[:kind]}" if ent.kind!=opts[:kind]
|
68
|
+
end
|
69
|
+
return ent
|
70
|
+
rescue AppEngine::Datastore::Timeout => ex
|
71
|
+
raise ex if retries<=0
|
72
|
+
sleep(0.001)
|
73
|
+
end
|
63
74
|
end
|
64
|
-
ent
|
65
75
|
end
|
66
76
|
|
67
77
|
# update
|
@@ -18,12 +18,22 @@ class PropertyDefinition
|
|
18
18
|
def has_default?
|
19
19
|
@opts.has_key?(:default)
|
20
20
|
end
|
21
|
+
|
22
|
+
# default is true
|
23
|
+
def index?
|
24
|
+
(!@opts.has_key?(:index)) || @opts[:index]
|
25
|
+
end
|
26
|
+
|
21
27
|
def to_ds_value(v)
|
22
28
|
case @ptype
|
23
29
|
when :string
|
24
30
|
v.nil? ? nil : v.to_s
|
25
31
|
when :integer
|
26
32
|
v.nil? ? nil : v.to_i
|
33
|
+
when :float
|
34
|
+
v.nil? ? nil : v.to_f
|
35
|
+
when :boolean
|
36
|
+
![nil, false].include?(v)
|
27
37
|
when :text
|
28
38
|
v.nil? ? nil : com.google.appengine.api.datastore::Text.new(v.to_s)
|
29
39
|
when :time
|
@@ -54,6 +64,10 @@ class PropertyDefinition
|
|
54
64
|
ds_v.nil? ? nil : ds_v.to_s
|
55
65
|
when :integer
|
56
66
|
ds_v.nil? ? nil : ds_v.to_i
|
67
|
+
when :float
|
68
|
+
ds_v.nil? ? nil : ds_v.to_f
|
69
|
+
when :boolean
|
70
|
+
ds_v
|
57
71
|
when :text
|
58
72
|
ds_v.nil? ? nil : ds_v.to_s
|
59
73
|
when :time
|
data/lib/tiny_ds/query.rb
CHANGED
@@ -42,8 +42,30 @@ class Query
|
|
42
42
|
def count #todo(tx=nil)
|
43
43
|
@q.count
|
44
44
|
end
|
45
|
+
def count2
|
46
|
+
_count = 0
|
47
|
+
max_key = nil
|
48
|
+
loop do
|
49
|
+
q = @q.clone # or ruby dup
|
50
|
+
q.filter(:__key__, ">", max_key) if max_key
|
51
|
+
q.sort(:__key__)
|
52
|
+
q.java_query.setKeysOnly
|
53
|
+
entries = q.fetch(:limit=>1000).to_a
|
54
|
+
#p ["entries=", entries.collect{|e| e.key }]
|
55
|
+
c = entries.size
|
56
|
+
break if c==0
|
57
|
+
_count += c
|
58
|
+
max_key = entries.last.key
|
59
|
+
#p ["max_key=", max_key]
|
60
|
+
end
|
61
|
+
_count
|
62
|
+
end
|
45
63
|
def one #todo(tx=nil)
|
46
|
-
@
|
64
|
+
if @q.entity
|
65
|
+
@model_class.new_from_entity(@q.entity)
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
47
69
|
end
|
48
70
|
def all(opts={}) #todo(tx=nil)
|
49
71
|
models = []
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module TinyDS
|
2
|
+
class Base
|
3
|
+
# @user.tx{ |u| u.name="john" }
|
4
|
+
# @user.tx(10){ |u| u.name="john" }
|
5
|
+
# @user.tx( :name=>"john")
|
6
|
+
# @user.tx(10, :name=>"john")
|
7
|
+
# @user.tx( :name=>"john"){|u| u.name="JOHN" }
|
8
|
+
# @user.tx(10, :name=>"john"){|u| u.name="JOHN" }
|
9
|
+
def tx_update(*args)
|
10
|
+
retries = 3
|
11
|
+
retries = args.shift if args.first.kind_of?(Integer)
|
12
|
+
attrs = args.shift
|
13
|
+
raise ArgumentError if args.size!=0
|
14
|
+
raise ArgumentError if !block_given? && attrs.nil?
|
15
|
+
obj = nil
|
16
|
+
TinyDS.tx(:retries=>retries) do
|
17
|
+
obj = get_self_and_check_lock_version
|
18
|
+
obj.attributes = attrs if attrs
|
19
|
+
yield(obj) if block_given?
|
20
|
+
obj.save!
|
21
|
+
end
|
22
|
+
obj
|
23
|
+
end
|
24
|
+
def get_self_and_check_lock_version
|
25
|
+
obj = self.class.get(self.key)
|
26
|
+
if self.class.has_property?(:lock_version)
|
27
|
+
if obj.lock_version != self.lock_version
|
28
|
+
raise StaleObjectError.new
|
29
|
+
end
|
30
|
+
obj.lock_version += 1
|
31
|
+
end
|
32
|
+
obj
|
33
|
+
end
|
34
|
+
class StaleObjectError < StandardError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/tiny_ds/version.rb
CHANGED
data/lib/tiny_ds.rb
CHANGED
@@ -5,13 +5,35 @@ require File.dirname(__FILE__)+"/tiny_ds/property_definition.rb"
|
|
5
5
|
require File.dirname(__FILE__)+"/tiny_ds/base.rb"
|
6
6
|
require File.dirname(__FILE__)+"/tiny_ds/query.rb"
|
7
7
|
require File.dirname(__FILE__)+"/tiny_ds/validations.rb"
|
8
|
+
require File.dirname(__FILE__)+"/tiny_ds/transaction.rb"
|
8
9
|
require File.dirname(__FILE__)+"/tiny_ds/base_tx.rb"
|
9
10
|
require File.dirname(__FILE__)+"/tiny_ds/version.rb"
|
10
11
|
|
11
12
|
module TinyDS
|
12
|
-
|
13
|
-
|
13
|
+
# execute block in new transaction.
|
14
|
+
# if current_transaction is exists, no new tx is begin.
|
15
|
+
# if force_begin=true, always begin new tx.
|
16
|
+
def self.tx(opts={}, &block)
|
17
|
+
retries = opts[:retries] || 0
|
18
|
+
cur_tx = nil
|
19
|
+
unless opts[:force_begin]
|
20
|
+
cur_tx = AppEngine::Datastore.current_transaction(nil)
|
21
|
+
end
|
22
|
+
if cur_tx
|
14
23
|
yield(block)
|
15
|
-
|
24
|
+
else
|
25
|
+
AppEngine::Datastore.transaction(retries){
|
26
|
+
yield(block)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def self.readonly(&block)
|
31
|
+
raise "todo"
|
32
|
+
end
|
33
|
+
def batch_get
|
34
|
+
raise "todo"
|
35
|
+
end
|
36
|
+
def batch_put
|
37
|
+
raise "todo"
|
16
38
|
end
|
17
39
|
end
|
data/spec/basic_spec.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
|
-
#require File.dirname(File.expand_path(__FILE__)) + '/spec_helper.rb'
|
2
|
-
|
3
1
|
require File.dirname(__FILE__) + '/spec_helper'
|
4
2
|
|
5
3
|
class Comment < TinyDS::Base
|
6
|
-
#property :ekey, String, :key=>true
|
7
4
|
property :num, :integer
|
8
5
|
property :title, :string
|
9
6
|
property :body, :text
|
10
7
|
property :flag, :integer, :default=>5
|
11
8
|
property :new_at, :time, :default=>proc{ Time.now }
|
9
|
+
property :rate, :float
|
12
10
|
property :updated_at, :time
|
13
11
|
property :created_at, :time
|
14
12
|
property :view_at, :time
|
@@ -16,13 +14,16 @@ end
|
|
16
14
|
|
17
15
|
class Animal < TinyDS::Base
|
18
16
|
property :nickname, :string
|
19
|
-
property :color, :string
|
17
|
+
property :color, :string, :index=>true
|
18
|
+
property :memo, :string, :index=>false
|
19
|
+
property :age, :integer, :index=>nil
|
20
20
|
end
|
21
21
|
|
22
22
|
class User < TinyDS::Base
|
23
23
|
property :nickname, :string
|
24
24
|
property :age, :integer
|
25
25
|
property :favorites, :list
|
26
|
+
property :height, :float
|
26
27
|
end
|
27
28
|
|
28
29
|
describe TinyDS::Base do
|
@@ -35,9 +36,96 @@ describe TinyDS::Base do
|
|
35
36
|
Comment.kind.should == "Comment"
|
36
37
|
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
describe "tx" do
|
40
|
+
it "should not begin new tx if current_transaction exists." do
|
41
|
+
TinyDS.tx{
|
42
|
+
tx1 = AppEngine::Datastore.current_transaction
|
43
|
+
TinyDS.tx{
|
44
|
+
tx2 = AppEngine::Datastore.current_transaction
|
45
|
+
tx1.getId.to_s.should == tx2.getId.to_s
|
46
|
+
}
|
47
|
+
}
|
48
|
+
end
|
49
|
+
it "should begin new tx if :force_begin=true." do
|
50
|
+
TinyDS.tx{
|
51
|
+
tx1 = AppEngine::Datastore.current_transaction
|
52
|
+
TinyDS.tx(:force_begin=>true){
|
53
|
+
tx2 = AppEngine::Datastore.current_transaction
|
54
|
+
# p [tx1.getId, tx2.getId]
|
55
|
+
tx1.getId.to_s.should_not == tx2.getId.to_s
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
it "should retried if concurrent modify"
|
60
|
+
it "should not overwrite properties modified by other tx"
|
61
|
+
it "should not retry when application exception raised"
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "instance tx_update" do
|
65
|
+
it "should updated with tx(attrs)" do
|
66
|
+
c0 = Comment.create(:body=>"hello")
|
67
|
+
c1 = Comment.get(c0.key)
|
68
|
+
c1.body.should == c0.body
|
69
|
+
c2 = c1.tx_update(:body=>"HELLO")
|
70
|
+
c2.body.should == "HELLO"
|
71
|
+
c1.body.should == c0.body # should not changed! BUT I WANT TO BE CHANGED
|
72
|
+
c3 = Comment.get(c0.key)
|
73
|
+
c3.body.should == "HELLO"
|
74
|
+
end
|
75
|
+
it "should updated with tx(block)" do
|
76
|
+
c0 = Comment.create(:body=>"hello")
|
77
|
+
c1 = Comment.get(c0.key)
|
78
|
+
c1.body.should == c0.body
|
79
|
+
c2 = c1.tx_update{|c|
|
80
|
+
c.body = "HELLO"
|
81
|
+
}
|
82
|
+
c2.body.should == "HELLO"
|
83
|
+
c1.body.should == c0.body # should not changed!
|
84
|
+
c3 = Comment.get(c0.key)
|
85
|
+
c3.body.should == "HELLO"
|
86
|
+
end
|
87
|
+
it "should updated with tx(attrs,block)" do
|
88
|
+
c0 = Comment.create(:body=>"hello", :title=>"world", :num=>10)
|
89
|
+
c1 = Comment.get(c0.key)
|
90
|
+
c1.body.should == c0.body
|
91
|
+
c1.title.should == c0.title
|
92
|
+
c1.num.should == c0.num
|
93
|
+
c2 = c1.tx_update(:title=>"WORLD", :num=>20){|c|
|
94
|
+
c.body = "HELLO"
|
95
|
+
c.num = 30
|
96
|
+
}
|
97
|
+
c2.body.should == "HELLO"
|
98
|
+
c2.title.should == "WORLD"
|
99
|
+
c2.num.should == 30
|
100
|
+
c1.body.should == c0.body # should not changed!
|
101
|
+
c1.title.should == c0.title # should not changed!
|
102
|
+
c1.num.should == c0.num # should not changed!
|
103
|
+
c3 = Comment.get(c0.key)
|
104
|
+
c3.body.should == "HELLO"
|
105
|
+
c3.title.should == "WORLD"
|
106
|
+
c3.num.should == 30
|
107
|
+
end
|
108
|
+
it "should retried if concurrent modify" do
|
109
|
+
c0 = Comment.create(:body=>"hello", :num=>0)
|
110
|
+
c1 = Comment.get(c0.key)
|
111
|
+
c1.body.should == c0.body
|
112
|
+
loop_count = 0
|
113
|
+
c2 = c1.tx_update(10){|c|
|
114
|
+
loop_count += 1
|
115
|
+
c.body += " world"
|
116
|
+
if loop_count<=5
|
117
|
+
# modify entity by other tx
|
118
|
+
TinyDS.tx(:force_begin=>true){ com = Comment.get(c0.key); com.num+=1; com.save; }
|
119
|
+
end
|
120
|
+
}
|
121
|
+
loop_count.should == 6
|
122
|
+
c2.num.should == 5
|
123
|
+
c2.body.should == "hello world" # should not be "hello world world world..."
|
124
|
+
c1.body.should == c0.body # should not be changed!
|
125
|
+
end
|
126
|
+
it "should not overwrite properties modified by other tx"
|
127
|
+
it "should not retry when application exception raised"
|
128
|
+
end
|
41
129
|
|
42
130
|
describe :property_definitions do
|
43
131
|
it "should convert to Text" do
|
@@ -45,7 +133,7 @@ describe TinyDS::Base do
|
|
45
133
|
text.class.should == com.google.appengine.api.datastore.Text
|
46
134
|
end
|
47
135
|
it "should correct properties count" do
|
48
|
-
Comment.property_definitions.size.should ==
|
136
|
+
Comment.property_definitions.size.should == 9
|
49
137
|
end
|
50
138
|
it "should initialized with default value" do
|
51
139
|
a = Comment.new
|
@@ -55,9 +143,40 @@ describe TinyDS::Base do
|
|
55
143
|
a = Comment.new
|
56
144
|
(Time.now-a.new_at).should <= 2.0
|
57
145
|
end
|
146
|
+
it "should not index if :index=>false" do
|
147
|
+
a = Animal.create({:nickname=>"pochi", :color=>"white", :memo=>"POCHI", :age=>5})
|
148
|
+
Animal.query.filter(:nickname, "==", "pochi").one.memo.should == a.memo
|
149
|
+
Animal.query.filter(:color, "==", "white").one.memo.should == a.memo
|
150
|
+
Animal.query.filter(:memo, "==", "POCHI").one.should be_nil
|
151
|
+
Animal.query.filter(:age, "==", 5 ).one.should be_nil
|
152
|
+
end
|
153
|
+
it "should be saved large unindexed list props" do
|
154
|
+
favs = (0...10000).to_a
|
155
|
+
# should be raised. test env skips index count limit=5000???
|
156
|
+
User.create({:favorites=>favs, :nickname=>"john"})
|
157
|
+
u = User.query.filter(:nickname=>"john").one
|
158
|
+
u.favorites.size.should == 10000
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "allocate_ids" do
|
163
|
+
it "should be allocated keys" do
|
164
|
+
kr = User.allocate_ids(10)
|
165
|
+
kr.should be_kind_of(Java::ComGoogleAppengineApiDatastore::KeyRange)
|
166
|
+
(kr.end.id-kr.start.id).should == 10-1
|
167
|
+
kr.each do |k|
|
168
|
+
k.should be_kind_of(AppEngine::Datastore::Key)
|
169
|
+
k.inspect.should match(/^User\(\d+\)$/)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
it "should be allocate a id" do
|
173
|
+
k0 = User.allocate_id
|
174
|
+
k0.should be_kind_of(AppEngine::Datastore::Key)
|
175
|
+
k0.inspect.should match(/^User\(\d+\)$/)
|
176
|
+
end
|
58
177
|
end
|
59
178
|
|
60
|
-
describe "key" do
|
179
|
+
describe "create with key" do
|
61
180
|
it "should be got id/name" do
|
62
181
|
k1 = AppEngine::Datastore::Key.from_path("Com", 9999)
|
63
182
|
a1 = Comment.create({:title=>"ccccc"}, :key=>k1)
|
@@ -319,6 +438,18 @@ describe TinyDS::Base do
|
|
319
438
|
Comment.new( ).num.should be_kind_of(NilClass)
|
320
439
|
Comment.new( ).num.should == nil
|
321
440
|
end
|
441
|
+
it "should convert to float" do
|
442
|
+
User.new(:height => "zzzz" ).height.should be_kind_of(Float)
|
443
|
+
User.new(:height => "zzzz" ).height.should be_close(0.0, 0.00001)
|
444
|
+
User.new(:height => 123.5 ).height.should be_kind_of(Float)
|
445
|
+
User.new(:height => 123.5 ).height.should be_close(123.5, 0.00001)
|
446
|
+
User.new(:height => "123.5").height.should be_kind_of(Float)
|
447
|
+
User.new(:height => "123.5").height.should be_close(123.5, 0.00001)
|
448
|
+
User.new(:height => nil ).height.should be_kind_of(NilClass)
|
449
|
+
User.new(:height => nil ).height.should == nil
|
450
|
+
User.new( ).height.should be_kind_of(NilClass)
|
451
|
+
User.new( ).height.should == nil
|
452
|
+
end
|
322
453
|
it "should convert to text" do
|
323
454
|
Comment.new(:body => "zzzz").body.should be_kind_of(String)
|
324
455
|
Comment.new(:body => "zzzz").body.should == "zzzz"
|
@@ -350,15 +481,20 @@ describe TinyDS::Base do
|
|
350
481
|
before :all do
|
351
482
|
Comment.destroy_all
|
352
483
|
raise if Comment.count!=0
|
484
|
+
rate = -1.0
|
353
485
|
3.times do
|
354
|
-
Comment.create(:num=>10, :title=>"BBB")
|
486
|
+
Comment.create(:num=>10, :title=>"BBB", :rate=>rate)
|
487
|
+
rate += 0.1
|
355
488
|
end
|
356
489
|
5.times do
|
357
|
-
Comment.create(:num=>10, :title=>"AAA")
|
490
|
+
Comment.create(:num=>10, :title=>"AAA", :rate=>rate)
|
491
|
+
rate += 0.1
|
358
492
|
end
|
359
493
|
7.times do
|
360
|
-
Comment.create(:num=>50, :title=>"AAA")
|
494
|
+
Comment.create(:num=>50, :title=>"AAA", :rate=>rate)
|
495
|
+
rate += 0.1
|
361
496
|
end
|
497
|
+
# -1.0,-0.9,...,0.0,0.1,....,0.3,0.4
|
362
498
|
end
|
363
499
|
it "should fetched all" do
|
364
500
|
Comment.query.count.should == 15
|
@@ -384,6 +520,15 @@ describe TinyDS::Base do
|
|
384
520
|
Comment.query.filter(:num, ">=", 20).all.all?{|c| c.num==50 }.should be_true
|
385
521
|
Comment.query.filter(:num, "<=", 20).all.all?{|c| c.num==10 }.should be_true
|
386
522
|
end
|
523
|
+
it "should be fetched by float" do
|
524
|
+
Comment.query.filter(:rate, "<", -0.01).count.should == 10
|
525
|
+
Comment.query.filter(:rate, ">", -0.01).count.should == 5
|
526
|
+
Comment.query.filter(:rate, "<", 0.01).count.should == 11
|
527
|
+
Comment.query.filter(:rate, ">", 0.01).count.should == 4
|
528
|
+
|
529
|
+
Comment.query.filter(:rate, ">", 0.01).filter(:rate, "<", 0.21).count.should == 2
|
530
|
+
Comment.query.filter(:rate, ">", 0.01).filter(:rate, "<", 0.09).count.should == 0
|
531
|
+
end
|
387
532
|
it "should be sorted" do
|
388
533
|
comments = Comment.query.sort(:title).sort(:num).all
|
389
534
|
comments[ 0, 5].all?{|c| c.title=="AAA" && c.num==10 }.should be_true
|
@@ -410,6 +555,7 @@ describe TinyDS::Base do
|
|
410
555
|
Comment.count.should == 2
|
411
556
|
Comment.query.filter(:num, "<", 0).count.should == 0
|
412
557
|
end
|
558
|
+
it "should return 1000+ count2"
|
413
559
|
end
|
414
560
|
describe "query(2) parent-children" do
|
415
561
|
before :all do
|
@@ -442,7 +588,7 @@ describe TinyDS::Base do
|
|
442
588
|
c.parent_key.to_s.should == parent.key.to_s
|
443
589
|
end
|
444
590
|
}
|
445
|
-
Comment.query.
|
591
|
+
Comment.query. filter(:num, "==", 10).count.should == 3
|
446
592
|
Comment.query.ancestor(parent).filter(:num, "==", 10).count.should == 2
|
447
593
|
Comment.query.ancestor(parent).filter(:num, "==", 10).each{|c| # [C1,C2]
|
448
594
|
c.key.inspect.index(parent.key.inspect).should == 0
|
@@ -459,6 +605,7 @@ describe TinyDS::Base do
|
|
459
605
|
child2 = Comment.create({:title=>"C2", :num=>10})
|
460
606
|
end
|
461
607
|
it "should raise error from one" do
|
608
|
+
Comment.query.filter(:num=>999).one.should == nil
|
462
609
|
proc{
|
463
610
|
Comment.query.one
|
464
611
|
}.should raise_error(AppEngine::Datastore::TooManyResults)
|
@@ -644,5 +791,57 @@ describe TinyDS::Base do
|
|
644
791
|
User.query.filter(:favorites, ">=", 20).count.should == 6
|
645
792
|
User.query.filter(:favorites, ">", 20).count.should == 4
|
646
793
|
end
|
794
|
+
it "should be default is []" do
|
795
|
+
u1 = User.new
|
796
|
+
u1.favorites.should == []
|
797
|
+
u1.save
|
798
|
+
u1.favorites.should == []
|
799
|
+
u1 = User.get(u1.key)
|
800
|
+
u1.favorites.should == []
|
801
|
+
end
|
802
|
+
it "should be added" do
|
803
|
+
u1 = User.new
|
804
|
+
u1.favorites.size.should == 0
|
805
|
+
u1.favorites << "DOG"
|
806
|
+
u1.favorites.size.should == 0 #1
|
807
|
+
u1.favorites << "CAT"
|
808
|
+
u1.favorites.size.should == 0 #2
|
809
|
+
|
810
|
+
u1.favorites += ["DOG"]
|
811
|
+
u1.favorites.size.should == 1
|
812
|
+
u1.favorites += ["CAT"]
|
813
|
+
u1.favorites.size.should == 2
|
814
|
+
u1.save
|
815
|
+
|
816
|
+
u1 = User.get(u1.key)
|
817
|
+
u1.favorites.sort.should == ["CAT","DOG"]
|
818
|
+
end
|
819
|
+
it "should be removed" do
|
820
|
+
u1 = User.new
|
821
|
+
u1.favorites += ["CAT","DOG"]
|
822
|
+
u1.save
|
823
|
+
|
824
|
+
u1 = User.get(u1.key)
|
825
|
+
u1.favorites.sort.should == ["CAT","DOG"]
|
826
|
+
u1.favorites -= ["DOG"]
|
827
|
+
u1.favorites.sort.should == ["CAT"]
|
828
|
+
u1.save
|
829
|
+
|
830
|
+
u1 = User.get(u1.key)
|
831
|
+
u1.favorites.sort.should == ["CAT"]
|
832
|
+
u1.favorites -= ["CAT"]
|
833
|
+
u1.favorites.sort.should == []
|
834
|
+
u1.save
|
835
|
+
|
836
|
+
u1 = User.get(u1.key)
|
837
|
+
u1.favorites.sort.should == []
|
838
|
+
end
|
839
|
+
end
|
840
|
+
describe "boolean property" do
|
841
|
+
it "true/false"
|
842
|
+
it "nil"
|
843
|
+
it "123"
|
844
|
+
it "str"
|
845
|
+
it "query"
|
647
846
|
end
|
648
847
|
end
|