Skip to content

Instantly share code, notes, and snippets.

@gumayunov
Created June 13, 2012 22:06
Show Gist options
  • Save gumayunov/2926795 to your computer and use it in GitHub Desktop.
Save gumayunov/2926795 to your computer and use it in GitHub Desktop.

Примеры сложных правил для проверки вариантов реализации

  • Название бизнеса может менять либо создатель бизнеса, до того как бизнес одобрил модератор, либо бизнес-владелец.

  • Если название бизнеса недоступно для редактирования, то его нужно отображать в форме особым образом.

  • Владелец бизнеса может загружать 3, 5 или 10 фотографий соответственно тарифным планам "луковица", "цветок", "букет"

  • Бизнес может быть привязан только к владельцу из того же города и только аккаунт-менеджером этого города

  • Поле "сайт" выводится ссылкой только если заведение некоммерческое или его владелец подписан на тарифный план "цветок" или "букет"

Механизмы

Types - Для определенных типов взаимодействия с моделями: списки полей (mass-assignment), особые валидации, специальные сеттеры. Types реалзуют правила, которые не зависят от того, кто именно инициирует взаимодействие (его ролей, его отношений с затрагиваемыми сущностями). Type задает более строние органичения на изменения сущности чем модель.

ACL - Привелегии для ролей делать вызовы контроллеров. Могут учитывать отношения сущностей и параметры запроса.

policy - Произвольные привилегии для ролей, не завязанные на контроллеры, для использования повсеместно в приложении (в том числе и в ACL). Описывает привелегии ролей на конкретные операции с ресурсами и параметрами. Затрагивают взаимосвязи и состояния сущностей.

Вопросы

  • Nested attributes - они ведь не через соотв форму пойдут? Как наложить на них какие либо ограничения?
  • Критика презентеров в Кипре. Будут ли завернуты в презентеры коллекции?
  • Когда для сценария делать отдельные контроллеры?
  • Когда ограничения задавать в форме а когда делать вызов policy?
  • Автоматическая проверка привелегий на вызов контроллера. Должна ли автоматически опрашиваться acl или достаточно проверять факт была ли проверка вообще?
  • Роли в типах. Переопределять attributes= и update_attributes чтобы гарантировать передачу роли и соотв санитизацию?
# acl.rb
roles do
role :guest
role :tulper
role :owner
role :admin
end
asserts do
assert :owner, [:user_id] do
subject.owner.id == user_id
end
assert :not_owner, [:user_id] do
subject.owner.id != user_id
end
end
resource "BusinessController" do
privilege :show
privilege :edit do
# указываем роль, т.к. guest также не является владельцем
pass :not_owner, [:tulper]
end
privilege :update do
pass :not_owner, [:tupler]
end
end
resource "Owners::BusinessController" do
privilege :show do
pass :owner
end
privilege :edit do
pass :owner
end
privilege :update do
pass :owner
end
end
# types/business.rb
resource "Business" do
type :create do
attr_accessible :name, :city, :contact
end
type :edit do
# Do not list city attr.
attr_accessible :name
attr_accessible :name, :credit_rating, :as => :admin
#def assign_attributes(values, options = {})
# sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
# send("#{k}=", v)
# end
#end
end
end
# policies/busnesses.rb
roles do
role :admin
role :tulper
role :bulb
role :flower
role :bouquet
end
asserts do
assert :allowed_attrs, [:disallow_attrs] do
(subject.changed & disallow_attrs.map(&:to_s)).empty?
end
end
resource "Business" do
privilege :update do
pass [:creator, :not_approved], [:tulper]
pass :allowed_attrs, [:tulper], disallow_attrs: [:name]
end
end
#access_schema_helper.rb
class AccessSchemaHelper
# before_filter { required! :reviews, :delete }
#
def required!(route_method, action = nil, options = {})
url_options = send "hash_for_#{route_method}_path"
resource = "#{url_options[:controller].to_s.camelize}Controller"
privilege = action || url_options[:action]
acl.require! resource, privilege, options
end
# - if can? :reviews, :delete, :subject => review
# = link_to "Delete", review_path(review)
def can?(*args)
required!(*args)
rescue AccessSchema::NotAllowed => e
false
else
true
end
def acl
AccessSchema.schema(:acl).with_options({
roles: current_roles,
user_id: current_user.try(:id)
})
end
# Use in controllers and views
# tarifF plans or other domain logic policies
#
# policy.allow? review, :add_photo
#
def policy
# Policy have to check actor roles and subject owner state (tariff plans for example)
# to evaluate permission. So we pass proc and deal with particular subject to
# calculate roles.
#
roles_calculator = proc do |options|
plan = options[:subject].try(:owner).try(:plan)
plan ||= [ current_user.try(:plan) || :none ]
current_roles | plan
end
AccessSchema.schema(:policy).with_options({
roles: roles_calculator,
user_id: current_user.try(:id)
})
end
end
# controllers/businesses_controller.rb
class BusinessesController < ApplicationController
before_filter only: [:show, :index] do
required! :onwers_businesses
end
before_filter only: [:new, :create, :edit, :update] do
required! :onwers_businesses, subject: business_form
end
def show
@business = W::Business.find(params[:id])
end
def index
@businesses = W::Business.order(:rank).all
end
def new
@business = business_form
end
def create
@business = business_form
@business.attributes = params[:business]
# при создании бизнеса нет каких то особых правил, которые
# необходимо выносить в policy
if @business.save
redirect_to @business
else
render :edit
end
end
def edit
@business = business_form
end
def update
@business = business_form
@business.attributes = params[:business]
policy.require! @business, :update # AR::Dirty
if @business.save
redirect_to @business
else
render :edit
end
end
private
def business_type(type)
FormFactory.build(Business, type)
end
def business_form
@business_form ||= begin
if params[:id].present?
business_type(:edit).find(params[:id])
else
business_type(:create).new
end
end
end
end
# controllers/owners/businesses_controller.rb
class Owners::BusinessesController < Owners::ApplicationController
before_filter only: [:show] do
required! :onwers_businesses
end
before_filter only: [:edit, :update] do
required! :onwers_businesses, subject: business_form
end
def show
@business = W::Business.find(params[:id])
end
def edit
@business = business_form
end
def update
@business = business_form
@business.attributes = params[:business]
if @business.save
redirect_to @business
else
render :edit
end
end
private
def business_form
@business_form ||= FormFactory.build(Business, :edit).find(params[:id])
end
end
# services/business_service.rb
class BusinessService < Services::Base
def update_as_owner(business_id, attrs, options)
@business = FormFactory.build(Business, :edit_as_owner).find(business_id)
@business.attributes = attrs
policy(options[:actor]).require! @business, :update
@business.save!
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment