Rails: Zugriff auf current_user aus einem Modell in Ruby on Rails

74

Ich muss eine differenzierte Zugriffssteuerung in einer Ruby on Rails-App implementieren. Die Berechtigungen für einzelne Benutzer werden in einer Datenbanktabelle gespeichert, und ich dachte, es wäre am besten, die jeweilige Ressource (dh die Instanz eines Modells) entscheiden zu lassen, ob ein bestimmter Benutzer daraus lesen oder darauf schreiben darf. Diese Entscheidung jedes Mal im Controller zu treffen, wäre sicherlich nicht sehr trocken.
Das Problem ist, dass das Modell dazu Zugriff auf den aktuellen Benutzer benötigt, um so etwas aufzurufen . Modelle haben im Allgemeinen jedoch keinen Zugriff auf Sitzungsdaten. may_read?(current_user, attribute_name)

Es gibt einige Vorschläge, um einen Verweis auf den aktuellen Benutzer im aktuellen Thread zu speichern, z. B. in diesem Blog-Beitrag . Dies würde das Problem sicherlich lösen.

In den benachbarten Google-Ergebnissen wurde mir jedoch empfohlen, einen Verweis auf den aktuellen Benutzer in der Benutzerklasse zu speichern, der vermutlich von jemandem entwickelt wurde, dessen Anwendung nicht viele Benutzer gleichzeitig aufnehmen muss. ;)

Kurz gesagt, ich habe das Gefühl, dass mein Wunsch, aus einem Modell heraus auf den aktuellen Benutzer (dh Sitzungsdaten) zuzugreifen, darauf zurückzuführen ist, dass ich es falsch gemacht habe .

Kannst du mir sagen, wie ich falsch liege?

Knuton
quelle
Ironischerweise ist sieben Jahre nachdem dies gefragt wurde, "es falsch zu machen" 404ing. :)
Andrew Grimm
1
"Ich dachte, es wäre am besten, die jeweilige Ressource (dh die Instanz eines Modells) entscheiden zu lassen, ob ein bestimmter Benutzer daraus lesen oder darauf schreiben darf." Das Modell ist möglicherweise nicht der beste Ort für diese Logik. Edelsteine ​​wie Pundit und Authority legen ein Muster fest, nach dem für jedes Modell eine separate "Richtlinie" - oder "Autorisierungs" -Klasse festgelegt wird (und Modelle können diese teilen, wenn Sie möchten). Diese Klassen bieten Möglichkeiten, um problemlos mit dem aktuellen Benutzer zu arbeiten.
Nathan Long

Antworten:

46

Ich würde sagen, Ihre Instinkte, sich current_useraus dem Modell herauszuhalten, sind korrekt.

Wie Daniel bin ich alles für Skinny Controller und Fat Models, aber es gibt auch eine klare Aufteilung der Verantwortlichkeiten. Der Zweck des Controllers besteht darin, die eingehende Anforderung und Sitzung zu verwalten. Das Modell sollte in der Lage sein, die Frage "Kann Benutzer x y mit diesem Objekt tun?" Zu beantworten, aber es ist unsinnig, auf das zu verweisen current_user. Was ist, wenn Sie in der Konsole sind? Was ist, wenn ein Cron-Job läuft?

In vielen Fällen kann dies mit der richtigen Berechtigungs-API im Modell einzeilig behandelt werden before_filters, die für mehrere Aktionen gilt. Wenn die Dinge jedoch komplexer werden, möchten Sie möglicherweise eine separate Ebene (möglicherweise in lib/) implementieren , die die komplexere Autorisierungslogik kapselt, um zu verhindern, dass Ihr Controller aufgebläht wird und Ihr Modell nicht zu eng an den Webanforderungs- / Antwortzyklus gekoppelt wird .

gtd
quelle
Vielen Dank, Ihre Bedenken überzeugen. Ich muss genauer untersuchen, wie einige der Zugriffssteuerungs-Plugins für Rails ticken.
Knuton
4
Autorisierungsmuster brechen MVC von Natur aus. Jede Möglichkeit, dies zu umgehen, ist in der Regel schlimmer als das einfache Brechen von MVC in diesem Bereich. Wenn Sie User.current_userüber die Registrierung des aktuellen Threads verwenden müssen, tun Sie dies, tun Sie einfach Ihr Bestes, um es nicht zu
überbeanspruchen
1
Ich denke nur, dass die Idee, dass Modelle nicht wissen sollten, auf welchen Benutzer sie zugreifen, nur eine schlecht durchdachte Anstrengung zur Perfektion ist. NICHT EINE EINZELNE PERSON HIER WÜRDE IHNEN RATEN, IN DER ZEITZONE das Modell in den Methodenparametern zu übergeben. Das wäre dumm. Aber das ist das Gleiche ... Sie haben ein Zeitobjekt, und bei den meisten Apps hat der Anwendungscontroller die Zeitzone festgelegt, häufig basierend darauf, wer angemeldet ist, und dann kennen die Modelle die Zeitzone.
Nroose
@nroose Überprüfen Sie Ihre Annahmen, da die meisten Zeitmodelle am besten von einfachen UTC-Zeitstempeln bedient und auf der Ebene der Benutzeroberfläche lokalisiert werden. Manchmal muss Ihr Modell zeitzonenbezogene Dinge wissen, aber nur, wenn das Modell tatsächlich einem physischen Standort zugeordnet ist. Gleiches gilt für Benutzer. Möglicherweise hat das Modell die Verantwortung, den Zugriff zu bestimmen, modelliert ihn jedoch entsprechend. Um zu sehen, warum current_user für Modelle eine schlechte Idee ist, sollten Sie Folgendes berücksichtigen: Sollte es unmöglich sein, ein Objekt in einem Skript oder Prozess zu manipulieren, ohne einen Benutzer zu injizieren?
GTD
1
Die Verwendung von Richtlinien sollte hier sinnvoll sein. Kasse ein Juwel namens Pandit. Es bietet eine separate Ebene (um Ihren Controller dünn zu halten) für die Autorisierung.
Nirav Gandhi
36

Obwohl diese Frage von vielen beantwortet wurde, wollte ich nur meine zwei Cent schnell hinzufügen.

Die Verwendung des Ansatzes #current_user für das Benutzermodell sollte aus Gründen der Thread-Sicherheit mit Vorsicht implementiert werden.

Es ist in Ordnung, eine Klassen- / Singleton-Methode für Benutzer zu verwenden, wenn Sie daran denken, Thread.current als Methode zum Speichern und Abrufen Ihrer Werte zu verwenden. Dies ist jedoch nicht so einfach, da Sie auch Thread.current zurücksetzen müssen, damit die nächste Anforderung keine Berechtigungen erbt, die sie nicht erben sollte.

Der Punkt, den ich ansprechen möchte, ist, wenn Sie den Status in Klassen- oder Singleton-Variablen speichern, daran zu denken, dass Sie die Thread-Sicherheit aus dem Fenster werfen.

Josh K.
quelle
14
+10 wenn ich für diese Bemerkung könnte. Das Speichern des Anforderungsstatus in Methoden / Variablen für Singleton-Klassen ist eine sehr schlechte Idee.
molf
32

Der Controller sollte der Modellinstanz mitteilen

Die Arbeit mit der Datenbank ist Aufgabe des Modells. Die Bearbeitung von Webanforderungen, einschließlich der Kenntnis des Benutzers für die aktuelle Anforderung, ist Aufgabe des Controllers.

Wenn eine Modellinstanz den aktuellen Benutzer kennen muss, sollte ein Controller dies mitteilen.

def create
  @item = Item.new
  @item.current_user = current_user # or whatever your controller method is
  ...
end

Dies setzt voraus, dass Itemein attr_accessorfür hat current_user.

(Hinweis - Ich habe diese Antwort zum ersten Mal auf eine andere Frage gepostet , aber ich habe gerade bemerkt, dass diese Frage ein Duplikat dieser Frage ist.)

Nathan Long
quelle
3
Sie machen ein prinzipielles Argument, aber praktisch kann dies dazu führen, dass der aktuelle Benutzer an vielen Stellen viel zusätzlichen Code erhält. Meiner Meinung nach User.current_usermacht der threadlokale Ansatz manchmal den Rest des Codes kürzer und verständlicher, obwohl MVC nicht funktioniert.
Antinom
tolle! Ich habe mehrere verschachtelte Objekte, also habe ich die IDs in die Ansichten (versteckt) und den attr_accessor in das Modell eingefügt
Cris R
1
@antinome Vielleicht besteht das eigentliche Problem im Fall des OP darin, dass das Modell Berechtigungsprüfungen abwickelt. Eine separate "Richtlinie" - oder "Autorisierungs" -Klasse kann dem Modell und dem aktuellen Benutzer übergeben und von dort aus autorisiert werden. Dieser Ansatz bedeutet, dass Sie nicht über die Berechtigungen zur Modellprüfung in Kontexten verfügen, in denen diese nicht überprüft werden sollen, z. B. bei Rake-Aufgaben.
Nathan Long
14

Ich bin ganz auf dünne Controller- und Fat-Modelle eingestellt, und ich denke, Auth sollte dieses Prinzip nicht brechen.

Ich programmiere jetzt seit einem Jahr mit Rails und komme aus der PHP-Community.Für mich ist es eine triviale Lösung, den aktuellen Benutzer als "anforderungslang global" festzulegen. Dies erfolgt standardmäßig in einigen Frameworks, zum Beispiel:

In Yii können Sie auf den aktuellen Benutzer zugreifen, indem Sie Yii :: $ app-> user-> identity aufrufen. Siehe http://www.yiiframework.com/doc-2.0/guide-rest-authentication.html

In Lavavel können Sie dasselbe auch tun, indem Sie Auth :: user () aufrufen. Siehe http://laravel.com/docs/4.2/security

Warum, wenn ich nur den aktuellen Benutzer vom Controller übergeben kann?

Nehmen wir an, wir erstellen eine einfache Blog-Anwendung mit Unterstützung für mehrere Benutzer. Wir erstellen sowohl eine öffentliche Site (Anon-Benutzer können Blog-Beiträge lesen und kommentieren) als auch eine Admin-Site (Benutzer sind angemeldet und haben CRUD-Zugriff auf ihre Inhalte in der Datenbank.)

Hier sind "die Standard-ARs":

class Post < ActiveRecord::Base
  has_many :comments
  belongs_to :author, class_name: 'User', primary_key: author_id
end

class User < ActiveRecord::Base
  has_many: :posts
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

Jetzt auf der öffentlichen Seite:

class PostsController < ActionController::Base
  def index
    # Nothing special here, show latest posts on index page.
    @posts = Post.includes(:comments).latest(10)
  end
end

Das war sauber und einfach. Auf der Admin-Site wird jedoch etwas mehr benötigt. Dies ist die Basisimplementierung für alle Admin-Controller:

class Admin::BaseController < ActionController::Base
  before_action: :auth, :set_current_user
  after_action: :unset_current_user

  private

    def auth
      # The actual auth is missing for brievery
      @user = login_or_redirect
    end

    def set_current_user
      # User.current needs to use Thread.current!
      User.current = @user
    end

    def unset_current_user
      # User.current needs to use Thread.current!
      User.current = nil
    end
end

Daher wurde die Anmeldefunktion hinzugefügt und der aktuelle Benutzer wird in einem globalen Benutzer gespeichert. Das Benutzermodell sieht nun folgendermaßen aus:

# Let's extend the common User model to include current user method.
class Admin::User < User
  def self.current=(user)
    Thread.current[:current_user] = user
  end

  def self.current
    Thread.current[:current_user]
  end
end

User.current ist jetzt threadsicher

Lassen Sie uns andere Modelle erweitern, um dies zu nutzen:

class Admin::Post < Post
  before_save: :assign_author

  def default_scope
    where(author: User.current)
  end

  def assign_author
    self.author = User.current
  end
end

Das Post-Modell wurde erweitert, sodass das Gefühl besteht, dass derzeit nur die Beiträge des Benutzers angemeldet sind. Wie cool ist das!

Der Admin Post Controller könnte ungefähr so ​​aussehen:

class Admin::PostsController < Admin::BaseController
  def index
    # Shows all posts (for the current user, of course!)
    @posts = Post.all
  end

  def new
    # Finds the post by id (if it belongs to the current user, of course!)
    @post = Post.find_by_id(params[:id])

    # Updates & saves the new post (for the current user, of course!)
    @post.attributes = params.require(:post).permit()
    if @post.save
      # ...
    else
      # ...
    end
  end
end

Für das Kommentarmodell könnte die Administratorversion folgendermaßen aussehen:

class Admin::Comment < Comment
  validate: :check_posts_author

  private

    def check_posts_author
      unless post.author == User.current
        errors.add(:blog, 'Blog must be yours!')
      end
    end
end

IMHO: Dies ist eine leistungsstarke und sichere Methode, um sicherzustellen, dass Benutzer nur auf einmal auf ihre Daten zugreifen / diese ändern können. Überlegen Sie, wie viel Entwickler Testcode schreiben muss, wenn jede Abfrage mit "current_user.posts.whatever_method (...)" beginnen muss. Viel.

Korrigieren Sie mich, wenn ich falsch liege, aber ich denke:

Es geht um die Trennung von Bedenken. Selbst wenn klar ist, dass nur der Controller die Authentifizierungsprüfungen durchführen soll, sollte der aktuell angemeldete Benutzer keinesfalls in der Controller-Schicht bleiben.

Einziges, woran man sich erinnern sollte: NICHT überbeanspruchen! Denken Sie daran, dass es möglicherweise E-Mail-Mitarbeiter gibt, die User.current nicht verwenden, oder dass Sie über eine Konsole usw. auf die Anwendung zugreifen.

Hessen
quelle
8

Nun, meine Vermutung hier ist, dass current_useres sich endlich um eine Benutzerinstanz handelt. Warum fügen Sie diese Berechtigungen nicht dem UserModell oder dem Datenmodell hinzu und möchten, dass die Berechtigungen angewendet oder abgefragt werden?

Ich vermute, dass Sie Ihr Modell irgendwie umstrukturieren und den aktuellen Benutzer als Parameter übergeben müssen, wie zum Beispiel:

class Node < ActiveRecord
  belongs_to :user

  def authorized?(user)
    user && ( user.admin? or self.user_id == user.id )
  end
end

# inside controllers or helpers
node.authorized? current_user
khelll
quelle
5

Ich bin immer wieder erstaunt über die Antworten von Leuten, die nichts über die zugrunde liegenden Geschäftsanforderungen des Fragestellers wissen. Ja, im Allgemeinen sollte dies vermieden werden. Es gibt jedoch Umstände, unter denen dies angemessen und äußerst nützlich ist. Ich hatte selbst nur einen.

Hier war meine Lösung:

def find_current_user
  (1..Kernel.caller.length).each do |n|
    RubyVM::DebugInspector.open do |i|
      current_user = eval "current_user rescue nil", i.frame_binding(n)
      return current_user unless current_user.nil?
    end
  end
  return nil
end

Dies führt den Stapel rückwärts und sucht nach einem Rahmen, auf den reagiert wird current_user. Wenn keine gefunden wird, wird null zurückgegeben. Es könnte robuster gemacht werden, indem der erwartete Rückgabetyp bestätigt wird und möglicherweise bestätigt wird, dass der Besitzer des Rahmens ein Controller-Typ ist, der jedoch im Allgemeinen nur gut funktioniert.

keredson
quelle
1
Wirklich, wie wann? Ich kann mir keinen einzigen Grund vorstellen, warum jemand dies tun möchte. Tu es einfach nicht. Es ist unsinnig, fehleranfällig und macht Ihren Code unbrauchbar (wenn der Kontext etwas anderes als eine HTTP-Anfrage / Antwort war). Es gibt weitaus bessere Lösungen, z. B. das Festlegen einer attr_acessorvom Anrufer auf den aktuellen Benutzer. Ich bin mir ziemlich sicher, dass fast jeder erfahrene Programmierer Ihnen sagen wird, dass dies eine beschissene Idee ist, und Sie sollten es nicht tun. Ich mag die Neuheit Ihrer Antwort, aber es ist ein Greuel.
Mohamad
Mein Fall war die Erfassung des Benutzers (falls vorhanden), der eine Finanztransaktion ausgelöst hat, unabhängig davon, wie / welcher Codepfad er dort ankam.
Keredson
2
@ Mohamad, wäre schön, wenn Sie mehr über die Verwendungattr_acessor
WM
Kein Produktionscode sollte von solchen Laufzeit-Debug-Funktionen abhängen.
Shyam Habarakada
5

Ich habe dies in einer Anwendung von mir. Es sucht einfach nach der aktuellen Controller-Sitzung [: user] und setzt sie auf eine User.current_user-Klassenvariable. Dieser Code funktioniert in der Produktion und ist ziemlich einfach. Ich wünschte, ich könnte sagen, ich hätte es mir ausgedacht, aber ich glaube, ich habe es von einem anderen Internet-Genie ausgeliehen.

class ApplicationController < ActionController::Base
   before_filter do |c|
     User.current_user = User.find(c.session[:user]) unless c.session[:user].nil?  
   end
end

class User < ActiveRecord::Base
  attr_accessor :current_user
end
Jimfish
quelle
1
Warum ich verwirrt bin: Sie setzen eine Klassenvariable, User.current_user. Ich dachte, das wäre eine schlechte Idee, da ich davon ausgegangen bin, dass dieselbe Instanz der User-Klasse (wie in „Klassen in Ruby sind erstklassige Objekte - jede ist eine Instanz der Klassenklasse“) ruby-doc.org/core /classes/Class.html ) wird in einer Rails-Anwendung verwendet. Wenn dies der Fall wäre, würde diese Methode offensichtlich zu unerwünschten Ergebnissen führen, wenn mehrere Benutzer gleichzeitig aktiv wären. Da Sie dies in der Produktion verwenden, gehe ich davon aus, dass es funktioniert, aber das bedeutet, dass es mehrere Instanzen der User-Klasse gibt.
Knuton
5
Tatsächlich weist dies im Produktionsmodus einen schwerwiegenden Fehler auf, den Sie im Entwicklungsmodus niemals finden würden. In der Entwicklung wird die Klasse bei jeder Anforderung neu geladen, sodass die Variable immer leer beginnt. In der Produktion bleibt die Klasse jedoch. Die Logik von Jimfish ist fast korrekt, aber wegen der 'es sei denn, c.session [: user] .nil?' Klausel, ein nicht angemeldeter Benutzer könnte möglicherweise auf die Berechtigungen der vorherigen Benutzer zurückgreifen. Wenn Sie dies wirklich verwenden möchten, sollten Sie diese Klausel entfernen.
GTD
1
Sehr schlechte Idee. Benutzer werden jetzt für mehrere Sitzungen freigegeben. Sicherheitslücke.
lzap
4
@ dasil003 denke, diese einfache Änderung wird das beheben: User.current_user = c.session [: user] .present? ? User.find (c.session [: user]): nil
tybro0103
1
Sie sollten ein verwenden around_filterund dies am Ende des Blocks ensure User.current_user = nilhinzufügen. Versuchen Sie anstelle von cattr auch, einen Threadsafe-Ansatz zu verwenden def self.current_user=(user) Thread.current[:current_user] = user end def self.current_user Thread.current[:current_user] end
scanales
4

Ich verwende das Plugin für die deklarative Autorisierung und es funktioniert ähnlich wie das, was Sie mit erwähnen. current_userEs verwendet a before_filter, um es current_userherauszuziehen und dort zu speichern, wo die Modellebene darauf zugreifen kann. Sieht aus wie das:

# set_current_user sets the global current user for this request.  This
# is used by model security that does not have access to the
# controller#current_user method.  It is called as a before_filter.
def set_current_user
  Authorization.current_user = current_user
end

Ich verwende jedoch nicht die Modellfunktionen der deklarativen Autorisierung. Ich bin alle für den "Skinny Controller - Fat Model" -Ansatz, aber ich bin der Meinung, dass die Autorisierung (sowie die Authentifizierung) in die Controller-Ebene gehört.

Daniel Kristensen
quelle
Die Autorisierung (sowie die Authentifizierung) gehört sowohl zur Controller-Schicht (wo Sie den Zugriff auf eine Aktion zulassen) als auch zum Modell (Sie erlauben dort den Zugriff auf eine Ressource)
lzap
2
Verwenden Sie keine Klassenvariablen! Verwenden Sie stattdessen Thread.current ['current_user'] oder ähnliches. Das ist threadsicher! Vergessen Sie nicht, den current_user am Ende der Anfrage zurückzusetzen!
Reto
1
Dies mag wie eine Klassenvariable aussehen, aber es ist eigentlich nur eine Methode, die den Thread.current festlegt. github.com/stffn/declarative_authorization/blob/master/lib/…
evanbikes
3

Um mehr Licht auf die Antwort von armchairdj zu werfen

Ich habe mich dieser Herausforderung gestellt, als ich an einer Rails 6- Anwendung gearbeitet habe.

So habe ich es gelöst :

Ab Rails 5.2 können Sie jetzt einen magischen CurrentSingleton hinzufügen, der sich wie ein globaler Store verhält, auf den Sie von überall in Ihrer App zugreifen können.

Definieren Sie es zunächst in Ihren Modellen :

# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
  attribute :user
end

Stellen Sie den Benutzer als Nächstes irgendwo in Ihrem Controller ein , um ihn in Modellen, Jobs, Mailern oder wo immer Sie möchten zugänglich machen zu können:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_current_user

  private

  def set_current_user
    Current.user = current_user
  end
end

Jetzt können Sie das Current.userin Ihren Modellen aufrufen :

# app/models/post.rb
class Post < ApplicationRecord
  # You don't have to specify the user when creating a post,
  # the current one would be used by default
  belongs_to :user, default: -> { Current.user }
end

ODER Sie können das Current.userin Ihren Formularen aufrufen :

# app/forms/application_registration.rb
class ApplicationRegistration
  include ActiveModel::Model

  attr_accessor :email, :user_id, :first_name, :last_name, :phone,
                    
  def save
    ActiveRecord::Base.transaction do
      return false unless valid?

      # User.create!(email: email)
      PersonalInfo.create!(user_id: Current.user.id, first_name: first_name,
                          last_name: last_name, phone: phone)

      true
    end
  end
end

ODER Sie können das Current.userin Ihren Ansichten aufrufen :

# app/views/application_registrations/_form.html.erb
<%= form_for @application_registration do |form| %>
  <div class="field">
    <%= form.label :email %>
    <%= form.text_field :email, value: Current.user.email %>
  </div>

  <div class="field">
    <%= form.label :first_name %>
    <%= form.text_field :first_name, value: Current.user.personal_info.first_name %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

Hinweis : Sie können sagen: "Diese Funktion verstößt gegen das Prinzip der Trennung von Bedenken!" Ja tut es. Verwenden Sie es nicht, wenn es sich falsch anfühlt.

Mehr über diese Antwort können Sie hier lesen: Alles aktuell

Das ist alles.

ich hoffe das hilft

Versprich Preston
quelle
0

Mein Gefühl ist, dass der aktuelle Benutzer Teil des "Kontexts" Ihres MVC-Modells ist. Denken Sie an den aktuellen Benutzer wie die aktuelle Zeit, den aktuellen Protokollierungsdatenstrom, die aktuelle Debugging-Ebene, die aktuelle Transaktion usw. Sie könnten alle diese übergeben. " Modalitäten "als Argumente in Ihre Funktionen. Oder Sie stellen es durch Variablen in einem Kontext außerhalb des aktuellen Funktionskörpers zur Verfügung. Der lokale Thread-Kontext ist aufgrund der einfachsten Thread-Sicherheit die bessere Wahl als globale oder Variablen mit anderweitigem Gültigkeitsbereich. Wie Josh K sagte, besteht die Gefahr bei Thread-Einheimischen darin, dass sie nach der Aufgabe gelöscht werden müssen, was ein Abhängigkeitsinjektions-Framework für Sie tun kann. MVC ist ein etwas vereinfachtes Bild der Anwendungsrealität und nicht alles wird davon abgedeckt.

Markus
quelle
0

Ich bin so spät zu dieser Party, aber wenn Sie eine differenzierte Zugriffskontrolle benötigen oder komplexe Berechtigungen haben, würde ich das Cancancan Gem auf jeden Fall empfehlen: https://github.com/CanCanCommunity/cancancan

Sie können Berechtigungen für jede Aktion in Ihrem Controller nach Belieben definieren. Da Sie die aktuelle Fähigkeit auf jedem Controller definieren, können Sie alle erforderlichen Parameter senden, z current_user. Sie können eine generische current_abilityMethode definieren ApplicationControllerund die Dinge automatisch einrichten:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
  def current_ability
    klass = Object.const_defined?('MODEL_CLASS_NAME') ? MODEL_CLASS_NAME : controller_name.classify
    @current_ability ||= "#{klass.to_s}Abilities".constantize.new(current_user, request)
  end
end

Auf diese Weise können Sie eine UserAbilitiesKlasse mit Ihrem UserController, PostAbilities mit Ihrem PostController usw. verknüpfen. Und dann definieren Sie die komplexen Regeln darin:

class UserAbilities
  include CanCan::Ability

  def initialize(user, request)
    if user
      if user.admin?
        can :manage, User
      else
        # Allow operations for logged in users.
        can :show, User, id: user.id
        can :index, User if user
      end
    end
  end
end

Es ist ein tolles Juwel! ich hoffe es hilft!

Carlos Soto
quelle