This is the official documentation of the agent_ruby Ruby agent.
Forest Admin can connect to any data source, as long as it can be represented as a collection of records that have a common structure.
To achieve that, Forest Admin needs to abstract away data source differences: each connector "speaks" the language of a given API on one side and exposes the Forest Admin Query Interface on the other.
This interface is called the Forest Admin Query Interface, it is not a full-featured ORM: its objective is to be "just enough" to fuel Forest Admin.
Choosing how to query your data
The Forest Admin Query Interface is used to implement all native features of the admin panel, however, when writing custom code (creating new actions, fields, ...), you can either access your data using the Forest Admin Query Interface or using the native driver.
The choice is yours, and you will probably use both depending on the situation.
-
Forest Admin Query Interface
Native Driver
Code consistency
👍 Use the same query interface for all data sources
👎 Different API for each database / SaaS
Customizations can be queried (computed field, relationships, ...)
👍 Yes
👎 No
Features
👎 Common denominator is exposed
👍 All features of the underlying API
In-app deployments
👎 Difficult to reuse your existing code
👍 Re-use your existing code
Learning curve
👎 The interface is Forest Admin specific
👍 You already know how to write SQL
Native support for filters from the UI
👍 Yes
👎 No
Total
3 x 👍 + 3 x 👎
3 x 👍 + 3 x 👎
In practice
Querying with the native driver
As the name implies, native drivers have different interfaces for each data source.
module ForestAdminRails
class CreateAgent
def self.customize
@create_agent.customize_collection('customer') do |collection|
collection.add_segment('highPrice') do |context|
rows = ActiveRecord::Base.connection.execute(
'SELECT p.id as product_id, COUNT(o.id) as nb
FROM orders o
INNER JOIN products p ON o.product_id = p.id
GROUP BY p.id
ORDER BY nb DESC
LIMIT 10;'
)
{
field: 'id',
operator: 'In',
value: rows.map { |r| r['product_id'] }
}
end
end
end
end
end
Querying with the Forest Admin Query Interface
Queries can be executed directly, by calling the methods exposed by context.dataSource and context.collection.
include ForestAdminDatasourceToolkit::Components::Query
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
module ForestAdminRails
class CreateAgent
def self.customize
@create_agent.customize_collection('order') do |collection|
collection.add_segment('mySegment') do |context|
rows = context.datasource.get_collection('order').aggregate(
Filter.new,
Aggregation.new(operation: 'Count', groups: [{ field: 'product_id' }]),
10
)
{
field: 'id',
operator: Operators::IN,
value: rows.map { |r| r['product_id'] }
}
end
end
end
end
end
Data Source Interface
class DatasourceContract
def collections
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def charts
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def get_collection(name)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def add_collection(collection)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def render_chart(caller, name)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
Collection Interface
Parameters are explained in depth on the following pages:
class CollectionContract
def datasource
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def schema
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def name
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def execute(caller, name, data, filter = nil)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def get_form(caller, name, data = nil, filter = nil, metas = {})
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def create(caller, data)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def list(caller, filter, projection)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def update(caller, filter, data)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def delete(caller, filter)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def aggregate(caller, filter, aggregation, limit = nil)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def render_chart
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end