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 +8 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +53 -0
- data/README.textile +38 -0
- data/Rakefile +9 -0
- data/activerecord-datastore-adapter.gemspec +25 -0
- data/examples/rails3.rb +71 -0
- data/examples/rails3_local.rb +81 -0
- data/lib/active_record/connection_adapters/datastore_adapter.rb +266 -0
- data/lib/active_record/datastore_associations_patch.rb +65 -0
- data/lib/arel/visitors/datastore.rb +144 -0
- data/spec/create_table_spec.rb +15 -0
- data/spec/datatypes_spec.rb +43 -0
- data/spec/operators_spec.rb +29 -0
- data/spec/query_spec.rb +81 -0
- data/spec/relations_spec.rb +38 -0
- data/spec/spec_helper.rb +97 -0
- data/spec/table_schema.rb +40 -0
- metadata +108 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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,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
|
data/examples/rails3.rb
ADDED
@@ -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,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
|
data/spec/query_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
|