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 CHANGED
@@ -1,25 +1,28 @@
1
- * TinyDS -- tiny datastore library for GAE/JRuby
2
- github: http://github.com/takeru/tiny_ds
1
+ = TinyDS -- tiny datastore library for GAE/JRuby
2
+ github: http://github.com/takeru/tiny_ds
3
3
 
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..
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
- ** run shout demo
11
+ == run example
12
+ % examples/basic
12
13
  % dev_appserver.rb .
13
14
 
14
- ** how to run specs
15
+ == how to run specs
15
16
  install jruby 1.4.0
16
- % source set_classpath_for_jruby.sh (set classpath, please read spec/spec_helper.rb)
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
- ** demo
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
- ** author
25
- takeru : sasaki.takeru@gmail.com, twitter:@urekat (almost japanese!), blog:http://d.hatena.ne.jp/urekat
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/**/*") + Dir.glob("lib/**/*")
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
- @entity = opts.delete(:entity)
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
- @entity[k] = ds_v
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
@@ -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
@@ -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
- ent = AppEngine::Datastore.get(key)
61
- if opts[:kind]
62
- raise "kind missmatch. #{ent.kind}!=#{opts[:kind]}" if ent.kind!=opts[:kind]
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
- @model_class.new_from_entity(@q.entity)
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
@@ -1,3 +1,3 @@
1
1
  module TinyDS
2
- VERSION = '0.0.2'.freeze
2
+ VERSION = '0.0.3.pre'.freeze
3
3
  end
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
- def self.tx(retries=0, &block)
13
- AppEngine::Datastore.transaction(retries){
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
- #describe "tx" do
39
- # # todo
40
- #end
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 == 8
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. filter(:num, "==", 10).count.should == 3
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