I'd like to be able to inject my Session singleton into my Ember models. The use case I'm trying to support is having computed properties on the model that react to the user's profile (a property on the Session object).
App = window.App = Ember.Application.create({
ready: function() {
console.log('App ready');
this.register('session:current', App.Session, {singleton: true});
this.inject('session:current','store','store:main');
this.inject('controller','session','session:current');
this.inject('model','session','session:current');
}
});
The injection works fine into the controller but I'm having trouble with getting it to the model. Is there any limitation here? Any special techniques?
-------- Additional Context ---------
Here's an example of what I'd like to be able to do in my model definition:
App.Product = DS.Model.extend({
name: DS.attr("string"),
company: DS.attr("string"),
categories: DS.attr("raw"),
description: DS.attr("string"),
isConfigured: function() {
return this.session.currentUser.configuredProducts.contains(this.get('id'));
}.property('id')
});
By default injections in models don't work. To do so you need to set the flag Ember.MODEL_FACTORY_INJECTIONS = true:
Ember.MODEL_FACTORY_INJECTIONS = true;
App = window.App = Ember.Application.create({
ready: function() {
console.log('App ready');
this.register('session:current', App.Session, {singleton: true});
this.inject('session:current','store','store:main');
this.inject('controller','session','session:current');
this.inject('model','session','session:current');
}
});
The downside of this, is that it create some break changes:
If you have App.Product.FIXTURES = [...] you neeed to use App.Product.reopenClass({ FIXTURES: [...] });
productRecord.constructor === App.Product will evaluate to false. To solve this you can use App.Product.detect(productRecord.constructor).
Related
I'm trying to develop an extremely simple auth system in Ember.js 1.5 but I am having some trouble for the front-end part.
I am very new to ember so please excuse my ignorance.
So here are my routes and their implementations
App.Router.map(function() {
this.route('login');
this.resource("posts");
});
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (Ember.isEmpty(App.Auth.get('authToken'))) {
this.transitionTo('login');
}
}
});
App.LoginRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('login_template');
},
actions: {
createSession: function() {
var router = this;
var email = this.controller.get('email_login');
var password = this.controller.get('password_login');
if (!Ember.isEmpty(email) && !Ember.isEmpty(password)) {
$.post('/api/v1/session', {email: email, password: password}, function(data) {
var authToken = data.session.auth_token;
var user_id = data.session.user_id;
App.Store.authToken = authToken;
App.Auth = Ember.object.create({
authToken: authToken,
user_id: user_id
});
router.transitionTo('index');
});
}
}
}
});
App.ApplicationRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.find('post');
}
});
App.ProceduresRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.all('post');
}
});
// Ommitting the models because they aren't necessary for this question
So the main concern of course is that App.Auth is initially undefined. I've copied most of the code from a tutorial and I don't know if ember has any session variables that I can initialize.
In any case, I can always just define App.Auth at the beginning with initial values of null for it's properties but that might have other consequences that I am not aware of (again I am new to ember).
So my question here is that how can I properly store the response from the server (auth_token and user_id) in a nice manner. Also, have the user redirected to the login route if auth_token is not set (which is what I am trying to do with the authenticated route part).
You do not need to provide a full answer if you cannot, even some tips / comments will be extremely helpful, thanks!
I'd probably use the container to register an auth manager, and inject it into everything.
App.initializer({
name: "auth",
before: ['ember-data'],
initialize: function (container, application) {
var auth = Ember.Object.extend({
isAuthenticated: Em.computed.notEmpty('authToken')
});
application.register("my:authToken", auth);
application.inject("controller", "auth", "my:authToken");
application.inject("route", "auth", "my:authToken");
application.inject("store:main", "auth", "my:authToken");
application.inject("adapter", "auth", "my:authToken");
}
});
Then on your adapter you could hook up headers easily (if needed)
App.ApplicationAdapter = DS.RESTAdapter.extend({
headers: function() {
return {
authToken: this.get('auth.authToken')
};
}.property("auth.authToken")
});
From any controller/route you could easily access the auth property from this.auth
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!this.auth.get('isAuthenticated')) {
this.transitionTo('login');
}
}
});
Here's an example with the injection (note, I'm using Ember Data 1.0 beta 8, some of this functionality, the headers, isn't available in older versions of Ember Data).
http://emberjs.jsbin.com/OxIDiVU/639/edit
I've implemented "change page" in my one page application with Backbone.js. However, I'm not sure if my Router should contain so much business logic. Should I consider go with Marionette.js to implement such functionality and make my Router thin? Should I worry about destroying Backbone models and views attached to "previous" active page/view when I change it (in order to avoid memory leaks) or it's enough to empty html attached to those models/views.
Here is my Router:
App.Router = Backbone.Router.extend({
routes: {
'users(/:user_id)' : 'users',
'dashboard' : 'dashboard'
},
dashboard: function() {
App.ActiveView.destroy_view();
App.ActiveViewModel.destroy();
App.ActiveViewModel = new App.Models.Dashboard;
App.ActiveViewModel.fetch().then(function(){
App.ActiveView = new App.Views.Dash({model: App.ActiveViewModel });
App.ActiveView.render();
});
},
users: function(user_id) {
App.ActiveView.destroy_view();
App.ActiveViewModel.destroy();
App.ActiveViewModel = new App.Models.User;
App.ActiveViewModel.fetch().then(function() {
App.ActiveView = new App.Views.UsersView({model: App.ActiveViewModel});
App.ActiveView.render();
});
}
});
Another approach:
Create an AbstractView
Having an AbstractView declared and then extending your other application specific View's from AbstractView has many advantages. You always have a View where you can put all the common functionalities.
App.AbstractView = Backbone.View.extend({
render : function() {
App.ActiveView && App.ActiveView.destroy_view();
// Instead of destroying model this way you can destroy
// it in the way mentioned in below destroy_view method.
// Set current view as ActiveView
App.ActiveView = this;
this.renderView && this.renderView.apply(this, arguments);
},
// You can declare destroy_view() here
// so that you don't have to add it in every view.
destroy_view : function() {
// do destroying stuff here
this.model.destroy();
}
});
Your App.Views.UsersView should extend from AbstractView and have renderView in place of render because AbstractView's render will make a call to renderView. From the Router you can call render the same way App.ActiveView.render();
App.Views.UsersView = AbstractView.extend({
renderView : function() {
}
// rest of the view stuff
});
App.Views.Dash = AbstractView.extend({
renderView : function() {
}
// rest of the view stuff
});
Router code would then change to :
dashboard: function() {
App.ActiveViewModel = new App.Models.Dashboard;
App.ActiveViewModel.fetch().then(function(){
new App.Views.Dash({model: App.ActiveViewModel }).render();
});
}
I am new to EmberJs and I don't clearly understand in Ember's Adapter .I just try the ember adapter in my App.Js like that and I got this error ( Assertion failed: You tried to set adapter property to an instance of DS.Adapter, where it should be a name or a factory ) . My ember's code in App.js is :
//Store
App.Adapter = DS.RESTAdapter.extend();
App.Store = DS.Store.extend({
revision: 12,
adapter: App.Adapter.create()
});
//Models
App.Product = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
price: DS.attr('number')
});
// Products Route
App.ProductsRoute = Ember.Route.extend({
model: (function() {
return this.store.find('Product');
})
});
return App;
I think you misunderstand the way you setup and configure adapters.
//
// Application-wide adapter, everything will use this unless you override it
//
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'https://api.example.com'
});
//
// Product model, will use ApplicationAdapter
//
App.Product = DS.Model.extend({
name : DS.attr('string'),
description : DS.attr('string'),
price : DS.attr('number')
});
//
// Invoice model, will use fixtures, so specify a different adapter
//
App.InvoiceAdapter = DS.FixtureAdapter.extend({ /* options */ });
App.Invoice = DS.Model.extend({
name : DS.attr('string'),
amount : DS.attr('number')
});
//
// Routes, these should work as expected
//
App.ProductRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('product', params.id);
}
});
App.ProductsRoute = Ember.Route.extend({
model: function() {
return this.store.find('product');
}
});
App.InvoicesRoute = Ember.Route.extend({
model: function() {
return this.store.find('invoice');
}
});
return App;
Ember will know which models/routes/etc to use based on their names - see http://emberjs.com/guides/concepts/naming-conventions/ for the details.
Define the store in this way
App.Store = DS.Store.extend({
revision: 12,
adapter: App.Adapter
});
Without the create().
The main use of the adapter is to do the serialization and deserailzation of the data according to some conventions like constructing the url to post or get data from and then constructing the actual objects from the response. The Default adapter used by ember data models is the Rest adapter.
see
http://emberjs.com/guides/models/the-rest-adapter/
for more details
To use a different adapter other than the rest adapter you can specify its name like
Storm.Store = DS.Store.extend({
adapter: '_ams',
});
Try:
App.ApplicationAdapter = DS.RESTAdapter.extend();
This works for me
Got this route to get the data from a restful service
var App = Ember.Application.create({rootElement: '#planner'});
App.Store = DS.Store.extend();
App.Router.map(function(){
this.resource('home');
});
App.HomeRoute = Ember.Route.extend({
model: function(){
return Ember.$.getJSON('/api/get-planner/');
}
});
And template:
<script type="text/x-handlebars" data-template-name="home">
{{name}}
</script>
Somehow the value of name is not displayed. I can confirm the api is returning correct json data.
Ember-Data expects the JSON like this:
{
planner: {
name: 'Test'
// your data
}
}
So if your API returns this JSON:
{
name: 'Test'
}
It won't work.
I would suggest to use Ember-Model instead (https://github.com/ebryn/ember-model), since it is more stable and allows you to customize the behavior of the REST adapter.
Your code might look like this:
App.PlannerModel = Ember.Model.extend({
name: Ember.attr(),
// see the documentation of ember-model for this
});
App.PlannerModel.url = '/api/get-planner/';
App.PlannerModel.adapter = Ember.RESTAdapter.create();
App.HomeRoute = Ember.Route.extend({
model: function() {
return App.PlannerModel.find();
}
});
If you want to take this approach, make sure not to include Ember-Data and use Ember-Model instead.
We are in the process of learning Ember.js. We do all our development TDD, and want Ember.js to be no exception. We have experience building Backbone.js apps test-driven, so we are familiar with testing front-end code using Jasmine or Mocha/Chai.
When figuring out how to test views, we ran into a problem when the template for the view uses has a #linkTo statement. Unfortunately we are unable to find good test examples and practices. This gist is our quest to get answers how to decently unit-test ember applications.
When looking at the test for linkTo in Ember.js source code, we noticed it contains a full wiring of an ember app to support #linkTo. Does this mean we cannot stub this behaviour when testing a template?
How do you create tests for ember views using template renders?
Here is a gist with our test and a template that will make the test pass, and a template that will make it fail.
view_spec.js.coffee
# This test is made with Mocha / Chai,
# With the chai-jquery and chai-changes extensions
describe 'TodoItemsView', ->
beforeEach ->
testSerializer = DS.JSONSerializer.create
primaryKey: -> 'id'
TestAdapter = DS.Adapter.extend
serializer: testSerializer
TestStore = DS.Store.extend
revision: 11
adapter: TestAdapter.create()
TodoItem = DS.Model.extend
title: DS.attr('string')
store = TestStore.create()
#todoItem = store.createRecord TodoItem
title: 'Do something'
#controller = Em.ArrayController.create
content: []
#view = Em.View.create
templateName: 'working_template'
controller: #controller
#controller.pushObject #todoItem
afterEach ->
#view.destroy()
#controller.destroy()
#todoItem.destroy()
describe 'amount of todos', ->
beforeEach ->
# $('#konacha') is a div that gets cleaned between each test
Em.run => #view.appendTo '#konacha'
it 'is shown', ->
$('#konacha .todos-count').should.have.text '1 things to do'
it 'is livebound', ->
expect(=> $('#konacha .todos-count').text()).to.change.from('1 things to do').to('2 things to do').when =>
Em.run =>
extraTodoItem = store.createRecord TodoItem,
title: 'Moar todo'
#controller.pushObject extraTodoItem
broken_template.handlebars
<div class="todos-count"><span class="todos">{{length}}</span> things to do</div>
{{#linkTo "index"}}Home{{/linkTo}}
working_template.handlebars
<div class="todos-count"><span class="todos">{{length}}</span> things to do</div>
Our solution has been to essentially load the whole application, but isolate our test subjects as much as possible. For example,
describe('FooView', function() {
beforeEach(function() {
this.foo = Ember.Object.create();
this.subject = App.FooView.create({ foo: this.foo });
this.subject.append();
});
afterEach(function() {
this.subject && this.subject.remove();
});
it("renders the foo's favoriteFood", function() {
this.foo.set('favoriteFood', 'ramen');
Em.run.sync();
expect( this.subject.$().text() ).toMatch( /ramen/ );
});
});
That is, the router and other globals are available, so it's not complete isolation, but we can easily send in doubles for things closer to the object under test.
If you really want to isolate the router, the linkTo helper looks it up as controller.router, so you could do
this.router = {
generate: jasmine.createSpy(...)
};
this.subject = App.FooView.create({
controller: { router: this.router },
foo: this.foo
});
One way you can handle this is to create a stub for the linkTo helper and then use it in a before block. That will bypass all the extra requirements of the real linkTo (e.g. routing) and let you focus on the contents of the view. Here's how I'm doing it:
// Test helpers
TEST.stubLinkToHelper = function() {
if (!TEST.originalLinkToHelper) {
TEST.originalLinkToHelper = Ember.Handlebars.helpers['link-to'];
}
Ember.Handlebars.helpers['link-to'] = function(route) {
var options = [].slice.call(arguments, -1)[0];
return Ember.Handlebars.helpers.view.call(this, Em.View.extend({
tagName: 'a',
attributeBindings: ['href'],
href: route
}), options);
};
};
TEST.restoreLinkToHelper = function() {
Ember.Handlebars.helpers['link-to'] = TEST.originalLinkToHelper;
TEST.originalLinkToHelper = null;
};
// Foo test
describe('FooView', function() {
before(function() {
TEST.stubLinkToHelper();
});
after(function() {
TEST.restoreLinkToHelper();
});
it('renders the favoriteFood', function() {
var view = App.FooView.create({
context: {
foo: {
favoriteFood: 'ramen'
}
}
});
Em.run(function() {
view.createElement();
});
expect(view.$().text()).to.contain('ramen');
});
});