-
-
Save ivanvanderbyl/4560416 to your computer and use it in GitHub Desktop.
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'); | |
} | |
}); |
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.
@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.
@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.
@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');
}
});
@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.
@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.
@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 :)
@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.
This is awesome! Looking forward to using it.
@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)?
@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?
Looks like that issue is fixed in master
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')
@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
I couldn't get it to work, I kept returning Guest in my template view.
@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
@AlexanderZaytsev Thanks for that article, it helped a ton
Is there a non-private alternative to typeInjection
?
@amaanr @AlexanderZaytsev I use the readiness calls that @listrophy suggests.
Remember to tell currentUser Initializer to fire up after loading store, otherwise store will be undefined
Ember.Application.initializer({
name: 'currentUser',
after: 'store',
Incase you're wondering where
current_user_json
comes from, I'm simply using ActiveModelSerializers to render the current user: