NullVoxPopuli/skinny_controllers

View on GitHub
docs/defining_operations_and_policies.md

Summary

Maintainability
Test Coverage
## Defining Operations and Policies

Operations should be placed in `app/operations` of your rails app.

For operations concerning an `Event`, they should be under `app/operations/event_operations/`.

Using the example from the specs:
```ruby
module EventOperations
  class Read < SkinnyControllers::Operation::Base
    def run
      model if allowed?
    end
  end
end
```

alternatively, all operation verbs can be stored in the same file under (for example) `app/operations/user_operations.rb`

```ruby
module UserOperations
  class Read < SkinnyControllers::Operation::Base
    def run
      model if allowed?
    end
  end

  class ReadAll < SkinnyControllers::Operation::Base
    def run
      model if allowed?
    end
  end
end
```

### Creating

To achieve default functionality, this operation *may* be defined -- though, it is implicitly assumed to function this way if not defined.
```ruby
module UserOperations
  class Create < SkinnyControllers::Operation::Base
    def run
      @model = User.new(model_params)

      # raising an exception here allows the corresponding resource controller to
      # `rescue_from SkinnyControllers::DeniedByPolicy` and have a uniform error
      # returned to the frontend
      raise SkinnyControllers::DeniedByPolicy.new('Something Horrible') unless allowed?

      @model.save
      return @model # or just `model`
    end
  end
end
```

### Updating
```ruby
module UserOperations
  class Update < SkinnyControllers::Operation::Base
    def run
      # this throws a DeniedByPolicy exception if `allowed?` returns false
      check_allowed!

      model.update(model_params)
      model
    end
  end
end
```

### Deleting

Goal: Users should only be able to delete themselves

To achieve default functionality, this operation *may* be defined -- though, it is implicitly assumed to function this way if not defined.
```ruby
module UserOperations
  class Delete < SkinnyControllers::Operation::Base
    def run
      model.destroy if allowed?
    end
  end
end
```

NOTE: `allowed?` is `true` by default via the `SkinnyControllers.allow_by_default` option.



## Defining Policies

Policies should be placed in `app/policies` of your rails app.
These are where you define your access logic, and how to decide if a user has access to the `object`

```ruby
class EventPolicy < SkinnyControllers::Policy::Base
  def read?(event = object)
    event.user == user
  end
end
```


## More Advanced Usage

These are snippets taking from other projects.

### Using ransack

```ruby
# config/initializers/skinny_controllers.rb
SkinnyControllers.search_proc = lambda do |relation|
  relation.ransack(params[:q]).result
end
```

### Finding a record when the id parameter isn't passed

```ruby
module HostOperations
  class Read < SkinnyControllers::Operation::Base
    def run
       # always allowed, never restricted
       # (because there is now call to allowed?)
      model
    end

    # the params to this method should include the subdomain
    # e.g.: { subdomain: 'swingin2015' }
    def model_from_params
      subdomain = params[:subdomain]
      host = Host.find_by_subdomain(subdomain)
    end
  end
end
```

### The built in model-finding methods can be completely ignored

The `model` method does not need to be overridden. `run` is what is called on the operation.

```ruby
module MembershipRenewalOperations
  # MembershipRenewalsController#index
  class ReadAll < SkinnyControllers::Operation::Base

    def run
      # default 'model' functionality is avoided
      latest_renewals
    end

    private

    def organization
      id = params[:organization_id]
      Organization.find(id)
    end

    def renewals
      options = organization.membership_options.includes(renewals: [:user, :membership_option])
      options.map(&:renewals).flatten
    end

    def latest_renewals
      sorted_renewals = renewals.sort_by{|r| [r.user_id,r.updated_at]}.reverse

      # unique picks the first option.
      # so, because the list is sorted by user id, then updated at,
      # for each user, the first renewal will be chosen...
      # and because it is descending, that means the most recent renewal
      sorted_renewals.uniq { |r| r.user_id }
    end
  end

end
```

### Updating / Deleting the current_user

This is something you could do if you always know your model ahead of time.

```ruby
module UserOperations
  class Update < SkinnyControllers::Operation::Base
    def run
      return unless allowed_for?(current_user)
      # update with password provided by Devise
      current_user.update_with_password(model_params)
      current_user
    end
  end

  class Delete < SkinnyControllers::Operation::Base
    def run
      if allowed_for?(current_user)
        if current_user.upcoming_events.count > 0
          current_user.errors.add(
            :base,
            "You cannot delete your account when you are about to attend an event."
          )
        else
          current_user.destroy
        end

        current_user
      end
    end
  end

end
```