activerecord-datastore-adapter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+