var $, associable, callbacks, extend, model, modifiers, plural, root, singular, subscribers, __slice = [].slice; root = window; $ = require('jquery'); extend = require('assimilate'); require('./resource'); plural = { add: function() { var attributes, params, _i, _len, _results; params = 1 <= arguments.length ? __slice.call(arguments, 0) : []; _results = []; for (_i = 0, _len = params.length; _i < _len; _i++) { attributes = params[_i]; _results.push(this.push(this.build(attributes))); } return _results; }, create: function() { var attributes, params, record, _i, _len, _results; params = 1 <= arguments.length ? __slice.call(arguments, 0) : []; _results = []; for (_i = 0, _len = params.length; _i < _len; _i++) { attributes = params[_i]; record = this.build(attributes); this.push(record); _results.push(record.save()); } return _results; }, build: function(data) { var _name; if (data == null) { data = {}; } data.parent_resource = this.parent_resource; if (this.parent != null) { data.route || (data.route = "" + this.parent.route + "/" + this.parent._id + "/" + (model.pluralize(this.resource.toString()))); } if (this.route !== data.route && this.route) { throw "associable.has_many: cannot redefine route of association " + this.parent_resource + "." + this.resource + " from " + this.route + " to " + data.route; } data[_name = this.parent_resource] || (data[_name] = this.parent); return model[model.singularize(this.resource)](data); }, push: function() { console.warn("" + this.resource + ".push is deprecated and will be removed, please use add instead"); Array.prototype.push.apply(this, arguments); return arguments[0]; }, length: 0, json: function(methods, omissions) { var record, _i, _len, _results; _results = []; for (_i = 0, _len = this.length; _i < _len; _i++) { record = this[_i]; _results.push(record.json(methods, omissions)); } return _results; }, find: function(id) { var resource, _i, _len; for (_i = 0, _len = this.length; _i < _len; _i++) { resource = this[_i]; if (resource._id === id) { return resource; } } }, filter: Array.prototype.filter || (typeof _ !== "undefined" && _ !== null ? _.filter : void 0) }; singular = { create: function(data) { return model[this.resource].create(extend({}, this, data)); }, build: function(data) { return this.owner[this.resource.toString()] = model[this.resource.toString()](extend({}, this, data)); } }; subscribers = { belongs_to: { foreign_key: function(resource_id) { var association_name, current_resource_id, resource, _ref; association_name = this.resource.toString(); if (!resource_id) { this.dirty = true; this.owner[association_name] = resource_id; return resource_id; } current_resource_id = (_ref = this.owner.observed[association_name]) != null ? _ref._id : void 0; if (resource_id !== current_resource_id) { resource = model[association_name]; if (!resource) { console.warn("subscribers.belongs_to.foreign_key: associated factory not found for model: " + association_name); return resource_id; } this.owner.observed[association_name] = null; } return resource_id; }, associated_changed: function(associated) { return this.owner.observed["" + (this.resource.toString()) + "_id"] = associated ? associated._id : null; } } }; modifiers = { belongs_to: { associated_loader: function() { var association_name, definition, temporary_observed, _this = this; association_name = this.resource.toString(); if (this.owner.observed == null) { this.owner.observed = {}; temporary_observed = true; } definition = Object.defineProperty(this.owner, association_name, { set: function(associated) { return this.observed[association_name] = associated; }, get: function() { var associated, associated_id, resource; associated = _this.owner.observed[association_name]; associated_id = _this.owner.observed[association_name + '_id']; if (!(((associated != null ? associated._id : void 0) != null) || associated_id)) { return associated; } if (associated != null ? associated.sustained : void 0) { return associated; } resource = model[association_name]; if (!resource) { console.warn("subscribers.belongs_to.foreign_key: associated factory not found for model: " + association_name); return associated; } associated = resource.find(associated_id || associated._id); if (associated) { return _this.owner.observed[association_name] = associated; } associated || (associated = resource({ _id: associated_id })); associated.reload(); return _this.owner.observed[association_name] = associated; }, configurable: true, enumerable: true }); if (temporary_observed) { delete this.owner.observed; } return definition; } } }; callbacks = { has_many: { nest_attributes: function() { var association, association_name, association_names, associations_attributes, message, _i, _len, _results; association_names = model[this.resource].has_many; if (association_names) { _results = []; for (_i = 0, _len = association_names.length; _i < _len; _i++) { association_name = association_names[_i]; associations_attributes = this["" + association_name + "_attributes"]; association = this[model.pluralize(association_name)]; if (associations_attributes && associations_attributes.length) { if (!association) { message = "has_many.nest_attributes: Association not found for " + association_name + ". \n"; message += "did you set it on model declaration? \n has_many: " + association_name + " "; throw message; } association.resource = model.singularize(association.resource); association.add.apply(association, associations_attributes); _results.push(association.resource = model.pluralize(association.resource)); } else { _results.push(void 0); } } return _results; } }, update_association: function(data) { var associated, association, association_name, id, pluralized_association, _i, _j, _len, _len1, _ref; id = this._id || data && (data._id || data.id); if (!id) { return; } _ref = model[this.resource.toString()].has_many; for (_i = 0, _len = _ref.length; _i < _len; _i++) { association_name = _ref[_i]; pluralized_association = model.pluralize(association_name); association = this[pluralized_association]; if (!association.route) { association.route = "/" + (model.pluralize(this.resource.toString())) + "/" + id + "/" + (model.pluralize(association.resource)); for (_j = 0, _len1 = association.length; _j < _len1; _j++) { associated = association[_j]; if (!associated.route && (associated.parent != null)) { associated.route = "/" + (model.pluralize(this.resource.toString())) + "/" + id + "/" + (model.pluralize(association.resource)); } } } } return true; }, autosave: function() { throw 'Not implemented yet'; } }, has_one: { nest_attributes: function() { var association_name, association_names, associations_attributes, _i, _len, _results; association_names = model[this.resource].has_one; if (association_names) { _results = []; for (_i = 0, _len = association_names.length; _i < _len; _i++) { association_name = association_names[_i]; associations_attributes = this["" + association_name + "_attributes"]; if (associations_attributes) { this[association_name] = this["build_" + association_name](associations_attributes); _results.push(delete this["" + association_name + "_attributes"]); } else { _results.push(void 0); } } return _results; } } } }; associable = { model: { blender: function(definition) { var model; model = associable.model; this.create_after_hooks = model.create_after_hooks; this.create_before_hooks = model.create_before_hooks; if (this.has_many && $.type(this.has_many) !== 'array') { this.has_many = [this.has_many]; } if (this.has_one && $.type(this.has_one) !== 'array') { this.has_one = [this.has_one]; } if (this.belongs_to && $.type(this.belongs_to) !== 'array') { this.belongs_to = [this.belongs_to]; } this.has_many || (this.has_many = []); this.has_one || (this.has_one = []); this.belongs_to || (this.belongs_to = []); return true; }, create_after_hooks: function(definition) { var association_attributes, association_name, association_proxy, old_dirty, old_resource_id, options, resource, _i, _j, _k, _len, _len1, _len2, _name, _ref, _ref1, _ref2, _results; options = model[this.resource.name || this.resource.toString()]; if (options.has_many) { _ref = options.has_many; for (_i = 0, _len = _ref.length; _i < _len; _i++) { resource = _ref[_i]; association_proxy = { resource: resource, parent_resource: this.resource, parent: this }; association_name = model.pluralize(resource); association_attributes = this[association_name] || []; this[_name = "" + association_name + "_attributes"] || (this[_name] = []); if (association_attributes.length) { this["" + association_name + "_attributes"] = this["" + association_name + "_attributes"].concat(association_attributes); } this[association_name] = $.extend(association_proxy, plural); } this.after('saved', callbacks.has_many.update_association); callbacks.has_many.nest_attributes.call(this); } if (options.has_one) { _ref1 = options.has_one; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { resource = _ref1[_j]; association_proxy = { resource: resource, parent_resource: this.resource, owner: this }; association_proxy[this.resource.toString()] = this; this["build_" + resource] = $.proxy(singular.build, association_proxy); this["create_" + resource] = $.proxy(singular.create, association_proxy); } callbacks.has_one.nest_attributes.call(this); } if (options.belongs_to) { _ref2 = options.belongs_to; _results = []; for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { resource = _ref2[_k]; association_proxy = { resource: resource, parent_resource: this.resource, parent: this, owner: this }; association_proxy[this.resource.toString()] = this; this["build_" + resource] = $.proxy(singular.build, association_proxy); this["create_" + resource] = $.proxy(singular.create, association_proxy); old_resource_id = this["" + resource + "_id"]; old_dirty = this.dirty; this["" + resource + "_id"] = null; this.subscribe("" + resource + "_id", $.proxy(subscribers.belongs_to.foreign_key, association_proxy)); this.subscribe(resource.toString(), $.proxy(subscribers.belongs_to.associated_changed, association_proxy)); this["" + resource + "_id"] = old_resource_id; _results.push(this.dirty = old_dirty); } return _results; } }, create_before_hooks: function(record) { var association_proxy, definition, resource, _i, _len, _ref, _results; definition = this; if (definition.belongs_to) { _ref = definition.belongs_to; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { resource = _ref[_i]; association_proxy = { resource: resource, parent_resource: this.resource, owner: record }; _results.push(modifiers.belongs_to.associated_loader.call(association_proxy)); } return _results; } } }, record: { after_initialize: function(attributes) { if (this.resource == null) { throw new Error('resource must be defined in order to associate'); } return model[this.resource.name || this.resource.toString()].create_after_hooks.call(this); }, before_initialize: function(creation) { if (!this.resource) { throw new Error('resource must be defined in order to associate'); } return model[this.resource.name || this.resource.toString()].create_before_hooks(creation); } } }; model = root.model; model.mix(function(modelable) { modelable.after_mix.push(associable.model.blender); modelable.record.before_initialize.push(associable.record.before_initialize); return modelable.record.after_initialize.push(associable.record.after_initialize); }); model.associable = { mix: function(blender) { return blender(singular, plural); } };