You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
7.2 KiB

4 years ago
CARTO Gears API
===============
**DISCLAIMER: CARTO Gears API is still in development, it won't be considered stable until first major 1 version**
## An API for building CARTO Gears
CARTO Gear: Rails engine built on top of CARTO, using CARTO Gears API.
This is meant to be a documented way to add new features to CARTO, in a non-intrusive manner,
taking advantage of a domain API maintained by CARTO team. Our goal with this API and conventions is making
CARTO easily extensible.
References:
- [Component-based Rails Applications, Stephan Hagemann](https://leanpub.com/cbra).
- [Rails 4 Engines, Brian Leonard](http://tech.taskrabbit.com/blog/2014/02/11/rails-4-engines/).
### Value models
In order to make development as simple as possible, models returned by API services are immutable POROs.
They're instantiated with the sleek [values gem](https://github.com/tcrayford/Values).
#### What about Rails?
We don't want you to be coupled to CARTO internal models code, that's why Gears API returns just POROs.
Nevertheless, if you want to use some Rails magic you can decorate your classes with the minimum that you
need to make a class behave like a model without persistence (enabling form integration, for example):
```ruby
class MyUser < CartoGearsApi::Users::User
extend ActiveModel::Naming
include ActiveRecord::AttributeMethods::PrimaryKey
def id
# This is needed to make ActiveRecord::AttributeMethods::PrimaryKey work. Otherwise it
# won't find the id accessible thanks to Value. Magic is not always compatible.
@id
end
end
```
Then, you can instantiate yours based on the API one:
```ruby
MyUser.with(CartoGearsApi::Users::UsersService.new.logged_user(request).to_h)
```
_PS: you could also "open" `CartoGearsApi::Users::User` and add there what's needed, but you DON'T want to do that ;-)_
## HOWTO
### Private vs public gears
CARTO currently supports two directories for Gears:
`/gears`
Public engines. For example, for new features developed with a component-based approach.
It's part of the main repo and gears inside it will be automatically loaded. Alternatively, they could
be extracted to a separate repository and loaded via Gemfile.
`/private_gears`
Private engines. For example, for in-house developments that can't be shipped with the Open Source version.
It's skipped at `.gitignore`, and it's dynamically loaded, so it won't appear in `Gemfile` or `Gemfile.lock`.
You can have the code wherever you want, and add symbolic links there.
#### Gears limitations
Due to the custom handling of this in order to avoid polluting Gemfile and Gemfile.lock files, gears loaded from a
directory have several limitations:
- If you specify a runtime dependency of a gem already existing at Gemfile, it must have the exact version.
- Although the private gem itself doesn't appear in `Gemfile` or `Gemfile.lock`, dependencies do, because they need to
be installed.
- You should avoid adding paths to the `$LOAD_PATH` from the Gemfile. An alternative is to add them in the
initialization code.
#### Generating a clean Gemfile.lock
As said, `Gemfile.lock` won't mention private gears, but it contains private gears dependencies.
In order to generate a clean `Gemfile.lock`, you should:
1. `mv private_gears private_gears.bak`
2. `bundle update`
3. `git commit Gemfile.lock -m "Clean Gemfile.lock" && git push`
4. `mv private_gears.bak private_gears`
### Create a Rails engine
Assuming that you pick `/gears`, create a Rails engine by
```ruby
bundle exec rails plugin new gears/my_component --full --mountable
```
It will be mounted at root (`/`) and automatically loaded.
To enable automatic reload for development, you should add the `lib` path to the `autoload_path` in your `Engine`
subclass:
```
module MyComponent
class Engine < ::Rails::Engine
isolate_namespace MyComponent
config.autoload_paths << config.root.join('lib').to_s if Rails.env.development?
end
end
```
You must use only classes under `CartoGearsApi` namespace. _It's currently under `/gears/carto_gears_api/lib`,
but it will be documented before first public release._
### Tests
CartoDB runs the tests with `bundle exec rspec` at engine directory. If you want to use this, you should create
your tests with rspec.
In order to enable rspec:
1. `rspec --init`
2. Copy the contents of `test_helper.rb` into `spec_helper`.
3. Run `rspec` and fix path errors that you might get.
## Documentation
### `CartoGearsAPI::` (Ruby API)
Generate the documentation with the following command:
`yardoc --files app/views/shared/form/_input_text.html.erb`
Documented ERBs are listed in the "Files" top left section at the docs.
Note: YARD support for ERB files is quite limited, and ERBs documentation is still ongoing.
## Queue system
CARTO provides a queuing system. In order to send a job to the queue, do this:
```ruby
require 'carto_gears_api/queue/jobs_service'
CartoGearsApi::Queue::JobsService.new.send_job('MyModule::MyClass', :class_method, 'param1', 2)
```
See more details at {CartoGearsApi::Queue::JobsService}.
## Sending emails
You can use the queue system and whatever you need for sending emails. The most straightforward way is using
Rails mailers.
You can send a test email to check that Rails is properly configured:
```ruby
CartoGearsApi::Mailers::TestMail.test_mail('juanignaciosl@carto.com', 'juanignaciosl@carto.com', 'show!')
```
Using the queue:
```ruby
CartoGearsApi::Queue::JobsService.new.send_job('CartoGearsApi::Mailers::TestMail', :test_mail, from, to, subject)
```
Sending your own email:
```ruby
class MyMail < ActionMailer::Base
def a_mail(to)
mail(to: to, from: 'contact@carto.com', subject: 'the subject').deliver_now
end
end
CartoGearsApi::Queue::JobsService.new.send_job('MyMail', :a_mail, 'support@carto.com')
```
CARTO Gears API also provides a `CartoGearsApi::Mailers::BaseMail` mailer with CARTO standard layout and from.
Usage example:
```ruby
class MyMail < CartoGearsApi::Mailers::BaseMail
def a_mail(to)
mail(to: to, subject: 'the subject').deliver_now
end
end
```
## Extension points
Most extension points require a registration during intialization. Although you can use initializers for your gear,
everything that depends upon CARTO should be run in an `after_initialize` hook, to ensure it is loaded after CARTO. e.g:
```
module MyComponent
class Engine < ::Rails::Engine
isolate_namespace MyComponent
config.after_initialize do
# Your initialization code
end
end
end
```
### Adding links to profile page
See creation at {CartoGearsApi::Pages::SubheaderLink}.
Example:
```ruby
CartoGearsApi::Pages::Subheader.instance.links_generators << lambda do |context|
user = CartoGearsApi::Users::UsersService.new.logged_user(context.request)
if user.has_feature_flag?('carto_experimental_gear')
include CartoGearsApi::UrlHelper
[
CartoGearsApi::Pages::SubheaderLink.with(
path: carto_gear_path(:my_gear, context, 'something'),
text: 'The Text',
controller: 'my_gear/something')
]
end
end
```
#### Events
See a list of events at {CartoGearsApi::Events::BaseEvent} and how to subscribe to them with
{CartoGearsApi::Events::EventManager}.
Example:
```ruby
CartoGearsApi::Events::EventManager.instance.subscribe(CartoGearsApi::Events::UserCreationEvent) do |event|
puts "Welcome #{event.user.username}"
end
```