From 9d8a74d8968561b811c6aeca2dff643d2f19d1bf Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Wed, 12 Jan 2011 22:36:55 +1100 Subject: [PATCH 1/7] Fix specs to work with latest versions of gems and tests and implementation to work with active_model for validation --- Rakefile | 2 ++ lib/tiny_ds/base.rb | 12 ++++++++++ lib/tiny_ds/base_tx.rb | 1 + spec/am_spec.rb | 39 +++++++++++++++++++++++++++++++ spec/basic_spec.rb | 13 +++++++---- spec/helpers/active_model_lint.rb | 16 +++++++++++++ spec/spec_helper.rb | 9 +++++++ 7 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 spec/am_spec.rb create mode 100644 spec/helpers/active_model_lint.rb diff --git a/Rakefile b/Rakefile index 0f7b171..8c7a9cf 100644 --- a/Rakefile +++ b/Rakefile @@ -3,6 +3,7 @@ require 'rake/rdoctask' require 'rake/gempackagetask' require 'rubygems/specification' require 'date' +require 'rspec/core/rake_task' require File.dirname(__FILE__) + '/lib/tiny_ds/version' @@ -43,3 +44,4 @@ Rake::RDocTask.new do |rd| rd.rdoc_files.include("README.rdoc", "lib/**/*.rb") end +RSpec::Core::RakeTask.new(:spec) diff --git a/lib/tiny_ds/base.rb b/lib/tiny_ds/base.rb index 98e136d..c110b05 100644 --- a/lib/tiny_ds/base.rb +++ b/lib/tiny_ds/base.rb @@ -137,6 +137,18 @@ def new_record? @new_record end + def persisted? + !new_record? + end + + def to_param + self.key.to_s if persisted? + end + + def to_key + [self.key] if persisted? + end + # foo.save def save do_save diff --git a/lib/tiny_ds/base_tx.rb b/lib/tiny_ds/base_tx.rb index d67a579..b4ff32f 100644 --- a/lib/tiny_ds/base_tx.rb +++ b/lib/tiny_ds/base_tx.rb @@ -1,3 +1,4 @@ +require 'yaml' module TinyDS class BaseTx class SrcJournal < ::TinyDS::Base diff --git a/spec/am_spec.rb b/spec/am_spec.rb new file mode 100644 index 0000000..1dad2fc --- /dev/null +++ b/spec/am_spec.rb @@ -0,0 +1,39 @@ +require File.dirname(__FILE__) + '/spec_helper' + +require 'active_model' + +class ActiveComment < TinyDS::Base + include ActiveModel::Validations + property :num, :integer + property :title, :string + property :body, :text + property :flag, :integer, :default=>5 + property :new_at, :time, :default=>proc{ Time.now } + property :rate, :float + property :updated_at, :time + property :created_at, :time + property :view_at, :time +end + + +describe ActiveComment do + before :each do + AppEngine::Testing.install_test_datastore + end + after :all do + AppEngine::Testing.teardown + end + it_should_behave_like "ActiveModel" + + it "should return string key for to_param" do + c1 = ActiveComment.create({},{:id => 4}) + c1.to_param.should == c1.to_key.to_s + c1.to_param.should == 'agR0ZXN0chMLEg1BY3RpdmVDb21tZW50GAQM' + end + + it "should key array for to_key" do + c1 = ActiveComment.create({},{:id => 4}) + c1.to_key.should == [c1.key] + end + +end diff --git a/spec/basic_spec.rb b/spec/basic_spec.rb index 7bbfd1a..6f578ef 100644 --- a/spec/basic_spec.rb +++ b/spec/basic_spec.rb @@ -27,10 +27,13 @@ class User < TinyDS::Base end describe TinyDS::Base do - before :all do + before :each do #AppEngine::Testing.install_test_env AppEngine::Testing.install_test_datastore end + after :all do + AppEngine::Testing.teardown + end it "should return class name as kind" do Comment.kind.should == "Comment" @@ -570,7 +573,7 @@ def next_in_block(c0) end describe "query(1) basic" do - before :all do + before :each do Comment.destroy_all raise if Comment.count!=0 rate = -1.0 @@ -661,7 +664,7 @@ def next_in_block(c0) it "should return 1000+ count2" end describe "query(2) parent-children" do - before :all do + before :each do Comment.destroy_all raise if Comment.count!=0 gparent = Comment.create(:title=>"GP") @@ -701,7 +704,7 @@ def next_in_block(c0) end end describe "query(3) raise" do - before :all do + before :each do Comment.destroy_all raise if Comment.count!=0 child1 = Comment.create({:title=>"C1", :num=>10}) @@ -1042,7 +1045,7 @@ class Parent::Food < TinyDS::Base end describe "batch_get_by_struct" do - before :all do + before :each do @c1 = Comment.create(:num=>1) @c2 = Comment.create(:num=>2) @c3 = Comment.create(:num=>3) diff --git a/spec/helpers/active_model_lint.rb b/spec/helpers/active_model_lint.rb new file mode 100644 index 0000000..172d738 --- /dev/null +++ b/spec/helpers/active_model_lint.rb @@ -0,0 +1,16 @@ +require 'active_model' + +shared_examples_for "ActiveModel" do + include ActiveModel::Lint::Tests + # + # to_s is to support ruby-1.9 + # + ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m| + example m.gsub('_',' ') do + send m + end + end + def model + subject + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8ab4571..0d83914 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,15 @@ require "pp" $:.push File.join(File.dirname(__FILE__), '..', 'lib') begin + require 'rubygems' require "java" + require 'rspec' + require 'rspec/unit' + Dir["#{File.dirname(__FILE__)}/helpers/*.rb"].each do |file| + require file + end + require 'appengine-apis' + require 'appengine-api-1.0-sdk-1.4.0.jar' if false puts "=======" puts "$LOAD_PATH" @@ -52,6 +60,7 @@ AppEngine::Testing::install_test_env end end + at_exit {java.lang.System.exit(0)} AppEngine::Testing::install_test_datastore $app_logger = Logger.new($stderr) rescue Object => e From fd7f387559f3f5d9bb33ff498e49bdf557e7bbbe Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Thu, 13 Jan 2011 17:05:16 +1100 Subject: [PATCH 2/7] Add support for single table inheritance --- lib/tiny_ds/base.rb | 48 ++++++++++++++++++++++++--- lib/tiny_ds/property_definition.rb | 5 +-- spec/spec_helper.rb | 7 +++- spec/sti_spec.rb | 52 ++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 spec/sti_spec.rb diff --git a/lib/tiny_ds/base.rb b/lib/tiny_ds/base.rb index c110b05..58cac23 100644 --- a/lib/tiny_ds/base.rb +++ b/lib/tiny_ds/base.rb @@ -4,17 +4,28 @@ module TinyDS class Base class << self; attr_accessor :_property_definitions; end RESERVED_PROPERTY_NAME = [:id, :name, :key, :entity, :parent_key, :parent] - VALID_PROPERTY_TYPE = [:string, :integer, :float, :boolean, :text, :time, :list] + VALID_PROPERTY_TYPE = [:string, :integer, :float, :boolean, :text, :time, :list, :type] def self.property(pname, ptype, opts={}) pname = pname.to_sym if RESERVED_PROPERTY_NAME.include?(pname) raise "property name '#{pname}' is reserved." end - property_definitions[pname] = PropertyDefinition.new(pname, ptype, opts) + (self._property_definitions||={})[pname] = PropertyDefinition.new(pname, ptype, opts) + define_method "#{pname}" do + get_property(pname) + end + define_method "#{pname}=" do |value| + set_property(pname, value) + end end def self.property_definitions - self._property_definitions ||= {} + if superclass != Base + defs = superclass.property_definitions + else + defs = {} + end + defs.merge(self._property_definitions ||= {}) end def self.property_definition(name) @@ -46,9 +57,22 @@ def self.default_attrs attrs end + def self.sti_property_definition + name, sti_def = property_definitions.detect {|pname, pdef| pdef.ptype == :type} + sti_def + end + def self.sti? + !!sti_property_definition + end + + # kind-string of entity def self.kind - name + if superclass != Base && superclass.sti? + superclass.kind + else + name + end end #include ActiveModel::Naming @@ -129,7 +153,13 @@ def initialize(attrs={}, opts={}) end def self.new_from_entity(_entity) - new(nil, :entity=>_entity) + clazz = self + if sti_def = self.sti_property_definition + if type_name = _entity[sti_def.pname] + clazz = constantize(type_name) + end + end + clazz.new(nil, :entity=>_entity) end attr_reader :entity @@ -160,6 +190,7 @@ def do_save # raise "entity is readonly." logger.warn "entity is readonly. key=[#{self.key.inspect}]" end + __before_save_set_type __before_save_set_timestamps # if @new_record && @entity.key && parent # TinyDS.tx{ @@ -176,6 +207,11 @@ def do_save end # class KeyIsAlreadyTaken < StandardError # end + def __before_save_set_type + if type_prop = self.class.sti_property_definition + self.set_property(type_prop.pname, self.class.name) + end + end def __before_save_set_timestamps if self.class.has_property?(:created_at) && new_record? @@ -348,6 +384,7 @@ def get_property(k) v end +=begin def method_missing(m, *args) k, is_set = if m.to_s =~ /(.+)=$/ [$1.to_sym, true] @@ -367,6 +404,7 @@ def method_missing(m, *args) super(m, *args) end end +=end def logger self.class.logger diff --git a/lib/tiny_ds/property_definition.rb b/lib/tiny_ds/property_definition.rb index 59a4e62..9a5caa1 100644 --- a/lib/tiny_ds/property_definition.rb +++ b/lib/tiny_ds/property_definition.rb @@ -1,5 +1,6 @@ module TinyDS class PropertyDefinition + attr_reader :ptype, :pname def initialize(pname, ptype, opts) @pname = pname @ptype = ptype @@ -26,7 +27,7 @@ def index? def to_ds_value(v) case @ptype - when :string + when :string, :type v.nil? ? nil : v.to_s when :integer v.nil? ? nil : v.to_i @@ -65,7 +66,7 @@ def to_ds_value(v) end def to_ruby_value(ds_v) case @ptype - when :string + when :string, :type ds_v.nil? ? nil : ds_v.to_s when :integer ds_v.nil? ? nil : ds_v.to_i diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0d83914..8c221ce 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -60,7 +60,12 @@ AppEngine::Testing::install_test_env end end - at_exit {java.lang.System.exit(0)} + at_exit do + $stdout.flush + $stderr.flush + sleep 1 + java.lang.System.exit(0) + end AppEngine::Testing::install_test_datastore $app_logger = Logger.new($stderr) rescue Object => e diff --git a/spec/sti_spec.rb b/spec/sti_spec.rb new file mode 100644 index 0000000..14851d1 --- /dev/null +++ b/spec/sti_spec.rb @@ -0,0 +1,52 @@ +require File.dirname(__FILE__) + '/spec_helper' + +class Page < TinyDS::Base + property :title, :string + property :type, :type +end + +class ExtendedPage < Page + property :extra_data, :string +end + +class FurtherExtendedPage < Page + property :extra_extra_data, :string +end + +describe ExtendedPage do + before :each do + AppEngine::Testing.install_test_datastore + end + after :all do + AppEngine::Testing.teardown + end + it "should allow assigning of base attributes" do + subject.attributes = {:title => 'aaa'} + subject.title.should == 'aaa' + end + it "should return base class Page as its kind" do + subject.key.kind.should == 'Page' + end + it "should update the type field on save" do + subject.save + subject.type.should == 'ExtendedPage' + end + it "should return the subclass from a find on the parent" do + subject.save + p2 = Page.get(subject.key) + p2.should be_kind_of(ExtendedPage) + end +=begin + describe "new_from_entity" do + it "should build from low-entity" do + ent = FurtherExtendedPage.create({:title=>"aaa", :extra_extra_data=>'444', :extra_data=>"x"}).entity + + a1 = FurtherExtendedPage.new_from_entity(ent) + a1.title.should == "aaa" + a1.extra_extra_data.should == '444' + a1.extra_data.should == "x" + end + end +=end + +end From 00d8ea7be66903618a9a181e3c9e395f0a6bd36f Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Thu, 13 Jan 2011 22:49:26 +1100 Subject: [PATCH 3/7] Add :user as a property type --- lib/tiny_ds/base.rb | 2 +- lib/tiny_ds/property_definition.rb | 11 +++++++++++ spec/basic_spec.rb | 12 +++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/tiny_ds/base.rb b/lib/tiny_ds/base.rb index 58cac23..f40c8a5 100644 --- a/lib/tiny_ds/base.rb +++ b/lib/tiny_ds/base.rb @@ -4,7 +4,7 @@ module TinyDS class Base class << self; attr_accessor :_property_definitions; end RESERVED_PROPERTY_NAME = [:id, :name, :key, :entity, :parent_key, :parent] - VALID_PROPERTY_TYPE = [:string, :integer, :float, :boolean, :text, :time, :list, :type] + VALID_PROPERTY_TYPE = [:string, :integer, :float, :boolean, :text, :time, :list, :type, :user] def self.property(pname, ptype, opts={}) pname = pname.to_sym if RESERVED_PROPERTY_NAME.include?(pname) diff --git a/lib/tiny_ds/property_definition.rb b/lib/tiny_ds/property_definition.rb index 9a5caa1..cf63d80 100644 --- a/lib/tiny_ds/property_definition.rb +++ b/lib/tiny_ds/property_definition.rb @@ -42,6 +42,15 @@ def to_ds_value(v) # ![nil, false].include?(v) when :text v.nil? ? nil : com.google.appengine.api.datastore::Text.new(v.to_s) + when :user + case v + when NilClass, com.google.appengine.api.users::User + v + when String + com.google.appengine.api.users::User.new(v, 'gmail.com') + else + raise "not User or String value" + end when :time case v when Time @@ -74,6 +83,8 @@ def to_ruby_value(ds_v) ds_v.nil? ? nil : ds_v.to_f when :boolean ds_v + when :user + ds_v when :text ds_v.nil? ? nil : ds_v.to_s when :time diff --git a/spec/basic_spec.rb b/spec/basic_spec.rb index 6f578ef..8f3a54a 100644 --- a/spec/basic_spec.rb +++ b/spec/basic_spec.rb @@ -3,6 +3,7 @@ class Comment < TinyDS::Base property :num, :integer property :title, :string + property :creator, :user property :body, :text property :flag, :integer, :default=>5 property :new_at, :time, :default=>proc{ Time.now } @@ -209,7 +210,7 @@ def next_in_block(c0) text.class.should == com.google.appengine.api.datastore.Text end it "should correct properties count" do - Comment.property_definitions.size.should == 9 + Comment.property_definitions.size.should == 10 end it "should initialized with default value" do a = Comment.new @@ -566,6 +567,15 @@ def next_in_block(c0) Comment.new(:view_at => nil).view_at.should == nil Comment.new( ).view_at.should == nil end + it "should covert to user" do + c0 = Comment.new(:creator => 'test@example.com') + c0.creator.should be_kind_of(com.google.appengine.api.users::User) + c0.creator.email.should == 'test@example.com' + c0.creator.auth_domain.should == 'gmail.com' + + c1 = Comment.new(:creator => com.google.appengine.api.users::User.new('test@example.com','gmail.com')) + c1.creator.email.should == 'test@example.com' + end it "should not convert to array" do proc{ User.new(:favorites => "zzzz" ) }.should raise_error proc{ User.new(:favorites => 123 ) }.should raise_error From 3bb60c51914da9232943c7a5075587587c3848ed Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Fri, 14 Jan 2011 09:59:50 +1100 Subject: [PATCH 4/7] Use appengine api for user creation if it exists --- lib/tiny_ds/property_definition.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/tiny_ds/property_definition.rb b/lib/tiny_ds/property_definition.rb index cf63d80..ed1b305 100644 --- a/lib/tiny_ds/property_definition.rb +++ b/lib/tiny_ds/property_definition.rb @@ -47,7 +47,11 @@ def to_ds_value(v) when NilClass, com.google.appengine.api.users::User v when String - com.google.appengine.api.users::User.new(v, 'gmail.com') + if defined?(AppEngine::Users::User) + AppEngine::Users::User.new(v) + else + com.google.appengine.api.users::User.new(v, 'gmail.com') + end else raise "not User or String value" end From b4910c498fb571b40e35e6f2918c34636450f03b Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Fri, 14 Jan 2011 10:48:13 +1100 Subject: [PATCH 5/7] Allow key as a property type --- lib/tiny_ds/base.rb | 2 +- lib/tiny_ds/property_definition.rb | 4 +++- spec/basic_spec.rb | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/tiny_ds/base.rb b/lib/tiny_ds/base.rb index f40c8a5..0bdb4a5 100644 --- a/lib/tiny_ds/base.rb +++ b/lib/tiny_ds/base.rb @@ -4,7 +4,7 @@ module TinyDS class Base class << self; attr_accessor :_property_definitions; end RESERVED_PROPERTY_NAME = [:id, :name, :key, :entity, :parent_key, :parent] - VALID_PROPERTY_TYPE = [:string, :integer, :float, :boolean, :text, :time, :list, :type, :user] + VALID_PROPERTY_TYPE = [:string, :integer, :float, :boolean, :text, :time, :list, :type, :user, :key] def self.property(pname, ptype, opts={}) pname = pname.to_sym if RESERVED_PROPERTY_NAME.include?(pname) diff --git a/lib/tiny_ds/property_definition.rb b/lib/tiny_ds/property_definition.rb index ed1b305..0d6d4b5 100644 --- a/lib/tiny_ds/property_definition.rb +++ b/lib/tiny_ds/property_definition.rb @@ -42,6 +42,8 @@ def to_ds_value(v) # ![nil, false].include?(v) when :text v.nil? ? nil : com.google.appengine.api.datastore::Text.new(v.to_s) + when :key + v.nil? ? nil : TinyDS::Base.to_key(v) when :user case v when NilClass, com.google.appengine.api.users::User @@ -87,7 +89,7 @@ def to_ruby_value(ds_v) ds_v.nil? ? nil : ds_v.to_f when :boolean ds_v - when :user + when :user, :key ds_v when :text ds_v.nil? ? nil : ds_v.to_s diff --git a/spec/basic_spec.rb b/spec/basic_spec.rb index 8f3a54a..bc93654 100644 --- a/spec/basic_spec.rb +++ b/spec/basic_spec.rb @@ -18,6 +18,7 @@ class Animal < TinyDS::Base property :color, :string, :index=>true property :memo, :string, :index=>false property :age, :integer, :index=>nil + property :owner_key, :key end class User < TinyDS::Base @@ -567,7 +568,7 @@ def next_in_block(c0) Comment.new(:view_at => nil).view_at.should == nil Comment.new( ).view_at.should == nil end - it "should covert to user" do + it "should convert to user" do c0 = Comment.new(:creator => 'test@example.com') c0.creator.should be_kind_of(com.google.appengine.api.users::User) c0.creator.email.should == 'test@example.com' @@ -576,6 +577,20 @@ def next_in_block(c0) c1 = Comment.new(:creator => com.google.appengine.api.users::User.new('test@example.com','gmail.com')) c1.creator.email.should == 'test@example.com' end + it "should convert to key" do + u = User.create({}) + a0 = Animal.new(:owner_key => u) + a0.owner_key.should == u.key + + a1 = Animal.new(:owner_key => u.key) + a1.owner_key.should == u.key + + a2 = Animal.new(:owner_key => u.key.to_s) + a2.owner_key.should == u.key + + a3 = Animal.new(:owner_key => nil) + a3.owner_key.should == nil + end it "should not convert to array" do proc{ User.new(:favorites => "zzzz" ) }.should raise_error proc{ User.new(:favorites => 123 ) }.should raise_error From da7dbe016dedd81303fd96b51e7fcfba50ba0731 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Wed, 19 Jan 2011 22:40:46 +1100 Subject: [PATCH 6/7] Add spec for batch getting by parent + id --- spec/basic_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/basic_spec.rb b/spec/basic_spec.rb index bc93654..a245221 100644 --- a/spec/basic_spec.rb +++ b/spec/basic_spec.rb @@ -470,6 +470,16 @@ def next_in_block(c0) a[1].should be_nil a[2].key.to_s.should == k2.to_s end + it "should get by ids with parent" do + k0 = Comment.create({}).key + k1 = Comment.create({}, :parent => k0).key + k2 = Comment.create({}, :parent => k0).key + + a = Comment.get_by_ids([k1.id,k2.id], k0) + a.size.should == 2 + a[0].key.to_s.should == k1.to_s + a[1].key.to_s.should == k2.to_s + end it "should be got by names" end From 3bae1507d51eef614d878b389f79764af4d3e4fa Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Wed, 19 Jan 2011 23:09:52 +1100 Subject: [PATCH 7/7] Add equality methods --- lib/tiny_ds/base.rb | 16 ++++++++++++++++ spec/basic_spec.rb | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/lib/tiny_ds/base.rb b/lib/tiny_ds/base.rb index 0bdb4a5..e524d07 100644 --- a/lib/tiny_ds/base.rb +++ b/lib/tiny_ds/base.rb @@ -384,6 +384,22 @@ def get_property(k) v end + def ==(other) + return true if equal?(other) + return false unless other.kind_of?(Base) + return key == other.key + end + + def eql?(other) + return true if equal?(other) + return false unless other.kind_of?(Base) + return key.eql? other.key + end + + def hash + key.hash + end + =begin def method_missing(m, *args) k, is_set = if m.to_s =~ /(.+)=$/ diff --git a/spec/basic_spec.rb b/spec/basic_spec.rb index a245221..4c95566 100644 --- a/spec/basic_spec.rb +++ b/spec/basic_spec.rb @@ -1079,6 +1079,49 @@ class Parent::Food < TinyDS::Base end end + describe "two objects with the same key" do + before :each do + @c1 = Comment.create(:num=>1) + end + it "should be ==" do + (Comment.get(@c1.key) == Comment.get(@c1.key)).should be_true + end + it "should be ===" do + (Comment.get(@c1.key) === Comment.get(@c1.key)).should be_true + end + it "should be eql?" do + (Comment.get(@c1.key).eql? Comment.get(@c1.key)).should be_true + end + it "should have the same hash" do + Comment.get(@c1.key).hash.should == Comment.get(@c1.key).hash + end + it "should not be equal?" do + (Comment.get(@c1.key).equal? Comment.get(@c1.key)).should be_false + end + it "should work with uniq" do + [Comment.get(@c1.key), Comment.get(@c1.key)].uniq.size.should == 1 + end + end + + describe "two unsaved objects" do + before :each do + @c1 = Comment.new(:num=>1) + @c1 = Comment.new(:num=>1) + end + it "should not be ==" do + (@c1 == @c2).should be_false + end + it "should not be ===" do + (@c1 === @c2).should be_false + end + it "should not be eql?" do + (@c1.eql? @c2).should be_false + end + it "should not have the same hash" do + @c1.hash.should_not == @c2.hash + end + end + describe "batch_get_by_struct" do before :each do @c1 = Comment.create(:num=>1)