├── .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 | 
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 |
2 |
3 |
4 |
{{#linkTo "contacts"}}Ember Contacts{{/linkTo}}
5 |
6 | {{#linkTo "contacts.new" class="btn"}} Add Contact{{/linkTo}}
7 |
8 |
9 |
10 |
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 |
29 | {{/if}}
30 |
--------------------------------------------------------------------------------
/app/assets/javascripts/templates/contact_edit.hbs:
--------------------------------------------------------------------------------
1 |
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 |
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