Creating a Current User Property in Ember

I recently used the Ember Simple Auth library to build user authorization into an Ember app with a Rails API back-end for the first time. Once I had it up and running, and a user could successfully log in, my next logical step was to implement a "current user" feature. I wanted Ember to be able to communicate to the current user, by, for example, greeting them my name in the nav bar. This is fairly easy to implement in Rails, and I assumed it would be easy to do in Ember as well.

I was wrong. Setting a "current user" property in my Ember app so that Ember could hold on to a conception of who the current user was and greet that user in every page proved to be an interesting challenge (this is an understatement, in case you couldn't tell).

To get this working, I had to do the following:

  1. Upon receiving confirmation from Rails that the user logging in via Ember was successfully authenticated, send another request to Rails to server the data of the current user.
  2. Trigger a closure action (because my login logic was being run from a login-form component) that would set a property on the Application Controller, currentUser, equal to that object.
  3. But wait! When the route transitions to a new page, that currentUser property is persisted, BUT, if the page refreshes, that property is set back to null. So, write an on('init') function in the Application Controller to set the current user by once again requesting the data from Rails and setting it equal to a currentUser property, every time the page reloads.

Okay, let's do it.

Note: This code assumes you have a User model and serializer defined in your Rails API, as well as a User model defined in your Ember application.

Requesting the Current User on Successful Authentication

 // app/components/login-form.js

import Ember from 'ember';

const { service } = Ember.inject;
const { store } = Ember.inject;

export default Ember.Component.extend({
  session: service('session'),
  store: service("store"),


  actions: {
    authenticate() {
     let { identification, password } = this.getProperties('identification', 'password');
     this.get('session').authenticate('authenticator:devise', identification, password).then(()=>{
       this.get('store').findRecord('user', 'me').then((user) => {
        this.attrs.triggerSetCurrentUser(user);
       })
     }).catch((reason) => {
       this.set('errorMessage', reason.error || reason);
     });
   },
 }
});

Here, I added a then function that will run when the authentication request is sent to my Rails API is resolved. This function does two things:

  • Send a subsequent request to the API to get the data representing the current user.
  • Trigger a closure action to set the current user property.

We'll start with the first part: requesting the current user's data from the Rails API. Here's the line of code that sends that request:

this.get('store').findRecord('user', 'me')

To get this working, a built the following endpoint in my Rails API:

 Rails.application.routes.draw do
  
  devise_for :users, controllers: { sessions: 'sessions' }

  namespace :api do
    namespace :v1 do
     ... other routes
      get "users/me", to: "users#me"
  
    end
  end
end

To my Users Controller, I added the following action:

module Api
  module V1
    class UsersController < ApplicationController
      def me
        render json: current_user
      end
    end
  end
end

Ember is expecting to receive a user data in the following format:

user: {
  name: "whatever",
  email: "whatever",
  whatever_other_attribute: "whatever"
}

Note that is is assuming you have defined a User model in your Ember app.

Setting the Current User with a Closure Action

Once Ember sends the request to <my rails api>/users/me and receives the response of the user's data, we need to store that user as the current user somewhere where it will be accessible to the application.hbs template.

For this reason, we will set a property, currentUser, on our application controller. But, we can't set a controller property from a component. We need to use a closure action to trigger a controller action from the component.

Let's assume we'll define a controller action, setCurrentUser. So, when we render our login-form component on the log in page, we need to pass in this action as a closure action:

 <h2>Log In:</h2>
{{login-form triggerSetCurrentUser=(action "setCurrentUser")}}

Then, in our component, once we receive the user information from the Rails API, we can invoke our closure action, passing it an argument of this user. The line that takes care of this in our login-from.js is:

this.attrs.triggerSetCurrentUser(user);

Now let's go ahead and define that setCurrentUser action.

Defining the Controller Action

In my Login Controller, I defined the setCurrentUser action:

import Ember from 'ember';

export default Ember.Controller.extend({
  applicationController: Ember.inject.controller('application'),
  actions: {
    setCurrentUser(user){    this.get("applicationController").set('currentUser', user);
    }
  }
});

Let's break this down:

  • First, I had to inject the Application Controller, in order to set the currentUser property of that controller, so that it is accessible across any route, on any template.
applicationController: Ember.inject.controller('application')

Then, I defined the setCurrentUser action to take in argument of the user object, as passed in from the component.

The action then sets the Application Controller's currentUser property equal to this object:

this.get("applicationController").set('currentUser', user)

However, I found that, while transitioning to different routes persisted this currentUser property, refreshing the page had the effect of setting it back to null. Let's solve that problem.

Setting the Current User on Page Refresh

To solve this problem, I wrote a function the Application Controller that would fire every time the page refreshed. The function first checks to see if the session is authenticated, i.e. if there even is a current user to worry about, and, if so, sets the currentUser property:

import Ember from 'ember';

export default Ember.Controller.extend({
  currentUser: null, 
  session: Ember.inject.service('session'),

  updateCurrentUser: function () { 
      
    if (this.get("session.isAuthenticated")){
      this.get('store').findRecord('user', 'me').then((user) => {
        this.set("currentUser", user);
      })
    }
  }.on('init') 
});

Here, we have an .on('init') function, which will fire when the page refreshes. It first checks if the session is authenticated, then, goes through the steps of requesting the current user from Rails and setting the currentUser property equal to that user object.

All of that so that we can do the following in our application.hbs template:

{#if currentUser}}
  <li class="active">
    <a href="#">Hi, {{currentUser.name}}</a>
  </li>
{{/if}}

####### Disclaimer

Please feel free to comment with thoughts/critiques/recommendations. I'm sure this is not the most elegant solution, but it is the one I stumbled upon. As always, happy coding!

subscribe and never miss a post!

Blog Logo

Sophie DeBenedetto

comments powered by Disqus
comments powered by Disqus