1

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.

Community
  • 1
  • 1
alt
  • 13,357
  • 19
  • 80
  • 120

4 Answers4

4

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.

deefour
  • 34,974
  • 7
  • 97
  • 90
  • Thanks for the thorough response. I tried pundit and it worked well! But ended up re-thinking it based on your "note" and I'm now only rendering the form in the view if the user has sufficient karma. – alt Jan 12 '13 at 05:45
  • It's a valid answer, but what if you need to do something more fine-grained than just "forbid the user from saving the model?" I have a requirement of "some user can leave a field blank, others cannot"... – art-solopov Apr 17 '17 at 10:48
2

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
Ismael Abreu
  • 16,443
  • 6
  • 61
  • 75
  • How big is cancan? Lots of code? Or lightweight? Can I do it from scratch? – alt Jan 12 '13 at 02:36
  • 3
    Some feel cancan has gotten a bit unwieldy. A great, light-weight alternative is **[Pundit](https://github.com/elabs/pundit)** – deefour Jan 12 '13 at 02:42
  • Well, it adds some view helpers and it has a lot of things but ofc you could made your own code. You just need to add roles to users, there also a gem for that named rolify. But if your roles are app based and not model based you should be fine without it. And then you can have the logic in your controller like `redirect_to 'index', message: 'You haz no permissions dude!' unless current_user.admin?` or you could make a class to keep the "abilities" rules. – Ismael Abreu Jan 12 '13 at 02:42
  • @Deefour: +1 Didn't know about Pundit but seems to be really nice. – Ismael Abreu Jan 12 '13 at 02:46
  • @Deefour could you show me how I would use pundit in this situation. The documentation seems a bit... confusing. – alt Jan 12 '13 at 02:48
  • @JacksonGariety see my updated answer. It should bump you in the right direction – deefour Jan 12 '13 at 02:55
1

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
accounted4
  • 1,685
  • 1
  • 11
  • 13
0

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.

Mark Swardstrom
  • 17,217
  • 6
  • 62
  • 70