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