Skip to content

User dependent validation

Alessandro Desantis edited this page Aug 19, 2018 · 3 revisions

You may have noticed policies are always run before the operation's contract is synced with the record. This has two practical outcomes:

  • The #create? action of your policies will always receive an empty record, so you don't have access to the parameters set by the user.
  • The #update? action of your policies will always receive the record as it was before the update, so you don't have access to the new parameters set by the user.

In other words, you can't write code like this:

# app/resources/api/v1/article/policy.rb
module API
  module V1
    module Article
      class Policy < Pragma::Policy::Base
        def create?
          # Only admins are allowed to publish articles, everyone else can only submit drafts.
          user.admin? || record.status.blank? || record.status == 'draft'
        end

        def update?
          # Same as for creation.
          create?
        end
      end
    end
  end
end

This will not work. You can test it by trying to create or update an article with a non-draft status as a non-admin user - you'll see that Pragma doesn't raise the slightest complain.

While this may sound inconvenient, it makes sense when you think about policies as having the only job of determining whether the user can perform the given operation on the record, irrespectively of what the operation's parameters actually are. Ensuring that the parameters are appropriate for that user and record is a validation concern and should be in the contract.

Let's try to rewrite the code above and put it in the contract instead:

# app/resources/api/v1/article/contract/base.rb
module API
  module V1
    module Article
      module Contract
        class Base < Pragma::Contract::Base
          configure do
            def self.messages
              super.merge(en: {
                errors: {
                  acceptable_status: 'status is not acceptable'
                }
              })
            end
          end

          property :status, type: coercible(:string)

          validation do
            optional(:current_user).maybe
            required(:status).filled(included_in?: %w(draft published))

            validate(acceptable_status: %i[current_user status]) do |current_user, status|
              current_user.admin? || status == 'draft'
            end
          end
        end
      end
    end
  end
end

This contract uses a custom validation block to check that the status is acceptable for the user's role. current_user is a virtual attribute that Pragma operations will automatically pass to the contract, and the optional rule is required to trigger the custom validation block.

Clone this wiki locally