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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01e1d0c198065533e68a2417d669ce374b79ab3a
4
- data.tar.gz: 42a742da3ffcd437106c950b4ead8d38cc0b1d1e
3
+ metadata.gz: a0fbc5f3c7344ef55064c2696beba8f4da742aba
4
+ data.tar.gz: 6f70e3f8bbb7bc480d048a2732a95ea15d89c4c3
5
5
  SHA512:
6
- metadata.gz: e8bfce38d280954975035fcf532ff8092d6bfe4b0438723aa00affdf251064bc607bb3c9e01e0cda5a590e8d98ad18344ab4ac3e7bdc441f7852ba3b85351c1f
7
- data.tar.gz: 59edfad052f96f049c52534e7f5d2ccdee6ef87000aa6b8d5e0d65886f1596152eafb17a68089225565f1cf7ffdd45debd14405922c90434996fe08fe6fb79af
6
+ metadata.gz: 3593083bc7e8fb6ac6a51bc98586d664d45839629c4694817ebcc2a3c158e3ea2b51c5a0a6c4f1846e0d947ba4ffcf67a07ae1dc8ab578bfbead3f93910ba369
7
+ data.tar.gz: a2be9798ef4500dc4e6ba2aeafeeceef33f4077a0f5e1eb7842d5f5b0f286179db532816f1dee2c6b102cefcc8357c0c2c68c0eaf9814367e9cd021fbe9de78e
data/README.md CHANGED
@@ -1,8 +1,14 @@
1
- [![endorse](https://api.coderwall.com/jsilverMDX/endorsecount.png)](https://coderwall.com/jsilverMDX)
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 are the exact same from CoreDataQuery and go in the same place. (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 "0001" do
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 name (the "0001") does nothing.
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
- # Set the context
90
+ # Setup SwissDB
65
91
 
66
- Set the context in your bluepotion_application.rb.
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
- DataStore.context = self
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: This ORM compiles in the database name and the database version as a constant. Unfortunately I don't know of a way around this yet. This means no DB migrations yet by doing the simple version bump that is supported by Android. If we get a way to configure these from outside the gem, it will open up possibilities such as multiple schemas and migrations. 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 my convenience command:
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
@@ -1,27 +1,41 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # SwissDB by jsilverMDX
3
3
 
4
- if defined?(Motion) && defined?(Motion::Project::Config)
5
- lib_dir_path = File.dirname(File.expand_path(__FILE__))
6
- Motion::Project::App.setup do |app|
7
- # unless platform_name == "android"
8
- # raise "Sorry, the platform #{platform_name} is not supported by SwissDB"
9
- # end
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
- # scans app.files until it finds app/ (the default)
12
- # if found, it inserts just before those files, otherwise it will insert to
13
- # the end of the list
14
- insert_point = app.files.find_index { |file| file =~ /^(?:\.\/)?app\// } || 0
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
- # change to "swiss_db" for just swiss_db
17
- Dir.glob(File.join(lib_dir_path, "**/*.rb")).each do |file|
18
- app.files.insert(insert_point, file)
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
- # load their schemas folder
22
- app.files += Dir.glob("schemas/*.rb")
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
- # puts "APP FILES: #{app.files.inspect}"
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
@@ -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
- # class CursorModel # won't use model properties (custom methods)
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
- # def initialize(h)
9
- # h.each do |k,v|
10
- # instance_variable_set("@#{k}", v)
11
- # end
12
- # end
14
+ attr_accessor :cursor, :model
13
15
 
14
- # def method_missing(methId, *args)
15
- # str = methId.id2name
16
- # instance_variable_get("@#{str}")
17
- # end
16
+ def initialize(cursor, model)
17
+ @cursor = cursor
18
+ @model = model
19
+ @values = {}
20
+ end
18
21
 
19
- # end
22
+ def model
23
+ @model
24
+ end
20
25
 
21
- class Cursor
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
- FIELD_TYPE_BLOB = 4
24
- FIELD_TYPE_FLOAT = 2
25
- FIELD_TYPE_INTEGER = 1
26
- FIELD_TYPE_NULL = 0
27
- FIELD_TYPE_STRING = 3
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
- attr_accessor :cursor, :model
48
+ def current
49
+ model.new(to_hash)
50
+ end
30
51
 
31
- def initialize(cursor, model)
32
- @cursor = cursor
33
- @model = model
34
- @values = {}
35
- end
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
- def model
38
- @model
39
- end
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
- def first
42
- return nil if count == 0
43
- cursor.moveToFirst ? self : nil
44
- model.new(to_hash, cursor)
45
- end
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
- def last
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
- def [](pos)
54
- return nil if count == 0
55
- cursor.moveToPosition(pos) ? self : nil
56
- model.new(to_hash, cursor)
57
- end
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
- def to_hash
60
- hash_obj = {}
61
- $current_schema[model.table_name].each do |k, v|
62
- hash_obj[k.to_sym] = self.send(k.to_sym)
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
- hash_obj
65
- end
130
+ end
66
131
 
67
- def to_a
68
- return nil if count == 0
69
- arr = []
70
- (0...count).each do |i|
71
- # puts i
72
- cursor.moveToPosition(i)
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
- # todo: take out setter code. it's not used anymore. leave the getter code. it is used. (see #to_hash)
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
- def method_missing(methId, *args)
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
- def valid_getter?(method_name)
91
- column_names.include? method_name
92
- end
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
- def is_setter?(method_name)
95
- method_name[-1] == '='
96
- end
163
+ arr
164
+ end
97
165
 
98
- def get_method(method_name)
99
- index = cursor.getColumnIndex(method_name)
100
- type = cursor.getType(index)
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
- def count
120
- cursor.getCount
121
- end
171
+ def moveToLast
172
+ cursor.moveToLast
173
+ end
122
174
 
123
- def column_names
124
- cursor.getColumnNames
175
+ def moveToFirst
176
+ cursor.moveToFirst
177
+ end
125
178
  end
126
179
  end
@@ -1,50 +1,29 @@
1
- # main connection point
2
- # creates and upgrades our database for us
3
- # and provides low level SQL features
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
- @@context.deleteDatabase(DATABASE_NAME)
14
+ SwissDB.context.deleteDatabase(SwissDB.db_name)
29
15
  end
30
16
 
31
17
  def onUpgrade(db, oldVersion, newVersion)
32
- # maybe drop if needed...
33
- db.execSQL("DROP *")
34
- onCreate(db)
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
- # puts "table creation... running schema"
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
- # create table
101
- def create_table(db=writable_db, table_name, fields)
102
- fields_string = fields.map { |k, v| "#{k} #{v}" }.join(',')
103
- sql = "CREATE TABLE #{table_name}(#{fields_string})"
104
- puts sql
105
- db.execSQL sql
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
@@ -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
- class SwissModel
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
- # 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
12
+ attr_accessor :values
11
13
 
12
- attr_accessor :cursor, :values
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
- def initialize(h, cursor)
15
- @cursor = cursor
16
- h.each do |k,v|
17
- instance_variable_set("@#{k}", v)
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
- def method_missing(methId, *args)
23
- str = methId.id2name
24
- if instance_variable_get("@#{str}")
25
- return instance_variable_get("@#{str}")
26
- elsif str[-1] == '=' # setter
27
- @values[str.chop] = args[0]
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
- def save
32
- pk_value = self.send(self.class.primary_key.to_sym)
33
- self.class.store.update(self.class.table_name, @values, {self.class.primary_key => pk_value})
34
- end
43
+ def new_record?
44
+ @new_record
45
+ end
35
46
 
36
- def update_attribute(key, value)
37
- pk_value = self.send(self.class.primary_key.to_sym)
38
- self.class.store.update(self.class.table_name, {key => value}, {self.class.primary_key => pk_value})
39
- end
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
- def update_attributes(hash)
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
- def self.store
49
- context = DataStore.context
50
- @store ||= DataStore.new(context)
51
- @store
52
- end
66
+ def update_attributes(hash)
67
+ hash.each { |k, v| update_attribute(k, v) }
68
+ end
53
69
 
54
- def self.class_name
55
- @class_name
56
- end
70
+ def primary_key
71
+ self.class.primary_key
72
+ end
57
73
 
58
- def self.set_class_name(class_name) # hack, class.name not functioning in RM Android...
59
- @class_name = class_name
60
- set_table_name(class_name.tableize)
61
- end
74
+ def primary_key_value
75
+ self.send(primary_key.to_sym)
76
+ end
62
77
 
63
- def self.set_table_name(table_name)
64
- @table_name = table_name
65
- end
78
+ def table_name
79
+ self.class.table_name
80
+ end
66
81
 
67
- def self.table_name
68
- @table_name
69
- end
82
+ def store
83
+ self.class.store
84
+ end
70
85
 
71
- def self.set_primary_key(primary_key)
72
- @primary_key = primary_key
73
- end
86
+ # def destroy
87
+ # # destroy this row
88
+ # end
74
89
 
75
- def self.primary_key
76
- @primary_key.nil? ? "id" : @primary_key
77
- end
90
+ # -------------
91
+ # CLASS METHODS
92
+ # -------------
93
+ class << self
94
+ def store
95
+ SwissDB.store
96
+ end
78
97
 
79
- def self.all
80
- # select_all
81
- cursor = store.select_all(@table_name, self)
82
- cursor
83
- end
98
+ def class_name
99
+ @class_name
100
+ end
84
101
 
85
- def self.where(values)
86
- # select <table> where <field> = <value>
87
- cursor = store.select(@table_name, values, self)
88
- cursor
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
- def self.first
92
- # select all and get first
93
- cursor = all.first
94
- cursor
95
- end
107
+ def set_table_name(table_name)
108
+ @table_name = table_name
109
+ end
96
110
 
97
- def self.last
98
- # select all and get last
99
- cursor = all.last
100
- cursor
101
- end
111
+ def table_name
112
+ @table_name
113
+ end
102
114
 
103
- def self.create(obj)
104
- # create a row
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
- # def destroy
114
- # # destroy this row
115
- # end
119
+ def primary_key
120
+ @primary_key.nil? ? "id" : @primary_key
121
+ end
116
122
 
117
- def self.destroy_all!
118
- # destroy all of this kind (empty table)
119
- store.destroy_all(@table_name)
120
- end
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
- end
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.7.2
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: 2015-10-25 00:00:00.000000000 Z
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/db.rb
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:
@@ -1,8 +0,0 @@
1
-
2
- private static final java.lang.String DB_NAME = "swissdb";
3
- private static final int VERSION = 1;
4
-
5
- public DataStore(android.content.Context context){
6
- super(context, DB_NAME, null, VERSION);
7
- }
8
-
@@ -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