I am using Ruby on Rails 3.2.2 and I would like to know what is a common approach when it must be checked if an user has proper authorizations to \"read\" records present in
I think you sholud look for declarative_authorization gem. With its with_permissions_to
method you can easily perfom such database queries. For example: Article.with_permissions_to(:read).limit(10).offset(20)
The cancan gem has this functionality. In fact, as of version 1.4 it will automatically scope your queries to return objects that are accessible to the current user.
See this page for more info: https://github.com/ryanb/cancan/wiki/Fetching-Records
For best performance I would suggest storing list of user readable articles in the session - the user is not going to change within the session and you may consider refresh frequency and/or conditions separately. Assuming that your Articles.list() can be filtered by ids all you will need to do will be to pass the list of user readable ids to Articles.list() functionality. Re user readable list update: you should really update it relatively infrequently - at most once per search, you don' t want to refresh the complete list on every page load for the simple reason that the new results may appear in the pages that user already scrolled through anyway.
It depends on your readable_by_user
function. If it is easy to translate into an SQL, than it is the way forward. If it is more complicated than that then you most probably have to do the check manually.
UPDATE: To clarify the point of creating an SQL query for the readable list I present an example. Assume, that a readability of an article to a given user is dependent of the following:
SELECT a.user == ? FROM Articles a WHERE a.id = ?
)SELECT a.state == 0 FROM Articles a WHERE a.user = ?
)sql:
SELECT max(g.rights) > 64
FROM Groups g
JOIN Groups_users gu on g.id = ug.group_id
WHERE gu.id = ?
sql:
SELECT 1
FROM Articles_users au
WHERE au.article_id = ? AND au.user_id = ?
These can be summarized in the following query:
def articles_for_user(user)
Articles.find_by_sql(["
SELECT a.*
FROM Articles a
LEFT OUTER JOIN Articles_users au on au.article_id = a.id and au.user_id = ?
WHERE a.user_id = ?
OR au.user_id = ?
OR 64 <= (SELECT max(g.rights)
FROM Groups g
JOIN Groups_users gu on g.id = ug.group_id
WHERE gu.id = ?)
", user.id, user.id, user.id, user.id])
end
This is sure a complicated query, but the most efficient solution. The database should do database stuff, if you only use SQL queries and some logic to evaluate your readable_bu_user
then you can translate it into one pure SQL query.
I had the same issue on a system I'm currently worked on.
The most efficient way I found was to implement a batch job that pre-calculates the authorization state of each record. I went with something like accessible_by_companies
and stored an array with all the company codes that could access those records, but you might as well work with accessible_by_users
if that's your case.
On the "show" action, I recalculate the list of authorized companies for the record, use it to perform the authorization check, and store it again.
I used ElasticSearch to store the pre-calculated values and all the data I needed to perform queries and listings. The database is only touched when viewing a record or by the batch job. There's a big performance gain on this approach, give it a try.