swiss_db 0.7.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +44 -14
- data/lib/schema_tools/schema_builder.rb +122 -0
- data/lib/schema_tools/writer.rb +37 -0
- data/lib/swiss_db.rb +31 -17
- data/lib/swiss_db/cursor.rb +148 -95
- data/lib/swiss_db/data_store.rb +24 -44
- data/lib/swiss_db/swiss_db.rb +66 -0
- data/lib/swiss_db/swiss_model.rb +134 -92
- metadata +5 -4
- data/lib/swiss_db/data_store.java +0 -8
- data/lib/swiss_db/db.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0fbc5f3c7344ef55064c2696beba8f4da742aba
|
4
|
+
data.tar.gz: 6f70e3f8bbb7bc480d048a2732a95ea15d89c4c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3593083bc7e8fb6ac6a51bc98586d664d45839629c4694817ebcc2a3c158e3ea2b51c5a0a6c4f1846e0d947ba4ffcf67a07ae1dc8ab578bfbead3f93910ba369
|
7
|
+
data.tar.gz: a2be9798ef4500dc4e6ba2aeafeeceef33f4077a0f5e1eb7842d5f5b0f286179db532816f1dee2c6b102cefcc8357c0c2c68c0eaf9814367e9cd021fbe9de78e
|
data/README.md
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
-
|
1
|
+
If you'd like to support the continued development of SwissDB, please check out our Gratipay: https://gratipay.com/swissdb/
|
2
2
|
|
3
3
|
# SwissDb
|
4
4
|
|
5
|
-
RubyMotion Android ActiveRecord-like ORM for SQLite
|
5
|
+
This is SwissDb, a RubyMotion Android ActiveRecord-like ORM for SQLite.
|
6
|
+
|
7
|
+
SwissDb 1.0 is working and stable as of RubyMotion 4.11.
|
8
|
+
|
9
|
+
## Example
|
10
|
+
|
11
|
+
See: https://github.com/KCErb/swissdb_debug
|
6
12
|
|
7
13
|
## Installation
|
8
14
|
|
@@ -22,12 +28,28 @@ Or install it yourself as:
|
|
22
28
|
|
23
29
|
## Usage
|
24
30
|
|
31
|
+
Usage is the same as ActiveRecord and CoreDataQuery.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
Car.create(is_red: true, mileage: (rand * 100_000).floor)
|
35
|
+
car = Car.first
|
36
|
+
car.is_red = true
|
37
|
+
car.save
|
38
|
+
mp Car.all.to_a
|
39
|
+
car = Car.last
|
40
|
+
car.update_attribute('is_red', false)
|
41
|
+
|
42
|
+
```
|
43
|
+
|
44
|
+
|
25
45
|
# Schemas
|
26
46
|
|
27
|
-
Schemas
|
47
|
+
Schemas go in the project root under `schemas/`. You can stash multiple schema files here for your own records (`schema1.rb`, `schema2.rb`, etc.) but the schema your app actually uses must be named `schema.rb`.
|
48
|
+
|
49
|
+
We're attempting to support both Core Data Query and Active Record type specifications like `datetime` `integer32` and `integer`. In SQLite there are only a few types anyways so that `integer32` and `integer` get created the same.
|
28
50
|
|
29
51
|
```ruby
|
30
|
-
schema
|
52
|
+
schema version: 1 do
|
31
53
|
|
32
54
|
entity "Car" do
|
33
55
|
boolean :is_red
|
@@ -46,14 +68,18 @@ schema "0001" do
|
|
46
68
|
end
|
47
69
|
```
|
48
70
|
|
49
|
-
Schema
|
71
|
+
### Schema Version and Migrations
|
72
|
+
|
73
|
+
You must specify a version with your schema. This integer (which must start at 1) is stored internally to the SQLite database and is used to determine if migrations need to be run.
|
74
|
+
|
75
|
+
Migrating your database is not yet supported by SwissDB but is the next thing on the To-Do list.
|
50
76
|
|
51
77
|
# Models
|
52
78
|
|
53
79
|
Models are as such:
|
54
80
|
|
55
81
|
```ruby
|
56
|
-
class Model < SwissModel
|
82
|
+
class Model < SwissDB::SwissModel
|
57
83
|
|
58
84
|
set_class_name "Model" # there are currently no hacks to automatically get this. sorry.
|
59
85
|
set_primary_key "primary_key_name" # if not set, will default to "id"
|
@@ -61,9 +87,9 @@ class Model < SwissModel
|
|
61
87
|
end
|
62
88
|
```
|
63
89
|
|
64
|
-
#
|
90
|
+
# Setup SwissDB
|
65
91
|
|
66
|
-
|
92
|
+
Since SwissDB needs to use your app's context you need to help it get setup like so:
|
67
93
|
|
68
94
|
```ruby
|
69
95
|
class BluePotionApplication < PMApplication
|
@@ -71,7 +97,7 @@ class BluePotionApplication < PMApplication
|
|
71
97
|
home_screen HomeScreen
|
72
98
|
|
73
99
|
def on_create
|
74
|
-
|
100
|
+
SwissDB.setup(self)
|
75
101
|
end
|
76
102
|
end
|
77
103
|
```
|
@@ -82,10 +108,15 @@ end
|
|
82
108
|
Model.first.name
|
83
109
|
Model.all.last.name
|
84
110
|
Model.all.count
|
111
|
+
Model.find_by_<column>("some value") # dynamic finders
|
112
|
+
Model.create(hash_values) # returns created model
|
85
113
|
m = Model.first
|
86
|
-
m.name = "Sam"
|
87
|
-
m.save # will persist the data
|
114
|
+
m.name = "Sam" # changed values will persist in the model instance
|
115
|
+
m.save # will persist the data to the database
|
88
116
|
m.update_attribute("name", "chucky")
|
117
|
+
m = Model.new
|
118
|
+
m.thing = "stuff"
|
119
|
+
m.save # upsert, (insert if doesn't exist, and update if does)
|
89
120
|
```
|
90
121
|
|
91
122
|
|
@@ -101,10 +132,10 @@ As of 0.7.1 all returned objects are SwissModel instances. Model methods will no
|
|
101
132
|
|
102
133
|
* detect class names of models for tableize
|
103
134
|
|
104
|
-
KNOWN LIMITATION:
|
135
|
+
KNOWN LIMITATION: No DB migrations yet by doing the simple version bump that is supported by Android. To get around this simply delete your local database when you need to migrate. You can delete the app from the simulator/device (probably) or use this convenience command:
|
105
136
|
|
106
137
|
```ruby
|
107
|
-
DataStore.drop_db #=> true if the DB was dropped, false if not
|
138
|
+
SwissDB::DataStore.drop_db #=> true if the DB was dropped, false if not
|
108
139
|
```
|
109
140
|
|
110
141
|
## Contributors
|
@@ -124,4 +155,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jsilve
|
|
124
155
|
## License
|
125
156
|
|
126
157
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
127
|
-
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# load code for `tableize` here since this file is called outside of RubyMotion
|
2
|
+
require 'motion-support/inflector/methods'
|
3
|
+
require 'motion-support/inflector/inflections'
|
4
|
+
require 'motion-support/array'
|
5
|
+
require 'motion-support/string'
|
6
|
+
require 'motion-support/default_inflections'
|
7
|
+
|
8
|
+
module SchemaTools
|
9
|
+
class SchemaBuilder
|
10
|
+
|
11
|
+
def self.build_schema(app)
|
12
|
+
schema_filename = File.join(app.project_dir, 'schemas/schema.rb')
|
13
|
+
dsl = new
|
14
|
+
dsl.instance_eval(File.read(schema_filename))
|
15
|
+
return dsl.schema_hash, dsl.version
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :schema_hash, :version
|
19
|
+
|
20
|
+
def schema(opts={}, &block)
|
21
|
+
check_opts(opts)
|
22
|
+
@version = opts[:version]
|
23
|
+
raise 'schema version must be an integer greater than 0' unless version_ok?
|
24
|
+
@schema_hash = {}
|
25
|
+
block.call
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_opts(opts)
|
29
|
+
raise 'schema must supply a hash before the block' unless opts.is_a? Hash
|
30
|
+
raise 'schema must specify a version' unless opts[:version]
|
31
|
+
end
|
32
|
+
|
33
|
+
def version_ok?
|
34
|
+
version.is_a?(Integer) && version > 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def entity(class_name, opts={}, &block)
|
38
|
+
# set default opts and merge passed in opts
|
39
|
+
@opts = { id: true }.merge opts
|
40
|
+
init_table(class_name)
|
41
|
+
block.call
|
42
|
+
ensure_primary_key
|
43
|
+
end
|
44
|
+
|
45
|
+
def init_table(class_name)
|
46
|
+
@current_table = class_name.tableize
|
47
|
+
@schema_hash[@current_table] = {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def ensure_primary_key
|
51
|
+
# Check that the schema has one and only one primary key somewhere
|
52
|
+
table_schema = @schema_hash[@current_table]
|
53
|
+
valid_table = false
|
54
|
+
if @opts[:id]
|
55
|
+
table_schema['id'] = 'INTEGER PRIMARY KEY AUTOINCREMENT' unless table_schema.has_key? 'id'
|
56
|
+
else
|
57
|
+
primary_keys = table_schema.values.select{ |val| val.include? 'PRIMARY KEY' }
|
58
|
+
raise_primary_keys_error(table_schema.keys) unless primary_keys.length == 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_column(name, type)
|
63
|
+
if @opts[:id] && name == 'id'
|
64
|
+
raise_id_error unless type == 'INTEGER'
|
65
|
+
type << ' PRIMARY KEY AUTOINCREMENT'
|
66
|
+
end
|
67
|
+
@schema_hash[@current_table][name] = type
|
68
|
+
end
|
69
|
+
|
70
|
+
%w(boolean float double integer datetime).each do |type|
|
71
|
+
define_method(type) do |*args|
|
72
|
+
column_name = args.first
|
73
|
+
column_opts = args[1].is_a?(Hash) ? args[1] : {}
|
74
|
+
type = type.upcase
|
75
|
+
type = add_primary(type, column_name) if column_opts[:primary_key]
|
76
|
+
add_column column_name.to_s, type
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def string(column_name, column_opts={})
|
81
|
+
type = 'VARCHAR'
|
82
|
+
type = add_primary(type, column_name) if column_opts[:primary_key]
|
83
|
+
add_column column_name.to_s, type
|
84
|
+
end
|
85
|
+
|
86
|
+
def integer32(column_name, column_opts={})
|
87
|
+
type = 'INTEGER'
|
88
|
+
type = add_primary(type, column_name) if column_opts[:primary_key]
|
89
|
+
add_column column_name.to_s, type
|
90
|
+
end
|
91
|
+
|
92
|
+
def raise_id_error
|
93
|
+
error_message = %Q(
|
94
|
+
Your schema defines a non integer `id` column for #{@current_table}.
|
95
|
+
If you do not wish to use the default id column, then you should
|
96
|
+
specify the `id: false` option.
|
97
|
+
)
|
98
|
+
raise error_message
|
99
|
+
end
|
100
|
+
|
101
|
+
def raise_primary_keys_error(primary_keys)
|
102
|
+
error_message = %Q(
|
103
|
+
|
104
|
+
Your schema specified `id: false` for #{@current_table} and therefore must
|
105
|
+
specify one primary key for this table. Instead you specified #{primary_keys.length}.
|
106
|
+
|
107
|
+
)
|
108
|
+
error_message += "These are the keys you gave: #{primary_keys}\n" if primary_keys.length > 1
|
109
|
+
raise error_message
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_primary(type, name)
|
113
|
+
if @opts[:id]
|
114
|
+
puts "SWISS DB WARNING: ignoring primary_key: #{name} because `id` is the default primary key"
|
115
|
+
else
|
116
|
+
type << ' PRIMARY KEY'
|
117
|
+
type << ' AUTOINCREMENT' if type == 'INTEGER PRIMARY KEY'
|
118
|
+
end
|
119
|
+
type
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module SchemaTools
|
2
|
+
module Writer
|
3
|
+
class << self
|
4
|
+
attr_accessor :schema, :app
|
5
|
+
|
6
|
+
def create_schema_sql(schema, app)
|
7
|
+
@schema = schema
|
8
|
+
@app = app
|
9
|
+
write_schema_file
|
10
|
+
end
|
11
|
+
|
12
|
+
def write_version_file(version, app)
|
13
|
+
# TODO: Version checking, android expects monotonically increasing version ints
|
14
|
+
filename = File.join(app.resources_dirs.first, 'raw', 'version')
|
15
|
+
write_raw_resource_file(filename, version)
|
16
|
+
end
|
17
|
+
|
18
|
+
def write_schema_file
|
19
|
+
sql = ''
|
20
|
+
|
21
|
+
schema.each do |table_name, fields|
|
22
|
+
fields_string = fields.map { |k, v| " #{k} #{v}" }.join(",\n")
|
23
|
+
sql += "CREATE TABLE #{table_name}(\n#{fields_string}\n);\n\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
filename = File.join(app.resources_dirs.first, 'raw', 'schema.sql')
|
27
|
+
write_raw_resource_file(filename, sql)
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_raw_resource_file(filename, content)
|
31
|
+
# create raw directory if it doesn't exist
|
32
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
33
|
+
File.write(filename, content)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/swiss_db.rb
CHANGED
@@ -1,27 +1,41 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
# SwissDB by jsilverMDX
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
def setup_schema(app)
|
5
|
+
require 'schema_tools/schema_builder'
|
6
|
+
require 'schema_tools/writer'
|
7
|
+
schema, version = SchemaTools::SchemaBuilder.build_schema(app)
|
8
|
+
SchemaTools::Writer.create_schema_sql(schema, app)
|
9
|
+
SchemaTools::Writer.write_version_file(version, app)
|
10
|
+
# TODO
|
11
|
+
# migrations = SwissDB::MigrationsBuilder.build_migrations
|
12
|
+
# SwissDB::SQLWriter.create_migration_sql(migrations)
|
13
|
+
end
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
def building_app?(args)
|
16
|
+
# Don't write the schema to sql unless we're building the app
|
17
|
+
intersection = (args & %w(device archive build release emulator newclear))
|
18
|
+
!intersection.empty? || args == ""
|
19
|
+
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
21
|
+
def add_app_files(app)
|
22
|
+
lib_dir_path = File.dirname(__FILE__)
|
23
|
+
insert_point = app.files.find_index { |file| file =~ /^(?:\.\/)?app\// } || 0
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
# Specify which folders to put into the app
|
26
|
+
swiss_db_files = Dir.glob(File.join(lib_dir_path, "/swiss_db/**/**.rb"))
|
27
|
+
motion_files = Dir.glob(File.join(lib_dir_path, "/motion-support/**/*.rb"))
|
23
28
|
|
24
|
-
|
29
|
+
(swiss_db_files + motion_files).each do |file|
|
30
|
+
app.files.insert(insert_point, file)
|
31
|
+
end
|
32
|
+
end
|
25
33
|
|
34
|
+
if defined?(Motion) && defined?(Motion::Project::Config)
|
35
|
+
Motion::Project::App.setup do |app|
|
36
|
+
setup_schema(app) if building_app?(ARGV)
|
37
|
+
add_app_files(app)
|
26
38
|
end
|
39
|
+
else
|
40
|
+
puts 'ERROR: SwissDB must be included in a RubyMotion App' if ARGV[0] != "gem:install"
|
27
41
|
end
|
data/lib/swiss_db/cursor.rb
CHANGED
@@ -2,125 +2,178 @@
|
|
2
2
|
# Helps move around a result set
|
3
3
|
# Convenience methods over the standard cursor
|
4
4
|
# Used by Swiss DataStore
|
5
|
+
module SwissDB
|
6
|
+
class Cursor
|
5
7
|
|
6
|
-
|
8
|
+
FIELD_TYPE_BLOB = 4
|
9
|
+
FIELD_TYPE_FLOAT = 2
|
10
|
+
FIELD_TYPE_INTEGER = 1
|
11
|
+
FIELD_TYPE_NULL = 0
|
12
|
+
FIELD_TYPE_STRING = 3
|
7
13
|
|
8
|
-
|
9
|
-
# h.each do |k,v|
|
10
|
-
# instance_variable_set("@#{k}", v)
|
11
|
-
# end
|
12
|
-
# end
|
14
|
+
attr_accessor :cursor, :model
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
def initialize(cursor, model)
|
17
|
+
@cursor = cursor
|
18
|
+
@model = model
|
19
|
+
@values = {}
|
20
|
+
end
|
18
21
|
|
19
|
-
|
22
|
+
def model
|
23
|
+
@model
|
24
|
+
end
|
20
25
|
|
21
|
-
|
26
|
+
def first
|
27
|
+
begin
|
28
|
+
return nil if count == 0
|
29
|
+
cursor.moveToFirst ? self : nil
|
30
|
+
swiss_model = model.new(to_hash)
|
31
|
+
ensure
|
32
|
+
cursor.close
|
33
|
+
end
|
34
|
+
swiss_model
|
35
|
+
end
|
22
36
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
37
|
+
def last
|
38
|
+
begin
|
39
|
+
return nil if count == 0
|
40
|
+
cursor.moveToLast ? self : nil
|
41
|
+
swiss_model = model.new(to_hash)
|
42
|
+
ensure
|
43
|
+
cursor.close
|
44
|
+
end
|
45
|
+
swiss_model
|
46
|
+
end
|
28
47
|
|
29
|
-
|
48
|
+
def current
|
49
|
+
model.new(to_hash)
|
50
|
+
end
|
30
51
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
52
|
+
def [](pos)
|
53
|
+
begin
|
54
|
+
return nil if count == 0
|
55
|
+
cursor.moveToPosition(pos) ? self : nil
|
56
|
+
swiss_model = model.new(to_hash)
|
57
|
+
ensure
|
58
|
+
cursor.close
|
59
|
+
end
|
60
|
+
swiss_model
|
61
|
+
end
|
36
62
|
|
37
|
-
|
38
|
-
|
39
|
-
|
63
|
+
def to_hash
|
64
|
+
hash_obj = {}
|
65
|
+
column_names.each do |k|
|
66
|
+
hash_obj[k] = self.send(k)
|
67
|
+
end
|
68
|
+
hash_obj
|
69
|
+
end
|
40
70
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
71
|
+
def to_a
|
72
|
+
begin
|
73
|
+
return nil if count == 0
|
74
|
+
arr = []
|
75
|
+
(0...count).each do |i|
|
76
|
+
# puts i
|
77
|
+
cursor.moveToPosition(i)
|
78
|
+
arr << model.new(to_hash)
|
79
|
+
end
|
80
|
+
ensure
|
81
|
+
cursor.close
|
82
|
+
end
|
83
|
+
arr
|
84
|
+
end
|
46
85
|
|
47
|
-
|
48
|
-
return nil if count == 0
|
49
|
-
cursor.moveToLast ? self : nil
|
50
|
-
model.new(to_hash, cursor)
|
51
|
-
end
|
86
|
+
# todo: take out setter code. it's not used anymore. leave the getter code. it is used. (see #to_hash)
|
52
87
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
88
|
+
def method_missing(method_name, *args)
|
89
|
+
# puts "cursor method missing #{method_name}"
|
90
|
+
if valid_getter?(method_name)
|
91
|
+
get_method(method_name)
|
92
|
+
else
|
93
|
+
super
|
94
|
+
end
|
95
|
+
end
|
58
96
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
97
|
+
def valid_getter?(method_name)
|
98
|
+
column_names.include? method_name
|
99
|
+
end
|
100
|
+
|
101
|
+
def is_setter?(method_name)
|
102
|
+
method_name[-1] == '='
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_method(method_name)
|
106
|
+
index = cursor.getColumnIndex(method_name)
|
107
|
+
type = cursor.getType(index)
|
108
|
+
# puts "getting field #{method_name} at index #{index} of type #{type}"
|
109
|
+
|
110
|
+
if type == FIELD_TYPE_STRING #also boolean
|
111
|
+
str = cursor.getString(index).to_s
|
112
|
+
|
113
|
+
if str =~ /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}/
|
114
|
+
formatter = Java::Text::SimpleDateFormat.new('yyyy-MM-dd hh:mm:ss.SSS')
|
115
|
+
str = formatter.parse(str)
|
116
|
+
end
|
117
|
+
|
118
|
+
str = true if str == "true"
|
119
|
+
str = false if str == "false"
|
120
|
+
str
|
121
|
+
elsif type == FIELD_TYPE_INTEGER
|
122
|
+
cursor.getInt(index).to_i
|
123
|
+
elsif type == FIELD_TYPE_NULL
|
124
|
+
nil #??
|
125
|
+
elsif type == FIELD_TYPE_FLOAT
|
126
|
+
cursor.getFloat(index).to_f
|
127
|
+
elsif type == FIELD_TYPE_BLOB
|
128
|
+
cursor.getBlob(index)
|
63
129
|
end
|
64
|
-
|
65
|
-
end
|
130
|
+
end
|
66
131
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
cursor.
|
73
|
-
arr << model.new(to_hash, cursor)
|
132
|
+
def count
|
133
|
+
cursor.getCount
|
134
|
+
end
|
135
|
+
|
136
|
+
def column_names
|
137
|
+
cursor.getColumnNames.map(&:to_sym)
|
74
138
|
end
|
75
|
-
arr
|
76
|
-
end
|
77
139
|
|
78
|
-
|
140
|
+
def map(&block)
|
141
|
+
return [] if count == 0
|
142
|
+
arr = []
|
143
|
+
(0...count).each do |i|
|
144
|
+
# puts i
|
145
|
+
cursor.moveToPosition(i)
|
146
|
+
arr << yield(model.new(to_hash))
|
147
|
+
end
|
79
148
|
|
80
|
-
|
81
|
-
method_name = methId.id2name
|
82
|
-
# puts "cursor method missing #{method_name}"
|
83
|
-
if valid_getter?(method_name)
|
84
|
-
get_method(method_name)
|
85
|
-
else
|
86
|
-
super
|
149
|
+
arr
|
87
150
|
end
|
88
|
-
end
|
89
151
|
|
90
|
-
|
91
|
-
|
92
|
-
|
152
|
+
def each(&block)
|
153
|
+
return [] if count == 0
|
154
|
+
arr = []
|
155
|
+
(0...count).each do |i|
|
156
|
+
# puts i
|
157
|
+
cursor.moveToPosition(i)
|
158
|
+
m = model.new(to_hash)
|
159
|
+
yield(m)
|
160
|
+
arr << m
|
161
|
+
end
|
93
162
|
|
94
|
-
|
95
|
-
|
96
|
-
end
|
163
|
+
arr
|
164
|
+
end
|
97
165
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
# puts "getting field #{method_name} at index #{index} of type #{type}"
|
102
|
-
|
103
|
-
if type == FIELD_TYPE_STRING #also boolean
|
104
|
-
str = cursor.getString(index).to_s
|
105
|
-
str = true if str == "true"
|
106
|
-
str = false if str == "false"
|
107
|
-
str
|
108
|
-
elsif type == FIELD_TYPE_INTEGER
|
109
|
-
cursor.getInt(index).to_i
|
110
|
-
elsif type == FIELD_TYPE_NULL
|
111
|
-
nil #??
|
112
|
-
elsif type == FIELD_TYPE_FLOAT
|
113
|
-
cursor.getFloat(index).to_f
|
114
|
-
elsif type == FIELD_TYPE_BLOB
|
115
|
-
cursor.getBlob(index)
|
166
|
+
# those methods allow the use of PMCursorAdapter with SwissDB
|
167
|
+
def moveToPosition(i)
|
168
|
+
cursor.moveToPosition(i)
|
116
169
|
end
|
117
|
-
end
|
118
170
|
|
119
|
-
|
120
|
-
|
121
|
-
|
171
|
+
def moveToLast
|
172
|
+
cursor.moveToLast
|
173
|
+
end
|
122
174
|
|
123
|
-
|
124
|
-
|
175
|
+
def moveToFirst
|
176
|
+
cursor.moveToFirst
|
177
|
+
end
|
125
178
|
end
|
126
179
|
end
|
data/lib/swiss_db/data_store.rb
CHANGED
@@ -1,50 +1,29 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
# main connection point
|
2
|
+
# creates and upgrades our database for us
|
3
|
+
# and provides low level SQL features
|
4
|
+
module SwissDB
|
5
5
|
class DataStore < Android::Database::SQLite::SQLiteOpenHelper
|
6
6
|
|
7
|
-
DATABASE_NAME = "swissdb"
|
8
|
-
DATABASE_VERSION = 1
|
9
7
|
ContentValues = Android::Content::ContentValues
|
10
8
|
|
11
|
-
def self.current_schema=(schema)
|
12
|
-
@@current_schema = schema
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.context=(context)
|
16
|
-
@@context = context
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.context
|
20
|
-
@@context
|
21
|
-
end
|
22
|
-
|
23
9
|
def writable_db
|
24
10
|
getWritableDatabase
|
25
11
|
end
|
26
12
|
|
27
13
|
def self.drop_db
|
28
|
-
|
14
|
+
SwissDB.context.deleteDatabase(SwissDB.db_name)
|
29
15
|
end
|
30
16
|
|
31
17
|
def onUpgrade(db, oldVersion, newVersion)
|
32
|
-
#
|
33
|
-
|
34
|
-
|
18
|
+
# TODO when migrations are implemented
|
19
|
+
mp "Calling onUpgrade"
|
20
|
+
mp "old version: #{oldVersion}"
|
21
|
+
mp "new version: #{newVersion}"
|
35
22
|
end
|
36
23
|
|
37
24
|
#create
|
38
25
|
def onCreate(db)
|
39
|
-
|
40
|
-
# THIS RELIES ON SCHEMA CODE TO SUCCEED
|
41
|
-
# NOTE: I don't know a better way of passing the schema here
|
42
|
-
# If you do just change it. For now this works.
|
43
|
-
# Thanks.
|
44
|
-
@@current_schema.each do |k, v|
|
45
|
-
create_table db, k, v
|
46
|
-
end
|
47
|
-
# database.execSQL("CREATE TABLE credentials(username TEXT, password TEXT)")
|
26
|
+
SwissDB.create_tables_from_schema(db)
|
48
27
|
end
|
49
28
|
|
50
29
|
#insert
|
@@ -52,7 +31,7 @@
|
|
52
31
|
# puts "inserting data in #{table}"
|
53
32
|
values = ContentValues.new(hash_values.count)
|
54
33
|
hash_values.each do |k, v|
|
55
|
-
values.put(k, v)
|
34
|
+
values.put(k, coerces(v))
|
56
35
|
end
|
57
36
|
result = db.insert(table, nil, values)
|
58
37
|
result
|
@@ -68,7 +47,7 @@
|
|
68
47
|
def select(db=writable_db, table, values, model)
|
69
48
|
puts "selecting data from #{table}"
|
70
49
|
value_str = values.map do |k, v|
|
71
|
-
"#{k} = '#{v}'"
|
50
|
+
"#{k} = '#{coerces(v)}'"
|
72
51
|
end.join(" AND ")
|
73
52
|
sql = "select * from '#{table}' where #{value_str}"
|
74
53
|
puts sql
|
@@ -80,10 +59,10 @@
|
|
80
59
|
|
81
60
|
def update(db=writable_db, table, values, where_values)
|
82
61
|
value_str = values.map do |k, v|
|
83
|
-
"'#{k}' = '#{v}'"
|
62
|
+
"'#{k}' = '#{coerces(v)}'"
|
84
63
|
end.join(",")
|
85
64
|
where_str = where_values.map do |k, v|
|
86
|
-
"#{k} = '#{v}'"
|
65
|
+
"#{k} = '#{coerces(v)}'"
|
87
66
|
end.join(",")
|
88
67
|
sql = "update '#{table}' set #{value_str} where #{where_str}"
|
89
68
|
puts sql
|
@@ -97,13 +76,14 @@
|
|
97
76
|
db.delete(table, nil, nil)
|
98
77
|
end
|
99
78
|
|
100
|
-
#
|
101
|
-
def
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
79
|
+
# transform values
|
80
|
+
def coerces(v)
|
81
|
+
if v.is_a?(Time)
|
82
|
+
formatter = Java::Text::SimpleDateFormat.new('yyyy-MM-dd hh:mm:ss.SSS')
|
83
|
+
formatter.format(v).to_s
|
84
|
+
else
|
85
|
+
v.to_s
|
86
|
+
end
|
106
87
|
end
|
107
|
-
|
108
|
-
|
109
|
-
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module SwissDB
|
2
|
+
class << self
|
3
|
+
|
4
|
+
attr_accessor :store, :context, :resources, :version
|
5
|
+
|
6
|
+
def db_name
|
7
|
+
'swissdb'
|
8
|
+
end
|
9
|
+
|
10
|
+
def setup(context)
|
11
|
+
@context = context
|
12
|
+
@resources = context.getResources
|
13
|
+
get_version_from_raw
|
14
|
+
@store = DataStore.new(context, db_name, nil, version)
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_version_from_raw
|
18
|
+
message = 'Error reading schema version'
|
19
|
+
read_from_raw_resource('version', message) do |reader|
|
20
|
+
@version = reader.readLine.to_i
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_tables_from_schema(db)
|
25
|
+
message = 'Error reading schema SQL'
|
26
|
+
read_from_raw_resource('schema', message) do |reader|
|
27
|
+
execute_sql_script(db, reader)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_from_raw_resource(resource_name, error_message, &block)
|
32
|
+
resource_id = find_resource(resource_name, 'raw')
|
33
|
+
stream = resources.openRawResource(resource_id)
|
34
|
+
is_reader = Java::IO::InputStreamReader.new(stream)
|
35
|
+
reader = Java::IO::BufferedReader.new(is_reader)
|
36
|
+
begin
|
37
|
+
block.call(reader)
|
38
|
+
rescue
|
39
|
+
raise error_message
|
40
|
+
ensure
|
41
|
+
[stream, is_reader, reader].each(&:close)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def find_resource(name, type)
|
48
|
+
package_name = PMApplication.current_application.package_name
|
49
|
+
resources.getIdentifier(name, type, package_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def execute_sql_script(db, reader)
|
53
|
+
# is there a better way?
|
54
|
+
sql = ''
|
55
|
+
line = ''
|
56
|
+
while line = reader.readLine
|
57
|
+
sql << line
|
58
|
+
if line[-1] == ';'
|
59
|
+
db.execSQL(sql)
|
60
|
+
sql = ''
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
data/lib/swiss_db/swiss_model.rb
CHANGED
@@ -1,122 +1,164 @@
|
|
1
1
|
# Swiss Model
|
2
2
|
# An ActiveRecord like Model for RubyMotion Android
|
3
|
+
module SwissDB
|
4
|
+
class SwissModel
|
3
5
|
|
4
|
-
|
6
|
+
# meh? .. won't work for now in java... created classes become java packages
|
7
|
+
# name will become the namespace of the package...
|
8
|
+
# def self.inherited(subclass)
|
9
|
+
# puts "New subclass: #{subclass.class.name.split('.').last}"
|
10
|
+
# end
|
5
11
|
|
6
|
-
|
7
|
-
# name will become the namespace of the package...
|
8
|
-
# def self.inherited(subclass)
|
9
|
-
# puts "New subclass: #{subclass.class.name.split('.').last}"
|
10
|
-
# end
|
12
|
+
attr_accessor :values
|
11
13
|
|
12
|
-
|
14
|
+
def initialize(h={})
|
15
|
+
h.each do |k,v|
|
16
|
+
instance_variable_set("@#{k}", v)
|
17
|
+
end
|
18
|
+
@new_record = (h == {}) # any loaded record should have atleast SOME data
|
19
|
+
@values = {}
|
20
|
+
end
|
13
21
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
22
|
+
def self.method_missing(methId, *args)
|
23
|
+
str = methId.id2name
|
24
|
+
if str.include?("find_by")
|
25
|
+
where(str.split("find_by_")[1] => args[0]).first
|
26
|
+
end
|
18
27
|
end
|
19
|
-
@values = {}
|
20
|
-
end
|
21
28
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
29
|
+
def method_missing(methId, *args)
|
30
|
+
str = methId.id2name
|
31
|
+
|
32
|
+
if str[-1] == '=' # setter
|
33
|
+
instance_variable_set("@#{str.chop}", args[0])
|
34
|
+
@values[str.chop] = args[0]
|
35
|
+
return args[0]
|
36
|
+
end
|
37
|
+
|
38
|
+
if instance_variable_get("@#{str}")
|
39
|
+
return instance_variable_get("@#{str}")
|
40
|
+
end
|
28
41
|
end
|
29
|
-
end
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
43
|
+
def new_record?
|
44
|
+
@new_record
|
45
|
+
end
|
35
46
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
47
|
+
def save
|
48
|
+
unless new_record?
|
49
|
+
store.update(table_name,
|
50
|
+
@values,
|
51
|
+
{primary_key => primary_key_value})
|
52
|
+
else
|
53
|
+
store.insert(table_name,
|
54
|
+
@values)
|
55
|
+
end
|
40
56
|
|
41
|
-
|
42
|
-
hash.each do |k, v|
|
43
|
-
update_attribute(k, v)
|
57
|
+
@values = {}
|
44
58
|
end
|
45
|
-
end
|
46
59
|
|
60
|
+
def update_attribute(key, value)
|
61
|
+
store.update(table_name,
|
62
|
+
{key => value},
|
63
|
+
{primary_key => primary_key_value})
|
64
|
+
end
|
47
65
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
@store
|
52
|
-
end
|
66
|
+
def update_attributes(hash)
|
67
|
+
hash.each { |k, v| update_attribute(k, v) }
|
68
|
+
end
|
53
69
|
|
54
|
-
|
55
|
-
|
56
|
-
|
70
|
+
def primary_key
|
71
|
+
self.class.primary_key
|
72
|
+
end
|
57
73
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
74
|
+
def primary_key_value
|
75
|
+
self.send(primary_key.to_sym)
|
76
|
+
end
|
62
77
|
|
63
|
-
|
64
|
-
|
65
|
-
|
78
|
+
def table_name
|
79
|
+
self.class.table_name
|
80
|
+
end
|
66
81
|
|
67
|
-
|
68
|
-
|
69
|
-
|
82
|
+
def store
|
83
|
+
self.class.store
|
84
|
+
end
|
70
85
|
|
71
|
-
|
72
|
-
|
73
|
-
|
86
|
+
# def destroy
|
87
|
+
# # destroy this row
|
88
|
+
# end
|
74
89
|
|
75
|
-
|
76
|
-
|
77
|
-
|
90
|
+
# -------------
|
91
|
+
# CLASS METHODS
|
92
|
+
# -------------
|
93
|
+
class << self
|
94
|
+
def store
|
95
|
+
SwissDB.store
|
96
|
+
end
|
78
97
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
cursor
|
83
|
-
end
|
98
|
+
def class_name
|
99
|
+
@class_name
|
100
|
+
end
|
84
101
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end
|
102
|
+
def set_class_name(class_name) # hack, class.name not functioning in RM Android...
|
103
|
+
@class_name = class_name
|
104
|
+
set_table_name(class_name.tableize)
|
105
|
+
end
|
90
106
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
cursor
|
95
|
-
end
|
107
|
+
def set_table_name(table_name)
|
108
|
+
@table_name = table_name
|
109
|
+
end
|
96
110
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
cursor
|
101
|
-
end
|
111
|
+
def table_name
|
112
|
+
@table_name
|
113
|
+
end
|
102
114
|
|
103
|
-
|
104
|
-
|
105
|
-
result = store.insert(@table_name, obj)
|
106
|
-
if result == -1
|
107
|
-
puts "An error occured inserting values into #{@table_name}"
|
108
|
-
else
|
109
|
-
return result
|
115
|
+
def set_primary_key(primary_key)
|
116
|
+
@primary_key = primary_key
|
110
117
|
end
|
111
|
-
end
|
112
118
|
|
113
|
-
|
114
|
-
|
115
|
-
|
119
|
+
def primary_key
|
120
|
+
@primary_key.nil? ? "id" : @primary_key
|
121
|
+
end
|
116
122
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
123
|
+
def all
|
124
|
+
# select_all
|
125
|
+
cursor = store.select_all(@table_name, self)
|
126
|
+
cursor
|
127
|
+
end
|
128
|
+
|
129
|
+
def where(values)
|
130
|
+
# select <table> where <field> = <value>
|
131
|
+
cursor = store.select(@table_name, values, self)
|
132
|
+
cursor
|
133
|
+
end
|
134
|
+
|
135
|
+
def first
|
136
|
+
# select all and get first
|
137
|
+
model = all.first
|
138
|
+
model
|
139
|
+
end
|
140
|
+
|
141
|
+
def last
|
142
|
+
# select all and get last
|
143
|
+
model = all.last
|
144
|
+
model
|
145
|
+
end
|
121
146
|
|
122
|
-
|
147
|
+
def create(obj)
|
148
|
+
# create a row
|
149
|
+
result = store.insert(@table_name, obj)
|
150
|
+
if result == -1
|
151
|
+
puts "An error occured inserting values into #{@table_name}"
|
152
|
+
else
|
153
|
+
return self.where(primary_key => result.intValue).first
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def destroy_all!
|
158
|
+
# destroy all of this kind (empty table)
|
159
|
+
store.destroy_all(@table_name)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: swiss_db
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Silverman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -51,11 +51,12 @@ files:
|
|
51
51
|
- lib/motion-support/inflector/inflections.rb
|
52
52
|
- lib/motion-support/inflector/methods.rb
|
53
53
|
- lib/motion-support/string.rb
|
54
|
+
- lib/schema_tools/schema_builder.rb
|
55
|
+
- lib/schema_tools/writer.rb
|
54
56
|
- lib/swiss_db.rb
|
55
57
|
- lib/swiss_db/cursor.rb
|
56
|
-
- lib/swiss_db/data_store.java
|
57
58
|
- lib/swiss_db/data_store.rb
|
58
|
-
- lib/swiss_db/
|
59
|
+
- lib/swiss_db/swiss_db.rb
|
59
60
|
- lib/swiss_db/swiss_model.rb
|
60
61
|
homepage: http://github.com/jsilverMDX
|
61
62
|
licenses:
|
data/lib/swiss_db/db.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
# "Swiss", RubyMotion Android SQLite by VirtualQ
|
2
|
-
|
3
|
-
|
4
|
-
# schema loader stuff
|
5
|
-
# i know it's rough but it works
|
6
|
-
|
7
|
-
class Object
|
8
|
-
|
9
|
-
attr_accessor :current_schema
|
10
|
-
|
11
|
-
# convenience methods
|
12
|
-
|
13
|
-
def log(tag, str)
|
14
|
-
Android::Util::Log.d(tag, str)
|
15
|
-
end
|
16
|
-
|
17
|
-
def puts(str)
|
18
|
-
log "general", str
|
19
|
-
end
|
20
|
-
|
21
|
-
def current_schema
|
22
|
-
@current_schema
|
23
|
-
end
|
24
|
-
|
25
|
-
def schema(schema_name, &block)
|
26
|
-
puts "running schema for #{schema_name}"
|
27
|
-
@current_schema = {}
|
28
|
-
@database_name = schema_name
|
29
|
-
block.call
|
30
|
-
puts @current_schema.inspect
|
31
|
-
end
|
32
|
-
|
33
|
-
def entity(class_name, &block)
|
34
|
-
table_name = class_name.tableize
|
35
|
-
puts "adding entity #{table_name} to schema"
|
36
|
-
@table_name = table_name
|
37
|
-
@current_schema[@table_name] = {}
|
38
|
-
block.call
|
39
|
-
$current_schema = @current_schema
|
40
|
-
DataStore.current_schema = @current_schema
|
41
|
-
end
|
42
|
-
|
43
|
-
def add_column(name, type)
|
44
|
-
@current_schema[@table_name][name] = type
|
45
|
-
end
|
46
|
-
|
47
|
-
%w(boolean float double integer datetime).each do |type|
|
48
|
-
define_method(type) do |column_name|
|
49
|
-
add_column column_name.to_s, type.upcase
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def string(column_name)
|
54
|
-
add_column column_name.to_s, "VARCHAR"
|
55
|
-
end
|
56
|
-
|
57
|
-
def integer32(column_name)
|
58
|
-
add_column column_name.to_s, "INTEGER"
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|