tiger
Version:
A full port of Spine.js MVC framework to Titanium Mobile, with enhancements
338 lines (256 loc) • 8.55 kB
text/coffeescript
Tiger = @Tiger or require('./tiger')
isArray = Tiger.isArray
class BaseCollection extends Tiger.Class
constructor: (options = {}) ->
for key, value of options
@[key] = value
first: ->
@all()[0]
last: ->
values = @all()
values[values.length - 1]
create: (record) ->
newRecord = @model.create(record)
(@add newRecord) if newRecord
class Collection extends BaseCollection
add: (fItem) ->
fItem[@fkey] = @record.id
fItem.save()
remove: (fItem) ->
fItem = @find(fItem) if typeof fItem is 'string'
fItem.destroy()
all: ->
@model.select (rec) => @associated(rec)
find: (id) ->
records = @select (rec) =>
rec.id + '' is id + ''
throw('Unknown record') unless records[0]
records[0]
findAllByAttribute: (name, value) ->
@model.select (rec) =>
@associated(rec) and rec[name] is value
findByAttribute: (name, value) ->
@findAllByAttribute(name, value)[0]
select: (cb) ->
@model.select (rec) =>
@associated(rec) and cb(rec)
refresh: (values) ->
delete @model.records[record.id] for record in @all()
records = @model.fromJSON(values)
records = [records] unless isArray(records)
for record in records
record.newRecord = false
record[@fkey] = @record.id
@model.records[record.id] = record
@model.trigger('refresh', @model.cloneArray(records))
# Private
associated: (record) ->
record[@fkey] is @record.id
class O2MCollection extends BaseCollection
add: (item, save = true) ->
if isArray(item)
@add i, false for i in item
else
item = @model.find item unless item instanceof @model
@record[@lkey].push item[@fkey]
@record.save() if save
remove: (item) ->
item = @model.find item unless item instanceof @model
@record[@lkey].splice (@record[@lkey].indexOf item[@fkey]), 1
@record.save()
all: ->
(@model.find lkey for lkey in @record[@lkey])
find: (id) ->
id in @record[@lkey] and @model.find id or throw 'Unknown record'
class M2MCollection extends BaseCollection
add: (item, save = true) ->
if isArray(item)
@add i, false for i in item
else
item = @model.find item unless item instanceof @model
hub = new @Hub()
if @left_to_right
hub["#{@rev_name}_id"] = @record.id
hub["#{@name}_id"] = item.id
else
hub["#{@rev_name}_id"] = item.id
hub["#{@name}_id"] = @record.id
hub.save() if save
remove: (item) ->
i.destroy() for i in @Hub.select (item) =>
@associated(item)
_link: (items) ->
items.map (item) =>
if @left_to_right then return @model.find item["#{@name}_id"]
else return @model.find item["#{@rev_name}_id"]
all: ->
@_link @Hub.select (item) =>
@associated(item)
find: (id) ->
records = @Hub.select (rec) =>
@associated(rec, id)
throw 'Unknown record' unless records[0]
@_link(records)[0]
associated: (record, id) ->
if @left_to_right
return false unless record["#{@rev_name}_id"] is @record.id
return record["#{@rev_name}_id"] is id if id
else
return false unless record["#{@name}_id"] is @record.id
return record["#{@name}_id"] is id if id
true
class Instance extends Tiger.Class
constructor: (options = {}) ->
for key, value of options
@[key] = value
exists: ->
@record[@fkey] and @model.exists(@record[@fkey])
update: (value) ->
unless value instanceof @model
value = new @model(value)
value.save() if value.isNew()
@record[@fkey] = value and value.id
@record.save()
class Singleton extends Tiger.Class
constructor: (options = {}) ->
for key, value of options
@[key] = value
find: ->
@record.id and @model.findByAttribute(@fkey, @record.id)
update: (value) ->
unless value instanceof @model
value = @model.fromJSON(value)
value[@fkey] = @record.id
value.save()
singularize = (str) ->
str.replace(/s$/, '')
underscore = (str) ->
str.replace(/::/g, '/')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
.replace(/-/g, '_')
.toLowerCase()
loadModel = (model, parent) ->
if typeof model is 'string'
model = require(model)
model.bind 'error', (record, msg) => parent.trigger 'error', record, msg, model.className
if typeof model.loadTigerDB is 'function'
model.bindTigerDB().fetch()
model
Relations =
__filter: (args, revert=false) ->
(rec) ->
q = !!revert
for key, value of args
return q unless rec[key] is value
!q
filter: (args) -> @select @__filter args
exclude: (args) -> @select @__filter args, true
oneToMany: (model, name, fkey) ->
parent = @
unless name?
model = loadModel model, parent
name = model.className.toLowerCase()
name = singularize underscore name
lkey = "#{name}_ids"
unless lkey in @attributes
@attributes.push lkey
fkey ?= 'id'
association = (record, model) ->
model = loadModel model, parent
record[lkey] = [] unless record[lkey]
new O2MCollection {lkey, fkey, record, model}
@::["#{name}s"] = (value) ->
association(@, model)
hasMany: (model, name, fkey) ->
parent = @
unless name?
model = loadModel model, parent
name = model.className.toLowerCase()
name = singularize underscore name
fkey ?= "#{underscore(this.className)}_id"
association = (record) ->
model = loadModel model, parent
new Collection(
name: name, model: model,
record: record, fkey: fkey
)
@::["#{name}s"] = (value) ->
association(@).refresh(value) if value?
association(@)
belongsTo: (model, name, fkey) ->
parent = @
unless name?
model = loadModel model, parent
name = model.className.toLowerCase()
name = singularize underscore name
fkey ?= "#{name}_id"
association = (record) ->
model = loadModel model, parent
new Instance(
name: name, model: model,
record: record, fkey: fkey
)
@::[name] = (value) ->
association(@).update(value) if value?
association(@).exists()
@attributes.push(fkey)
hasOne: (model, name, fkey) ->
parent = @
unless name?
model = loadModel model, parent
name = model.className.toLowerCase()
name = singularize underscore name
fkey ?= "#{underscore(@className)}_id"
association = (record) ->
model = loadModel model, parent
new Singleton(
name: name, model: model,
record: record, fkey: fkey
)
@::[name] = (value) ->
association(@).update(value) if value?
association(@).find()
foreignKey: (model, name, rev_name) ->
parent = @
unless name?
model = loadModel model, parent
name = model.className.toLowerCase()
name = singularize underscore name
unless rev_name?
rev_name = @className.toLowerCase()
rev_name = singularize underscore rev_name
rev_name = "#{rev_name}s"
@belongsTo name, model
model.hasMany rev_name, @
manyToMany: (model, name, rev_name) ->
parent = @
unless name?
model = loadModel model, parent
name = model.className.toLowerCase()
name = singularize underscore name
unless rev_name?
rev_name = @className.toLowerCase()
rev_name = singularize underscore rev_name
rev_name = "#{rev_name}s"
rev_model = @
local = typeof model.loadLocal is 'function' or typeof rev_model.loadLocal is 'function'
tigerDB = typeof model.loadTigerDB is 'function' or typeof rev_model.loadTigerDB is 'function'
class Hub extends Tiger.Model
@extend Tiger.Model.Local if local
@extend Tiger.Model.TigerDB if tigerDB
@configure "_#{rev_name}_to_#{name}", "#{@rev_name}_id", "#{@name}_id"
Hub.fetch() if local or tigerDB
Hub.foreignKey rev_model, "#{rev_name}"
Hub.foreignKey model, "#{name}"
association = (record, model, left_to_right) ->
model = loadModel model, parent
new M2MCollection {name, rev_name, record, model, Hub: Hub, left_to_right}
rev_model::["#{name}s"] = (value) ->
association(@, model, true)
model::["#{rev_name}s"] = (value) ->
association(@, rev_model, false)
Tiger.Model.extend Relations
Relations.loadModel = loadModel
Tiger.Model.Relations = Relations
module?.exports = Relations