alt
alt

Reputation: 13907

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.

Upvotes: 1

Views: 3997

Answers (4)

Mark Swardstrom
Mark Swardstrom

Reputation: 18070

I agree with Ismael - this is normally done in the controller. It's not an attribute of the model, it's a permission issue and related to the controller business logic.

If you don't need all the power of a gem like CanCan, you can role your own.

class BooksController < ApplicationController

  before_filter :gold_required, :only => :create

  def create
    book = Book.new
    book.save!
  end


  # Can be application controller
  private
  def gold_required
    return current_user && current_user.is_gold?
  end

end

You may want to put the filter on the 'new' method as well.

Upvotes: 0

deefour
deefour

Reputation: 35360

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.

Upvotes: 4

Ismael
Ismael

Reputation: 16720

Authorization shouldn't be done in models. Models have already many responsibilities don't you think?

That's a controller thing, and actually you can have the logic in other place using some gem like cancan and in your controller you would do something like:

authorize! :create, Post

Upvotes: 2

accounted4
accounted4

Reputation: 1705

You can define a "virtual attribute" in your model like this:

class Book < ActiveRecord::Base
  attr_accessor :current_user
end

Its value can be set directly in your controller like this:

class BooksController < ApplicationController
  def create
    book = Book.new
    book.current_user = current_user
    book.save!
  end
end

And inside your model's validation routine, you can access it like any other ActiveRecord field:

def validate_user_permission
  errors[:current_user] = "user does not have permission" unless current_user.is_gold?
end

I can't remember if this is the case with ActiveRecord, but you might be able to set virtual attributes via the mass-assignment methods like create, update, and new in the controller:

def create
  Book.create!(current_user: current_user)
end

In order to do that, you would probably have to add the following line to your model to enable mass-assignment of that virtual attribute:

attr_accessible :current_user

Upvotes: 1

Related Questions