├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── Brocfile.js ├── LICENSE ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── index.js └── mixins │ └── copyable.js ├── app └── .gitkeep ├── bower.json ├── config └── environment.js ├── index.js ├── package.json ├── testem.json ├── tests ├── .jshintrc ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ ├── templates │ │ │ ├── application.hbs │ │ │ └── components │ │ │ │ └── .gitkeep │ │ └── views │ │ │ └── .gitkeep │ ├── config │ │ └── environment.js │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── fabricate.js │ ├── resolver.js │ └── start-app.js ├── index.html ├── test-helper.js └── unit │ ├── .gitkeep │ ├── async-copying-test.js │ └── sync-copying-test.js └── vendor └── .gitkeep /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [*.css] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [*.html] 29 | indent_style = space 30 | indent_size = 2 31 | 32 | [*.{diff,md}] 33 | trim_trailing_whitespace = false 34 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esnext": true, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | tmp/ 4 | 5 | .bowerrc 6 | .editorconfig 7 | .ember-cli 8 | .travis.yml 9 | .npmignore 10 | **/.gitkeep 11 | bower.json 12 | Brocfile.js 13 | testem.json 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | 4 | sudo: false 5 | 6 | cache: 7 | directories: 8 | - node_modules 9 | 10 | before_install: 11 | - "npm config set spin false" 12 | - "npm install -g npm@^2" 13 | 14 | install: 15 | - npm install -g bower 16 | - npm install 17 | - bower install 18 | 19 | script: 20 | - npm test 21 | -------------------------------------------------------------------------------- /Brocfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | /* global require, module */ 3 | 4 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 5 | 6 | var app = new EmberAddon(); 7 | 8 | // Use `app.import` to add additional libraries to the generated 9 | // output files. 10 | // 11 | // If you need to use different assets in different 12 | // environments, specify an object as the first parameter. That 13 | // object's keys should be the environment name and the values 14 | // should be the asset to use in that environment. 15 | // 16 | // If the library that you are including contains AMD or ES6 17 | // modules that you would like to import into your application 18 | // please specify an object with the list of modules as keys 19 | // along with the exports of each module as its value. 20 | 21 | module.exports = app.toTree(); 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Benjamin Schönburg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember-cli-copyable 2 | [![Build Status](https://travis-ci.org/lazybensch/ember-cli-copyable.svg)](https://travis-ci.org/lazybensch/ember-cli-copyable) 3 | [![Code Climate](https://codeclimate.com/github/lazybensch/ember-cli-copyable/badges/gpa.svg)](https://codeclimate.com/github/lazybensch/ember-cli-copyable) 4 | 5 | This addon provides you with a mixin, that can deeply copy your ember data models without hassle. It figures out what attributes and relations exist on your model and is smart enough to resolve not loaded relations if needed. 6 | 7 | ```javascript 8 | copy = record.copy(options); 9 | ``` 10 | 11 | ## shallow and deep copying 12 | 13 | If the record you want to copy has a child, by default `ember-cli-copyable` will create a **shallow copy**. That means afterwards two records will exist *(the original and the copy)* but both will point to the very same child. If that child extends the `ember-cli-copyable` mixin aswell however, the addon will create a **deep copy**, where the copy of your record will point to a newly created child, which itself is a copy *(so two new objects where created)*. You can have a mix of shallow and deep relations in your record and the copy will be generated accordingly. This might sound a bit confusing but will be very clear once you have a look at the examples below. 14 | 15 | ## sync and async relations 16 | 17 | `ember-cli-copyable` does not mind if the data that needs to be copied is already embedded, or still has to be fetched from your backend *(or both!)*. It will simply resolve and then copy any async child and generates a fully resolved copy for you. 18 | 19 | ## simple example 20 | 21 | Before we dive into how you can configure the way your record is copied lets refresh ourselfs with a simple code example. Assume the following relations: Every account has a favorite song, and multiple playlists, which in turn host a number of songs. Now lets copy one of those accounts: Lets assume our `currentAccount` has five playlists and a favorite song. 22 | 23 | 24 | ```javascript 25 | import Copyable from 'ember-cli-copyable'; 26 | 27 | Account = DS.Model.extend( Copyable, { 28 | name: DS.attr('string'), 29 | playlists: DS.hasMany('playList'), 30 | favoriteSong: DS.belongsTo('song') 31 | }); 32 | 33 | PlayList = DS.Model.extend( Copyable, { 34 | name: DS.attr('string'), 35 | songs: DS.hasMany('song'), 36 | }); 37 | 38 | //notice how Song does not extend Copyable 39 | Song = DS.Model.extend({ 40 | name: DS.attr('string'), 41 | artist: DS.belongsTo('artist'), 42 | }); 43 | ``` 44 | 45 | 46 | ```javascript 47 | this.get('currentAccount.id') // => 1 48 | this.get('currentAccount.name') // => 'lazybensch' 49 | this.get('currentAccount.playlists.length') // => 5 50 | this.get('currentAccount.playlists.firstObject.id') // => 1 51 | this.get('currentAccount.favoriteSong.id') // => 1 52 | 53 | this.get('currentAccount').copy().then(function(copy) { 54 | 55 | copy.get('id') // => 2 (differs from currentAccount) 56 | copy.get('name') // => 'lazybensch' 57 | copy.get('playlists.length') // => 5 58 | copy.get('playlists.firstObject.id') // => 6 (differs from currentAccount) 59 | copy.get('favoriteSong.id') // => 1 (the same object as in currentAccount.favoriteSong) 60 | 61 | }); 62 | ``` 63 | 64 | Once the copy method resolved it created a new account record for us. It also created 5 new playlist objects for us which attributes itself are copies of the original playlists. Notice that we never extended Songs with the `Copyable Mixin` because we really dont want to copy songs ever, its enough to copy the reference to them. Sometimes however it is not as simple. Lets assume that although in this particular usecase, we *dont* want to copy the songs, maybe in another part of your application you do want to do that. `ember-cli-copyable` lets you configure the whole copying process as demonstrated in the next section. 65 | 66 | 67 | 68 | ## configuration 69 | 70 | For those cases where you need total control over how your record should be copied, you are able to pass an options hash to the `copy` in which you can specify what relations and attributes should be excluded from the copy process. You can also overwrite attributes this way. 71 | 72 | ### simple options example 73 | 74 | Imagine the following setup: Every `Event` has a date at which it takes place and many participants that are going to join. 75 | 76 | ```javascript 77 | import Copyable from 'ember-cli-copyable'; 78 | 79 | Event = DS.Model.extend( Copyable, { 80 | date: DS.attr('date'), 81 | participants: DS.hasMany('User') 82 | }); 83 | 84 | User = DS.Model.extend({ 85 | name: DS.attr('string'), 86 | }); 87 | ``` 88 | 89 | You could now copy an event, assigning the same group of participants but overwriting its date to today. 90 | 91 | ```javascript 92 | today = moment().format(); 93 | event.copy({date: today}) 94 | ``` 95 | 96 | ### nested options example 97 | 98 | Lets look at another example that passes additional copy instructions down to the relations nested under your record. 99 | 100 | ```javascript 101 | import Copyable from 'ember-cli-copyable'; 102 | 103 | Foo = DS.Model.extend( Copyable, { 104 | property: DS.attr('string') 105 | }); 106 | 107 | Bar = DS.Model.extend( Copyable, { 108 | foo: DS.belongsTo('foo') 109 | }); 110 | 111 | Baz = DS.Model.extend( Copyable, { 112 | bar: DS.belongsTo('bar'), 113 | property: DS.attr('string') 114 | }); 115 | 116 | ``` 117 | 118 | `baz.copy()` by default will create three new records, a copy of `baz` a copy of its `bar` and a copy of the `foo` model. 119 | 120 | ```javascript 121 | baz.copy({bar: null}) 122 | 123 | ``` 124 | creates a copy of baz with baz.get('bar') === null 125 | 126 | 127 | ```javascript 128 | baz.copy({bar: {foo: null}}) 129 | ``` 130 | creates a copy of baz and a copy of bar with baz.get('bar.foo') === null 131 | 132 | 133 | ```javascript 134 | baz.copy({property: null, {bar: {foo: {property: 'asdf'}}}}) 135 | ``` 136 | 137 | creates a copy of `baz` with its property set to null, also creates a copy of `bar` and a copy of `foo`, but foo.get('property') will be overwritten with `'asdf'` 138 | 139 | 140 | ## coming up 141 | 142 | For the next days I plan a few backwards compatible updates, including: 143 | 144 | * allow user to overwrite hasMany relations 145 | * allow user to force shallow copying on copyable models 146 | * detect circular relations 147 | 148 | ## Installation 149 | 150 | To use this addon in your project, just type: 151 | ``` 152 | $ ember install ember-cli-copyable 153 | ``` 154 | or for older versions of ember-cli *(pre 1.4.0)*: 155 | ``` 156 | $ npm install --save-dev ember-cli-copyable 157 | ``` 158 | and then import the mixin wherever you need it: 159 | ``` 160 | import Copyable from 'ember-cli-copyable; 161 | ``` 162 | 163 | ## Contributing 164 | 165 | Im happy about everyone that wants to contribute. 166 | 167 | * `git clone https://github.com/lazybensch/ember-cli-copyable` 168 | * `cd ember-cli-copyable` 169 | * `npm install` 170 | * `bower install` 171 | * `ember test` 172 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/addon/.gitkeep -------------------------------------------------------------------------------- /addon/index.js: -------------------------------------------------------------------------------- 1 | import Copyable from 'ember-cli-copyable/mixins/copyable'; 2 | export default Copyable; 3 | -------------------------------------------------------------------------------- /addon/mixins/copyable.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import DS from 'ember-data'; 3 | 4 | export default Ember.Mixin.create({ 5 | copyable: true, 6 | copy: function(options, copied) { 7 | options = options || {}; 8 | copied = copied || {}; 9 | 10 | var _this = this; 11 | return new Ember.RSVP.Promise(function(resolve) { 12 | 13 | var model = _this.constructor; 14 | var modelName = model.modelName || model.typeKey; 15 | var id = modelName + "--" + _this.get('id'); 16 | if (copied.hasOwnProperty(id)) { 17 | return resolve(copied[id]); 18 | } 19 | 20 | var copy = _this.get('store').createRecord(modelName); 21 | copied[id] = copy; 22 | var queue = []; 23 | 24 | model.eachAttribute(function(attr) { 25 | switch(Ember.typeOf(options[attr])) { 26 | case 'undefined': 27 | copy.set(attr, _this.get(attr)); 28 | break; 29 | case 'null': 30 | copy.set(attr, null); 31 | break; 32 | default: 33 | copy.set(attr, options[attr]); 34 | } 35 | }); 36 | 37 | model.eachRelationship(function(relName, meta) { 38 | var rel = _this.get(relName); 39 | if (!rel) { return; } 40 | 41 | var overwrite; 42 | var passedOptions = {}; 43 | switch(Ember.typeOf(options[relName])) { 44 | case 'null': 45 | return; 46 | case 'instance': 47 | overwrite = options[relName]; 48 | break; 49 | case 'object': 50 | passedOptions = options[relName]; 51 | break; 52 | case 'array': 53 | overwrite = options[relName]; 54 | break; 55 | default: 56 | } 57 | 58 | if (rel.constructor === DS.PromiseObject) { 59 | 60 | queue.push(rel.then(function(obj) { 61 | 62 | if (obj && obj.get('copyable') && !overwrite) { 63 | return obj.copy(passedOptions, copied).then(function(objCopy) { 64 | copy.set(relName, objCopy); 65 | }); 66 | 67 | } else { 68 | copy.set(relName, overwrite || obj); 69 | } 70 | 71 | })); 72 | 73 | 74 | } else if (rel.constructor === DS.PromiseManyArray) { 75 | 76 | if (overwrite) { 77 | copy.get(relName).setObjects(overwrite); 78 | } else { 79 | queue.push(rel.then(function(array) { 80 | var resolvedCopies = 81 | array.map(function(obj) { 82 | if (obj.get('copyable')) { 83 | return obj.copy(passedOptions, copied); 84 | } else { 85 | return obj; 86 | } 87 | }); 88 | return Ember.RSVP.all(resolvedCopies).then(function(copies){ 89 | copy.get(relName).setObjects(copies); 90 | }); 91 | })); 92 | } 93 | } else { 94 | if (meta.kind === 'belongsTo') { 95 | var obj = rel; 96 | 97 | if (obj && obj.get('copyable') && !overwrite) { 98 | queue.push( obj.copy(passedOptions, copied).then(function(objCopy) { 99 | copy.set(relName, objCopy); 100 | })); 101 | } else { 102 | copy.set(relName, overwrite || obj); 103 | } 104 | 105 | } else { 106 | var objs = rel; 107 | 108 | if (objs.get('content')) { 109 | objs = objs.get('content').compact(); 110 | } 111 | 112 | if (objs.get('firstObject.copyable') && !overwrite) { 113 | 114 | var copies = objs.map(function(obj) { 115 | return obj.copy(passedOptions, copied); 116 | }); 117 | 118 | queue.push( Ember.RSVP.all(copies).then( function(resolvedCopies) { 119 | copy.get(relName).setObjects(resolvedCopies); 120 | })); 121 | 122 | } else { 123 | copy.get(relName).setObjects(overwrite || objs); 124 | } 125 | } 126 | 127 | } 128 | }); 129 | 130 | 131 | Ember.RSVP.all(queue).then(function() { 132 | resolve(copy); 133 | }); 134 | }); 135 | } 136 | }); 137 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/app/.gitkeep -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-copyable", 3 | "dependencies": { 4 | "jquery": "^1.11.1", 5 | "ember": "1.10.0", 6 | "ember-data": "1.0.0-beta.15", 7 | "ember-resolver": "~0.1.12", 8 | "loader.js": "ember-cli/loader.js#3.2.0", 9 | "ember-cli-shims": "ember-cli/ember-cli-shims#0.0.3", 10 | "ember-cli-test-loader": "ember-cli-test-loader#0.1.3", 11 | "ember-load-initializers": "ember-cli/ember-load-initializers#0.0.2", 12 | "ember-qunit": "0.2.8", 13 | "ember-qunit-notifications": "0.0.7", 14 | "qunit": "~1.17.1" 15 | } 16 | } -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'ember-cli-copyable' 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-copyable", 3 | "version": "0.9.5", 4 | "description": "Deeply copies your records including their relations. The mixin is smart enough to resolve not loaded relations and is configurable to what should be shallow/deeply copied or excluded entirely.", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "start": "ember server", 11 | "build": "ember build", 12 | "test": "ember test" 13 | }, 14 | "repository": "https://github.com/lazybensch/ember-cli-copyable", 15 | "engines": { 16 | "node": ">= 0.10.0" 17 | }, 18 | "author": "Benjamin Schoenburg ", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "broccoli-asset-rev": "^2.0.0", 22 | "ember-cli": "0.2.0", 23 | "ember-cli-babel": "^4.0.0", 24 | "ember-cli-app-version": "0.3.2", 25 | "ember-cli-content-security-policy": "0.3.0", 26 | "ember-cli-dependency-checker": "0.0.8", 27 | "ember-cli-htmlbars": "0.7.4", 28 | "ember-cli-ic-ajax": "0.1.1", 29 | "ember-cli-inject-live-reload": "^1.3.0", 30 | "ember-cli-qunit": "0.3.9", 31 | "ember-cli-uglify": "1.0.1", 32 | "ember-data": "1.0.0-beta.15", 33 | "ember-export-application-global": "^1.0.2" 34 | }, 35 | "keywords": [ 36 | "ember-addon", 37 | "ember-cli-copyable", 38 | "copy", 39 | "copyable", 40 | "clone", 41 | "cloneable", 42 | "mixin" 43 | ], 44 | "ember-addon": { 45 | "configPath": "tests/dummy/config" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tests/index.html?hidepassed", 4 | "launch_in_ci": [ 5 | "PhantomJS" 6 | ], 7 | "launch_in_dev": [ 8 | "PhantomJS", 9 | "Chrome" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName" 25 | ], 26 | "node": false, 27 | "browser": false, 28 | "boss": true, 29 | "curly": false, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": false, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": false, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": true, 47 | "strict": false, 48 | "white": false, 49 | "eqnull": true, 50 | "esnext": true 51 | } 52 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from 'ember/resolver'; 3 | import loadInitializers from 'ember/load-initializers'; 4 | import config from './config/environment'; 5 | 6 | Ember.MODEL_FACTORY_INJECTIONS = true; 7 | 8 | var App = Ember.Application.extend({ 9 | modulePrefix: config.modulePrefix, 10 | podModulePrefix: config.podModulePrefix, 11 | Resolver: Resolver 12 | }); 13 | 14 | loadInitializers(App, config.modulePrefix); 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | 12 | 13 | 14 | 15 | {{content-for 'head-footer'}} 16 | 17 | 18 | {{content-for 'body'}} 19 | 20 | 21 | 22 | 23 | {{content-for 'body-footer'}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | var Router = Ember.Router.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() { 9 | }); 10 | 11 | export default Router; 12 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember.js

2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/tests/dummy/app/views/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'dummy', 6 | environment: environment, 7 | baseURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | } 20 | }; 21 | 22 | if (environment === 'development') { 23 | // ENV.APP.LOG_RESOLVER = true; 24 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 25 | // ENV.APP.LOG_TRANSITIONS = true; 26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 27 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 28 | } 29 | 30 | if (environment === 'test') { 31 | // Testem prefers this... 32 | ENV.baseURL = '/'; 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | } 41 | 42 | if (environment === 'production') { 43 | 44 | } 45 | 46 | return ENV; 47 | }; 48 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /tests/helpers/fabricate.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | import Copyable from 'ember-cli-copyable'; 3 | 4 | var setupModels = function(app, async) { 5 | app.Foo = DS.Model.extend( Copyable, { 6 | property: DS.attr('string') 7 | }); 8 | 9 | app.Bar = DS.Model.extend( Copyable, { 10 | foo: DS.belongsTo('foo', { async: async }) 11 | }); 12 | 13 | app.Baz = DS.Model.extend( Copyable, { 14 | foos: DS.hasMany('foo', {async: async }), 15 | bar: DS.belongsTo('bar', {async: async }) 16 | }); 17 | 18 | app.NestedList = DS.Model.extend( Copyable, { 19 | baz: DS.hasMany('baz', {async: async }) 20 | }); 21 | 22 | app.Multi = DS.Model.extend( Copyable, { 23 | bars: DS.hasMany('bar', {async: async }), 24 | baz: DS.belongsTo('baz', {async: async }) 25 | }); 26 | 27 | app.FooFix = DS.Model.extend( { 28 | property: DS.attr('string') 29 | }); 30 | 31 | app.FooBar = DS.Model.extend( Copyable, { 32 | fooFix: DS.belongsTo('fooFix', { async: async }) 33 | }); 34 | 35 | app.FooEmpty = DS.Model.extend( Copyable, { 36 | property: DS.attr('string'), 37 | foo: DS.belongsTo('foo', { async: async }) 38 | }); 39 | }; 40 | 41 | var setupFixtures = function(app) { 42 | app.Foo.reopenClass({ 43 | FIXTURES: [ 44 | { 45 | 'id': '1', 46 | 'property': 'prop1' 47 | }, 48 | { 49 | 'id': '2', 50 | 'property': 'prop2' 51 | }, 52 | { 53 | 'id': '3', 54 | 'property': 'prop3' 55 | } 56 | ] 57 | }); 58 | 59 | app.Bar.reopenClass({ 60 | FIXTURES: [ 61 | { 62 | 'id': '1', 63 | 'foo': '1' 64 | } 65 | ] 66 | }); 67 | 68 | app.Baz.reopenClass({ 69 | FIXTURES: [ 70 | { 71 | 'id': '1', 72 | 'foos': ['1', '2'], 73 | 'bar': '1' 74 | }, 75 | { 76 | 'id': '2', 77 | 'foos': ['3'], 78 | 'bar': '1' 79 | } 80 | ] 81 | }); 82 | 83 | app.Multi.reopenClass({ 84 | FIXTURES: [ 85 | { 86 | 'id': '1', 87 | 'bars': ['1'], 88 | 'baz': '1' 89 | }, 90 | { 91 | 'id': '2', 92 | 'bars': [], 93 | 'baz': '1' 94 | } 95 | ] 96 | }); 97 | 98 | app.NestedList.reopenClass({ 99 | FIXTURES: [ 100 | { 101 | 'id': '1', 102 | 'baz': ['1'] 103 | }, 104 | ] 105 | }); 106 | 107 | app.FooFix.reopenClass({ 108 | FIXTURES: [ 109 | { 110 | 'id': '1', 111 | 'property': 'fix1' 112 | } 113 | ] 114 | }); 115 | 116 | app.FooBar.reopenClass({ 117 | FIXTURES: [ 118 | { 119 | 'id': '1', 120 | 'fooFix': '1' 121 | } 122 | ] 123 | }); 124 | 125 | app.FooEmpty.reopenClass({ 126 | FIXTURES: [ 127 | { 128 | 'id': '1', 129 | 'property': '2' 130 | } 131 | ] 132 | }); 133 | }; 134 | 135 | export default function fabricate(app, async) { 136 | 137 | setupModels(app, async); 138 | setupFixtures(app); 139 | app.ApplicationAdapter = DS.FixtureAdapter; 140 | return app.__container__.lookup('store:main'); 141 | } 142 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | var resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import Router from '../../router'; 4 | import config from '../../config/environment'; 5 | 6 | export default function startApp(attrs) { 7 | var application; 8 | 9 | var attributes = Ember.merge({}, config.APP); 10 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 11 | 12 | Ember.run(function() { 13 | application = Application.create(attributes); 14 | application.setupForTesting(); 15 | application.injectTestHelpers(); 16 | }); 17 | 18 | return application; 19 | } 20 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for 'head'}} 11 | {{content-for 'test-head'}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for 'head-footer'}} 18 | {{content-for 'test-head-footer'}} 19 | 20 | 21 | 22 | {{content-for 'body'}} 23 | {{content-for 'test-body'}} 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for 'body-footer'}} 31 | {{content-for 'test-body-footer'}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/async-copying-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { test } from 'ember-qunit'; 3 | import { module } from 'qunit'; 4 | import startApp from '../helpers/start-app'; 5 | import fabricate from '../helpers/fabricate'; 6 | 7 | var store; 8 | 9 | module('async copying', { 10 | beforeEach: function() { 11 | store = fabricate(startApp(), true); 12 | } 13 | }); 14 | 15 | test('it overwrites attributes', function(assert) { 16 | assert.expect(1); 17 | 18 | return Ember.run(function() { 19 | return store.find('foo', '1').then( function(foo) { 20 | return foo.copy({property: 'custom'}).then(function (copy) { 21 | assert.equal(copy.get('property'), 'custom'); 22 | }); 23 | }); 24 | }); 25 | }); 26 | 27 | test('it shallow copies relation', function(assert) { 28 | assert.expect(1); 29 | 30 | return Ember.run(function() { 31 | return store.find('fooBar', '1').then( function(fooBar) { 32 | 33 | return fooBar.copy().then(function (copy) { 34 | assert.equal(copy.get('fooFix.id'), '1'); 35 | }); 36 | }); 37 | }); 38 | }); 39 | 40 | test('it copies belongsTo relation', function(assert) { 41 | assert.expect(2); 42 | 43 | return Ember.run(function() { 44 | return store.find('bar', '1').then( function(bar) { 45 | 46 | return bar.copy().then(function (copy) { 47 | assert.notEqual(copy.get('foo.id'), bar.get('foo.id')); 48 | assert.equal(copy.get('foo.property'), 'prop1'); 49 | }); 50 | }); 51 | }); 52 | }); 53 | 54 | test('it copies with empty belongsTo relation', function(assert) { 55 | assert.expect(2); 56 | 57 | return Ember.run(function() { 58 | return store.find('fooEmpty', '1').then( function(fooEmpty) { 59 | 60 | return fooEmpty.copy().then(function (copy) { 61 | assert.equal(copy.get('property'), fooEmpty.get('property')); 62 | return copy.get('foo').then(function(foo) { 63 | assert.equal(foo, null); 64 | }); 65 | }); 66 | }); 67 | }); 68 | }); 69 | 70 | test('it copies hasMany relation', function(assert) { 71 | assert.expect(5); 72 | 73 | return Ember.run(function() { 74 | return store.find('baz', '1').then( function(baz) { 75 | 76 | return baz.copy().then(function (copy) { 77 | assert.equal(copy.get('foos.length'), 2); 78 | assert.notEqual(copy.get('foos.firstObject.id'), '1'); 79 | assert.notEqual(copy.get('foos.lastObject.id'), '2'); 80 | assert.equal(copy.get('foos.firstObject.property'), 'prop1'); 81 | assert.equal(copy.get('foos.lastObject.property'), 'prop2'); 82 | }); 83 | }); 84 | }); 85 | }); 86 | 87 | test('it copies complex objects', function(assert) { 88 | assert.expect(6); 89 | 90 | return Ember.run(function() { 91 | return store.find('multi', '1').then( function(multi) { 92 | 93 | return multi.copy().then(function (copy) { 94 | assert.notEqual(copy.get('bars.firstObject.id'), '1'); 95 | assert.notEqual(copy.get('bars.firstObject.foo.id'), '1'); 96 | assert.equal(copy.get('bars.firstObject.foo.property'), 'prop1'); 97 | assert.notEqual(copy.get('baz.id'), '1'); 98 | assert.notEqual(copy.get('baz.foos.lastObject.id'), '2'); 99 | assert.equal(copy.get('baz.foos.lastObject.property'), 'prop2'); 100 | }); 101 | }); 102 | }); 103 | }); 104 | 105 | test('it copies objects with nested hasMany', function(assert) { 106 | assert.expect(1); 107 | 108 | return Ember.run(function() { 109 | return store.find('nestedList', '1').then( function(nested) { 110 | 111 | return nested.copy().then(function (copy) { 112 | assert.equal(copy.get('baz.firstObject.foos.firstObject.property'), 'prop1'); 113 | 114 | }); 115 | }); 116 | }); 117 | }); 118 | 119 | test('it overwrites relations', function(assert) { 120 | assert.expect(2); 121 | 122 | return Ember.run(function() { 123 | return store.find('baz', '2').then( function(myBaz) { 124 | return store.find('multi', '1').then( function(multi) { 125 | 126 | return multi.copy({baz: myBaz, bars: []}).then(function (copy) { 127 | assert.equal(copy.get('bars.length'), 0); 128 | assert.equal(copy.get('baz.foos.length'), 1); 129 | }); 130 | }); 131 | }); 132 | }); 133 | }); 134 | 135 | test('it passes options to relations', function(assert) { 136 | assert.expect(2); 137 | 138 | return Ember.run(function() { 139 | return store.find('multi', '1').then( function(multi) { 140 | 141 | return multi.copy({baz: {bar: {foo: {property: 'asdf'}}}}).then(function (copy) { 142 | assert.equal(copy.get('bars.firstObject.foo.property'), 'prop1'); 143 | assert.equal(copy.get('baz.bar.foo.property'), 'asdf'); 144 | }); 145 | }); 146 | }); 147 | }); 148 | 149 | test('it copies empty objects', function(assert) { 150 | assert.expect(3); 151 | 152 | return Ember.run(function() { 153 | return store.find('multi', '2').then( function(multi) { 154 | 155 | return multi.copy().then(function (copy) { 156 | assert.notEqual(copy.get('id'), '2'); 157 | assert.equal(copy.get('bars.length'), 0); 158 | assert.equal(copy.get('baz.foos.firstObject.property'), 'prop1'); 159 | }); 160 | }); 161 | }); 162 | }); 163 | -------------------------------------------------------------------------------- /tests/unit/sync-copying-test.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import { test } from 'ember-qunit'; 3 | import { module } from 'qunit'; 4 | import startApp from '../helpers/start-app'; 5 | import fabricate from '../helpers/fabricate'; 6 | 7 | var store; 8 | 9 | module('sync copying', { 10 | beforeEach: function() { 11 | store = fabricate(startApp(), false); 12 | 13 | return Ember.RSVP.all(['foo','bar','baz','multi','fooBar','fooFix', 'FooEmpty'].map(function(type) { 14 | return store.find(type); 15 | })); 16 | } 17 | }); 18 | 19 | test('it excludes attributes', function(assert) { 20 | assert.expect(1); 21 | 22 | var foo = store.getById('foo', '1'); 23 | return Ember.run(function() { 24 | return foo.copy({property: null}).then(function (copy) { 25 | assert.equal(copy.get('property'), null); 26 | }); 27 | }); 28 | }); 29 | 30 | test('it shallow copies relation', function(assert) { 31 | assert.expect(1); 32 | 33 | var fooBar = store.getById('fooBar', '1'); 34 | return Ember.run(function() { 35 | return fooBar.copy().then(function (copy) { 36 | assert.equal(copy.get('fooFix.id'), '1'); 37 | }); 38 | }); 39 | }); 40 | 41 | test('it copies belongsTo relation', function(assert) { 42 | assert.expect(2); 43 | 44 | var bar = store.getById('bar', '1'); 45 | return Ember.run(function() { 46 | return bar.copy().then(function (copy) { 47 | assert.notEqual(copy.get('foo.id'), bar.get('foo.id')); 48 | assert.equal(copy.get('foo.property'), 'prop1'); 49 | }); 50 | }); 51 | }); 52 | 53 | test('it copies with empty belongsTo relation', function(assert) { 54 | assert.expect(2); 55 | 56 | var fooEmpty = store.getById('fooEmpty', '1'); 57 | return Ember.run(function() { 58 | return fooEmpty.copy().then(function (copy) { 59 | assert.equal(copy.get('property'), fooEmpty.get('property')); 60 | assert.equal(copy.get('foo'), null); 61 | }); 62 | }); 63 | }); 64 | 65 | test('it copies hasMany relation', function(assert) { 66 | assert.expect(5); 67 | 68 | var baz = store.getById('baz', '1'); 69 | return Ember.run(function() { 70 | return baz.copy().then(function (copy) { 71 | assert.equal(copy.get('foos.length'), 2); 72 | assert.notEqual(copy.get('foos.firstObject.id'), '1'); 73 | assert.notEqual(copy.get('foos.lastObject.id'), '2'); 74 | assert.equal(copy.get('foos.firstObject.property'), 'prop1'); 75 | assert.equal(copy.get('foos.lastObject.property'), 'prop2'); 76 | }); 77 | }); 78 | }); 79 | 80 | test('it copies complex objects', function(assert) { 81 | assert.expect(6); 82 | 83 | var multi = store.getById('multi', '1'); 84 | return Ember.run(function() { 85 | return multi.copy().then(function (copy) { 86 | assert.notEqual(copy.get('bars.firstObject.id'), '1'); 87 | assert.notEqual(copy.get('bars.firstObject.foo.id'), '1'); 88 | assert.equal(copy.get('bars.firstObject.foo.property'), 'prop1'); 89 | assert.notEqual(copy.get('baz.id'), '1'); 90 | assert.notEqual(copy.get('baz.foos.lastObject.id'), '2'); 91 | assert.equal(copy.get('baz.foos.lastObject.property'), 'prop2'); 92 | }); 93 | }); 94 | }); 95 | 96 | test('it overwrites relations', function(assert) { 97 | assert.expect(2); 98 | 99 | var multi = store.getById('multi', '1'); 100 | var myBaz = store.getById('baz', '2'); 101 | return Ember.run(function() { 102 | return multi.copy({baz: myBaz, bars: []}).then(function (copy) { 103 | assert.equal(copy.get('bars.length'), 0); 104 | assert.equal(copy.get('baz.foos.length'), myBaz.get('foos.length')); 105 | }); 106 | }); 107 | }); 108 | 109 | test('it excludes relations', function(assert) { 110 | assert.expect(2); 111 | 112 | var multi = store.getById('multi', '1'); 113 | return Ember.run(function() { 114 | return multi.copy({baz: null}).then(function (copy) { 115 | assert.equal(copy.get('bars.firstObject.foo.property'), 'prop1'); 116 | assert.equal(copy.get('baz'), null); 117 | }); 118 | }); 119 | }); 120 | 121 | test('it copies empty objects', function(assert) { 122 | assert.expect(3); 123 | 124 | var multi = store.getById('multi', '2'); 125 | return Ember.run(function() { 126 | return multi.copy().then(function (copy) { 127 | assert.notEqual(copy.get('id'), '2'); 128 | assert.equal(copy.get('bars.length'), 0); 129 | assert.equal(copy.get('baz.foos.firstObject.property'), 'prop1'); 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazybensch/ember-cli-copyable/56637848605acb3fbeb4ad9421835c96525155d2/vendor/.gitkeep --------------------------------------------------------------------------------