├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── rails.png │ ├── javascripts │ │ ├── app.js │ │ ├── application.js │ │ ├── controllers │ │ │ ├── contact_controller.js │ │ │ ├── contact_edit_controller.js │ │ │ ├── contacts_controller.js │ │ │ └── contacts_new_controller.js │ │ ├── helpers │ │ │ └── forms.js │ │ ├── lib │ │ │ └── ember-extensions.js │ │ ├── models │ │ │ ├── contact.js │ │ │ └── phone_number.js │ │ ├── router.js │ │ ├── routes │ │ │ ├── contact_route.js │ │ │ ├── contacts_new_route.js │ │ │ ├── contacts_route.js │ │ │ └── index_route.js │ │ ├── store.js │ │ ├── templates │ │ │ ├── _contact_edit_fields.hbs │ │ │ ├── application.hbs │ │ │ ├── contact.hbs │ │ │ ├── contact_edit.hbs │ │ │ ├── contact_in_list.hbs │ │ │ ├── contacts.hbs │ │ │ └── contacts │ │ │ │ └── new.hbs │ │ └── views │ │ │ ├── contact_edit_view.js │ │ │ ├── contact_in_list_view.js │ │ │ ├── contact_view.js │ │ │ ├── contacts_new_view.js │ │ │ └── contacts_view.js │ └── stylesheets │ │ ├── application.scss │ │ └── bootstrap.css ├── controllers │ ├── application_controller.rb │ └── contacts_controller.rb ├── helpers │ ├── application_helper.rb │ └── contacts_helper.rb ├── mailers │ └── .gitkeep ├── models │ ├── contact.rb │ └── phone_number.rb ├── serializers │ ├── contact_serializer.rb │ └── phone_number_serializer.rb └── views │ ├── application │ └── index.html.erb │ └── layouts │ └── application.html.erb ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backtrace_silencers.rb │ ├── inflections.rb │ ├── konacha.rb │ ├── mime_types.rb │ ├── secret_token.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml └── routes.rb ├── db ├── migrate │ ├── 20111230160032_create_contacts.rb │ ├── 20120913181318_add_contact_fields.rb │ └── 20120924225330_create_phone_numbers.rb ├── schema.rb └── seeds.rb ├── doc ├── README_FOR_APP └── ss.png ├── lib ├── assets │ └── .gitkeep └── tasks │ ├── .gitkeep │ └── serializers_test.rake ├── log └── .gitkeep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico └── robots.txt ├── script └── rails ├── test ├── controllers │ └── contacts_controller_test.rb ├── fixtures │ ├── contacts.yml │ └── phone_numbers.yml ├── integration │ └── contact_creation_test.rb ├── javascripts │ ├── adapter.js │ ├── integration │ │ └── add_contact_test.js │ ├── integration_test_helper.js │ ├── konacha_config.js │ ├── test_helper.js │ ├── test_utils.js │ └── unit │ │ ├── controllers │ │ ├── contact_controller_test.js │ │ ├── contacts_controller_test.js │ │ └── edit_contact_controller_test.js │ │ ├── helpers │ │ └── forms_test.js │ │ ├── models │ │ └── contact_test.js │ │ ├── router_test.js │ │ ├── routes │ │ └── add_contact_route_test.js │ │ └── store_test.js ├── models │ └── contact_test.rb ├── performance │ └── browsing_test.rb ├── serializers │ ├── contact_serializer_test.rb │ └── phone_number_serializer_test.rb └── test_helper.rb └── vendor ├── assets ├── javascripts │ ├── .gitkeep │ ├── md5.js │ └── sinon.js └── stylesheets │ └── .gitkeep └── plugins └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore bundler config 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | 13 | # Ignore all logfiles and tempfiles. 14 | /log/*.log 15 | /tmp 16 | 17 | # Ignore bin directory 18 | /bin 19 | 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '3.2.9' 4 | gem 'sqlite3' 5 | gem 'strong_parameters' 6 | 7 | # Gems used only for assets and not required 8 | # in production environments by default. 9 | group :assets do 10 | gem 'sass-rails' 11 | gem 'uglifier' 12 | gem 'anjlab-bootstrap-rails', '>= 2.1', :require => 'bootstrap-rails' 13 | gem 'therubyracer', '~> 0.12.0', :platforms => :ruby 14 | end 15 | 16 | gem 'active_model_serializers', github: 'rails-api/active_model_serializers' 17 | gem 'jquery-rails' 18 | gem 'ember-rails', github: 'emberjs/ember-rails' 19 | gem 'ember-source', '1.0.0.rc6' 20 | gem 'handlebars-source', '1.0.0.rc4' 21 | 22 | group :test, :development do 23 | gem 'minitest' 24 | gem 'minitest-rails' 25 | gem 'minitest-rails-capybara' 26 | gem 'capybara' 27 | gem 'konacha' 28 | gem 'poltergeist' 29 | end 30 | 31 | group :test do 32 | # Pretty printed test output 33 | gem 'turn', '~> 0.8.3' 34 | end 35 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/emberjs/ember-rails.git 3 | revision: 243c9b4e420bfdeb7a63e43cdf00fb15cbace3d4 4 | specs: 5 | ember-rails (0.13.0) 6 | active_model_serializers 7 | barber (>= 0.4.1) 8 | ember-data-source 9 | ember-source 10 | execjs (>= 1.2) 11 | handlebars-source 12 | railties (>= 3.1) 13 | 14 | GIT 15 | remote: git://github.com/rails-api/active_model_serializers.git 16 | revision: cee570557627af55ee6b00571eabfee37094bb7b 17 | specs: 18 | active_model_serializers (0.8.1) 19 | activemodel (>= 3.2) 20 | 21 | GEM 22 | remote: http://rubygems.org/ 23 | specs: 24 | actionmailer (3.2.9) 25 | actionpack (= 3.2.9) 26 | mail (~> 2.4.4) 27 | actionpack (3.2.9) 28 | activemodel (= 3.2.9) 29 | activesupport (= 3.2.9) 30 | builder (~> 3.0.0) 31 | erubis (~> 2.7.0) 32 | journey (~> 1.0.4) 33 | rack (~> 1.4.0) 34 | rack-cache (~> 1.2) 35 | rack-test (~> 0.6.1) 36 | sprockets (~> 2.2.1) 37 | activemodel (3.2.9) 38 | activesupport (= 3.2.9) 39 | builder (~> 3.0.0) 40 | activerecord (3.2.9) 41 | activemodel (= 3.2.9) 42 | activesupport (= 3.2.9) 43 | arel (~> 3.0.2) 44 | tzinfo (~> 0.3.29) 45 | activeresource (3.2.9) 46 | activemodel (= 3.2.9) 47 | activesupport (= 3.2.9) 48 | activesupport (3.2.9) 49 | i18n (~> 0.6) 50 | multi_json (~> 1.0) 51 | anjlab-bootstrap-rails (2.3.0.0) 52 | railties (>= 3.0) 53 | sass (>= 3.2) 54 | ansi (1.4.3) 55 | arel (3.0.2) 56 | barber (0.4.2) 57 | ember-source 58 | execjs 59 | handlebars-source 60 | builder (3.0.4) 61 | capybara (2.0.2) 62 | mime-types (>= 1.16) 63 | nokogiri (>= 1.3.3) 64 | rack (>= 1.0.0) 65 | rack-test (>= 0.5.4) 66 | selenium-webdriver (~> 2.0) 67 | xpath (~> 1.0.0) 68 | childprocess (0.3.9) 69 | ffi (~> 1.0, >= 1.0.11) 70 | colorize (0.5.8) 71 | ember-data-source (0.13) 72 | ember-source 73 | ember-source (1.0.0.rc6) 74 | handlebars-source (= 1.0.0.rc4) 75 | erubis (2.7.0) 76 | eventmachine (1.0.1) 77 | execjs (1.4.0) 78 | multi_json (~> 1.0) 79 | faye-websocket (0.4.7) 80 | eventmachine (>= 0.12.0) 81 | ffi (1.4.0) 82 | handlebars-source (1.0.0.rc4) 83 | hike (1.2.3) 84 | http_parser.rb (0.5.3) 85 | i18n (0.6.4) 86 | journey (1.0.4) 87 | jquery-rails (2.2.1) 88 | railties (>= 3.0, < 5.0) 89 | thor (>= 0.14, < 2.0) 90 | json (1.8.0) 91 | konacha (2.4.0) 92 | actionpack (>= 3.1, < 5) 93 | capybara 94 | colorize 95 | railties (>= 3.1, < 5) 96 | sprockets 97 | libv8 (3.11.8.13) 98 | mail (2.4.4) 99 | i18n (>= 0.4.0) 100 | mime-types (~> 1.16) 101 | treetop (~> 1.4.8) 102 | mime-types (1.21) 103 | minitest (4.6.0) 104 | minitest-capybara (0.1.0) 105 | capybara (>= 1.0) 106 | minitest-matchers (>= 1.2) 107 | minitest-matchers (1.2.0) 108 | minitest (>= 2.5.0) 109 | minitest-rails (0.5.1) 110 | minitest (~> 4.0) 111 | rails (>= 3.0) 112 | minitest-rails-capybara (0.5.1) 113 | capybara (~> 2.0) 114 | minitest-capybara (~> 0.1) 115 | minitest-matchers (>= 1.2) 116 | minitest-rails (~> 0.5) 117 | multi_json (1.7.7) 118 | nokogiri (1.5.6) 119 | poltergeist (1.1.0) 120 | capybara (~> 2.0, >= 2.0.1) 121 | faye-websocket (~> 0.4, >= 0.4.4) 122 | http_parser.rb (~> 0.5.3) 123 | polyglot (0.3.3) 124 | rack (1.4.5) 125 | rack-cache (1.2) 126 | rack (>= 0.4) 127 | rack-ssl (1.3.3) 128 | rack 129 | rack-test (0.6.2) 130 | rack (>= 1.0) 131 | rails (3.2.9) 132 | actionmailer (= 3.2.9) 133 | actionpack (= 3.2.9) 134 | activerecord (= 3.2.9) 135 | activeresource (= 3.2.9) 136 | activesupport (= 3.2.9) 137 | bundler (~> 1.0) 138 | railties (= 3.2.9) 139 | railties (3.2.9) 140 | actionpack (= 3.2.9) 141 | activesupport (= 3.2.9) 142 | rack-ssl (~> 1.3.2) 143 | rake (>= 0.8.7) 144 | rdoc (~> 3.4) 145 | thor (>= 0.14.6, < 2.0) 146 | rake (10.1.0) 147 | rdoc (3.12.2) 148 | json (~> 1.4) 149 | ref (1.0.2) 150 | rubyzip (0.9.9) 151 | sass (3.2.5) 152 | sass-rails (3.2.6) 153 | railties (~> 3.2.0) 154 | sass (>= 3.1.10) 155 | tilt (~> 1.3) 156 | selenium-webdriver (2.31.0) 157 | childprocess (>= 0.2.5) 158 | multi_json (~> 1.0) 159 | rubyzip 160 | websocket (~> 1.0.4) 161 | sprockets (2.2.2) 162 | hike (~> 1.2) 163 | multi_json (~> 1.0) 164 | rack (~> 1.0) 165 | tilt (~> 1.1, != 1.3.0) 166 | sqlite3 (1.3.7) 167 | strong_parameters (0.1.6) 168 | actionpack (~> 3.0) 169 | activemodel (~> 3.0) 170 | railties (~> 3.0) 171 | therubyracer (0.11.4) 172 | libv8 (~> 3.11.8.12) 173 | ref 174 | thor (0.18.1) 175 | tilt (1.4.1) 176 | treetop (1.4.12) 177 | polyglot 178 | polyglot (>= 0.3.1) 179 | turn (0.8.3) 180 | ansi 181 | tzinfo (0.3.35) 182 | uglifier (1.3.0) 183 | execjs (>= 0.3.0) 184 | multi_json (~> 1.0, >= 1.0.2) 185 | websocket (1.0.7) 186 | xpath (1.0.0) 187 | nokogiri (~> 1.3) 188 | 189 | PLATFORMS 190 | ruby 191 | 192 | DEPENDENCIES 193 | active_model_serializers! 194 | anjlab-bootstrap-rails (>= 2.1) 195 | capybara 196 | ember-rails! 197 | ember-source (= 1.0.0.rc6) 198 | handlebars-source (= 1.0.0.rc4) 199 | jquery-rails 200 | konacha 201 | minitest 202 | minitest-rails 203 | minitest-rails-capybara 204 | poltergeist 205 | rails (= 3.2.9) 206 | sass-rails 207 | sqlite3 208 | strong_parameters 209 | therubyracer 210 | turn (~> 0.8.3) 211 | uglifier 212 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Dan Gebhardt 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember Data Example 2 | 3 | This is a simple Rails 3.2 app created to demo [Ember.js](https://github.com/emberjs/ember.js), 4 | [Ember-Data](https://github.com/emberjs/data) and 5 | [Active Model Serializers](https://github.com/rails-api/active_model_serializers). 6 | It uses the edge versions of Ember and Ember Data. 7 | 8 | The app itself is a simple, single-page contact manager styled with Twitter Bootstrap. 9 | 10 | ![Screen shot](https://raw.github.com/dgeb/ember_data_example/master/doc/ss.png) 11 | 12 | ## Installation 13 | 14 | Assuming Ruby 1.9.2+ with bundler gem installed: 15 | 16 | $ bundle install 17 | $ bundle exec rake db:migrate 18 | $ rails s 19 | 20 | ## Test 21 | 22 | ### Rails 23 | 24 | MiniTest::Unit is used for testing the Rails application. To invoke tests: 25 | 26 | $ bundle exec rake test 27 | 28 | ### Integration 29 | 30 | Capybara is used for integration testing. By default, the `poltergeist` driver is used, although `selenium` could be used as well. 31 | To change drivers, update the following line in `test_helper.rb`: 32 | 33 | Capybara.default_driver = :selenium 34 | 35 | Integration tests are performed by default when running `bundle exec rake test`. You can *just* run integration tests with: 36 | 37 | $ bundle exec rake test:integration 38 | 39 | ### Ember 40 | 41 | The [konacha](https://github.com/jfirebaugh/konacha) test framework is used for testing the Ember application. 42 | To invoke the tests from the command line: 43 | 44 | $ bundle exec rake konacha:run 45 | 46 | To debug and run the tests in the browser, invoke: 47 | 48 | $ bundle exec rake konacha:serve 49 | 50 | ... and then navigate to [http://localhost:3500](http://localhost:3500). 51 | 52 | ## Contributions Welcome :) 53 | 54 | Please help improve this example by filing issues and pull requests! 55 | 56 | ## Variations 57 | 58 | [Chandu Tennety](https://github.com/tennety) is maintaining a 59 | [fork of this project](https://github.com/tennety/ember_data_example) that uses 60 | [Emblem.js](http://emblemjs.com/) templates. 61 | 62 | ## License 63 | 64 | Copyright 2012 Dan Gebhardt. MIT License (see LICENSE for details). 65 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | EmberDataExample::Application.load_tasks 8 | -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgeb/ember_data_example/4d18f6ea684e85bfe54a6fd91bc456f0f0f5b1b9/app/assets/images/rails.png -------------------------------------------------------------------------------- /app/assets/javascripts/app.js: -------------------------------------------------------------------------------- 1 | //= require_self 2 | //= require ./store 3 | //= require_tree ./models 4 | //= require_tree ./controllers 5 | //= require_tree ./views 6 | //= require_tree ./helpers 7 | //= require_tree ./templates 8 | //= require ./router 9 | //= require_tree ./routes 10 | 11 | App = Em.Application.create({LOG_TRANSITIONS: true}); 12 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into including all the files listed below. 2 | // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically 3 | // be included in the compiled file accessible from http://example.com/assets/application.js 4 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 5 | // the compiled file. 6 | // 7 | //= require jquery 8 | //= require handlebars 9 | //= require ember 10 | //= require ember-data 11 | //= require md5 12 | //= require jquery_ujs 13 | //= require_tree ./lib 14 | //= require app 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/contact_controller.js: -------------------------------------------------------------------------------- 1 | App.ContactController = Em.ObjectController.extend({ 2 | isEditing: false, 3 | needs: ['contactEdit'], 4 | 5 | startEditing: function() { 6 | var contactEditController = this.get('controllers.contactEdit'); 7 | contactEditController.set('model', this.get('model')); 8 | contactEditController.startEditing(); 9 | this.set('isEditing', true); 10 | }, 11 | 12 | stopEditing: function() { 13 | var contactEditController = this.get('controllers.contactEdit'); 14 | contactEditController.stopEditing(); 15 | this.set('isEditing', false); 16 | }, 17 | 18 | destroyRecord: function() { 19 | if (window.confirm("Are you sure you want to delete this contact?")) { 20 | this.get('model').deleteRecord(); 21 | this.get('store').commit(); 22 | 23 | // return to the main contacts listing page 24 | this.get('target.router').transitionTo('contacts.index'); 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/contact_edit_controller.js: -------------------------------------------------------------------------------- 1 | App.ContactEditController = Em.ObjectController.extend({ 2 | needs: ['contact'], 3 | 4 | startEditing: function() { 5 | // add the contact and its associated phone numbers to a local transaction 6 | var contact = this.get('model'); 7 | var transaction = contact.get('store').transaction(); 8 | transaction.add(contact); 9 | contact.get('phoneNumbers').forEach(function(phoneNumber) { 10 | transaction.add(phoneNumber); 11 | }); 12 | this.transaction = transaction; 13 | }, 14 | 15 | stopEditing: function() { 16 | // rollback the local transaction if it hasn't already been cleared 17 | var transaction = this.transaction; 18 | if (transaction) { 19 | transaction.rollback(); 20 | this.transaction = undefined; 21 | } 22 | }, 23 | 24 | save: function() { 25 | this.transaction.commit(); 26 | this.transaction = undefined; 27 | this.get('controllers.contact').stopEditing(); 28 | }, 29 | 30 | cancel: function() { 31 | this.get('controllers.contact').stopEditing(); 32 | }, 33 | 34 | addPhoneNumber: function() { 35 | this.get('model.phoneNumbers').createRecord(); 36 | }, 37 | 38 | removePhoneNumber: function(phoneNumber) { 39 | phoneNumber.deleteRecord(); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/contacts_controller.js: -------------------------------------------------------------------------------- 1 | App.ContactsController = Em.ArrayController.extend({ 2 | sortProperties: ['lastName', 'firstName'], 3 | activeContactId: null 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/contacts_new_controller.js: -------------------------------------------------------------------------------- 1 | App.ContactsNewController = Em.ObjectController.extend({ 2 | startEditing: function() { 3 | // create a new record on a local transaction 4 | this.transaction = this.get('store').transaction(); 5 | this.set('model', this.transaction.createRecord(App.Contact, {})); 6 | }, 7 | 8 | stopEditing: function() { 9 | // rollback the local transaction if it hasn't already been cleared 10 | if (this.transaction) { 11 | this.transaction.rollback(); 12 | this.transaction = null; 13 | } 14 | }, 15 | 16 | save: function() { 17 | // commit and then clear the local transaction 18 | this.transaction.commit(); 19 | this.transaction = null; 20 | }, 21 | 22 | transitionAfterSave: function() { 23 | // when creating new records, it's necessary to wait for the record to be assigned 24 | // an id before we can transition to its route (which depends on its id) 25 | if (this.get('model.id')) { 26 | this.transitionToRoute('contact', this.get('model')); 27 | } 28 | }.observes('model.id'), 29 | 30 | cancel: function() { 31 | this.stopEditing(); 32 | this.transitionToRoute('contacts.index'); 33 | }, 34 | 35 | addPhoneNumber: function() { 36 | this.get('model.phoneNumbers').createRecord(); 37 | }, 38 | 39 | removePhoneNumber: function(phoneNumber) { 40 | phoneNumber.deleteRecord(); 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /app/assets/javascripts/helpers/forms.js: -------------------------------------------------------------------------------- 1 | Ember.Handlebars.helper('submitButton', function(text) { 2 | return new Handlebars.SafeString(''); 3 | }); 4 | 5 | Ember.Handlebars.helper('mailto', function(address) { 6 | if (address) { 7 | return new Handlebars.SafeString('' + address + ''); 8 | } 9 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/lib/ember-extensions.js: -------------------------------------------------------------------------------- 1 | Em.TextField.reopen({ 2 | attributeBindings: ['required'] 3 | }); 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/models/contact.js: -------------------------------------------------------------------------------- 1 | App.Contact = DS.Model.extend({ 2 | firstName: DS.attr('string'), 3 | lastName: DS.attr('string'), 4 | email: DS.attr('string'), 5 | notes: DS.attr('string'), 6 | phoneNumbers: DS.hasMany('App.PhoneNumber'), 7 | 8 | fullName: function() { 9 | var firstName = this.get('firstName'), 10 | lastName = this.get('lastName'); 11 | 12 | if (!firstName && !lastName) { 13 | if (Ember.isNone(this.get('id'))) { 14 | return '(New Contact)'; 15 | } else { 16 | return '(No Name)'; 17 | } 18 | } 19 | 20 | if (firstName === undefined) firstName = ''; 21 | if (lastName === undefined) lastName = ''; 22 | 23 | return firstName + ' ' + lastName; 24 | }.property('firstName', 'lastName'), 25 | 26 | gravatar: function() { 27 | var email = this.get('email'); 28 | if (!email) email = ''; 29 | return 'http://www.gravatar.com/avatar/' + MD5(email); 30 | }.property('email') 31 | }); 32 | -------------------------------------------------------------------------------- /app/assets/javascripts/models/phone_number.js: -------------------------------------------------------------------------------- 1 | App.PhoneNumber = DS.Model.extend({ 2 | number: DS.attr('string'), 3 | contact: DS.belongsTo('App.Contact') 4 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/router.js: -------------------------------------------------------------------------------- 1 | App.Router.map(function() { 2 | this.resource('contacts', function() { 3 | this.route('new'); 4 | this.resource('contact', {path: ':contact_id'}); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/routes/contact_route.js: -------------------------------------------------------------------------------- 1 | App.ContactRoute = Ember.Route.extend({ 2 | setupController: function(controller, model) { 3 | this._super.apply(this, arguments); 4 | 5 | // reset editing state 6 | // note: this is necessary here because `deactivate` won't be called when transitioning 7 | // from one ContactRoute directly into another 8 | if (controller.get('isEditing')) { 9 | controller.stopEditing(); 10 | } 11 | 12 | // highlight this contact as active 13 | this.controllerFor('contacts').set('activeContactId', model.get('id')); 14 | }, 15 | 16 | deactivate: function() { 17 | var controller = this.controllerFor('contact'); 18 | 19 | // reset editing state 20 | if (controller.get('isEditing')) { 21 | controller.stopEditing(); 22 | } 23 | 24 | // un-highlight the active contact (perhaps temporarily) 25 | this.controllerFor('contacts').set('activeContactId', null); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /app/assets/javascripts/routes/contacts_new_route.js: -------------------------------------------------------------------------------- 1 | App.ContactsNewRoute = Ember.Route.extend({ 2 | model: function() { 3 | // Because we are maintaining a transaction locally in the controller for editing, 4 | // the new record needs to be created in the controller. 5 | return null; 6 | }, 7 | 8 | setupController: function(controller) { 9 | this._super.apply(this, arguments); 10 | controller.startEditing(); 11 | }, 12 | 13 | deactivate: function() { 14 | this.controllerFor('contacts.new').stopEditing(); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/routes/contacts_route.js: -------------------------------------------------------------------------------- 1 | App.ContactsRoute = Ember.Route.extend({ 2 | model: function() { 3 | // request all contacts from adapter 4 | App.Contact.find(); 5 | 6 | // filter contacts to exclude new ones 7 | return App.Contact.filter(function(contact) { 8 | return !contact.get('isNew'); 9 | }); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /app/assets/javascripts/routes/index_route.js: -------------------------------------------------------------------------------- 1 | App.IndexRoute = Ember.Route.extend({ 2 | redirect: function() { 3 | this.transitionTo('contacts'); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/store.js: -------------------------------------------------------------------------------- 1 | App.Adapter = DS.RESTAdapter.extend({ 2 | bulkCommit: false 3 | }); 4 | 5 | App.Adapter.map('App.Contact', { 6 | phoneNumbers: {embedded: 'always'} 7 | }); 8 | 9 | App.Store = DS.Store.extend({ 10 | revision: 12, 11 | adapter: App.Adapter.create() 12 | }); 13 | -------------------------------------------------------------------------------- /app/assets/javascripts/templates/_contact_edit_fields.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{view Ember.TextField valueBinding="firstName" id="firstName" placeholder="First name" required="true"}} 5 |
6 |
7 |
8 | 9 |
10 | {{view Ember.TextField valueBinding="lastName" id="lastName" placeholder="Last name" required="true"}} 11 |
12 |
13 |
14 | 15 |
16 | {{view Ember.TextField valueBinding="email" id="email" type="email" placeholder="Email address"}} 17 |
18 |
19 |
20 | 21 |
22 | {{view Ember.TextArea valueBinding="notes" id="notes" placeholder="Notes"}} 23 |
24 |
25 |
26 | 27 |
28 | {{#each phoneNumbers}} 29 |
30 | {{view Ember.TextField valueBinding="number" placeholder="Number"}} 31 | Remove 32 |
33 | {{/each}} 34 | Add a phone number 35 |
36 |
37 | -------------------------------------------------------------------------------- /app/assets/javascripts/templates/application.hbs: -------------------------------------------------------------------------------- 1 | 11 |
12 |
13 | {{outlet}} 14 |
15 |
16 | -------------------------------------------------------------------------------- /app/assets/javascripts/templates/contact.hbs: -------------------------------------------------------------------------------- 1 | {{#if controller.isEditing}} 2 | {{render "contactEdit"}} 3 | {{else}} 4 |

{{fullName}}

5 | 6 |
7 | {{#if email}} 8 |
Email:
9 |
{{mailto email}}
10 | {{/if}} 11 | {{#if notes}} 12 |
Notes:
13 |
{{notes}}
14 | {{/if}} 15 | {{#if phoneNumbers.length}} 16 |
Phone:
17 |
18 | {{#each phoneNumber in phoneNumbers}} 19 |
{{phoneNumber.number}}
20 | {{/each}} 21 |
22 | {{/if}} 23 |
24 | 25 |
26 | Edit 27 | Remove 28 |
29 | {{/if}} 30 | -------------------------------------------------------------------------------- /app/assets/javascripts/templates/contact_edit.hbs: -------------------------------------------------------------------------------- 1 |
2 | Edit Contact 3 | 4 | {{partial "contact_edit_fields"}} 5 | 6 |
7 | {{submitButton "Update"}} 8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /app/assets/javascripts/templates/contact_in_list.hbs: -------------------------------------------------------------------------------- 1 | {{#linkTo "contact" contact}}{{contact.fullName}}{{/linkTo}} 2 | -------------------------------------------------------------------------------- /app/assets/javascripts/templates/contacts.hbs: -------------------------------------------------------------------------------- 1 |
2 | 10 |
11 |
12 | {{outlet}} 13 |
14 | 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/templates/contacts/new.hbs: -------------------------------------------------------------------------------- 1 |
2 | Create Contact 3 | 4 | {{partial "contact_edit_fields"}} 5 | 6 |
7 | {{submitButton "Create"}} 8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /app/assets/javascripts/views/contact_edit_view.js: -------------------------------------------------------------------------------- 1 | App.ContactEditView = Ember.View.extend({ 2 | didInsertElement: function() { 3 | this.$('input:first').focus(); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/views/contact_in_list_view.js: -------------------------------------------------------------------------------- 1 | App.ContactInListView = Em.View.extend({ 2 | templateName: 'contact_in_list', 3 | tagName: 'li', 4 | classNameBindings: 'isActive:active', 5 | 6 | isActive: function() { 7 | return this.get('content.id') === this.get('controller.activeContactId'); 8 | }.property('controller.activeContactId') 9 | }); 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/views/contact_view.js: -------------------------------------------------------------------------------- 1 | App.ContactView = Em.View.extend({ 2 | classNames: 'contact-details' 3 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/views/contacts_new_view.js: -------------------------------------------------------------------------------- 1 | App.ContactsNewView = Ember.View.extend({ 2 | didInsertElement: function() { 3 | this.$('input:first').focus(); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/views/contacts_view.js: -------------------------------------------------------------------------------- 1 | App.ContactsView = Em.View.extend({ 2 | }); 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "twitter/bootstrap"; 2 | 3 | .navbar { 4 | .brand a { 5 | &:hover { 6 | text-decoration: none; 7 | } 8 | } 9 | } 10 | 11 | #main { 12 | margin-top: 60px; 13 | 14 | .contact-details { 15 | h2 img { 16 | margin-right: 15px; 17 | width: 80px; 18 | height: 80px; 19 | } 20 | 21 | dl.dl-horizontal { 22 | dt { 23 | width: 80px; 24 | margin-bottom: 8px; 25 | } 26 | dd { 27 | margin-left: 95px; 28 | margin-bottom: 8px; 29 | } 30 | } 31 | } 32 | 33 | form { 34 | .phone-number { 35 | margin-bottom: 8px; 36 | white-space: nowrap; 37 | 38 | input[type="text"] { 39 | width: 105px; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | 4 | def index 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/contacts_controller.rb: -------------------------------------------------------------------------------- 1 | class ContactsController < ApplicationController 2 | # GET /contacts.json 3 | def index 4 | render json: Contact.all 5 | end 6 | 7 | # GET /contacts/1.json 8 | def show 9 | contact = Contact.find(params[:id]) 10 | render json: contact 11 | end 12 | 13 | # POST /contacts.json 14 | def create 15 | contact = Contact.new 16 | if update_contact(contact) 17 | render json: contact, status: :created 18 | else 19 | render json: contact.errors, status: :unprocessable_entity 20 | end 21 | end 22 | 23 | # PUT /contacts/1.json 24 | def update 25 | contact = Contact.find(params[:id]) 26 | if update_contact(contact) 27 | render json: contact, status: :ok 28 | else 29 | render json: contact.errors, status: :unprocessable_entity 30 | end 31 | end 32 | 33 | # DELETE /contacts/1.json 34 | def destroy 35 | contact = Contact.find(params[:id]) 36 | contact.destroy 37 | render json: nil, status: :ok 38 | end 39 | 40 | private 41 | 42 | def permitted_params 43 | params.require(:contact).permit(:first_name, 44 | :last_name, 45 | :email, 46 | :notes, 47 | phone_numbers: [:id, :number]) 48 | end 49 | 50 | def update_contact(contact) 51 | contact_params = permitted_params 52 | phone_numbers_param = contact_params.extract!(:phone_numbers) 53 | phone_numbers_param = phone_numbers_param[:phone_numbers] 54 | phone_numbers_param ||= [] 55 | 56 | # Because updates to the contact and its associations should be atomic, 57 | # wrap them in a transaction. 58 | Contact.transaction do 59 | # Update the contact's own attributes first. 60 | contact.attributes = contact_params 61 | contact.save! 62 | 63 | # Update the contact's phone numbers, creating/destroying as appropriate. 64 | specified_phone_numbers = [] 65 | phone_numbers_param.each do |phone_number_params| 66 | if phone_number_params[:id] 67 | pn = contact.phone_numbers.find(phone_number_params[:id]) 68 | pn.update_attributes(phone_number_params) 69 | else 70 | pn = contact.phone_numbers.create(phone_number_params) 71 | end 72 | specified_phone_numbers << pn 73 | end 74 | contact.phone_numbers.each do |pn| 75 | pn.destroy unless specified_phone_numbers.include?(pn) 76 | end 77 | end 78 | 79 | # Important! Reload the contact to ensure that changes to its associations 80 | # (i.e. phone numbers) will be serialized correctly. 81 | contact.reload 82 | 83 | return true 84 | rescue 85 | return false 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/contacts_helper.rb: -------------------------------------------------------------------------------- 1 | module ContactsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgeb/ember_data_example/4d18f6ea684e85bfe54a6fd91bc456f0f0f5b1b9/app/mailers/.gitkeep -------------------------------------------------------------------------------- /app/models/contact.rb: -------------------------------------------------------------------------------- 1 | class Contact < ActiveRecord::Base 2 | validates :first_name, :presence => true 3 | validates :last_name, :presence => true 4 | has_many :phone_numbers, :dependent => :destroy 5 | 6 | def full_name 7 | self.first_name + ' ' + self.last_name 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/phone_number.rb: -------------------------------------------------------------------------------- 1 | class PhoneNumber < ActiveRecord::Base 2 | belongs_to :contact 3 | attr_accessible :number, :contact_id 4 | validates :contact_id, :presence => true 5 | end 6 | -------------------------------------------------------------------------------- /app/serializers/contact_serializer.rb: -------------------------------------------------------------------------------- 1 | class ContactSerializer < ActiveModel::Serializer 2 | attributes :id, 3 | :first_name, 4 | :last_name, 5 | :email, 6 | :notes 7 | 8 | has_many :phone_numbers, embed: :objects 9 | end 10 | -------------------------------------------------------------------------------- /app/serializers/phone_number_serializer.rb: -------------------------------------------------------------------------------- 1 | class PhoneNumberSerializer < ActiveModel::Serializer 2 | attributes :id, :number, :contact_id 3 | end -------------------------------------------------------------------------------- /app/views/application/index.html.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgeb/ember_data_example/4d18f6ea684e85bfe54a6fd91bc456f0f0f5b1b9/app/views/application/index.html.erb -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EmberDataExample 5 | <%= stylesheet_link_tag "application" %> 6 | <%= javascript_include_tag "application" %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | <%= yield %> 11 | 12 | 13 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run EmberDataExample::Application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | if defined?(Bundler) 6 | # If you precompile assets before deploying to production, use this line 7 | Bundler.require(*Rails.groups(:assets => %w(development test))) 8 | # If you want your assets lazily compiled in production, use this line 9 | # Bundler.require(:default, :assets, Rails.env) 10 | end 11 | 12 | module EmberDataExample 13 | class Application < Rails::Application 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration should go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded. 17 | 18 | # Custom directories with classes and modules you want to be autoloadable. 19 | # config.autoload_paths += %W(#{config.root}/extras) 20 | 21 | # Only load the plugins named here, in the order given (default is alphabetical). 22 | # :all can be used as a placeholder for all plugins not explicitly named. 23 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 24 | 25 | # Activate observers that should always be running. 26 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 27 | 28 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 29 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 30 | # config.time_zone = 'Central Time (US & Canada)' 31 | 32 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 33 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 34 | # config.i18n.default_locale = :de 35 | 36 | # Configure the default encoding used in templates for Ruby 1.9. 37 | config.encoding = "utf-8" 38 | 39 | # Configure sensitive parameters which will be filtered from the log file. 40 | config.filter_parameters += [:password] 41 | 42 | # Enable the asset pipeline 43 | config.assets.enabled = true 44 | 45 | # Version of your assets, change this if you want to expire all your assets 46 | config.assets.version = '1.0' 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | EmberDataExample::Application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | EmberDataExample::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | 25 | # Do not compress assets 26 | config.assets.compress = false 27 | 28 | # Expands the lines which load the assets 29 | config.assets.debug = true 30 | 31 | # Use dev assets for ember 32 | config.ember.variant = :development 33 | end 34 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | EmberDataExample::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Compress JavaScripts and CSS 15 | config.assets.compress = true 16 | 17 | # Don't fallback to assets pipeline if a precompiled asset is missed 18 | config.assets.compile = false 19 | 20 | # Generate digests for assets URLs 21 | config.assets.digest = true 22 | 23 | # Defaults to Rails.root.join("public/assets") 24 | # config.assets.manifest = YOUR_PATH 25 | 26 | # Specifies the header that your server uses for sending files 27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 29 | 30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 31 | # config.force_ssl = true 32 | 33 | # See everything in the log (default is :info) 34 | # config.log_level = :debug 35 | 36 | # Use a different logger for distributed setups 37 | # config.logger = SyslogLogger.new 38 | 39 | # Use a different cache store in production 40 | # config.cache_store = :mem_cache_store 41 | 42 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 43 | # config.action_controller.asset_host = "http://assets.example.com" 44 | 45 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 46 | # config.assets.precompile += %w( search.js ) 47 | 48 | # Disable delivery errors, bad email addresses will be ignored 49 | # config.action_mailer.raise_delivery_errors = false 50 | 51 | # Enable threaded mode 52 | # config.threadsafe! 53 | 54 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 55 | # the I18n.default_locale when a translation can not be found) 56 | config.i18n.fallbacks = true 57 | 58 | # Send deprecation notices to registered listeners 59 | config.active_support.deprecation = :notify 60 | 61 | # Use production assets for ember 62 | config.ember.variant = :production 63 | end 64 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | EmberDataExample::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_assets = true 12 | config.static_cache_control = "public, max-age=3600" 13 | 14 | # Log error messages when you accidentally call methods on nil 15 | config.whiny_nils = true 16 | 17 | # Show full error reports and disable caching 18 | config.consider_all_requests_local = true 19 | config.action_controller.perform_caching = false 20 | 21 | # Raise exceptions instead of rendering exception templates 22 | config.action_dispatch.show_exceptions = false 23 | 24 | # Disable request forgery protection in test environment 25 | config.action_controller.allow_forgery_protection = false 26 | 27 | # Tell Action Mailer not to deliver emails to the real world. 28 | # The :test delivery method accumulates sent emails in the 29 | # ActionMailer::Base.deliveries array. 30 | config.action_mailer.delivery_method = :test 31 | 32 | # Use SQL instead of Active Record's schema dumper when creating the test database. 33 | # This is necessary if your schema can't be completely dumped by the schema dumper, 34 | # like if you have constraints or database-specific column types 35 | # config.active_record.schema_format = :sql 36 | 37 | # Print deprecation notices to the stderr 38 | config.active_support.deprecation = :stderr 39 | 40 | # Use dev assets for ember 41 | config.ember.variant = :development 42 | end 43 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /config/initializers/konacha.rb: -------------------------------------------------------------------------------- 1 | Konacha.configure do |config| 2 | require 'capybara/poltergeist' 3 | 4 | config.spec_dir = "test/javascripts" 5 | config.driver = :poltergeist 6 | config.stylesheets = %w(application) 7 | end if defined?(Konacha) -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | EmberDataExample::Application.config.secret_token = 'f13cf052124011bee5b6456191168cd76eb679a728da88dbe10facd7cb95a75dfedde89c17ef0eca723c362654f92aa7795c9c8cd5bcc9e235862905e0596806' 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | EmberDataExample::Application.config.session_store :cookie_store, key: '_ember_data_example_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # EmberDataExample::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | EmberDataExample::Application.routes.draw do 2 | resources :contacts 3 | root :to => 'application#index' 4 | match '/*path' => 'application#index' 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20111230160032_create_contacts.rb: -------------------------------------------------------------------------------- 1 | class CreateContacts < ActiveRecord::Migration 2 | def change 3 | create_table :contacts do |t| 4 | t.string :first_name 5 | t.string :last_name 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20120913181318_add_contact_fields.rb: -------------------------------------------------------------------------------- 1 | class AddContactFields < ActiveRecord::Migration 2 | def change 3 | change_table :contacts do |t| 4 | t.string :email 5 | t.text :notes 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20120924225330_create_phone_numbers.rb: -------------------------------------------------------------------------------- 1 | class CreatePhoneNumbers < ActiveRecord::Migration 2 | def change 3 | create_table :phone_numbers do |t| 4 | t.string :number 5 | t.references :contact 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended to check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(:version => 20120924225330) do 15 | 16 | create_table "contacts", :force => true do |t| 17 | t.string "first_name" 18 | t.string "last_name" 19 | t.datetime "created_at", :null => false 20 | t.datetime "updated_at", :null => false 21 | t.string "email" 22 | t.text "notes" 23 | end 24 | 25 | create_table "phone_numbers", :force => true do |t| 26 | t.string "number" 27 | t.integer "contact_id" 28 | t.datetime "created_at", :null => false 29 | t.datetime "updated_at", :null => false 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /doc/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgeb/ember_data_example/4d18f6ea684e85bfe54a6fd91bc456f0f0f5b1b9/doc/ss.png -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgeb/ember_data_example/4d18f6ea684e85bfe54a6fd91bc456f0f0f5b1b9/lib/assets/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgeb/ember_data_example/4d18f6ea684e85bfe54a6fd91bc456f0f0f5b1b9/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/serializers_test.rake: -------------------------------------------------------------------------------- 1 | Rake::TestTask.new do |t| 2 | t.libs << 'lib' << 'test' 3 | t.test_files = FileList['test/serializers/**/*_test.rb'] 4 | t.verbose = true 5 | end 6 | -------------------------------------------------------------------------------- /log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgeb/ember_data_example/4d18f6ea684e85bfe54a6fd91bc456f0f0f5b1b9/log/.gitkeep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgeb/ember_data_example/4d18f6ea684e85bfe54a6fd91bc456f0f0f5b1b9/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /test/controllers/contacts_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ContactsControllerTest < ActionController::TestCase 4 | describe ContactsController do 5 | setup do 6 | @contact = contacts(:dan) 7 | end 8 | 9 | it "should get index" do 10 | get :index 11 | assert_response :success 12 | end 13 | 14 | it "should create a contact with just a name" do 15 | assert_difference('Contact.count') do 16 | post :create, 17 | contact: {first_name: 'Test', 18 | last_name: 'Case'} 19 | end 20 | end 21 | 22 | it "should create a contact with a name and phone numbers" do 23 | assert_difference('Contact.count') do 24 | assert_difference('PhoneNumber.count', 2) do 25 | post :create, 26 | contact: {first_name: 'Test', 27 | last_name: 'Case', 28 | phone_numbers: [{number: '555-1212'}, 29 | {number: '555-1234'}]} 30 | end 31 | end 32 | end 33 | 34 | it "should show contact" do 35 | get :show, id: @contact.to_param 36 | assert_response :success 37 | end 38 | 39 | it "should update contact" do 40 | assert_equal 2, @contact.phone_numbers.length 41 | 42 | put :update, id: @contact.to_param, contact: @contact.attributes 43 | assert_response :success 44 | 45 | assert_equal 2, @contact.phone_numbers.length 46 | end 47 | 48 | it "should destroy contact" do 49 | assert_difference('Contact.count', -1) do 50 | delete :destroy, id: @contact.to_param 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/fixtures/contacts.yml: -------------------------------------------------------------------------------- 1 | dan: 2 | first_name: Dan 3 | last_name: Gebhardt 4 | email: dan@example.com 5 | notes: Writes sample code without tests :/ 6 | 7 | joe: 8 | first_name: Joe 9 | last_name: Blow 10 | email: joe@example.com 11 | notes: Lousy plumber 12 | -------------------------------------------------------------------------------- /test/fixtures/phone_numbers.yml: -------------------------------------------------------------------------------- 1 | dan_home: 2 | contact: dan 3 | number: 603-555-1212 4 | 5 | dan_mobile: 6 | contact: dan 7 | number: 603-555-1234 8 | -------------------------------------------------------------------------------- /test/integration/contact_creation_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ContactCreationTest < Capybara::Rails::TestCase 4 | describe "Adding a new contact" do 5 | setup do 6 | visit root_path 7 | click_on 'Add Contact' 8 | end 9 | 10 | describe "with valid data" do 11 | setup do 12 | fill_in 'First name', with: 'Wayne' 13 | fill_in 'Last name', with: 'Campbell' 14 | fill_in 'Email', with: 'wayne.campbell@worlds.com' 15 | fill_in 'Notes', with: 'Party on, Garth!' 16 | find(:xpath, '//a[text()=" Add a phone number"]').click 17 | find('div.phone-number input').set('1234567890') 18 | click_on 'Create' 19 | end 20 | 21 | it "should create the record and then show its details" do 22 | wayne_details = page.find('div.contact-details') 23 | assert wayne_details.has_content?('Wayne Campbell') 24 | assert wayne_details.has_content?('wayne.campbell@worlds.com') 25 | assert wayne_details.has_content?('Party on, Garth') 26 | assert wayne_details.has_content?('1234567890') 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/javascripts/adapter.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var done, doneTimeout, countAsync, emberBdd; 3 | 4 | done = null; 5 | doneTimeout = null; 6 | isAsync = false; 7 | 8 | Ember.Test.MochaAdapter = Ember.Test.Adapter.extend({ 9 | init: function() { 10 | this._super(); 11 | window.Mocha.interfaces['ember-bdd'] = emberBdd; 12 | window.mocha.ui('ember-bdd'); 13 | }, 14 | asyncStart: function() { 15 | isAsync = true; 16 | clearTimeout(doneTimeout); 17 | }, 18 | asyncEnd: function() { 19 | isAsync = false; 20 | if (done) { 21 | doneTimeout = setTimeout(function() { 22 | var d = done; 23 | done = null; 24 | d(); 25 | }); 26 | } 27 | }, 28 | exception: function(reason) { 29 | var error, d; 30 | 31 | error = new Error(reason); 32 | if (done) { 33 | d = done; 34 | done = null; 35 | d(error); 36 | } else { 37 | setTimeout(function() { 38 | throw error; 39 | }); 40 | } 41 | } 42 | }); 43 | 44 | 45 | function fixAsync(suites, methodName) { 46 | return function(fn) { 47 | if (fn.length === 1) { 48 | suites[0][methodName](fn); 49 | } else { 50 | suites[0][methodName](function(d) { 51 | invoke(this, fn, d); 52 | }); 53 | } 54 | }; 55 | } 56 | 57 | function invoke(context, fn, d) { 58 | done = d; 59 | fn.call(context); 60 | if (!isAsync) { 61 | done = null; 62 | d(); 63 | } 64 | } 65 | 66 | 67 | /** 68 | ember-bdd mocha interface. 69 | This interface allows 70 | the Ember.js tester 71 | to forget about sync / async 72 | and treat all tests the same. 73 | 74 | This interface, along with the adapter 75 | will take care of handling sync vs async 76 | */ 77 | 78 | emberBdd = function(suite) { 79 | var suites = [suite]; 80 | 81 | suite.on('pre-require', function(context, file, mocha) { 82 | 83 | context.before = fixAsync(suites, 'beforeAll'); 84 | 85 | context.after = fixAsync(suites, 'afterAll'); 86 | 87 | context.beforeEach = fixAsync(suites, 'beforeEach'); 88 | 89 | context.afterEach = fixAsync(suites, 'afterEach'); 90 | 91 | 92 | context.it = context.specify = function(title, fn){ 93 | var suite = suites[0], test; 94 | if (suite.pending) { 95 | fn = null; 96 | } 97 | if (!fn || fn.length === 1) { 98 | test = new Mocha.Test(title, fn); 99 | } else { 100 | var method = function(d) { 101 | invoke(this, fn, d); 102 | }; 103 | method.toString = function() { 104 | return fn.toString(); 105 | } 106 | test = new Mocha.Test(title, method); 107 | } 108 | suite.addTest(test); 109 | return test; 110 | }; 111 | 112 | context.describe = context.context = function(title, fn){ 113 | var suite = Mocha.Suite.create(suites[0], title); 114 | suites.unshift(suite); 115 | fn.call(suite); 116 | suites.shift(); 117 | return suite; 118 | }; 119 | 120 | context.xdescribe = 121 | context.xcontext = 122 | context.describe.skip = function(title, fn){ 123 | var suite = Mocha.Suite.create(suites[0], title); 124 | suite.pending = true; 125 | suites.unshift(suite); 126 | fn.call(suite); 127 | suites.shift(); 128 | }; 129 | 130 | context.describe.only = function(title, fn){ 131 | var suite = context.describe(title, fn); 132 | mocha.grep(suite.fullTitle()); 133 | }; 134 | 135 | 136 | context.it.only = function(title, fn){ 137 | var test = context.it(title, fn); 138 | mocha.grep(test.fullTitle()); 139 | }; 140 | 141 | 142 | context.xit = 143 | context.xspecify = 144 | context.it.skip = function(title){ 145 | context.it(title); 146 | }; 147 | 148 | 149 | }); 150 | 151 | }; 152 | 153 | 154 | }()); 155 | -------------------------------------------------------------------------------- /test/javascripts/integration/add_contact_test.js: -------------------------------------------------------------------------------- 1 | //= require integration_test_helper 2 | 3 | describe("Integration: Adding a Contact", function() { 4 | describe("Given I click on the add contact button", function(){ 5 | beforeEach(function(){ 6 | visit('/').then(function(){ 7 | click('.btn'); 8 | }); 9 | }); 10 | 11 | it("should display the create contact form", function(){ 12 | assert.equal(find('form legend').text(), "Create Contact"); 13 | }); 14 | 15 | describe('When I submit valid information', function(){ 16 | beforeEach(function(){ 17 | server.respondWith( 18 | "POST", 19 | "/contacts", 20 | [ 21 | 201, 22 | { "Content-Type": "application/json" }, 23 | JSON.stringify({ 24 | contact: { 25 | id: 25, 26 | first_name: "User", 27 | last_name: "Example", 28 | email: "user@example.com", 29 | notes: null, 30 | phone_numbers: [] 31 | } 32 | }) 33 | ] 34 | ); 35 | 36 | fillIn('#firstName', 'User'); 37 | fillIn('#lastName', 'Example'); 38 | fillIn('#email' , 'user@example.com'); 39 | click('.btn-primary'); 40 | }); 41 | 42 | it("should display my name under the all contacts list", function(){ 43 | assert.equal(find('.sidebar-nav a').text(), "User Example"); 44 | }); 45 | 46 | describe("And I click my name", function(){ 47 | beforeEach(function(){ 48 | click('.sidebar-nav a'); 49 | }); 50 | 51 | it("should be on my profile page", function(){ 52 | assert.equal(find('h2').text(), "User Example"); 53 | }); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/javascripts/integration_test_helper.js: -------------------------------------------------------------------------------- 1 | //= require konacha_config 2 | //= require sinon 3 | //= require application 4 | //= require test_utils 5 | //= require adapter 6 | 7 | // Sinon fake server 8 | var server; 9 | 10 | sinon.config = { 11 | useFakeTimers: false 12 | } 13 | 14 | // Stub out Konacha.reset() 15 | Konacha.reset = Ember.K; 16 | 17 | Ember.Test.adapter = Ember.Test.MochaAdapter.create(); 18 | App.setupForTesting(); 19 | App.injectTestHelpers(); 20 | 21 | beforeEach(function(done) { 22 | // Fake XHR 23 | server = sinon.fakeServer.create(); 24 | server.autoRespond = true; 25 | Ember.run(function() { 26 | // Advance App readiness, which was deferred above. 27 | server.respondWith( 28 | "GET", 29 | "/contacts", 30 | [200, { "Content-Type": "application/json" }, JSON.stringify({contacts: []})] 31 | ); 32 | App.advanceReadiness(); 33 | 34 | // Setup is complete when the App readiness promise resolves 35 | App.then(function() { 36 | done(); 37 | }); 38 | }); 39 | }); 40 | 41 | afterEach(function() { 42 | server.respondWith( 43 | "GET", 44 | "/contacts", 45 | [200, { "Content-Type": "application/json" }, JSON.stringify({contacts: []})] 46 | ); 47 | App.reset(); 48 | 49 | // Restore XHR 50 | server.restore(); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /test/javascripts/konacha_config.js: -------------------------------------------------------------------------------- 1 | // set the Mocha test interface 2 | // see http://visionmedia.github.com/mocha/#interfaces 3 | mocha.ui('bdd'); 4 | 5 | // ignore the following globals during leak detection 6 | mocha.globals(['Ember', 'DS', 'App', 'MD5']); 7 | 8 | // Show stack trace on failing assertion. 9 | chai.Assertion.includeStack = true; 10 | 11 | ENV = { 12 | TESTING: true 13 | }; 14 | -------------------------------------------------------------------------------- /test/javascripts/test_helper.js: -------------------------------------------------------------------------------- 1 | //= require konacha_config 2 | //= require sinon 3 | //= require application 4 | //= require test_utils 5 | 6 | // Sinon fake server 7 | var server; 8 | 9 | // Stub out Konacha.reset() 10 | Konacha.reset = Ember.K; 11 | 12 | // Prevent automatic scheduling of runloops. For tests, we 13 | // want to have complete control of runloops. 14 | Ember.testing = true; 15 | 16 | // Defer App readiness (it will be advanced in each test below) 17 | App.deferReadiness(); 18 | 19 | // Prevent the router from manipulating the browser's URL. 20 | App.Router.reopen({location: 'none'}); 21 | 22 | beforeEach(function(done) { 23 | // Fake XHR 24 | server = sinon.fakeServer.create(); 25 | 26 | Ember.run(function() { 27 | // Advance App readiness, which was deferred above. 28 | App.advanceReadiness(); 29 | 30 | // Setup is complete when the App readiness promise resolves 31 | App.then(function() { 32 | done(); 33 | }); 34 | }); 35 | }); 36 | 37 | afterEach(function() { 38 | // Reset App state 39 | App.reset(); 40 | 41 | // Restore XHR 42 | server.restore(); 43 | }); 44 | -------------------------------------------------------------------------------- /test/javascripts/test_utils.js: -------------------------------------------------------------------------------- 1 | var lookupStore = function() { 2 | return App.__container__.lookup('store:main'); 3 | }; 4 | 5 | var lookupRouter = function() { 6 | return App.__container__.lookup('router:main'); 7 | }; 8 | 9 | var lookupController = function(controllerName, options) { 10 | return App.__container__.lookup('controller:' + controllerName, options); 11 | }; 12 | 13 | var appendView = function(view) { 14 | Ember.run(function() { 15 | view.append('#konacha'); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /test/javascripts/unit/controllers/contact_controller_test.js: -------------------------------------------------------------------------------- 1 | //= require test_helper 2 | 3 | describe("Controllers: App.ContactController", function() { 4 | // TODO - fill out tests 5 | }); 6 | -------------------------------------------------------------------------------- /test/javascripts/unit/controllers/contacts_controller_test.js: -------------------------------------------------------------------------------- 1 | //= require test_helper 2 | 3 | describe("Controllers: App.ContactsController", function() { 4 | var controller, 5 | store; 6 | 7 | beforeEach(function() { 8 | store = lookupStore(); 9 | 10 | Ember.run(function() { 11 | store.loadMany(App.Contact, [ 12 | {id: 1, first_name: 'Aaron', last_name: 'Zeebob'}, 13 | {id: 2, first_name: 'Aaaron', last_name: 'Zeebob'}, 14 | {id: 3, first_name: 'Zeus', last_name: 'Aaardvaaark'}, 15 | ]); 16 | 17 | controller = App.ContactsController.create(); 18 | controller.set('content', store.findMany(App.Contact, [1, 2, 3])); 19 | }); 20 | }); 21 | 22 | it("sorts by [lastName, firstName]", function() { 23 | assert.equal(controller.get('length'), 3); 24 | assert.equal(controller.objectAt(0).get('id'), '3'); 25 | assert.equal(controller.objectAt(1).get('id'), '2'); 26 | assert.equal(controller.objectAt(2).get('id'), '1'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/javascripts/unit/controllers/edit_contact_controller_test.js: -------------------------------------------------------------------------------- 1 | //= require test_helper 2 | 3 | describe("Controllers: App.ContactEditController", function() { 4 | // TODO - fill out tests 5 | }); -------------------------------------------------------------------------------- /test/javascripts/unit/helpers/forms_test.js: -------------------------------------------------------------------------------- 1 | //= require test_helper 2 | 3 | describe("Helpers", function() { 4 | var view; 5 | 6 | describe("submitButton", function() { 7 | it("is registered as helper", function() { 8 | assert.ok(Handlebars.helpers.submitButton); 9 | }); 10 | 11 | it("renders a ' ); 18 | }); 19 | }); 20 | }); -------------------------------------------------------------------------------- /test/javascripts/unit/models/contact_test.js: -------------------------------------------------------------------------------- 1 | //= require test_helper 2 | 3 | describe("Models: App.Contact", function() { 4 | var store, contact; 5 | 6 | beforeEach(function() { 7 | store = lookupStore(); 8 | }); 9 | 10 | it("is a DS.Model", function() { 11 | assert.ok(App.Contact); 12 | assert.ok(DS.Model.detect(App.Contact)); 13 | }); 14 | 15 | describe("fullName", function() { 16 | it("concatenates 'firstName' and 'lastName'", function() { 17 | Ember.run(function() { 18 | contact = App.Contact.createRecord({ 19 | firstName: 'Joe', 20 | lastName: 'Blow' 21 | }); 22 | }) 23 | assert.equal(contact.get('fullName'), 'Joe Blow'); 24 | }); 25 | 26 | it("is '(No Name)' if an existing record has neither firstName nor lastName", function() { 27 | Ember.run(function() { 28 | store.load(App.Contact, {id: 1}); 29 | contact = App.Contact.find(1); 30 | }); 31 | assert.equal(contact.get('fullName'), '(No Name)'); 32 | }); 33 | 34 | it("is '(New Contact)' if a new record has neither firstName nor lastName", function() { 35 | Ember.run(function() { 36 | contact = App.Contact.createRecord(); 37 | }); 38 | assert.equal(contact.get('fullName'), '(New Contact)'); 39 | }); 40 | }); 41 | 42 | describe("gravatar", function() { 43 | it("uses the correct gravatar url", function() { 44 | Ember.run(function() { 45 | contact = App.Contact.createRecord({ 46 | email: 'joe.blow@example.com' 47 | }); 48 | }); 49 | var emailMd5 = MD5('joe.blow@example.com'); 50 | assert.equal(contact.get('gravatar'), "http://www.gravatar.com/avatar/" + emailMd5); 51 | }); 52 | 53 | it("uses an empty string if no email is provided", function() { 54 | Ember.run(function() { 55 | contact = App.Contact.createRecord(); 56 | }); 57 | var emailMd5 = MD5(''); 58 | assert.equal(contact.get('gravatar'), "http://www.gravatar.com/avatar/" + emailMd5); 59 | }); 60 | }); 61 | }); -------------------------------------------------------------------------------- /test/javascripts/unit/router_test.js: -------------------------------------------------------------------------------- 1 | //= require test_helper 2 | 3 | describe("App.Router", function() { 4 | // TODO - fill out tests 5 | }); -------------------------------------------------------------------------------- /test/javascripts/unit/routes/add_contact_route_test.js: -------------------------------------------------------------------------------- 1 | //= require test_helper 2 | 3 | describe("App.ContactsNewRoute", function() { 4 | // TODO - fill out tests 5 | }); -------------------------------------------------------------------------------- /test/javascripts/unit/store_test.js: -------------------------------------------------------------------------------- 1 | //= require test_helper 2 | //= require store 3 | 4 | describe("Data: App.Store", function() { 5 | var store; 6 | 7 | beforeEach(function() { 8 | store = lookupStore(); 9 | }); 10 | 11 | it("works with latest Ember-Data revision", function() { 12 | assert.equal(store.get('revision'), 12); 13 | }); 14 | 15 | describe("adapter", function() { 16 | it("is a DS.RESTAdapter", function() { 17 | assert.ok(DS.RESTAdapter.detectInstance(store.get('adapter'))); 18 | }); 19 | }); 20 | }); -------------------------------------------------------------------------------- /test/models/contact_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ContactTest < ActiveSupport::TestCase 4 | describe Contact do 5 | before do 6 | @contact = contacts(:dan) 7 | end 8 | 9 | it "has a full_name, concatenated from first_name and last_name" do 10 | assert_equal 'Dan Gebhardt', @contact.full_name 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | class BrowsingTest < ActionDispatch::PerformanceTest 5 | # Refer to the documentation for all available options 6 | # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] 7 | # :output => 'tmp/performance', :formats => [:flat] } 8 | 9 | def test_homepage 10 | get '/' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/serializers/contact_serializer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ContactSerializerTest < ActiveSupport::TestCase 4 | describe ContactSerializer do 5 | it "should serialize a contact with no phone numbers" do 6 | joe = contacts(:joe) 7 | 8 | expected = {contact: 9 | {id: joe.id, 10 | first_name: 'Joe', 11 | last_name: 'Blow', 12 | email: 'joe@example.com', 13 | notes: 'Lousy plumber', 14 | phone_numbers: []}} 15 | 16 | assert_equal expected, ContactSerializer.new(joe).as_json 17 | end 18 | 19 | it "should serialize a contact with phone numbers" do 20 | dan = contacts(:dan) 21 | dan_home = phone_numbers(:dan_home) 22 | dan_mobile = phone_numbers(:dan_mobile) 23 | 24 | expected = {contact: 25 | {id: dan.id, 26 | first_name: 'Dan', 27 | last_name: 'Gebhardt', 28 | email: 'dan@example.com', 29 | notes: 'Writes sample code without tests :/', 30 | phone_numbers: [{ 31 | id: dan_home.id, 32 | contact_id: dan.id, 33 | number: '603-555-1212' 34 | }, { 35 | id: dan_mobile.id, 36 | contact_id: dan.id, 37 | number: '603-555-1234' 38 | }]}} 39 | 40 | assert_equal expected, ContactSerializer.new(dan).as_json 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/serializers/phone_number_serializer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PhoneNumberSerializerTest < ActiveSupport::TestCase 4 | describe PhoneNumberSerializer do 5 | it "should serialize a phone number" do 6 | dan = contacts(:dan) 7 | dan_mobile = phone_numbers(:dan_mobile) 8 | 9 | expected = {phone_number: 10 | {id: dan_mobile.id, 11 | contact_id: dan.id, 12 | number: '603-555-1234'}} 13 | 14 | assert_equal expected, PhoneNumberSerializer.new(dan_mobile).as_json 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | 4 | require "minitest/autorun" 5 | require "minitest/rails" 6 | 7 | # Add `gem "minitest/rails/capybara"` to the test group of your Gemfile 8 | # and uncomment the following if you want Capybara feature tests 9 | require "minitest/rails/capybara" 10 | 11 | # Uncomment if you want awesome colorful output 12 | # require "minitest/pride" 13 | 14 | # Either selenium or poltergeist can be used with Capybara 15 | #Capybara.default_driver = :selenium 16 | Capybara.default_driver = :poltergeist 17 | 18 | class ActiveSupport::TestCase 19 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 20 | fixtures :all 21 | 22 | # Add more helper methods to be used by all tests here... 23 | end 24 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgeb/ember_data_example/4d18f6ea684e85bfe54a6fd91bc456f0f0f5b1b9/vendor/assets/javascripts/.gitkeep -------------------------------------------------------------------------------- /vendor/assets/javascripts/md5.js: -------------------------------------------------------------------------------- 1 | // Chris Coyier's MD5 Library 2 | // http://css-tricks.com/snippets/javascript/javascript-md5/ 3 | var MD5 = function (string) { 4 | 5 | function RotateLeft(lValue, iShiftBits) { 6 | return (lValue<>>(32-iShiftBits)); 7 | } 8 | 9 | function AddUnsigned(lX,lY) { 10 | var lX4,lY4,lX8,lY8,lResult; 11 | lX8 = (lX & 0x80000000); 12 | lY8 = (lY & 0x80000000); 13 | lX4 = (lX & 0x40000000); 14 | lY4 = (lY & 0x40000000); 15 | lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF); 16 | if (lX4 & lY4) { 17 | return (lResult ^ 0x80000000 ^ lX8 ^ lY8); 18 | } 19 | if (lX4 | lY4) { 20 | if (lResult & 0x40000000) { 21 | return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); 22 | } else { 23 | return (lResult ^ 0x40000000 ^ lX8 ^ lY8); 24 | } 25 | } else { 26 | return (lResult ^ lX8 ^ lY8); 27 | } 28 | } 29 | 30 | function F(x,y,z) { return (x & y) | ((~x) & z); } 31 | function G(x,y,z) { return (x & z) | (y & (~z)); } 32 | function H(x,y,z) { return (x ^ y ^ z); } 33 | function I(x,y,z) { return (y ^ (x | (~z))); } 34 | 35 | function FF(a,b,c,d,x,s,ac) { 36 | a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac)); 37 | return AddUnsigned(RotateLeft(a, s), b); 38 | }; 39 | 40 | function GG(a,b,c,d,x,s,ac) { 41 | a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac)); 42 | return AddUnsigned(RotateLeft(a, s), b); 43 | }; 44 | 45 | function HH(a,b,c,d,x,s,ac) { 46 | a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac)); 47 | return AddUnsigned(RotateLeft(a, s), b); 48 | }; 49 | 50 | function II(a,b,c,d,x,s,ac) { 51 | a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac)); 52 | return AddUnsigned(RotateLeft(a, s), b); 53 | }; 54 | 55 | function ConvertToWordArray(string) { 56 | var lWordCount; 57 | var lMessageLength = string.length; 58 | var lNumberOfWords_temp1=lMessageLength + 8; 59 | var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64; 60 | var lNumberOfWords = (lNumberOfWords_temp2+1)*16; 61 | var lWordArray=Array(lNumberOfWords-1); 62 | var lBytePosition = 0; 63 | var lByteCount = 0; 64 | while ( lByteCount < lMessageLength ) { 65 | lWordCount = (lByteCount-(lByteCount % 4))/4; 66 | lBytePosition = (lByteCount % 4)*8; 67 | lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<>>29; 75 | return lWordArray; 76 | }; 77 | 78 | function WordToHex(lValue) { 79 | var WordToHexValue="",WordToHexValue_temp="",lByte,lCount; 80 | for (lCount = 0;lCount<=3;lCount++) { 81 | lByte = (lValue>>>(lCount*8)) & 255; 82 | WordToHexValue_temp = "0" + lByte.toString(16); 83 | WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2); 84 | } 85 | return WordToHexValue; 86 | }; 87 | 88 | function Utf8Encode(string) { 89 | string = string.replace(/\r\n/g,"\n"); 90 | var utftext = ""; 91 | 92 | for (var n = 0; n < string.length; n++) { 93 | 94 | var c = string.charCodeAt(n); 95 | 96 | if (c < 128) { 97 | utftext += String.fromCharCode(c); 98 | } 99 | else if((c > 127) && (c < 2048)) { 100 | utftext += String.fromCharCode((c >> 6) | 192); 101 | utftext += String.fromCharCode((c & 63) | 128); 102 | } 103 | else { 104 | utftext += String.fromCharCode((c >> 12) | 224); 105 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 106 | utftext += String.fromCharCode((c & 63) | 128); 107 | } 108 | 109 | } 110 | 111 | return utftext; 112 | }; 113 | 114 | var x=Array(); 115 | var k,AA,BB,CC,DD,a,b,c,d; 116 | var S11=7, S12=12, S13=17, S14=22; 117 | var S21=5, S22=9 , S23=14, S24=20; 118 | var S31=4, S32=11, S33=16, S34=23; 119 | var S41=6, S42=10, S43=15, S44=21; 120 | 121 | string = Utf8Encode(string); 122 | 123 | x = ConvertToWordArray(string); 124 | 125 | a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476; 126 | 127 | for (k=0;k