Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ivanvanderbyl
Last active January 31, 2016 17:55
Show Gist options
  • Star 40 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save ivanvanderbyl/4560416 to your computer and use it in GitHub Desktop.
Save ivanvanderbyl/4560416 to your computer and use it in GitHub Desktop.
Using Ember initializers and injections to setup the `currentUser` within your app.
App.AccountEditRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', this.get('currentUser'));
}
});
%noscript
.container
.alert
%strong
Javascript is disabled!
The AppName UI is built entirely in Javascript, as such you need to enable
Javascript in your browser to continue.
:javascript
var currentUser = jQuery.parseJSON('#{current_user_json}');
Ember.Application.initializer({
name: "currentUser",
initialize: function(container, application) {
var store = container.lookup('store:main');
var obj = store.load(CrashLog.User, currentUser);
container.optionsForType('user', { instantiate: false, singleton: true });
container.register('user', 'current', CrashLog.User.find(obj.id));
}
});
Ember.Application.initializer({
name: "injectCurrentUser",
after: 'currentUser',
initialize: function(container) {
container.injection('controller:application', 'currentUser', 'user:current');
container.typeInjection('route', 'currentUser', 'user:current');
}
});
@ivanvanderbyl
Copy link
Author

You could also create an isUserSignedIn injection which just checks for the presence of user:current

@ivanvanderbyl
Copy link
Author

Incase you're wondering where current_user_json comes from, I'm simply using ActiveModelSerializers to render the current user:

def current_user_json
  UserSerializer.new(current_user, :scope => current_user, :root => false).to_json
end
helper_method :current_user_json

@AlexanderZaytsev
Copy link

Thanks for the info, I've updated my article: http://say26.com/using-rails-devise-with-ember-js

Here's my initializer now:

Ember.Application.initializer
  name: 'currentUser'

  initialize: (container) ->
    store = container.lookup('store:main')
    attributes = $('meta[name="current-user"]').attr('content')

    if attributes
      object = store.load(App.User, JSON.parse(attributes))
      user = App.User.find(object.id)

      controller = container.lookup('controller:currentUser').set('content', user)

      container.typeInjection('controller', 'currentUser', 'controller:currentUser')

I still prefer using a meta tag because I consider using global vars a bad practice.

I'm injecting currentUser into all controllers to make it work more like Rails (where you can access current_user in any of your controllers). Also, in my example currentUser is a controller, not a model. What's your motivation for using a model?

Also, it's funny that I initially implemented the same current_user_json method but then thought that creating a method in ApplicationController (which will be shared by every other controller) and having a current_user_json view helper was an overkill, considering the fact that you need to call it in exactly one place.

@boy-jer
Copy link

boy-jer commented Jan 18, 2013

@ivanvanderbyl you rock. @AlexanderZaytsev you also rock for putting out the blog post and telling us on twitter. Tying to solve this exact issue now. So really timely from you both.

@ivanvanderbyl
Copy link
Author

@boy-jer no worries :)

@AlexanderZaytsev I agree, making currentUser available to all controllers is a good idea, and very useful. However I ran into a strange bug which I'm yet to track down, which meant that the injections would fail to assign it if content was not yet assigned. I'm assuming it has something to do with the auto-generated controllers in the new router.

Out of curiosity why do you prefer to use a controller for currentUser? I guess I used a model purely because that is what I'm used to dealing with in the Rails world. And I couldn't think of a use-case for a controller — except maybe adding some methods to check if your signed in etc. like you have. However in my app the ember app can't be loaded unless you're logged in.

Also completely agree on the meta tag idea, my solution was more of an after thought. I used to have a meta tag for referencing the current user ID, which would then result in another ajax load.

@boy-jer
Copy link

boy-jer commented Jan 19, 2013

@ivanvanderbyl and @AlexanderZaytsev what's your suggestion on using thesame pattern that was used to pass current_user to pass CanCan permissions or pundit the new kid or any kind of rails permission system from the server to emberjs. I will mostly like use the activesupport::concerns pattern to include the CanCan permissions but for now lets keep them together. My User model will have roles like current_user.admin or current_user.owner etc and the Cancan permission access will be based on those roles.

I have taken a stab at it and will like your inputs and suggestions and @AlexanderZaytsev please blog about it too.

In approach 1 I couldn't figure out how to pass each User.role for the permission while using appraoch 2 I could figure it out partially. Please look at the code below and let me know if there is a better approach . Many thanks

Approach 1 based on

   class ApplicationController < ActionController::Base
       helper_method :current_permission_json

      def current_permission_json
          UserSerializer.new(ability, :scope => current_user, :root => false).to_json
     end
   end

    class UserSerializer < ActiveModel::Serializer
      attributes :id, :name, :email, :ability

      def  ability
        Ability.new(self).as_json
      end
   end

Approach 2 based on

   class ApplicationController < ActionController::Base
       helper_method :current_permission_json

       delegate :can_update, :can_delete, :can_manage, to: :current_permission

       def current_permission_json
           UserSerializer.new([can_update, can_delete, can_manage], :scope => current_user.role, :root => false).to_json
       end
    end

    class UserSerializer < ActiveModel::Serializer
        attributes :can_update, :can_delete, :can_manage

        def attributes
          hash =  super
          #if scope.admin?
         if scope.role? :admin
            can_manage
        else
            can_update
         if user.role?(:author)
            can_delete, Article do |article|
              article.try(:user) == user
            end
        end       
    end

    private

       def can_manage
           Ability.new.can?(:manage, all)
       end

       def can_update
        # `scope` is current_user
        Ability.new.can?(:update, object)
       end

       def can_delete
          Ability.new.can?(:delete, object)
      end
   end

injections.js

   Ember.Application.initializer({
    name: "permissions",

    initialize: function(container, application) {
       var store = container.lookup('store:main');
       var obj = store.load(CrashLog.User, permissions);

       container.optionsForType('user', { instantiate: false, singleton: true });
       container.register('user', 'current', CrashLog.User.find(obj.id));
   }
  });

 Ember.Application.initializer({
      name: "injectpermissions",
      after: 'permissions',

      initialize: function(container) {
     container.injection('controller:application', 'permissions', 'user:current');
     container.typeInjection('route', 'currentUser', 'user:current');
   }
});

@AlexanderZaytsev
Copy link

@ivanvanderbyl I think you aren't supposed to be working with a model directly in templates in Ember. That's why a controller is needed. Besides, Ember controllers are not Rails controllers, they are more like presenters.

@boy-jer so what's your problem exactly? Recreate every Rails model you have in Ember, then pass the json like how you already do.

@boy-jer
Copy link

boy-jer commented Jan 19, 2013

@AlexanderZaytsev my problem is to have some sort of authorization for my emberjs app. My rails app will have some authorization which will restrict actions based on role. So the problem is how to also restrict access to different parts of the ember app based on a user role on the client side. So an account admin can for instance add or remove members. A member of an account can only add and manage things created by him or her and cant remove other members. Therefore if a member has this restriction on the server side, I want them to have thesame restriction on the client side emberjs app.

We can write a kind of permission system in JavaScript meant for handling authorization in my emberjs app alone. This means we will have one authorization in the rails app and recreate something similar with Javascript code as shown in the attached link.

or 

We can serialize the permissions already created in the backend and avoid writing another permission system in javascript.

When I ran into your blog post and this gist, I thought it might make sense to use the pattern your are using to access current_user to access permissions created on the backend . So the code I pasted is an attempt at that and wanted feedback on it.

For now, I don't know if will recreate every Rails model in ember but i will like to assume that would most likely be the case though I am not hung on that.

I hope this helps clarify my problem.

@ivanvanderbyl
Copy link
Author

@AlexanderZaytsev you raise a good point, I might start using that pattern in the future. For now I'm passing the current user to a controller anyway, like AccountController for example, and interacting with the controller not the model.

@boy-jer I'm not overly sold on either of those solutions. I think the real question here is figuring out what you need on the client side to control authorization to certain parts of your app, and then expose the properties to query it.

Admittedly I haven't needed to do any authorization stuff which was very complex, my app only has two roles, 'Normal user' and 'Account owner', which are distinguished by whether or not they own the current account, which is exposed much the same way as the current user, for sake of argument.

If you plan to implement a full blown authorization system client side, I would first look at simpler ways to solve the main business problem :)

@boy-jer
Copy link

boy-jer commented Jan 20, 2013

@ivanvanderbyl thanks for the comments. I think I like your suggestion to first look for a simpler way. I will try to reduce the number of roles in my app to 3 at the most and just go with something simple as you are doing. I can then revisit it in the future if need be.

@mehulkar
Copy link

This is awesome! Looking forward to using it.

@conrad-vanl
Copy link

@ivanvanderbyl did you ever figure out the issue with not having a content property set when you do a typeInjection with a controller? I believe I'm running in to the same issue.

Example...doing something like this:

Ember.Application.initializer
  name: "currentUser"
  after: 'session'

  initialize: (container, application) ->
    controller = container.lookup("controller:currentUser")
    container.typeInjection('controller', 'currentUser', 'controller:currentUser')

Results in this chrome error:

Uncaught Error: assertion failed: Cannot delegate set('currentUser', <App.CurrentUserController:ember313>) to the 'content' property of object proxy <App.OtherController:ember848>: its 'content' is undefined. 

Interestingly enough...its that OtherController 's content is undefined that's causing the error, I think. I've noticed if I simply add a blank currentUser property to OtherController then it works as well.

I assume there's not really a way to run typeInjection somewhere else and at a later time (then in an initializer)?

@brennanmceachran
Copy link

@conrad-vanl & @ivanvanderbyl Would love to know if you guys got around the content is undefined error?

@rlivsey solves it in his pusher example by reopening the ControllerMixing and setting the value to null...

Ember.ControllerMixin.reopen({
  pusher: null
});

See: http://livsey.org/blog/2013/02/10/integrating-pusher-with-ember/

How did you guys get around this?

@brennanmceachran
Copy link

Looks like that issue is fixed in master

@rromanchuk
Copy link

FYI @AlexanderZaytsev I couldn't get this working as the $('meta[name="current-user"]').attr('content') dom element was not yet loaded. I finally got it working by adding a ready block

Ember.Application.initializer

  name: 'currentUser'

  initialize: (container) ->
    $ ->
      store = container.lookup('store:main')
      attributes = $('meta[name="current-user"]').attr('content')
      console.log attributes
      if attributes
        object = store.load(App.User, JSON.parse(attributes))
        user = App.User.find(object.id)

        controller = container.lookup('controller:currentUser').set('content', user)

        container.typeInjection('controller', 'currentUser', 'controller:currentUser')

@kristianmandrup
Copy link

@boy-yer your Approach #2 looks interesting, but where is the current_permission method you are delegating the can_update etc. to in the ApplicationController? and why do you pass an array with these values to the serializer? Something I'm not getting here... please advice. Thanks!

I'm trying to collect various Auth solutions in a new gem ember-beercan https://github.com/kristianmandrup/ember-beercan

@amaanr
Copy link

amaanr commented May 14, 2013

I couldn't get it to work, I kept returning Guest in my template view.

http://stackoverflow.com/q/16548010/1515899

@listrophy
Copy link

@amaanr @AlexanderZaytsev Perhaps hooking into the deferReadiness and advanceReadiness methods is necessary?

Ember.Application.initializer

  name: 'currentUser'

  initialize: (container) ->
    App.deferReadiness()
    $ ->
      store = container.lookup('store:main')
      attributes = $('meta[name="current-user"]').attr('content')
      console.log attributes
      if attributes
        object = store.load(App.User, JSON.parse(attributes))
        user = App.User.find(object.id)

        controller = container.lookup('controller:currentUser').set('content', user)

        container.typeInjection('controller', 'currentUser', 'controller:currentUser')
      App.advanceReadiness();

This might not work at all... I just learned about the Readiness methods a few minutes ago. :P

@DougPuchalski
Copy link

@AlexanderZaytsev Thanks for that article, it helped a ton

@DougPuchalski
Copy link

Is there a non-private alternative to typeInjection?

@DougPuchalski
Copy link

@amaanr @AlexanderZaytsev I use the readiness calls that @listrophy suggests.

@tomash
Copy link

tomash commented Jun 3, 2014

Remember to tell currentUser Initializer to fire up after loading store, otherwise store will be undefined

Ember.Application.initializer({
  name: 'currentUser',
  after: 'store',

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment