activerecord-datastore-adapter 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ *.sqlite3
5
+ database.yml
6
+ indexs.yml
7
+ *.log
8
+ WEB-INF
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dstore.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec'
7
+ gem 'appengine-sdk', '1.4.0'
data/Gemfile.lock ADDED
@@ -0,0 +1,53 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ activerecord-datastore-adapter (0.0.1)
5
+ activerecord (>= 3.0.3)
6
+ appengine-apis (= 0.0.22)
7
+ arel (>= 2.0.7)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ activemodel (3.0.5)
13
+ activesupport (= 3.0.5)
14
+ builder (~> 2.1.2)
15
+ i18n (~> 0.4)
16
+ activerecord (3.0.5)
17
+ activemodel (= 3.0.5)
18
+ activesupport (= 3.0.5)
19
+ arel (~> 2.0.2)
20
+ tzinfo (~> 0.3.23)
21
+ activesupport (3.0.5)
22
+ appengine-apis (0.0.22)
23
+ appengine-rack
24
+ appengine-rack (0.0.12)
25
+ jruby-jars (>= 1.5.1)
26
+ jruby-rack (>= 1.0.1)
27
+ rack
28
+ appengine-sdk (1.4.0)
29
+ arel (2.0.9)
30
+ builder (2.1.2)
31
+ diff-lcs (1.1.2)
32
+ i18n (0.5.0)
33
+ jruby-jars (1.6.0)
34
+ jruby-rack (1.0.7)
35
+ rack (1.2.2)
36
+ rspec (2.5.0)
37
+ rspec-core (~> 2.5.0)
38
+ rspec-expectations (~> 2.5.0)
39
+ rspec-mocks (~> 2.5.0)
40
+ rspec-core (2.5.1)
41
+ rspec-expectations (2.5.0)
42
+ diff-lcs (~> 1.1.2)
43
+ rspec-mocks (2.5.0)
44
+ tzinfo (0.3.25)
45
+
46
+ PLATFORMS
47
+ java
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ activerecord-datastore-adapter!
52
+ appengine-sdk (= 1.4.0)
53
+ rspec
data/README.textile ADDED
@@ -0,0 +1,38 @@
1
+ h1. Datastore Adapter
2
+
3
+ ActiveRecord Adatper for AppEngine Datastore. The AppEngine Datastore is not a relational database system. So, this adapter only support some basic features.
4
+
5
+ h2. Installation( under-construction )
6
+
7
+ Use appengine-sdk (1.4.0)
8
+
9
+ Create Rails 3 application:
10
+
11
+ bc. rails new app_name -m http://siddick.github.com/datastore/rails3.rb
12
+ cd app_name
13
+ appcfg.rb run bin/rails g scaffold post title:string content:text
14
+ appcfg.rb run bin/rake db:migrate
15
+
16
+ Run the server:
17
+
18
+ bc. dev_appserver.rb .
19
+
20
+ h2. Supported Features
21
+
22
+ # has_many and belongs_to
23
+ # migration
24
+ # table indexes
25
+ # operators( =, >, >=, <=, <>, in, and )
26
+
27
+ h2. Not Supported
28
+
29
+ Datastore is not a Relational Database system. So, you can't expect more.
30
+
31
+ # Joins
32
+ # operators( or, between, like, etc )
33
+
34
+ h2. Bugs
35
+
36
+ Please report the bugs.
37
+
38
+ https://github.com/siddick/datastore/issues
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ desc "Test examples"
5
+ task :test do
6
+ raise "Need jruby" if RUBY_ENGINE != "jruby"
7
+ system 'rspec spec/*_spec.rb'
8
+ end
9
+
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "activerecord-datastore-adapter"
6
+ s.version = "0.0.1"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Mohammed Siddick"]
9
+ s.email = ["siddick@gmail.com"]
10
+ s.homepage = "http://rubygems.org/gems/datastore"
11
+ s.summary = %q{ActiveRecord Adapter for Appengine Datastore}
12
+ s.description = %q{Just an ActiveRecord Adapter for the Appengine Datastore.
13
+ Create Rails3 application: rails new app_name -m http://siddick.github.com/datastore/rails3.rb}
14
+
15
+ s.rubyforge_project = "activerecord-datastore-adapter"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency( 'appengine-apis', '0.0.22' )
23
+ s.add_dependency( 'activerecord', '>= 3.0.3' )
24
+ s.add_dependency( 'arel', '>= 2.0.7' )
25
+ end
@@ -0,0 +1,71 @@
1
+
2
+ remove_file 'Gemfile'
3
+ create_file 'Gemfile' do
4
+ <<-GEMFILE
5
+
6
+ disable_system_gems
7
+ disable_rubygems
8
+ bundle_path ".gems/bundler_gems"
9
+
10
+ gem 'rails', '~> 3.0.5'
11
+ gem 'activerecord-datastore-adapter'
12
+ gem 'jruby-rack', '1.0.5'
13
+
14
+ GEMFILE
15
+ end
16
+
17
+ remove_file 'config/boot.rb'
18
+ create_file 'config/boot.rb' do
19
+ <<-BOOT
20
+
21
+ String.class_eval do
22
+ alias :old_plus :+
23
+ def +( val )
24
+ if( val.is_a? Array )
25
+ [ self ] + val
26
+ else
27
+ old_plus val
28
+ end
29
+ end
30
+ end
31
+ BOOT
32
+ end
33
+
34
+
35
+ remove_file 'config/database.yml'
36
+ create_file 'config/database.yml' do
37
+ <<-DATABASE
38
+ development:
39
+ adapter: datastore
40
+ database: db/database.yml
41
+
42
+ test:
43
+ adapter: datastore
44
+ database: db/test.yml
45
+
46
+ production:
47
+ adapter: datastore
48
+ database: db/database.yml
49
+ DATABASE
50
+ end
51
+
52
+ gsub_file 'config/application.rb', /Bundler/ do
53
+ "# Bundler"
54
+ end
55
+
56
+ gsub_file 'config/application.rb', /class Application < Rails::Application$/ do
57
+ "class Application < Rails::Application
58
+ require 'appengine-apis/logger'
59
+ config.logger = AppEngine::Logger.new
60
+ "
61
+ end
62
+
63
+ create_file 'config/initializers/cache_store.rb' do
64
+ <<-CACHE_STORE
65
+ #require 'appengine-apis/memcache'
66
+ #ActionController::Base.cache_store = AppEngine::Memcache.new
67
+ CACHE_STORE
68
+ end
69
+
70
+
71
+ system "appcfg.rb generate_app ."
@@ -0,0 +1,81 @@
1
+
2
+ remove_file 'Gemfile'
3
+ create_file 'Gemfile' do
4
+ <<-GEMFILE
5
+
6
+ disable_system_gems
7
+ disable_rubygems
8
+ bundle_path ".gems/bundler_gems"
9
+
10
+ gem 'rails', '~> 3.0.5'
11
+ gem 'appengine-apis', '>= 0.0.22'
12
+
13
+ GEMFILE
14
+ end
15
+
16
+ remove_file 'config/boot.rb'
17
+ create_file 'config/boot.rb' do
18
+ <<-BOOT
19
+
20
+ String.class_eval do
21
+ alias :old_plus :+
22
+ def +( val )
23
+ if( val.is_a? Array )
24
+ [ self ] + val
25
+ else
26
+ old_plus val
27
+ end
28
+ end
29
+ end
30
+
31
+ File.class_eval do
32
+ class << self
33
+ alias :old_expand_path :expand_path
34
+ def expand_path( *args )
35
+ fn = old_expand_path( *args )
36
+ fn.sub(/^.*file:/, 'file:') if fn
37
+ end
38
+ end
39
+ end
40
+
41
+ $LOAD_PATH.push( '../../lib' )
42
+ BOOT
43
+ end
44
+
45
+ remove_file 'config/database.yml'
46
+ create_file 'config/database.yml' do
47
+ <<-DATABASE
48
+ development:
49
+ adapter: datastore
50
+ database: development.yml
51
+
52
+ test:
53
+ adapter: datastore
54
+ database: test.yml
55
+
56
+ production:
57
+ adapter: datastore
58
+ database: development.yml
59
+ DATABASE
60
+ end
61
+
62
+ gsub_file 'config/application.rb', /Bundler/ do
63
+ "# Bundler"
64
+ end
65
+
66
+ gsub_file 'config/application.rb', /class Application < Rails::Application$/ do
67
+ "class Application < Rails::Application
68
+ require 'appengine-apis/logger'
69
+ config.logger = AppEngine::Logger.new
70
+ "
71
+ end
72
+
73
+ create_file 'config/initializers/cache_store.rb' do
74
+ <<-CACHE_STORE
75
+ #require 'appengine-apis/memcache'
76
+ #ActionController::Base.cache_store = AppEngine::Memcache.new
77
+ CACHE_STORE
78
+ end
79
+
80
+
81
+ system "appcfg.rb generate_app ."
@@ -0,0 +1,266 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_support/core_ext/kernel/requires'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'set'
5
+
6
+ # Patch
7
+ require 'active_record/datastore_associations_patch'
8
+
9
+ require 'yaml'
10
+ require 'arel/visitors/datastore'
11
+
12
+ module ActiveRecord
13
+ class Base
14
+ def self.datastore_connection(config) # :nodoc:
15
+ ConnectionAdapters::DatastoreAdapter.new( ConnectionAdapters::DatastoreAdapter::DB.new( config.symbolize_keys ), logger )
16
+ end
17
+ end
18
+
19
+ module ConnectionAdapters
20
+ class DatastoreAdapter < AbstractAdapter
21
+ ADAPTER_NAME = "Datastore"
22
+
23
+ def adapter_name #:nodoc:
24
+ ADAPTER_NAME
25
+ end
26
+
27
+ def supports_migrations? #:nodoc:
28
+ true
29
+ end
30
+
31
+ def supports_primary_key? #:nodoc:
32
+ true
33
+ end
34
+
35
+ def supports_autoincrement? #:nodoc:
36
+ true
37
+ end
38
+
39
+ def select( sql, name = nil, binds = [] )
40
+ log( sql.inspect, name ) {
41
+ @connection.select_query( sql.q, sql.options )
42
+ }
43
+ end
44
+
45
+ def select_rows(sql, name = nil)
46
+ select(sql, name).map{|r| r.map{|k,v| v } }
47
+ end
48
+
49
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
50
+ log( "Insert: " + sql.inspect, name ){
51
+ @connection.insert_query( sql )
52
+ }
53
+ end
54
+
55
+ def update( sql, name = nil)
56
+ log( "Update: VALUES(#{sql.options[:values].collect{|k,v|k.to_s + " = " + v.inspect}.join(", ")}) " + sql.inspect, name ) {
57
+ @connection.update_query( sql.q, sql.options[:values] )
58
+ }
59
+ end
60
+
61
+ def delete(sql, name = nil)
62
+ log( "Delete: " + sql.inspect, name ) {
63
+ @connection.delete_query( sql.q )
64
+ }
65
+ end
66
+
67
+
68
+ def execute(sql, name = nil)
69
+ log("Not Support: " + sql, name) {
70
+ #@connection.execute( sql )
71
+ }
72
+ end
73
+
74
+ def create_table( table_name, options = {} )
75
+ log( "CREATE TABLE #{table_name}", "Datastore Adapter" ) {
76
+ td = TableDefinition.new(self)
77
+ td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
78
+
79
+ yield td if block_given?
80
+
81
+ fields = {}
82
+ td.columns.each{|c| fields[c.name.to_s] = { "default" => c.default, "type" => c.type.to_s, "null" => c.null } }
83
+ @connection.create_table( table_name, fields )
84
+ td
85
+ }
86
+ end
87
+
88
+ def drop_table( tname, options = {} )
89
+ @connection.drop_table( tname )
90
+ end
91
+
92
+ def rename_table( tname, ntname )
93
+ @connection.drop_table( tname, ntname )
94
+ end
95
+
96
+ def add_column(table_name, column_name, type, options = {})
97
+ @connection.add_column( table_name, column_name, type, options )
98
+ end
99
+
100
+ alias :change_column :add_column
101
+
102
+ def add_index(table_name, column_name, options = {})
103
+ @connection.add_index( table_name, column_name, options )
104
+ end
105
+
106
+ def remove_index(table_name, column_name )
107
+ @connection.remove_index( table_name, column_name )
108
+ end
109
+
110
+ def tables
111
+ @connection.tables.keys
112
+ end
113
+
114
+ def columns( table_name, name = nil)
115
+ @connection.columns( table_name, name ).collect{|k,opt|
116
+ is_primary = opt["type"] == "primary_key"
117
+ c = Column.new( k, opt["default"], is_primary ? "integer" : opt["type"].to_s, opt["null"] )
118
+ c.primary = true if is_primary
119
+ c
120
+ }
121
+ end
122
+
123
+ def primary_key( table_name )
124
+ column = @connection.columns( table_name ).find{|k,opt|
125
+ opt["type"] == "primary_key"
126
+ }
127
+ column ? column[0] : nil
128
+ end
129
+
130
+ class DB
131
+ def initialize( config )
132
+ @config = { :database => 'database.yaml', :index => 'index.yaml', :namespace => 'dev' }.merge( config )
133
+ if( @config[:database] and File.exist? @config[:database] )
134
+ @tables = YAML.load( File.open( @config[:database], "r" ) )
135
+ else
136
+ @tables = {}
137
+ end
138
+
139
+ if( @config[:index] and File.exist? @config[:index] )
140
+ @indexes = YAML.load( File.open( @config[:index], "r" ) )
141
+ @indexes = @indexes[:indexes] || []
142
+ else
143
+ @indexes = []
144
+ end
145
+ end
146
+
147
+ def create_table( tname, fields )
148
+ @tables[tname] = fields
149
+ save_schema
150
+ end
151
+
152
+ def drop_table( tname, options = {} )
153
+ @tables.delete(tname)
154
+ save_schema
155
+ end
156
+
157
+ def rename_table( tname, ntname )
158
+ value = @tables.delete(tname)
159
+ if( value )
160
+ @tables[ntname] = value
161
+ save_schema
162
+ end
163
+ end
164
+
165
+ def add_column(table_name, column_name, type, options = {})
166
+ @tables[table_name][column_name] = { "type" => type.to_s, "default" => options[:default], "null" => options[:null] }
167
+ save_schema
168
+ end
169
+
170
+ alias :change_column :add_column
171
+
172
+ def add_index(table_name, column_name, options = {})
173
+ table_name = table_name.to_s
174
+ column_name = [ column_name ] unless column_name.is_a? Array
175
+ column_name.map!{|c| c.to_s }
176
+ inds = @indexes.find_all{|i| i["kind"] == table_name and i["properties"].map{|p| p["name"] } == column_name }
177
+ if inds.empty?
178
+ @indexes.push( { "kind" => table_name, "properties" => column_name.map{|c| { "name" => c } } } )
179
+ save_indexes
180
+ end
181
+ end
182
+
183
+ def remove_index(table_name, column_name )
184
+ table_name = table_name.to_s
185
+ column_name = column_name[:column] if column_name.is_a? Hash
186
+ column_name = [ column_name ] unless column_name.is_a? Array
187
+ column_name.map!{|c| c.to_s }
188
+ inds = @indexes.delete_all{|i| i["kind"] == table_name and i["properties"].map{|p| p["name"] } == column_name }
189
+ save_indexes
190
+ end
191
+
192
+ def save_indexes
193
+ f = File.open( @config[:index], 'w' )
194
+ f.write( { "indexes" => @indexes }.to_yaml )
195
+ f.close
196
+ end
197
+
198
+ def save_schema
199
+ f = File.open( @config[:database], 'w' )
200
+ f.write( @tables.to_yaml )
201
+ f.close
202
+ end
203
+
204
+ def tables
205
+ @tables
206
+ end
207
+
208
+ def columns( table_name, name = nil )
209
+ if tables[table_name]
210
+ tables[table_name]
211
+ end
212
+ end
213
+
214
+ def primary_key( tname )
215
+ 'id'
216
+ end
217
+
218
+ def select_query( q, options = {} )
219
+ output = []
220
+ if( options[:empty] )
221
+ []
222
+ elsif( options[:count] )
223
+ output.push( { "count" => q.count() } )
224
+ else
225
+ t_name = q.kind
226
+ p_key = primary_key( t_name )
227
+ column_list = columns( t_name )
228
+ q.fetch(options).each{|e|
229
+ h = {}
230
+ column_list.each{|n,opt|
231
+ h[n] = ( n == p_key ? e.key.id : e[n] )
232
+ }
233
+ output.push( h )
234
+ }
235
+ end
236
+ output
237
+ end
238
+
239
+ def insert_query( q )
240
+ AppEngine::Datastore.put q
241
+ q.key.id
242
+ end
243
+
244
+ def update_query( q, values = nil )
245
+ if( values and values.size > 0 )
246
+ entities = []
247
+ q.each{|e|
248
+ values.each{|k,v| e[k] = v }
249
+ entities.push(e)
250
+ }
251
+ AppEngine::Datastore.put entities
252
+ end
253
+ end
254
+
255
+ def delete_query( q )
256
+ keys = []
257
+ q.each{|e| keys.push e.key }
258
+ AppEngine::Datastore.delete keys
259
+ end
260
+ end
261
+
262
+ end
263
+ end
264
+ end
265
+
266
+
@@ -0,0 +1,65 @@
1
+ require 'active_record/associations'
2
+ require 'active_record/associations/has_many_association'
3
+ require 'active_record/associations/has_many_through_association'
4
+
5
+ ActiveRecord::Associations::HasManyAssociation.class_eval do
6
+ def owner_id
7
+ if @reflection.options[:primary_key]
8
+ @owner.send(@reflection.options[:primary_key])
9
+ else
10
+ @owner.id
11
+ end
12
+ end
13
+
14
+ def construct_sql
15
+ case
16
+ when @reflection.options[:finder_sql]
17
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
18
+ when @reflection.options[:as]
19
+ @finder_sql = { ( @reflection.options[:as]+ "_id" ) => owner_id,
20
+ ( @reflection.options[:as] + "_type" ) => @owner.class.quote_value(@owner.class.base_class.name.to_s) }
21
+ @finder_sql.merge!( conditions ) if conditions
22
+ else
23
+ @finder_sql = { @reflection.primary_key_name => owner_id }
24
+ @finder_sql.merge!( conditions ) if conditions
25
+ end
26
+
27
+ construct_counter_sql
28
+ end
29
+ end
30
+
31
+ ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
32
+
33
+ def owner_id
34
+ if @reflection.options[:primary_key]
35
+ @owner.send(@reflection.options[:primary_key])
36
+ else
37
+ @owner.id
38
+ end
39
+ end
40
+
41
+
42
+ def construct_conditions
43
+ if @reflection.source_reflection.macro == :belongs_to
44
+ source_primary_key = @reflection.source_reflection.primary_key_name
45
+ else
46
+ source_primary_key = @reflection.through_reflection.klass.primary_key
47
+ end
48
+ ids = @owner.send(@reflection.options[:through]).map{|t| t.send(source_primary_key) }
49
+ conditions = { :id => ids }
50
+ end
51
+
52
+
53
+ def construct_scope
54
+ { :create => construct_owner_attributes(@reflection),
55
+ :find => { :conditions => construct_conditions,
56
+ # :joins => construct_joins,
57
+ :include => @reflection.options[:include] || @reflection.source_reflection.options[:include],
58
+ :select => construct_select,
59
+ :order => @reflection.options[:order],
60
+ :limit => @reflection.options[:limit],
61
+ :readonly => @reflection.options[:readonly],
62
+ } }
63
+ end
64
+
65
+ end
@@ -0,0 +1,144 @@
1
+ require 'arel'
2
+ require 'arel/visitors'
3
+ require 'arel/visitors/to_sql'
4
+ require 'appengine-apis/datastore'
5
+
6
+ module Arel
7
+ module Visitors
8
+ class Datastore < Arel::Visitors::ToSql
9
+
10
+ class QString
11
+ attr :kind
12
+ attr :q
13
+ attr :options
14
+ attr :connection
15
+
16
+ def initialize( conn, kind, options = {} )
17
+ @connection = conn
18
+ @kind = kind
19
+ @q = AppEngine::Datastore::Query.new( kind )
20
+ @options = options
21
+ end
22
+
23
+ def to_s
24
+ out = q.inspect
25
+ out += " OFFSET #{options[:offset]} " if options[:offset]
26
+ out += " LIMIT #{options[:limit]} " if options[:limit]
27
+ out
28
+ end
29
+
30
+ alias :inspect :to_s
31
+
32
+ def projections( projs )
33
+ projs.each{|p|
34
+ if( p.is_a? Arel::Nodes::Count )
35
+ options[:count] = true
36
+ end
37
+ }
38
+ self
39
+ end
40
+
41
+ def wheres( conditions )
42
+ conditions.each{|w|
43
+ w_expr = w.expr rescue w
44
+ if( w_expr.class != Arel::Nodes::SqlLiteral )
45
+ key = w_expr.left.name
46
+ val = w_expr.right
47
+ opt = w_expr.operator
48
+ apply_filter( key, opt, val )
49
+ else
50
+ parese_expression_string( w_expr.to_s )
51
+ end
52
+ }
53
+ self
54
+ end
55
+
56
+ TypeCast = {
57
+ :primary_key => lambda{|k,v| AppEngine::Datastore::Key.from_path( k, v.to_i ) },
58
+ :integer => lambda{|k,i| i.to_i },
59
+ :datetime => lambda{|k,t| t.is_a?(Time)? t : Time.parse(t.to_s) },
60
+ :date => lambda{|k,t| t.is_a?(Date)? t : Date.parse(t.to_s) },
61
+ :float => lambda{|k,f| f.to_f }
62
+ }
63
+ InScan = /'((\\.|[^'])*)'|(\d+)/
64
+ def apply_filter( key, opt, value )
65
+ key, opt = key.to_sym, opt.to_sym
66
+ column = @connection.columns(kind).find{|c| c.name == key.to_s }
67
+ opt = :in if value.is_a? Array
68
+ type_cast_proc = TypeCast[ column.primary ? :primary_key : column.type ]
69
+ if opt == :in or opt == :IN
70
+ value = value.scan(InScan).collect{|d| d.find{|i| i}} if value.is_a? String
71
+ value.collect!{|v| type_cast_proc.call(kind,v) } if type_cast_proc
72
+ options[:empty], value = true, [ "EMPTY" ] if value.empty?
73
+ else
74
+ value = type_cast_proc.call( kind, value ) if type_cast_proc
75
+ end
76
+ key = :__key__ if column.primary
77
+ q.filter( key, opt, value )
78
+ end
79
+
80
+
81
+ RExpr = Regexp.union( /(in)/i,
82
+ /\(\s*(('((\\.|[^'])*)'|\d+)(\s*,\s*('((\\.|[^'])*)'|\d+))*)\s*\)/,
83
+ /"((\\.|[^"])*)"/,
84
+ /'((\\.|[^'])*)'/,
85
+ /([\w\.]+)/,
86
+ /([^\w\s\.'"]+)/ )
87
+ Optr = { "=" => :== }
88
+ ExOptr = ["(",")"]
89
+ def parese_expression_string( query )
90
+ datas = query.scan( RExpr ).collect{|a| a.find{|i| i } }
91
+ datas.delete_if{|d| ExOptr.include?(d) }
92
+ while( datas.size >= 3 )
93
+ key = datas.shift.sub(/^[^.]*\./,'').to_sym
94
+ opt = datas.shift
95
+ val = datas.shift
96
+ concat_opt = datas.shift
97
+ apply_filter( key, Optr[opt] || opt.to_sym, val )
98
+ end
99
+ end
100
+
101
+ def orders( ords )
102
+ ords.each{|o|
103
+ if( o.is_a? String )
104
+ key, dir, notuse = o.split
105
+ else
106
+ key, dir = o.expr, o.direction
107
+ end
108
+ q.sort( key, dir )
109
+ }
110
+ self
111
+ end
112
+ end
113
+
114
+ def get_limit_and_offset( o )
115
+ options = {}
116
+ options[:limit] = o.limit.expr if o.limit
117
+ options[:offset] = o.offset.expr if o.offset
118
+ options
119
+ end
120
+
121
+ def visit_Arel_Nodes_SelectStatement o
122
+ c = o.cores.first
123
+ QString.new( @connection, c.froms.name, get_limit_and_offset(o) ).wheres( c.wheres ).orders(o.orders).projections( c.projections )
124
+ end
125
+
126
+ def visit_Arel_Nodes_InsertStatement o
127
+ e = AppEngine::Datastore::Entity.new(o.relation.name)
128
+ o.columns.each_with_index{|c,i| e[c.name] = o.values.left[i] }
129
+ e
130
+ end
131
+
132
+ def visit_Arel_Nodes_UpdateStatement o
133
+ QString.new( @connection, o.relation.name, :values => o.values.collect{|v| [ v.left.name, v.right ] } ).wheres( o.wheres )
134
+ end
135
+
136
+ def visit_Arel_Nodes_DeleteStatement o
137
+ QString.new( @connection, o.relation.name ).wheres( o.wheres )
138
+ end
139
+
140
+ end
141
+ end
142
+ end
143
+
144
+ Arel::Visitors::VISITORS['datastore'] = Arel::Visitors::Datastore
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe AppEngine::Datastore do
5
+ Datastore = AppEngine::Datastore
6
+
7
+ before :each do
8
+ AppEngine::Testing.install_test_datastore
9
+ end
10
+
11
+ it "should support get/put" do
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ class User < ActiveRecord::Base
4
+
5
+ validates_uniqueness_of :name
6
+
7
+ connection.create_table table_name, :force => true do |t|
8
+ t.string :name, :limit => 25
9
+ t.datetime :dob
10
+ t.integer :rating
11
+ t.float :score
12
+
13
+ t.timestamps
14
+ end
15
+ end
16
+
17
+ describe Person do
18
+
19
+ before :each do
20
+ AppEngine::Testing.install_test_datastore
21
+ end
22
+
23
+ it "time object" do
24
+ name = "guest"
25
+ dob = Time.now
26
+ user = User.create!( :name => "guest", :dob => dob )
27
+ user.dob.should == dob
28
+
29
+ user = User.first
30
+ user.dob.should == dob
31
+
32
+ user = User.find_by_dob( dob )
33
+ user.dob.should == dob
34
+ end
35
+
36
+ it "rating object" do
37
+ 5.times{|i|
38
+ User.create!( :name => "guest#{i}", :rating => i )
39
+ }
40
+ users = User.where( " rating >= 1 and rating <= 3 " )
41
+ users.all.size.should == 3
42
+ end
43
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Person do
4
+ before :each do
5
+ AppEngine::Testing.install_test_datastore
6
+ end
7
+
8
+ it " in operator" do
9
+ Person.create!( :name => "guest" )
10
+ people = Person.where( " created_at <= ? ", Time.now + 1 )
11
+ people.all.size.should_not == 0
12
+
13
+ people = Person.where( :name => [ "guest", "not_guest" ] )
14
+ people.all.size.should == 1
15
+
16
+ people = Person.where( " name in('guest','not_guest')" )
17
+ people.all.size.should == 1
18
+
19
+ people = Person.where( " name in( 'guest', 'not_guest' )" )
20
+ people.all.size.should == 1
21
+
22
+ people = Person.where( "( name in( 'guest', 'not_guest' ) )" )
23
+ people.all.size.should == 1
24
+
25
+ people = Person.where( "(name in( 'guest', 'not_guest' ))" )
26
+ people.all.size.should == 1
27
+ end
28
+
29
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe Person do
5
+ before :each do
6
+ AppEngine::Testing.install_test_datastore
7
+ end
8
+
9
+ it "init record" do
10
+ person_name = "guest"
11
+ person = Person.new( :name => person_name )
12
+ person.id.should == nil
13
+ person.name.should == person_name
14
+ end
15
+
16
+ it "save record" do
17
+ person_name = "guest"
18
+ person = Person.new( :name => person_name )
19
+ person.save.should == true
20
+ end
21
+
22
+ it "record have id" do
23
+ person_name = "guest"
24
+ person = Person.new( :name => person_name )
25
+ person.save.should == true
26
+ person.id.should_not == nil
27
+ end
28
+
29
+ it "retrive the record by id" do
30
+ person_name = "guest"
31
+ person = Person.new( :name => person_name )
32
+ person.save
33
+ find_person = Person.find( person.id )
34
+ find_person.name.should == person_name
35
+ end
36
+
37
+ it "retrive the record by name" do
38
+ person_name = "guest"
39
+ person = Person.new( :name => person_name )
40
+ person.save
41
+ find_person = Person.find_by_name( person_name )
42
+ find_person.name.should == person_name
43
+ end
44
+
45
+ it "retrive the record by name and description" do
46
+ person_name = "guest"
47
+ person_description = "description"
48
+ Person.create!( :name => person_name, :description => person_description )
49
+ find_person = Person.where( :name => person_name, :description => person_description ).first
50
+ find_person.name.should == person_name
51
+ find_person.description.should == person_description
52
+ end
53
+
54
+ it "update the record" do
55
+ person_name = "guest"
56
+ new_person_name = "new_guest"
57
+ person = Person.create!( :name => person_name )
58
+ person.name.should == person_name
59
+
60
+ person.update_attributes( :name => new_person_name ).should == true
61
+ person.name.should == new_person_name
62
+
63
+ person = Person.find_by_name( new_person_name )
64
+ person.name.should == new_person_name
65
+ end
66
+
67
+ it "destroy the record" do
68
+ person_name = "guest"
69
+ person = Person.create!( :name => person_name )
70
+ person.should_not == nil
71
+
72
+ person = Person.find_by_name( person_name )
73
+ person.should_not == nil
74
+
75
+ person.destroy
76
+
77
+ person = Person.find_by_name( person_name )
78
+ person.should == nil
79
+ end
80
+
81
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Person do
4
+ before :each do
5
+ AppEngine::Testing.install_test_datastore
6
+ end
7
+
8
+ it "create person on role" do
9
+ person = Person.create!( :name => "guest" )
10
+ role = Role.create!( :name => "guest" )
11
+ person.roles.push( role )
12
+
13
+ person.save.should == true
14
+
15
+ person = Person.find_by_name( "guest" )
16
+ person.roles.size.should == 1
17
+
18
+ role = Role.find_by_name( "guest" )
19
+ role.people.size.should == 1
20
+
21
+ person.roles.build( :name => "admin" )
22
+ person.save.should == true
23
+
24
+ person = Person.find_by_name( "guest" )
25
+ person.roles.size.should == 2
26
+
27
+ Role.all.size.should == 2
28
+ PersonRole.all.size.should == 2
29
+
30
+ person = Person.find_by_name( "guest" )
31
+ person.destroy
32
+
33
+ PersonRole.all.size.should == 0
34
+ Person.all.size.should == 0
35
+ Role.all.size.should == 2
36
+ end
37
+
38
+ end
@@ -0,0 +1,97 @@
1
+ require 'rubygems'
2
+
3
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
4
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
5
+ Bundler.require(:default)
6
+
7
+ require 'appengine-sdk'
8
+ require 'appengine-apis/testing'
9
+ AppEngine::Testing.setup
10
+
11
+ require 'appengine-apis/datastore'
12
+
13
+ require 'active_record'
14
+ require 'logger'
15
+
16
+
17
+ ActiveRecord::Base.logger = Logger.new( File.open( File.expand_path('../test.log', __FILE__), "w") )
18
+ db_config_path = File.expand_path('../database.yml', __FILE__)
19
+ index_config_path = File.expand_path('../indexs.yml', __FILE__)
20
+ ActiveRecord::Base.establish_connection( :adapter => 'datastore', :database => db_config_path, :index => index_config_path )
21
+ require 'table_schema'
22
+
23
+
24
+ class ProtoMatcher
25
+ def compare(hash, proto, prefix='')
26
+ hash.each do |key, value|
27
+ name = "#{prefix}#{key}"
28
+ case value
29
+ when Array
30
+ if value[0].kind_of? Hash
31
+ count = proto.send("#{key}_size")
32
+ compare_value("#{name}.size", value.size, count)
33
+ value.each_with_index do |item, index|
34
+ break if index == count
35
+ compare(item, proto.send(key, index), "#{name}[#{index}].")
36
+ end
37
+ else
38
+ actual = proto.send("#{key}s").to_a
39
+ compare_value(name, value, actual)
40
+ end
41
+ when Hash
42
+ compare(value, proto.send(key), "#{name}.")
43
+ else
44
+ compare_value(name, value, proto.send(key))
45
+ end
46
+ end
47
+ end
48
+
49
+ def compare_value(label, expected, actual)
50
+ if expected != actual
51
+ @failures << "%s differs. expected: %s actual: %s" %
52
+ [label, expected.inspect, actual.inspect]
53
+ end
54
+ end
55
+
56
+ def initialize(klass, expected)
57
+ @klass = klass
58
+ @expected = expected
59
+ end
60
+
61
+ def matches(bytes)
62
+ @failures = []
63
+ @proto = @klass.new
64
+ @proto.parse_from(bytes)
65
+ compare(@expected, @proto)
66
+ @failures.empty?
67
+ end
68
+
69
+ def ==(bytes)
70
+ Spec::Expectations.fail_with(failure_message) unless matches(bytes)
71
+ true
72
+ end
73
+
74
+ def failure_message
75
+ @failures.join("\n")
76
+ end
77
+ end
78
+
79
+ module ProtoMethods
80
+ def proto(klass, hash)
81
+ ProtoMatcher.new(klass, hash)
82
+ end
83
+ alias be_proto proto
84
+
85
+ def mock_delegate
86
+ delegate = mock("apiproxy")
87
+ delegate.instance_eval do
88
+ class << self
89
+ include AppEngine::ApiProxy::Delegate
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ RSpec.configure do |config|
96
+ config.include(ProtoMethods)
97
+ end
@@ -0,0 +1,40 @@
1
+ class Person < ActiveRecord::Base
2
+ has_many :person_roles, :dependent => :destroy
3
+ has_many :roles, :through => :person_roles
4
+
5
+ validates_uniqueness_of :name
6
+
7
+ connection.create_table table_name, :force => true do |t|
8
+ t.string :name
9
+ t.string :description
10
+ t.text :data
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+
16
+ class Role < ActiveRecord::Base
17
+ has_many :person_roles, :dependent => :destroy
18
+ has_many :people, :through => :person_roles
19
+
20
+ connection.create_table table_name, :force => true do |t|
21
+ t.string :name
22
+
23
+ t.timestamps
24
+ end
25
+ end
26
+
27
+ class PersonRole < ActiveRecord::Base
28
+ belongs_to :person
29
+ belongs_to :role
30
+
31
+
32
+ connection.create_table table_name, :force => true do |t|
33
+ t.integer :person_id
34
+ t.integer :role_id
35
+
36
+ t.timestamps
37
+ end
38
+
39
+ connection.add_index table_name, [ :person_id, :role_id ]
40
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-datastore-adapter
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Mohammed Siddick
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-06 00:00:00 +05:30
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: appengine-apis
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - "="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.22
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 3.0.3
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: arel
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.0.7
47
+ type: :runtime
48
+ version_requirements: *id003
49
+ description: |-
50
+ Just an ActiveRecord Adapter for the Appengine Datastore.
51
+ Create Rails3 application: rails new app_name -m http://siddick.github.com/datastore/rails3.rb
52
+ email:
53
+ - siddick@gmail.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files: []
59
+
60
+ files:
61
+ - .gitignore
62
+ - Gemfile
63
+ - Gemfile.lock
64
+ - README.textile
65
+ - Rakefile
66
+ - activerecord-datastore-adapter.gemspec
67
+ - examples/rails3.rb
68
+ - examples/rails3_local.rb
69
+ - lib/active_record/connection_adapters/datastore_adapter.rb
70
+ - lib/active_record/datastore_associations_patch.rb
71
+ - lib/arel/visitors/datastore.rb
72
+ - spec/create_table_spec.rb
73
+ - spec/datatypes_spec.rb
74
+ - spec/operators_spec.rb
75
+ - spec/query_spec.rb
76
+ - spec/relations_spec.rb
77
+ - spec/spec_helper.rb
78
+ - spec/table_schema.rb
79
+ has_rdoc: true
80
+ homepage: http://rubygems.org/gems/datastore
81
+ licenses: []
82
+
83
+ post_install_message:
84
+ rdoc_options: []
85
+
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "0"
100
+ requirements: []
101
+
102
+ rubyforge_project: activerecord-datastore-adapter
103
+ rubygems_version: 1.5.0
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: ActiveRecord Adapter for Appengine Datastore
107
+ test_files: []
108
+