Search code examples
ruby-on-railsrubyvalidationvirtual-attribute

Get current_user in Rails form validation by defining a virtual attribute


Rails form validation is designed to go in the model most easily. But I need to make sure the current user has the required privileges to submit a post and the current_user variable is only accessible in the controller and view.

I found this answer in a similar question:

You could define a :user_gold virtual attribute for Book, set it in the controller where you have access to current_user and then incorporate that into your Book validation.`

How can I set this up with my post and user controller so that the current_user variable is accessible in the model?

Solution:

This whole thing is wrong from an application design perspective as @Deefour's answer pointed out. I changed it so my view doesn't render the form unless the condition is true.


Solution

  • The "similar question" is saying you can do something like this

    class YourModel < ActiveRecord::Base
      attr_accessor :current_user
    
      # ...
    end
    

    and then in your controller action you can do something like

    @your_model = YourModel.find(params[:id])
    @your_model.current_user = current_user
    @your_model.assign_attributes(params[:your_model])
    
    if @your_model.valid?
      # ...
    

    You can then use self.current_user within YourModel's validation methods.


    Note I don't think this is what you should be doing though, as I don't consider this "validation" as much as "authorization". An unauthorized user shouldn't even be able to get the part of your action where such an update to a YourModel instance could be saved.


    As for doing the authorization with Pundit as requested, you'd have a file in app/policies/your_model.rb

    class YourModelPolicy < Struct.new(:user, :your_model)
      def update?
        user.some_privilege == true # change this to suit your needs, checking the "required privileges" you mention
      end
    end
    

    Include Pundit in your ApplicationController

    class ApplicationController < ActionController::Base
      include Pundit
      # ...
    end
    

    Then, in your controller action you can do simply

    def update
      @your_model = YourModel.find(params[:id])
      authorize @your_model
    
      # ...
    

    The authorize method will call YourModelPolicy's update? method (it calls the method matching your action + ? by default) and if a falsy value is returned a 403 error will result.