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 a "list" of records. That is, at this time I have the following:
class Article < ActiveRecord::Base
def readable_by_user?(user)
# Implementation of multiple authorization checks that are not easy to
# translate into an SQL query (at database level, it executes a bunch of
# "separate" / "different" SQL queries).
... # return 'true' or 'false'
end
end
By using the above code I can perform authorization checks on a single article object:
@article.readable_by_user?(@current_user)
However, when I would like to make (usually, in my controller index
action) something like the following by retrieving exactly 10 objects
Article.readable_by_user(@current_user).search(...).paginate(..., :per_page => 10)
I must still to perform authorization checks on each object. So, what I can make to perform authorization checks on that "list" of records (an array of Article
objects) in a "smart" / "performant" way? That is, for example, should I load Article.all
(maybe ordering those by created data, limiting the SQL query to 10 records, ...) and then to iterate on each of those objects so to perform authorization checks? or should I make something different (maybe with some SQL query trick, some Ruby on Rails facility or something else)?
UPDATED after @Matzi answer
I tried to retrieve articles readable by an user "manually", for example by using the find_each
method:
# Note: This method is intended to be used as a "scope" method
#
# Article.readable_by_user(@current_user).search(...).paginate(..., :per_page => 10)
#
def self.readable_by_user(user, n = 10)
readable_article_ids = []
Article.find_each(:batch_size => 1000) do |article|
readable_article_ids << article.id if article.readable_by_user?(user)
# Breaks the block when 10 articles have passed the readable authorization
# check.
break if readable_article_ids.size == n
end
where("articles.id IN (?)", readable_article_ids)
end
At this time, the above code is the most "performant compromise" that I can think of, even if it has some pitfall: it "restricts" the amount of retrieved objects to a given amount of records with given id
s (10 records by default in the above example); practically speaking, it "really" doesn't retrieve all objects readable by an user since when you try to further scope the related ActiveRecord::Relation
"where" / "with which" the readable_by_user
scope method is used (for example, when you would also search articles by title
adding a further SQL query clause), it would restrict records to those where("articles.id IN (?)", readable_article_ids)
(that is, it "limits" / "restricts" the amount of retrieved and readable objects to first 10 and all others articles readable by the user will be ignored when searching by title
). A solution to the issue in order to make the readable_by_user
method to properly work with further scope methods could be to do not break
the block so to load all readable articles, but it is no good for performance reasons when there are a lot of records (maybe, another solution could be to store somewhere all article id
s readable by an user, but I think it is not a common/easy solution to solve the issue).
So, there is some way to accomplish what I would like to make in a performant and "really" correct way (maybe, by changing the above approach at all)?