2

私は Ruby on Rails 3.2.2 を使用しています。ユーザーがレコードの「リスト」に存在するレコードを「読み取る」ための適切な権限を持っているかどうかを確認する必要がある場合の一般的なアプローチを知りたいです。つまり、現時点では次のようなものがあります。

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

上記のコードを使用して、単一の記事オブジェクトに対して承認チェックを実行できます。

@article.readable_by_user?(@current_user)

ただし、正確に10個のオブジェクトを取得して、(通常、コントローラーindexアクションで)次のようなものを作成したい場合

Article.readable_by_user(@current_user).search(...).paginate(..., :per_page => 10)

各オブジェクトに対して承認チェックを実行する必要があります。では、レコードの「リスト」(オブジェクトの配列)に対して「スマート」/「パフォーマンス」な方法で承認チェックを実行するにはどうすればよいでしょうか? つまり、たとえば、ロードして (作成されたデータごとに並べ替えたり、SQL クエリを 10 レコードに制限したりするなど)、それらの各オブジェクトを反復処理して、承認チェックを実行する必要がありますか? または、何か違うものを作る必要がありますか (おそらく、SQL クエリのトリック、Ruby on Rails 機能など)。ArticleArticle.all

@Matziの回答後に更新

たとえば、次の方法を使用して、ユーザーが「手動で」読み取り可能な記事を取得しようとしました。find_each

# 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

現時点では、上記のコードは、落とし穴があるとしても、私が考えることができる最も「パフォーマンスの良い妥協案」です。取得するオブジェクトの量を、指定されたs (デフォルトでは 10 レコード) の指定された量のレコードに「制限id」します。上記の例では); 実際には、ユーザーが読み取り可能なすべてのオブジェクトを「実際に」取得するわけではありません。これは、関連するActiveRecord::Relation「どこで」/「どの方法で」をさらにスコープしようとすると、readable_by_userスコープ メソッドが使用されるためです (たとえば、次の方法でも記事を検索する場合)。titleさらに SQL クエリ句を追加する)、レコードをそれらに制限where("articles.id IN (?)", readable_article_ids)します(つまり、「制限」/「制限」します)。による検索では無視されますtitle)。readable_by_userメソッドが他のスコープ メソッドで適切に動作するようにするための問題の解決策は、読み取り可能なすべての記事を読み込むようにブロックしない ことですが、多くのレコードがある場合 (おそらく、別の解決策は、すべての記事をユーザーが読み取れる場所に保存することですが、問題を解決するための一般的/簡単な解決策ではないと思います)。breakid

それで、私がパフォーマントで「本当に」正しい方法で作りたいことを達成する方法はありますか(おそらく、上記のアプローチをまったく変更することによって)?

4

5 に答える 5

3

それはあなたの機能に依存しますreadable_by_user。SQL に変換するのが簡単であれば、それが進むべき道です。それよりも複雑な場合は、おそらく手動でチェックを行う必要があります。

更新: 読み取り可能なリストの SQL クエリを作成するポイントを明確にするために、例を示します。特定のユーザーにとっての記事の読みやすさは、次の要素に依存すると仮定します。

  • ユーザー自身の記事 ( 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 = ?

これらは、次のクエリに要約できます。

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

これは確かに複雑なクエリですが、最も効率的なソリューションです。データベースはデータベースの処理を行う必要があります。SQL クエリといくつかのロジックを使用して評価するreadable_bu_user場合は、それを 1 つの純粋な SQL クエリに変換できます。

于 2012-06-17T19:10:42.783 に答える
1

declarative_authorization gemを探すべきだと思います。そのwith_permissions_toメソッドを使用すると、このようなデータベース クエリを簡単に実行できます。例えば:Article.with_permissions_to(:read).limit(10).offset(20)

于 2012-06-17T19:30:47.847 に答える
0

カンカンジェムにはこの機能があります。実際、バージョン1.4以降、現在のユーザーがアクセスできるオブジェクトを返すようにクエリのスコープが自動的に設定されます。

詳細については、次のページを参照してください:https ://github.com/ryanb/cancan/wiki/Fetching-Records

于 2012-06-17T20:56:32.043 に答える
0

現在取り組んでいるシステムでも同じ問題が発生しました。

私が見つけた最も効率的な方法は、各レコードの承認状態を事前に計算するバッチ ジョブを実装することでした。私は次のようなものをaccessible_by_companies使用して、それらのレコードにアクセスできるすべての会社コードを含む配列を保存しましたが、それがaccessible_by_usersあなたの場合であれば、あなたも一緒に作業することができます.

「表示」アクションでは、レコード用に承認された企業のリストを再計算し、それを使用して承認チェックを実行し、再度保存します。

ElasticSearchを使用して、事前に計算された値と、クエリとリストの実行に必要なすべてのデータを保存しました。データベースは、レコードを表示するとき、またはバッチ ジョブによってのみ操作されます。このアプローチではパフォーマンスが大幅に向上します。試してみてください。

于 2012-06-20T04:21:07.600 に答える
0

最高のパフォーマンスを得るには、ユーザーが読み取り可能な記事のリストをセッションに保存することをお勧めします。ユーザーはセッション内で変更されることはなく、更新の頻度や条件を個別に検討することができます。Articles.list() を ID でフィルタリングできると仮定すると、ユーザーが読み取り可能な ID のリストを Articles.list() 機能に渡すだけで済みます。ユーザーが読めるリストの更新について: 実際には比較的頻繁に更新する必要はありません。多くても検索ごとに 1 回です。新しい結果がユーザーが既に使用しているページに表示される可能性があるという単純な理由から、ページが読み込まれるたびに完全なリストを更新する必要はありません。とにかくスクロール。

于 2012-06-26T08:22:24.070 に答える