Last active
August 29, 2015 14:06
-
-
Save abe33/eb3ea850da941498e155 to your computer and use it in GitHub Desktop.
ActiveRecord-like mixin
This file contains 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
# Creates a collection class for the given model. This class | |
# will be decorated with the scopes defined on the model class. | |
build_collection_class = (model) -> | |
# The Collection class behaves mostly like an array except that | |
# every methods that should return an array return a collection | |
# instead. | |
class Collection | |
@model: model | |
# We can't use `new Collection` because Collection's instances | |
# are not proxy. Use `Collection.create` instead. | |
@create: (content) -> | |
collection = new @(content) | |
new Proxy collection, { | |
get: (target, name) -> | |
if typeof target[name] is 'function' | |
target[name].bind(target) | |
else | |
if /-?\d+/.test(name) | |
# With this we can call `collection[index]` and access the content | |
# of the collection array. | |
index = parseInt(name) | |
index = target.length + index if index < 0 | |
target.array[index] | |
else | |
target[name] | |
} | |
@delegate_array_methods: (methods...) -> | |
methods.forEach (method) => | |
@::[method] = -> @array[method](arguments...) | |
@delegate_array_returning_methods: (methods...) -> | |
methods.forEach (method) => | |
@::[method] = -> @constructor.create(@array[method](arguments...)) | |
@delegate_array_methods 'push', 'pop', 'shift', 'unshift', 'length', 'forEach', 'some', 'every', 'indexOf' | |
@delegate_array_returning_methods 'concat', 'splice', 'slice', 'filter' | |
constructor: (@array=[]) -> | |
first: -> @array[0] | |
last: -> @array[@length - 1] | |
map: (block) -> | |
results = if typeof block is 'string' | |
@array.map (el) -> el[block] | |
else | |
@array.map(block) | |
new @constructor(results) | |
distinct: (field) -> | |
values = [] | |
@forEach (instance) -> | |
values.push(instance[field]) unless instance[field] in values | |
values | |
where: (conditions={}) -> | |
@filter (model) => @match_conditions(model, conditions) | |
match_conditions: (model, conditions) -> | |
res = true | |
for k,v of conditions | |
if typeof v is 'function' | |
res &&= v(model[k]) | |
else | |
res &&= model[k] is v | |
res | |
class Model | |
@delegate_collection_methods: (methods...) -> | |
methods.forEach (method) => @[method] = -> @instances[method](arguments...) | |
@delegate_collection_methods 'first', 'last', 'where', 'distinct' | |
### Public ### | |
# Initializes the instances collection on the model's class. | |
@initialize_collection: -> | |
return if @initialized | |
@Collection = build_collection_class(this) | |
@instances = @Collection.create() | |
@nextId = 1 | |
@initialized = true | |
@create_collection: (content=[]) -> | |
@initialize_collection() | |
@Collection.create(content) | |
@register: (instance) -> | |
@initialize_collection() | |
instance.id = @nextId++ | |
@instances.push(instance) | |
@unregister: (instance) -> | |
@instances.splice(@instances.indexOf(instance), 1) if instance in @instances | |
### Scopes ### | |
@all: -> | |
@initialize_collection() | |
@instances.concat() | |
@scope: (name, block) -> | |
@initialize_collection() | |
@Collection::[name] = (args...) -> | |
@filter (instance, index) -> block([instance].concat(args)...) | |
@delegate_collection_methods name | |
### Queries ### | |
@find: (id) -> | |
@initialize_collection() | |
@where({id}).first() | |
@find_or_create: (conditions={}) -> | |
@initialize_collection() | |
instance = @where(conditions).first() | |
return instance if instance? | |
new @(conditions) | |
class Base | |
# `concern` is a function extension added prior to this setup. | |
# It decorates the current class with both class and instance methods | |
# from the passed-in mixin. | |
@concern Model | |
contructor: -> | |
@constructor.register(this) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment