Skip to content
heartsentwined edited this page Oct 18, 2013 · 7 revisions

Model UI


CHANGES This page has been updated for the 9.x branch. Legacy instructions are gone - clone this wiki repo and checkout the 8.x tag.


Models

Define a set of models for our ember app. They will mirror the properties we have exposed in our serializers, except for the id (ember will handle the id automatically).

models/post.coffee:

EmberAuthRailsDemo.Post = DS.Model.extend
  title: DS.attr 'string'
  param: DS.attr 'string'

models/user.coffee:

EmberAuthRailsDemo.User = DS.Model.extend
  email: DS.attr 'string'
  param: DS.attr 'string'

We also need to configure the store for ember-data. Since we have namespaced all resource API end points inside /api, we'll also configure this in the adapter.

store.coffee:

EmberAuthRailsDemo.Store = DS.Store.extend
  adapter: DS.ActiveModelAdapter.reopen
    namespace: 'api'

We have used the DS.ActiveModelAdapter, which is an extension of the default DS.RESTAdapter. It understands the json structure that we have defined in our API, which is also the convention from the active_model_serializers gem.

ember-data integration to ember-auth

ember-auth comes with the emberData module, which patches ember-data's DS.RESTAdapter to automatically send along authentication credentials when a signed in session is present.

Since our API returns the user_id, we can also ask ember-auth to auto-load the user model upon a successful #.

auth.coffee:

EmberAuthRailsDemo.Auth = Em.Auth.extend
  emberData:
    userModel: 'user'

ember-auth will then call store.find('user', auth.get('userId')) for us, and set the current user model at auth.user. We can access it via auth.get('user')

We can, for example, display the current user's email in our auth/sign-out template.

templates/auth/sign-out.emblem:

form submit='signOut'
  auth.user.email
  button Sign out

UI Overview

We are going to stub out five simple "pages" for testing:

  • /posts: a list of posts
  • /posts/:post_id: show the requested post
  • /users: (auth-only) a list of users
  • /users/:user_id: (auth-only) show the requested user.
  • /sign-in: an error page for unauthorized access to user pages.

As in ember, each will require a route, a controller, and a template. Even if not specified, they will be created implicitly. You are encouraged to read more to familiarize yourself with the overall structure if you are new to ember.

Router and routes

Routes are standard, but we want the history API, because hashes in the URL do not look natural. This can be done by reopening the router, and setting the location config.

router.coffee:

EmberAuthRailsDemo.Router.reopen
  location: 'history'

EmberAuthRailsDemo.Router.map ->
  @resource 'posts', ->
    @route 'show', { path: '/:post_id' }
  @resource 'users', ->
    @route 'show', { path: '/:user_id' }
  @route 'sign-in'

Standard routes

The post set of routes are standard, except that we will customize the post_id in the posts.show route, using the param property we defined on the post model.

routes/posts.coffee:

EmberAuthRailsDemo.PostsIndexRoute = Em.Route.extend
  model: ->
    @store.findQuery 'post'

EmberAuthRailsDemo.PostsShowRoute = Em.Route.extend
  serialize: (model) ->
    post_id: model.get 'param'

Auth-only routes

We want the users set of routes to be auth-only - non-authenticated users should be redirected away to the sign-in page.

We will use the authRedirectable module for this. Add the gem and update the bundle.

Gemfile:

gem 'ember-auth-module-auth_redirectable-rails', '~> 1.0' # auth-only routes
$ bundle update

Include the new module into the ember app.

application.coffee:

# ...
#= require ember-auth
# ...
#= require ember-auth-module-auth-redirectable
# ...

auth.coffee:

EmberAuthRailsDemo.Auth = Em.Auth.create
  # ...
  modules: [
    'emberData'
    'authRedirectable'
  ]

  authRedirectable:
    route: 'sign-in'

Make a sign-in template to redirect non-authenticated users to.

templates/sign-in.emblem:

| You need to # first

Routes are same as the posts set, except that we will mark them as auth-only, so that the authRedirectable module will redirect unauthenticated users away.

routes/users.coffee:

EmberAuthRailsDemo.UsersRoute = Em.Route.extend
  authRedirectable: true

EmberAuthRailsDemo.UsersIndexRoute = Em.Route.extend
  model: ->
    @store.findQuery 'user'

EmberAuthRailsDemo.UsersShowRoute = Em.Route.extend
  serialize: (model) ->
    user_id: model.get 'param'

We could have added authRedirectable: true to every route, but since anyone entering the users.index route would go through the users route, declaring it in the users route would be enough. Same for the users.show route.

Controllers

We don't need custom logic or properties in controllers, so we will let ember generate controllers for us.

Templates

We'll stub out the minimum for sanity test: a list for the index routes, and either user.email or post.title for the show routes.

templates/posts.emblem:

outlet

templates/posts/index.emblem:

ul: each controller
  li: linkTo posts.show this
    = title

templates/posts/show.emblem:

h1
  = title

linkTo posts.index
  | Back to posts list

templates/users.emblem:

outlet

templates/users/index.emblem:

ul: each controller
  li: linkTo users.show this
    email

templates/users/show.emblem:

h1
  email
linkTo users.index
  | Back to users list

Finally, let's add some primitive navigation links site-wide.

templates/application.emblem:

render 'auth'

nav
  link-to 'posts.index'
    | Posts list
  link-to 'users.index'
    | Users list

outlet

Post-# redirection

To complete the expected UI flow, users should be redirected back to where they were after signing in.

Our expectation:

  • After a (successful) #, the user should be redirected to
    1. the previous route
    2. unless that route is sign-in, then redirect to the one before sign-in
    3. otherwise (i.e. entry from sign-in), redirect to users as fallback
  • After a (successful) sign out, the user should be redirected to
    1. posts - we use only static redirect here

We will use the actionRedirectable module to achieve this.

Install gem, update bundle, and include the module.

Gemfile:

gem 'ember-auth-module-action_redirectable-rails', '~> 1.0' # post- #/out redirect
$ bundle update

application.coffee:

# ...
#= require ember-auth
# ...
#= require ember-auth-module-action-redirectable
# ...

Configure the actionRedirectable module.

auth.coffee:

EmberAuthRailsDemo.Auth = Em.Auth.create
  # ...
  modules: [
    'emberData'
    'authRedirectable'
    'actionRedirectable'
  ]
  # ...
  actionRedirectable:
    signInRoute: 'users'
    signInSmart: true
    signInBlacklist: ['sign-in']
    signOutRoute: 'posts'
    # signOutSmart defaults to false already
    # since we are not using smart sign out redirect,
    # we don't have to touch signOutBlacklist

That's it! ember-auth will do all the redirect work for us.

Security note

There is an important caveat (bug?) regarding post- sign out behavior. In a nutshell, ember-data does not provide any way of clearing the data store, so the sign out is rather superficial at present. Read more about this issue in the security notes.

Quick hack-fix? Make the page reload on sign out. This will clear and reload the whole ember app.

controllers/auth/sign-out.coffee:

EmberAuthRailsDemo.AuthSignOutController = Em.Controller.extend
  actions:
    signOut: ->
      @auth.signOut().then -> window.location.reload true

Continue to Enhancements.