Advanced Caching with User Permissions and Authorization Discussion
A great episode again Chris! I wish you had released this one a week earlier though :). It would have made my life way easier. Could you tell me if there is any advantage of using meta tag like
<meta content="<%= current_user.id % >">
over just adding the data attr to the body like
<body data-currentuserid="<%= current_user.id %>"> ?
Hey Chris. Very interesting episode and solution.
I am trying to implement something like this (because you gave me an idea), but would like to extend it much further.
I am going with Gon gem, which is basically the same you are doing, but with different approach.
What I would like to do actually is a wee bit harder than what is shown here, because a proper authorization is needed, not just role based.
I would like to do this with CanCanCan, but I don't relly know how to do it properly and maintainable.
Currently I have a user's ability permissions hash, but how to actually do it in JS - here i have problems.
Any ideas? Do you think this is too much and doesn't really weight out caching?
Caching is great to make a site much faster, but with the cost of simplicity. Everything is ballooning in complexity.
It definitely does balloon in complexity which is why this is a tradeoff that often isn't valuable until your servers are falling down because you have too much traffic. It's a serious investment coding wise and requires a lot of engineering time to improve performance.
I'm not familiar with the Gon gem, but I would say that if you've got a resource like a BlogPost, you could take your CanCanCan or Pundit scopes and then just simply ask for all the things your view needs (show, edit, update, destroy, whatever you need) and convert that to a JSON object that you can pass to your JS. This way you've got a simple JSON object with a bunch of booleans that can be easily evaluated client side without having to do much logic duplicated on both the client and server.
So you can ignore my comment on the other thread because I found this.
One thing I am running into is cached results when I use filters in my params.
For instance, using your List example. Say your root_path is List#Index. But you also could tag your lists. So say you had a tag for 'rails', which basically just re-rendered your List#Index but changed the value of the `@lists` collection to just include the lists related to the tag `rails`.
How do you tackle Russian Doll caching in a scenario like that, where the views are identical, no partials or any data changes, the only thing that changes is the filter param in the URL?
Thanks again for all the wonderful work you do.
Edit: Oh and to make my scenario a bit more complicated, what actually happens is when a filter param is present, the results it shows on the `List#Index` is actually different based on the role of the user requesting. So just adding the `edit` link to the `rails` list, if the user is an admin, but if the user is a regular user that `edit` link on the `rails` list doesn't show up.
You could include the filter and role in your cache key and save them as separate caches.
I would be somewhat careful with that because if you have a lot, then you're just filling up the cache storage with stuff that's likely to get blown away and you won't get any benefits from caching. Compiling a list of small cache snippets is super fast, so you might consider just not caching the list, but cache each individual item instead. The compilation of everything will be fast enough and can save you on some cache storage space if you notice bloat saving all the different copies.
How would I save them as separate caches? My partial is `cache profile do`, would I change that to `cache [params[:rating], profile do`? Or did you have something else in mind?
I have tried the above version and it doesn't work properly. It still messes up the views when I switch between my `/profiles` and `/profiles?rating=speed`.
Edit: For more details, here is my Stack Overflow question: http://stackoverflow.com/qu...
That guy had the same suggestion you did, but that doesn't work :(
Chris, I have replied twice and both times Disqus has marked it as spam. Should I go ahead and put the response to this question in my SO question?
Edit: I just added the logs it to the SO question, so you can refresh it.
Doh, yeah they do that sometimes. At some point soon I'm going to swap out Disqus with my own comment system.
Hmm, I can't quite tell where you put it. Did you put it on your Index?
<% cache [params[:rating], @profiles] do %>
Sorry, I put it around the local variable when the partial renders each time:
`<% cache [params[:rating], profile] do %>`
Around the collection, the index is just `<% cache @profiles do %>`
I believe you're going to need it on the collection too because that's what the filter affects.
I just did that, so now I have it on both and no dice. I updated the question with the logs of the profile#index page refresh when it is added to both.
Nothing really. I tried building a sample app to cache in Rails 5 with what we talked about and everything works as expected there. :\
Hi Chris. Would be great to see more of your caching / performance videos as there seems like there isn't enough information out there and its a massive part of a web site. For example, what happens if you have a comments list and you need to find out if the current_user has already commented so don't show the "add comment" button. You won't have the users ID if the comment is on another page, so how would you get the users ID? Would you do an API call to the server via ajax, this seems like it could get sluggish OR maybe not?
Hi! chris. Im using Roles and Cancancan all over my app. I was thinking of adding the role record to the cache, has it will create a cache base on the role and not the current_user. What do you think about this approach?
Curious as to why you couldn't keep the logic in the ERB and cache the list scoped to the role, rather than the user?
Your role counts are going to be very limited, whereas your user count is not. With roles, you'll have a few caches, but not a ton, and you benefit from keeping that logic on the server.
Am I missing something with this approach?
This is the first video on your site, so I don't know if you post another updated video for this topic. Still, for everyone here I'd say, please take in consideration that must implement an authorization of some sorts (e.g. Gem Pundit, CanCanCan) your back-end, because CSS can be easy overwritten, and the user can delete the hidden class, and they will have access to whatever you're trying to hide. If you still want to use JS, instead of hiding the HTML tag, once the user requests a page, we can check for the metatag and maybe remove the HTML tag instead of hiding it. That's my two cents.
Hiding vs removing the tag in the HTML doesn't really make any difference. Just have to make sure that only admins are allowed on the server side of the request.
Just different point of view. Less options hidding on your html, less opportunies for unaunthorize users to do harm on your site, while adding an authorization option on your back-end tight your security even more your site. On the other side, givin less opportunities to an unthorize user to see a form or edit button less calls to the server and ugly response to the user. like I said just my two cents.
Hey Chris, great lesson! I'm using Cancancan in a project with fairly complex permissioning and looking to implement a system like you describe at the end where we create a json object based on what will be on the page then use JS to show/hide actions. Do you have any thoughts on how to do this without creating more N+1 problems when creating the json object?
Cancancan don't seem to have any way to easily go from their Ability class to something like a json object so I'm thinking I'll have to create something custom. Although this feels like a common enough problem that an extension for Cancancan may either already exist or be valuable to the community for someone (me?) to create.