Eluvia Base
This library provides a set of Ruby on Rails customizations and other helpers in order to meet common Eluvia standards for API communication (https://sparktech.myjetbrains.com/youtrack/articles/BASE-A-4/Communication-interface-FE-BE) and Eluvia requirements for backend application behavior.
It applies to both client and server sides:
- Pagination and ordering is handled for the API requests (server side).
- Extended params parsing is implemented for the API requests (server side).
- Extended filtering is handled for the API requests (server side).
- Errors are serialized in the API response in a standard way (server side).
- Integration helpers are implemented to provide easy communication with other Eluvia components (client side).
1. Installation
1.1. Gem
To install the library to your project, just add this to your Gemfile:
gem 'eluvia-base'
New dependencies should be installed with:
bundle install
1.2. Initializer
Next, create an initializer config/initializers/eluvia_base.rb with the configuration:
Eluvia::Base.setup do |config|
config.api_case = ENV.fetch('API_CASE') { '' }
config.private_api_key = ENV.fetch('PRIVATE_API_KEY') { '' }
config.public_api_key = ENV.fetch('PUBLIC_API_KEY') { '' }
end
For full list of configuration options, see lib/eluvia/base/config.rb file.
2. Usage
2.1. Application controller setup
In order to use all the implemented functionality, you should integrate all defined handlers to your
main ApplicationController:
class ApplicationController < ActionController::API
include Eluvia::ErrorHandler
include Eluvia::PaginationHandler
include Eluvia::ParamsHandler
# ...
end
2.2. Pagination
In order to interpret limit, offset and order_by parameters, you can user set_pagination decorator. It will
transform the input parameters into the @order_by, @page, @limit and @padding instance variables which can be
easily used in Active Record and Kaminari interfaces.
class TestRecordsController < ApplicationController
# ...
before_action -> { set_pagination(20, 0, 'created_at:desc') }, only: [:index]
# ...
def index
@test_records = TestRecord.all.order(@order_by).page(@page).per(@limit).padding(@padding)
end
# ...
end
2.3. Params parser
You can use parse_json_param helper to parse JSON without predefined structure. In case you know the input JSON
structure, you should use standard "strong params" mechanism.
class TestRecordsController < ApplicationController
# ...
def create
@test_record = TestRecord.create(test_record_params)
# ...
end
# ...
def test_record_params
result = params.permit(
:param_1,
:param_2,
)
result[:additional_params] = parse_json_param(params[:additional_params])
result
end
# ...
end
2.4. Eluvia integration
Integration class can be implemented with the help of Eluvia::EluviaIntegration concern. This concern acts as
a wrapper over RestClient and brings some functionality for JSON parsing, pagination and Eluvia specific headers
composition.
class TestRecordsIntegration
include Singleton
include Eluvia::EluviaIntegration
def initialize
@service_url = '...'
end
def get_some_data(session_id)
parse_get_request(compose_url('some-data'), compose_headers_both(session_id))
end
def post_some_data(session_id, data)
parse_post_request(compose_url('some-data'), compose_headers_both(session_id), data)
end
end
For the full list of features, see Eluvia::EluviaIntegration interface.
2.5. Raising error
You can raise en exception Eluvia::Errors::XXX anywhere in the code and this exception will be automatically
formatted as standardized error response.
For example, this exception:
raise Eluvia::Errors::NotFound.new('Test record was not found.')
will be formatted into this response:
{
"errors": [
{
"message": "Test record was not found.",
"error": "Not Found",
"status": 404,
"source": null,
"timestamp": "2022-11-04T17:11:00.969Z"
}
]
}
For example, this exception:
raise Eluvia::Errors::UnprocessableEntity.new('param_1' => 'Param 1 must not be blank.',
'param_2' => 'Param 2 must not be blank.')
will be formatted into this response:
{
"errors": [
{
"message": "Param 1 must not be blank.",
"error": "Unprocessable Entity",
"status": 422,
"source": "param_1",
"timestamp": "2022-11-04T17:11:00.969Z"
},
{
"message": "Param 2 must not be blank.",
"error": "Unprocessable Entity",
"status": 422,
"source": "param_2",
"timestamp": "2022-11-04T17:11:00.969Z"
}
]
}
For the full list of available exceptions, see content of lib/eluvia/errors folder.
2.6. Chunked/direct upload finalization
Eluvia::File carries an upload_key attribute, an alternative to content (base64 encoded file data)
for file_attr fields backed by an underlying ActiveStorage attachment. When upload_key is present,
the setter no longer base64-decodes content — instead it delegates to Eluvia::Uploads.finalizer, a
registrable extension point that finalizes a previously uploaded (chunked/direct) temp object into the
ActiveStorage attachment. This lets a separate library (e.g. a chunked upload provider) implement the
actual storage logic (S3 copy_object, tus, ...) without eluvia-base depending on it.
Register a finalizer, typically from an initializer:
Eluvia::Uploads.finalizer = ->(, upload_key, filename) do
# Resolve `upload_key` in your storage backend and attach the resulting blob to `attachment_record`,
# using `filename` as the final (sanitized) filename.
end
If upload_key is present on the assigned Eluvia::File but no finalizer is registered, a
Eluvia::Errors::StandardError is raised.