An application has users which can have one of three different account types. Let's call them Small, Medium, Large
I'm trying to figure out the best way to set limits depending on the account type. I looked into cancan but it seems to just have basic authentication options like read, manage, etc. whereas I am mostly going to be doing things like:
if user_has_hit_upload_limit?
# display a message
else
# display the upload form
What's the best way to do this? Am I correct in thinking cancan isn't ideal for this?
I started making a model class that sets all the limits and does all the checks, then added methods to ApplicationHelper to call that class, which is in turn called by the views.
Does this seem reasonable or not good? Is there a better way?
CanCan is for authorization based on abilities defined for users. It's probably not what you want for a quota type system like you described. Assuming you kept track of current usage and the user's quota both in the user model you could write a helper like this:
def user_has_hit_upload_limit?
current_user && current_user.upload_count < current_user.upload_limit
end
The hard part is actually tracking the usage and that will depend on what you are trying to do within your application.
You could still use CanCan define an ability that had a block condition but IMHO that's more complexity then it really needed.
Related
If I want to build a Rails app that has two different types of users, let's say one type is called players and the other one is owners, what is the best and most efficient approach to modeling the app?
Things to take into account:
There should only be one Login, but different Registration forms that Owners/Players can use.
Owners can have access to a control panel but Players cannot.
Owners cannot share any of Players capabilities, but both need to be able to perform Login/Registration.
I am not using Devise, so please do not suggest it.
Different Approaches I've considered:
Using cancancan gem, but it does not really seem to meet my needs in the sense that I am not looking to create a user/admin hierarchical approach but rather a if you're a Player, then you can see these pages and perform these actions but Owners cannot and vice versa. Almost like splitting the app in two. cancancan seems that it would treat Owners as "Players with extra privileges", not different privileges entirely.
Creating separate models with separate login and registration forms, which seems like a disaster waiting to happen. One small mixup between a Players table and the Owners table, especially with the primary keys, and that will be a world of trouble where people could end up logging in to the wrong accounts.
Creating a polymorphic or has_one relation toward an Account model, which so far, seems like the best way to probably go about it. If I created a polymorphic Account model, I can store different types of Players/Owners, but how could I compare login credentials against all types?
I had been trying to find something on this matter regarding how to map this out and was surprised to not find an information on how to do this without using Devise. If anyone has any good links they can point me to that also address this matter (without Devise), please leave them in your answer! Thanks.
I'd suggest one User class with a type attribute that determines whether the user is a Player or an Owner (single table inheritance). This way you keep the registration logic in one place but can customize the forms depending on the user's class.
There must be alternatives to cancancan that help with what you want to do, or you can implement helpers yourself:
def can_access_control_panel?
current_user.is_a?(Owner)
end
You have to have a way to separate one user from another. One way is to add an attribute to the User table so you can call current_user.role and it will return "owner" or return "player".
I have used Pundit gem in the past. It lets you define which controller actions the current user is allowed to access. So as you create resources for your application, you can add a policy that specifies who is allowed to that given resource. This is the repo to the application.
This answer might help you.
We're using cancan to authorize access to ~130 controllers, as things currently stand, this process takes ~100ms per authorization .. that is super slow (it literally takes around a second to just render the menu bar)
Is there techniques for breaking up cancan to simplify the work it needs to do per authorization, or in general speeding it up?
Does anyone have any cancan experiences to share?
Let me try to explain a bit better then .. We have cancan combined with a custom role system. Each role has a number of permissions, and based on if the user is allowed to do certain things, the cancan rules get dynamically built allowing it. In the end there is just one ability.rb file with a few hundred lines of rules which are dynamic based on the currently logged in user's role's permissions. (the roles are dynamic too).
The DB queries are already optimized, everything is eager loaded ahead of time and there is no wasteful queries done to fetch any of that stuff, but somewhere in there 100ms pass per check.
So, I've implemented some permissions between my users and the objects the users modify.. and I would like to lessen the coupling between the views/controllers with the models (calling said permissions). To do that, I had an idea: Implementing some of the permission functionality in the before_save / before_create / before_destroy callbacks. But since the permissions are tied to users (current_user.can_do_whatever?), I didn't know what to do.
This idea may even increase coupling, as current_user is specifically controller-level.
The reason why I initially wanted to do this is:
All over my controllers, I'm having to check if a user has the ability to save / create / destroy. So, why not just return false upon save / create / destroy like rails' .save already does, and add an error to the model object and return false, just like rails' validations?
Idk, is this good or bad? is there a better way to do this?
Let the controller check the user's privileges. To have the model handle authorization logic would lead to more coupling (just in different places, and there'd still be coupling to the controller to get the current user). Checking privileges isn't really internal logic to a model.
Simile: Imagine if it was a file's responsibility to check whether you can read/write to it, instead of having the OS (which is the mother of all controllers, really) handle the access.
If you want cleaner controllers you can (for instance) make some generalized before_filters that restrict access to CRUD actions based on the current user.
Flambino gave you the party line :) but let me add my 2 cents.
It is certainly possible to consider access control logic as part of models.
Model validations is a way of checking the manipulating user's rights on crud actions. A downside of this is exactly the fact that access logic usually generalizes across models, therefore we like to abstract them out like in a neat cancan ability file.
You can actually have your cake and eat it by making your manipulation itself a polymorphic association on models. This is how most audit/version control systems are implemented.
https://github.com/collectiveidea/audited
You could extend it and put your access control logic in audit class validations, so it is in one place.
Audited gem also offers a cunning way to pass the current user to the model level by using an observer as around filter on a controller.
https://github.com/collectiveidea/audited/blob/master/lib/audited/sweeper.rb
but there are other methods too https://github.com/bokmann/sentient_user often considered hacky.
Caveats
In either case you will have to guard against situations when models
are manipulated outside the request cycle (in a background routine,
cronjob, console) when current_user is ill-defined.
You usually do not want to treat access violations with validation
errors but use standard http status code responses.
Third, in a web app setting, you usually manipulate objects via forms
and typically you can already controll access to say an update form
in fact usually coupled with the same access rights as update itself
(cancan even have general alias for new/create and edit/update) so if
the latter is handled with validations, this generalized access logic
will be lost. Not to mention that read-access logic is impossible to
handle with model validations.
hope this helps
I know there are role based authorization gems/plugins for rails to determine if a user can do things based on which role their in. However, is there a best-practice approach to hiding actions from users based on ownership? ie: the show/edit/destroy methods for a user should only be available if they're being performed on the currently logged in user id. Hopefully that makes sense, but I've written some methods to protect against non-owners accessing methods and its become a bit bloated and ugly.
Look at cancan's implementation.
I can recommend on restful_authentication( https://github.com/technoweenie/restful-authentication ). quick tutorial http://railscasts.com/episodes/67-restful-authentication
I want to log user's actions in my Ruby on Rails application.
So far, I have a model observer that inserts logs to the database after updates and creates. In order to store which user performed the action that was logged, I require access to the session but that is problematic.
Firstly, it breaks the MVC model. Secondly, techniques range from the hackish to the outlandish, perhaps maybe even tying the implementation to the Mongrel server.
What is the right approach to take?
Hrm, this is a sticky situation. You pretty much HAVE to violate MVC to get it working nicely.
I'd do something like this:
class MyObserverClass < ActiveRecord::Observer
cattr_accessor :current_user # GLOBAL VARIABLE. RELIES ON RAILS BEING SINGLE THREADED
# other logging code goes here
end
class ApplicationController
before_filter :set_current_user_for_observer
def set_current_user_for_observer
MyObserverClass.current_user = session[:user]
end
end
It is a bit hacky, but it's no more hacky than many other core rails things I've seen.
All you'd need to do to make it threadsafe (this only matters if you run on jruby anyway) is to change the cattr_accessor to be a proper method, and have it store it's data in thread-local storage
I find this to be a very interesting question. I'm going to think out loud here a moment...
Ultimately, what we are faced with is a decision to violate a design-pattern acceptable practice in order to achieve a specific set of functionality. So, we must ask ourselves
1) What are the possible solutions that would not violate MVC pattern
2) What are the possible solutions that would violate the MVC pattern
3) Which option is best? I consider design patterns and standard practices very important, but at the same time if holding to them makes your code more complex, then the right solution may very well be to violate the practice. Some people might disagree with me on that.
Lets consider #1 first.
Off the top of my head, I would think of the following possible solutions
A) If you are really interested in who is performing these actions, should this data be stored in the model any way? It would make this information available to your Observer. And it also means that any other front-end caller of your ActiveRecord class gets the same functionality.
B) If you are not really interested in understanding who created a entry, but more interested in logging the web actions themselves, then you might consider "observing" the controller actions. It's been some time since I've poked around Rails source, so I'm not sure who their ActiveRecord::Observer "observes" the model, but you might be able to adapt it to a controller observer. In this sense, you aren't observing the model anymore, and it makes sense to make session and other controller type data information to that observer.
C) The simplest solution, with the least "structure", is to simply drop your logging code at the end of your action methods that you're watching.
Consider option #2 now, breaking MVC practices.
A) As you propose, you could find the means to getting your model Observer to have access to the Session data. You've coupled your model to your business logic.
B) Can't think of any others here :)
My personal inclination, without knowing anymore details about your project, is either 1A, if I want to attach people to records, or 1C if there are only a few places where I'm interested in doing this. If you are really wanting a robust logging solution for all your controllers and actions, you might consider 1B.
Having your model observer find session data is a bit "stinky", and would likely break if you tried to use your model in any other project/situation/context.
You're right about it breaking MVC. I would suggest using callbacks in your controllers, mostly because there are situations (like a model which save is called but fails validation) where you wouldn't want an observer logging anything.
I found a clean way to do what is suggested by the answer I picked.
http://pjkh.com/articles/2009/02/02/creating-an-audit-log-in-rails
This solution uses an AuditLog model as well as a TrackChanges module to add tracking functionality to any model. It still requires you to add a line to the controller when you update or create though.
In the past, when doing something like this, I have tended towards extending the User model class to include the idea of the 'current user'
Looking at the previous answers, I see suggestions to store the actual active record user in the session. This has several disadvantages.
It stores a possibly large object in the session database
It means that the copy of the user is 'cached' for all time (or until logout is forced). This means that any changes in status of this user will not be recognised until the user logs out and logs back in. This means for instance, that attempting to disable the user will await him logging off and back on. This is probably not the behaviour you want.
So that at the beginning of a request (in a filter) you take the user_id from the session and read the user, setting User.current_user.
Something like this...
class User
cattr_accessor :current_user
end
class Application
before_filter :retrieve_user
def retrieve_user
if session[:user_id].nil?
User.current_user = nil
else
User.current_user = User.find(session[:user_id])
end
end
end
From then on it should be trivial.
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails