Overview¶
Across the app, model manipulations are exposed as resources with standard rest api actions. Keeping with principles of DRY, all of the controllers extend resources controller to provide CRUD operations.
Api::V1::ResourcesController¶
The Api::V1::ResourcesController is a base controller that provides common actions and methods for managing resources via the API. It includes actions for CRUD operations, batch operations, and search functionalities. This controller is designed to be subclassed by specific resource controllers.
Why is it needed?¶
The Api::V1::ResourcesController is needed to provide a standardized way of handling resource operations across the application. By centralizing common actions and methods in a base controller, it ensures consistency and reduces code duplication. This approach also makes it easier to maintain and extend the functionality of resource controllers.
What functionalities does it provide across the app?¶
The Api::V1::ResourcesController provides the following functionalities:
- CRUD Operations: Standard actions for creating, reading, updating, and deleting resources.
- Batch Operations: Actions for creating and deleting multiple resources in a single request.
- Search and Filter: Actions for searching and filtering resources using Ransack and custom search filters.
- Pagination: Methods for applying pagination to resource collections.
- Authorization: Methods for authorizing resource actions using Pundit policies.
- Helper Methods: Utility methods for handling resource operations, such as
new_resource,batch_new_resources,requested_resource,apply_ransack,apply_order,apply_pagination,resource_scope,resource_class,permitted_params,batch_permitted_params,json_attributes,authorize_search, andauthorize_resource.
Methods¶
index¶
The index action retrieves a paginated list of resources. It applies authorization, ordering, and pagination to the resource scope.
def index
authorize_resource(resource_class)
resources = apply_order(resource_scope)
resources = apply_pagination(resources)
data = response_as(:paginated, scope: resources, json_attributes:)
status_code = :ok
render json: data, status: status_code
end
show¶
The show action retrieves a single resource by its ID. It applies authorization and returns the resource data if found, otherwise returns a not found response.
def show
resource = requested_resource
if resource.present?
authorize_resource(resource)
data = response_as :success, data: resource.as_json(json_attributes)
status_code = :ok
else
data = failure_response(:id, "Not Found")
status_code = :not_found
end
render json: data, status: status_code
end
create¶
The create action creates a new resource with the permitted parameters. It applies authorization and returns the created resource data if successful, otherwise returns validation errors.
def create
resource = new_resource
authorize_resource(resource)
if resource.save
data = response_as :success, data: resource.as_json(json_attributes)
status_code = :created
else
data = response_as :failure, data: resource.errors.as_pretty_json
status_code = :unprocessable_entity
end
render json: data, status: status_code
end
batch_create¶
The batch_create action creates multiple resources in a single request. It applies authorization and returns the created resources data if successful, otherwise returns validation errors.
def batch_create
resources = batch_new_resources
authorize resources.first, :create?
ActiveRecord::Base.transaction do
if resources.all? { |resource| resource.save }
data = response_as :success, data: resources.as_json(json_attributes)
render json: data, status: :created
else
data = response_as :failure, data: resources.filter { |res| !res.errors.empty? }.map { |res| res.errors.as_pretty_json }
render json: data, status: :unprocessable_entity
raise ActiveRecord::Rollback
end
end
end
update¶
The update action updates an existing resource with the permitted parameters. It applies authorization and returns the updated resource data if successful, otherwise returns validation errors.
def update
resource = requested_resource
if resource.present?
authorize_resource(resource)
if resource.update(permitted_params)
data = response_as :success, data: resource.as_json(json_attributes)
status_code = :ok
else
data = response_as :failure, data: resource.errors.as_pretty_json
status_code = :unprocessable_entity
end
else
data = failure_response(:id, "Not Found")
status_code = :not_found
end
render json: data, status: status_code
end
destroy¶
The destroy action deletes an existing resource by its ID. It applies authorization and returns the deleted resource data if successful, otherwise returns validation errors.
def destroy
resource = requested_resource
if resource.present?
authorize_resource(resource)
if resource.destroy
data = response_as :success, data: resource.as_json(json_attributes)
status_code = :ok
else
data = response_as :failure, data: resource.errors.as_pretty_json
status_code = :unprocessable_entity
end
else
data = failure_response(:id, "Not Found")
status_code = :not_found
end
render json: data, status: status_code
end
batch_destroy¶
The batch_destroy action deletes multiple resources by their IDs. It applies authorization and deletes the resources within a transaction.
def batch_destroy
resources = resource_class.where(id: params[:ids])
authorize resources.first, :destroy? if !resources.blank?
ActiveRecord::Base.transaction do
resources.destroy_all
end
end
batch_new_resources¶
Creates multiple new resources with the batch permitted parameters.
def batch_new_resources
attribs = batch_permitted_params
attribs.map { |attrs| resource_scope.new(attrs) }
end
requested_resource¶
Finds a resource by its ID.
apply_ransack¶
Applies Ransack search to the resource class. Inheriting classes can override this method to supply their own ransack behavior
apply_order¶
Applies ordering to the resource scope.
apply_pagination¶
Applies pagination to the resource scope. By default kaminari based pagination is applied.
resource_scope¶
Default behavior is to return the resource class, however, inheriting classes can override this method and supply their their own scope based on the query params and type of the request.
Here is an example from tags controller that includes taggings and normalizes the search query.
def resource_scope
tags = Library::Tag.includes(:taggings).all
tags = tags.joins(:taggings).where(taggings: {taggable_type: params[:taggable_type]}) if params[:taggable_type].present?
tags = tags.where("NORMALIZE(name, nfkc) ilike ?", "%#{normalize_search_text(params[:query])}%") if params[:query]
tags
end
resource_class¶
A mandatory method to be implemented by subclasses, this is usually the class name of the resource/model that is being dealt with.
permitted_params¶
A mandatory method to be implemented by subclasses. This usually should contain params permitted for creating and updating the resource/model.
json_attributes¶
A mandatory method to be implemented by subclasses. This should contain all the fields and relationships that model wants to expose as part of the result of the successful api request.
authorize_search¶
Authorizes the search action. This is a pundit based authorization, by default authorization is delegated to index method.
authorize_resource¶
Authorizes the resource for the current action. This is a pundit based authorization. A policy file is expected to be present.