UNPKG

coffee-shop

Version:

Coffee Web Framework.

219 lines (208 loc) 6.74 kB
sugar = require 'sugar' module.exports = class CoffeeShop @Table: class # like Arel constructor: -> @_table = '' @_select = [] @_primary_key = 'id' @_select = [] @_join = [] @_where = [] @_group = [] @_having = [] @_order = [] @_limit = 0 @_offset = 0 return table: (@_table) -> primary_key: (@_primary_key) -> # chainables _simple: (n) -> -> a = arguments if a.length >= 1 and all a, 's' for k of a when word a[k] a[k] = @escape_key a[k] # ['a'] # ['a=1'] # ['a=1, b=1'] # ['a=1', 'b=1'] concat @["_#{n}"], a # raw sql @ select: _Class::_simple 'select' project: _Class::select # alias join: -> a = arguments if word(a[0]) or a.length > 1 # ['table2'] # ['table2', 'table3'] # TODO: lookup relationship and build query here for k of a @_join.push "JOIN #{@escape_key a[k]}\n ON 1" else if y a[0] is 's' # ['LEFT OUTER JOIN table2 ON table1.id = table2.id'] @_join.push a[0] # raw sql @ joins: _Class::join # alias include: _Class::join # alias # TODO: implement OR; use raw sql conditions for now where: -> a = arguments s = sig a if a.length >= 2 and all(a, 's') and a[0].indexOf('?') isnt -1 # ["customers.last LIKE 'a%?'", 'son'] # ["customers.? LIKE 'a%?'", 'last', 'son'] i = 0 @_where.push a[0].replace /\?/g, => @escape a[++i] else if a.length >= 1 and all(a, 's') # ["customers.first = 'bob'"] # ["customers.first = 'bob'", "customers.last = 'anderson'"] concat @_where, a # raw sql else if s is 'o' if a[0].length is `undefined` # hash # [{'customers.first': 'bob'}] # [{'customers.first': 'bob', 'customers.last': 'anderson'}] # [{customers: { first: 'bob'}}] r = (o, prefix='') => _r = [] for k of o if typeof o[k] is 'object' concat _r, r o[k], "#{@escape_key k}." else _r.push "#{prefix}#{if word(k) then @escape_key k else k} = #{@escape o[k]}" return _r concat @_where, r a[0] @ group: _Class::_simple 'group' having: _Class::_simple 'having' order: _Class::_simple 'order' limit: (@_limit) -> @ take: _Class::limit # alias offset: (@_offset) -> @ skip: _Class::offset # alias #TODO: improve escape functions escape_key: (s) -> "`#{s.toString().replace(/`/g, '')}`" escape: (s) -> if typeof s is 'undefined' or s is null 'NULL' else "'"+s.toString().replace(/'/g,"\'")+"'" toString: -> @toSql() toSql: -> "SELECT\n #{@_select.join(",\n ")}\n"+ "FROM #{@escape_key @_table}\n"+ @_join.join("\n")+ (if @_where.length then "WHERE\n #{@_where.join(" AND \n ")}\n" else '')+ (if @_group.length then "GROUP BY #{@_group.join(', ')}\n" else '')+ (if @_order.length then "ORDER BY #{@_order.join(', ')}\n" else '')+ (if @_having.length then "HAVING #{@_having.join(', ')}\n" else '')+ (if @_limit then "LIMIT #{@_limit}\n" else '')+ (if @_offset then "OFFSET #{@_offset}\n" else '')+ ';' @Model: class extends CoffeeShop.Table # like ActiveRecord constructor: -> super() @id = null @table @constructor.name.pluralize().toLowerCase() @_attributes = {} @_has_one = [] @_has_many = [] @_has_and_belongs_to_many = [] @_belongs_to = [] a = arguments for k of a[0] @[k] = a[0][k] return attr_accessible: (a) -> for k of a @_attributes[a[k]] = true attributes: -> attrs = {} attrs[@_primary_key] = @[@_primary_key] for own k of @ when y(@_attributes[k]) isnt 'u' attrs[k] = @[k] return attrs serialize: -> JSON.stringify @attributes() all: (cb) -> @execute_sql @toSql(), (err, records) => return cb err if err for k of records records[k] = new @constructor records[k] cb null, records return return first: (cb) -> @limit 1 @all (err, results) -> return cb err if err cb null, results[0] # may as well ask for all() #last: (cb) -> # @limit 1 # @all (err, results) -> # return cb err if err # cb null, results[results.length-1] find: (id, cb) -> @select '*' @where id: id @first cb exists: (id, cb) -> conditions = {} conditions[@_primary_key] = id @select('1').where(conditions).limit(1).first (err, result) -> return cb err if err cb null, !!result save: (cb) -> attrs = @attributes() if @[@_primary_key] pairs = [] for k, v of attrs when not (k is @_primary_key) pairs.push "#{@escape_key k} = #{@escape v}" sql = "UPDATE #{@escape_key @_table}\n"+ "SET #{pairs.join(', ')}\n"+ "WHERE #{@escape_key @_primary_key} = #{@escape @[@_primary_key]};" else names = [] values = [] for k, v of attrs when not (k is @_primary_key) names.push @escape_key k values.push @escape v sql = "INSERT INTO #{@escape_key @_table} "+ "(#{names.join(', ')}) VALUES\n"+ "(#{values.join(', ')});" @execute_sql sql, cb @build: (o) -> return instance = new @ o @create: (o, cb) -> instance = new @ o #validate() instance.save cb or -> return instance execute_sql: (sql, cb) -> # db-proprietary; overridable # e.g. app.db.exec sql, cb console.log "would have executed sql:", sql console.log "override .execute_sql() function to make it happen for real." cb null # pending delete: -> deleteAll: -> has_one: (s) -> @_has_one.push s has_many: (s) -> @_has_many.push s has_and_belongs_to_many: (s) -> @_has_and_belongs_to_many.push s belongs_to: (s) -> @_belongs_to.push s validates_presence_of: -> #mount_uploader: -> # should be third-party provided validates_uniqueness_of: -> validates_format_of: -> transform_serialize: -> update_attributes: -> update_column: -> after_create: -> y=(v)->(typeof v)[0] # shorthand typeof sig=(a)->s=''; s+=y(a[k]) for k of a; s # argument signature word=(s)->y(s) is 's' and s.match(/^\w[\w\d]*$/) isnt null # when arguments = ['word'] concat=(a,b)->a[k] = b[k] for k of b; return all=(a,t)->sig(a) is (new Array(a.length+1)).join t