Skip to content

Instantly share code, notes, and snippets.

@gjcourt
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gjcourt/5086046622dd63b8a145 to your computer and use it in GitHub Desktop.
Save gjcourt/5086046622dd63b8a145 to your computer and use it in GitHub Desktop.
Database and ORM
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