Last active
August 29, 2015 14:07
-
-
Save gjcourt/5086046622dd63b8a145 to your computer and use it in GitHub Desktop.
Database and ORM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
App.Storage = _.extend { Database: {} }, App.Storage | |
class App.Storage.Database.Statement | |
__error: (args...) -> | |
console.error args... | |
constructor: (@query, @args=[], @success=(->), @error=@__error) -> | |
class App.Storage.Database.Cursor | |
constructor: (@__db) -> | |
@__statements = [] | |
__success: (args...) -> | |
console.log args... | |
__error: (args...) -> | |
console.error args... | |
__complete: (tx, result_set) -> | |
console.log result_set | |
execute: (statement, args, success=@__success, error=@__error) -> | |
@__statements.push new App.Storage.Database.Statement statement, args, success, error | |
return | |
fetch: (complete=@__complete, error=@__error) -> | |
@__db.transaction (tx) => | |
for statement in @__statements | |
tx.executeSql statement.query, statement.args, statement.success, statement.error | |
, | |
error | |
, | |
complete | |
return | |
class App.Storage.Database.Database | |
""" | |
Simple database API with CRUD capabilities. | |
""" | |
constructor: (name, version) -> | |
@__db = openDatabase(name, version, 'My database', 5*1024*1024) | |
__error: (args...) -> | |
console.error args... | |
cursor: -> | |
return new App.Storage.Database.Cursor @__db | |
_execute: (query, args_list, callback, error) -> | |
cursor = @cursor() | |
cursor.execute query, args, callback, error for args in args_list | |
cursor.fetch() | |
return | |
execute: (query, args, callback, error=@__error) -> | |
@_execute query, [args], callback, error | |
return | |
create: (name, columns, callback) -> | |
cursor = @cursor() | |
query = "CREATE TABLE IF NOT EXISTS #{name} (#{columns.join(',')})" | |
cursor.execute query, [], callback | |
cursor.fetch() | |
return | |
insert: (table, fields, args_list, callback) -> | |
throw "Missing fields to insert" if not fields.length | |
throw "Missing data to insert" if not args_list.length | |
query = "INSERT INTO #{table} (#{fields.join(',')}) VALUES (#{[0...fields.length].map(-> "?").join(',')})" | |
@_execute query, args_list, callback | |
return | |
read: (query, args, callback) -> | |
@execute query, args, callback | |
return | |
update: -> | |
throw "Not yet implemented" | |
delete: (table, fields, args, callback) -> | |
throw "Missing fields to insert" if not fields.length | |
throw "Missing mismatched args" if fields.length isnt args.length | |
query = "DELETE FROM #{table} WHERE #{fields.map((x) -> "#{x} = ?").join(' AND ')}" | |
@execute query, args, callback | |
App.Storage = _.extend { Database: {} }, App.Storage | |
class App.Storage.Database.Field | |
constructor: (@options={}) -> | |
@__type = "TEXT" | |
__serialize: (val) -> | |
return val | |
__deserialize: (val) -> | |
return val | |
__sql: (field_name) -> | |
__statement = [field_name, @__type] | |
if @options.pk | |
__statement.push 'PRIMARY KEY' | |
if @options.unique | |
__statement.push 'UNIQUE' | |
return __statement | |
get: -> | |
@__deserialize @__value | |
set: (val) -> | |
@__value = @__serialize(val) | |
return | |
sql: (field_name) -> | |
return @__sql(field_name).join ' ' | |
class App.Storage.Database.TextField extends App.Storage.Database.Field | |
class App.Storage.Database.IntegerField extends App.Storage.Database.Field | |
constructor: (options) -> | |
super options | |
@__type = 'INTEGER' | |
class App.Storage.Database.SequenceField extends App.Storage.Database.IntegerField | |
__sql: -> | |
__statement = super | |
__statement.push 'AUTOINCREMENT' | |
return __statement | |
class App.Storage.Database.FloatField extends App.Storage.Database.Field | |
constructor: (options) -> | |
super options | |
@__type = 'REAL' | |
class App.Storage.Database.BooleanField extends App.Storage.Database.Field | |
__serialize: (val) -> | |
return Number val | |
__deserialize: (val) -> | |
return Boolean val | |
class App.Storage.Database.JsonField extends App.Storage.Database.Field | |
__serialize: (val) -> | |
JSON.stringify val | |
__deserialize: (val) -> | |
JSON.parse val | |
class App.Storage.Database.Manager | |
constructor: (@__model) -> | |
@__filters = {} | |
__deserialize: (callback) -> | |
$.proxy(((tx, res) -> | |
rows = res.rows | |
values = (new this.__model(rows.item(x)) for x in [0...rows.length]) | |
callback.call(this, values) | |
), this) | |
__parse_arg: (arg, val) -> | |
unless /__/.test(arg) | |
attr = arg | |
attr_comp = 'eq' # default comparison is equality | |
else | |
[attr, attr_comp] = arg.split('__') | |
comp_map = | |
'eq': '=' | |
'gte': '>=' | |
'lte': '<=' | |
'contains': 'in' | |
throw "Unknown comparator #{comp}" unless attr_comp of comp_map | |
comp = comp_map[attr_comp] | |
# adjust for lists | |
attr = attr.split(',') if comp is "in" | |
return ["#{arg} #{comp} ?", val] | |
all: (callback=(->)) -> | |
model = new @__model() | |
model._meta.db.read("select * from #{model.__get_table_name()}", [], @__deserialize(callback)) | |
return | |
get: (options, callback=(->)) -> | |
model = new @__model() | |
sql = ["select * from #{model.__get_table_name()}"] | |
qargs = [] | |
qvals = [] | |
for arg, val of options | |
[qarg, qval] = @__parse_arg arg, val | |
qargs.push qarg | |
qvals.push qval | |
if qargs.length | |
sql = sql.concat(['where'].concat(qargs.join(' and '))) | |
query = sql.join ' ' | |
model._meta.db.read query, qvals, @__deserialize(callback) | |
return | |
filter: -> | |
throw "Not implemented" | |
return this | |
create: -> | |
throw "Not implemented" | |
delete: -> | |
throw "Not implemented" | |
class App.Storage.Database.Model | |
""" | |
The main database model. All models should in herit from Model. | |
""" | |
class Meta | |
constructor: (options={}) -> | |
_.extend this, | |
fields: [] | |
__fields: {} | |
db: new App.Storage.Database.Database('memorang', '1') | |
, options | |
get_field_by_name: (name) -> | |
if name of @__fields | |
return @__fields[name] | |
else | |
throw "No field with name #{name}" | |
get_value_list: -> | |
return (f.get() for f in @fields) | |
Object.defineProperty @constructor::, 'objects', | |
get: -> | |
return new App.Storage.Database.Manager(this) | |
@sync = -> | |
model = new @() | |
table_name = model.__get_table_name() | |
columns = (_field.sql(_name) for _name, _field of model._meta.__fields) | |
model._meta.db.create table_name, columns | |
constructor: (fields) -> | |
# Set field attributes on model | |
__fields = [] | |
for _name, _field of @constructor:: | |
__fields.push [_name, _field] if _field instanceof App.Storage.Database.Field | |
# Attach the _meta dict | |
@_meta = @__attach_meta __fields | |
# Assigns getters/setters | |
@__register_getter_setter _name, _field for [_name, _field] in __fields | |
# Allow for inline assignment of models | |
this[name] = value for name, value of fields | |
__get_table_name: -> | |
return @constructor.name.toLowerCase() | |
__attach_meta: (fields) -> | |
__fields = [] | |
__field_map = {} | |
for [_name, _field] in fields | |
__fields.push _field | |
__field_map[_name] = _field | |
return new Meta fields: __fields, __fields: __field_map | |
__register_getter_setter: (name, field) -> | |
throw "Error can't have property of field name <#{name}>" if this[name] isnt field | |
# Unregister the existing field property | |
delete this[name] | |
# Register getter/setter for the property instead | |
Object.defineProperty this, name, | |
get: -> | |
this._meta.get_field_by_name(name).get() | |
set: (val) -> | |
print "Setting prop #{name}" | |
this._meta.get_field_by_name(name).set val | |
save: (callback) -> | |
_table = @__get_table_name() | |
_fields = (_name for _name, _ of @_meta.__fields) | |
_values_list = [@_meta.get_value_list()] | |
@_meta.db.insert _table, _fields, _values_list, callback | |
delete: (callback) -> | |
# throw "Object has no primary identifier" unless 'id' of this | |
# throw "Not implemented #{@id}" | |
_table = @__get_table_name() | |
_fields = (_name for _name, _ of @_meta.__fields) | |
@_meta.db.delete _table, _fields, @_meta.get_value_list(), callback |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment