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