Integrate Solidus with external services using Cangaroo

In this article we’ll create a simple application to show how to integrate and use Cangaroo to handle any external service integration with your Solidus store. Our goal will be to send a tweet out each time a new product is created. Accomplishing this task will allow us to walk you throught Cangaroo installation and basic functionalities.
In my previous article I told the full story about why we created Cangaroo and I promised to write a tutorial that explains how to set it up to use an existing integration.
So let’s go ahead and make a plan. The steps we’ll follow are:
- Add Cangaroo to our Solidus application.
- Setup solidus_cangaroo extension to send products to Cangaroo
- Add Twitter Integration
- Add the TweetProduct Cangaroo Job
To follow this tutorial you need:
- A working Solidus store (check out how to boostrap a solidus store and deploy it to Heroku).
- A Twitter account and a Twitter application (https://apps.twitter.com/).
After that, we are ready to go!
Add Cangaroo to our Solidus application
Cangaroo is a Rails engine and can be mounted on any Rails application. In this case we are going to mount it on our Solidus store just for the sake of simplicity but keep in mind that you can (and we advice to) use it also on a separate Rails application. In the real world the best way to use Cangaroo is into a standalone application; that way it can serve multiple storefronts and applications like an ERP and a CMS.
To add Cangaroo to our application we just need to follow the README:
Add Cangaroo gem to Gemfile
# Gemfile
gem 'cangaroo'
Run bundle
as usual, then you have to install and run the needed migrations
$ bin/rake cangaroo:install:migrations
$ bin/rake db:migrate
The last step is to add the routes needed from Cangaroo to receive data from our apps:
# routes.rb
mount Cangaroo::Engine => "/cangaroo"
Now Cangaroo is configured into our app and ready to receive data and perform
Jobs, but first we have to create a Connection
. Basically a connection is an
external app that can send and receive data from Cangaroo.
To create the connection in the rails console
run:
Cangaroo::Connection.create(
name: 'mystore',
url: 'http://localhost:3000',
key: 'secretkey',
token: 'secrettoken'
)
The Connection has four fields:
-
name
: is used to identify the connection from Cangaroo jobs (we’ll see this later); -
url
: is the connection url, in our case we are running rails locally so it’slocalhost:3000
; -
key
: it’s used for authentication, you can set it to your liking; -
token
: also used for authentication, you can set it to your liking;
Setup solidus_cangaroo extension to send products to Cangaroo
solidus_cangaroo is a Solidus extension that provides the push API that we’ll use to send our products to Cangaroo. Again, to setup it up we can just follow its README:
Add solidus_cangaroo gem to our Gemfile as usual:
# Gemfile
gem 'solidus_cangaroo', github: 'nebulab/solidus_cangaroo', branch: 'master'
then run bundle
and bundle exec rails g spree_wombat:install
.
Now we have to add our Cangaroo credential into the file
config/initializers/cangaroo.rb
# config/initializers/cangaroo.rb
Spree::Wombat::Config.configure do |config|
config.connection_id = 'secretkey'
config.connection_token = 'secrettoken'
end
As you can see the connection_id
is the key
that we previously added to the
connection and the connection_token
is the token
. This way Cangaroo will
authenticate the store when it sends data to Cangaroo.
The next step is to specify which objects we want to send to Cangaroo, in our
case just the products.
To do so we use the push_objects
configuration:
# config/initializers/cangaroo.rb
config.push_objects = ["Spree::Product"]
Then we have to configure the payload_builder
, which is needed to normalize
the data and make it understandable to Cangaroo. Fortunately
almost all the work is done from solidus_cangaroo that already has the
Serializers for nearly all the Solidus objects.
The root
key (of the json object the app is sending to Cangaroo) is needed
to let Cangaroo understand what kind of data the app is sending:
# config/initializers/cangaroo.rb
config.payload_builder = {
"Spree::Product" => { serializer: "Spree::Wombat::ProductSerializer", root: "products" }
}
At last we have to setup the push_url
with the path to our Cangaroo
installation. In our case it’s the same path of our store (but remember that
it can be another application):
# config/initializers/cangaroo.rb
config.push_url = "http://localhost:3000/cangaroo/endpoint"
Now our config/initializers/cangaroo.rb
should look like this:
# config/initializers/cangaroo.rb
Spree::Wombat::Config.configure do |config|
config.connection_id = "secretkey"
config.connection_token = "secrettoken"
config.push_objects = ["Spree::Product"]
config.payload_builder = {
"Spree::Product" => { serializer: "Spree::Wombat::ProductSerializer", root: "products" }
}
config.push_url = "http://localhost:3000/cangaroo/endpoint"
end
Finally we can check if our application sends products to Cangaroo
correctly. First, start our rails server
and from rails console
run:
Spree::Wombat::Client.push_batches("Spree::Product")
after some info logs you should see the return message:
=> 9
this means that 9 products are sent to Cangaroo. On your project this number can change, it depends on how many products you have in your database. Somewhere in your rails logs you should be able to spot:
Completed 202 Accepted in 43ms (Views: 0.2ms | ActiveRecord: 0.9ms)
This confirms that Cangaroo has received our products, anyway nothing happened because for now we didn’t configure any job. A job is an action triggered when an object is received and uses integration to “talk” with external services. In the next step we are going to add the twitter integration that will be used by a job to let the world know about our amazing new products.
Add Twitter Integration
The Hard Way
At first we have to pull the twitter_integration repo and make it run. Available integrations are made of simple Sinatra apps that you can find here: https://github.com/cangaroo.
The first step is to clone the integration from GitHub:
$ git clone https://github.com/cangaroo/twitter_integration
then cd
into the directory and run bundle
. After that run the Sinatra
app with:
$ bundle exec rackup
To check if everything is working you can visit http://localhost:9292/
and
look for TwitterIntegration ok
.
The Easy Way
To make it even simpler, we have added the
button on the integration README.
If you push this button and follow the instructions you will have a working integration
on Heroku that you can eventually use also for production.
To check if you have correctly deployed the integration on heroku go to your
application root path and check that it responds with TwitterIntegration ok
.
To add a security layer we need to set the environment variable ENDPOINT_KEY
with a private token that will be used with the Connection that we are going to
create in the next step:
ENDPOINT_KEY=secrettoken
Now is time to teach our Cangaroo how to tweet. First of all, we have to
create the connection for this integration, we create it from rails console
:
Cangaroo::Connection.create(
name: 'twitter',
url: 'http://localhost:9292',
key: 'secrettoken',
token: 'secrettoken'
)
Change url
and token
accordling to values of your app url and your
security token.
Note: Even if key is not used for the integrations, Cangaroo will still validate its presence, so you have to set it. This strange behavior comes to support the old Wombat behavior. We hope to find a better way to implement it soon.
Add the TweetProduct Cangaroo Job
We are finally ready to create the job to send tweets. To do so we run the generator:
$ bundle exec rails g job Cangaroo::TweetProduct
now we have to change this job. First of all it must inherit from Cangaroo::Job. Then we need to remove the queue_as :default, because Cangaroo::Job already uses the cangaroo queue (you can change it if you want). At this point, we have to specify connection, path and parameters that the job will use:
# app/jobs/cangaroo/tweet_product.rb
class Cangaroo::TweetProductJob < Cangaroo::Job
connection 'twitter'
path '/send_tweet'
parameters {
consumer_key: 'your_twitter_consumer_key',
consumer_secret: 'your_twitter_consumer_secret',
access_token: 'your_twitter_access_token',
access_token_secret: 'your_twittwr_token_secret'
}
end
path
and parameters
are relative to integration you are using.
For now the integrations don’t have much documentation, so you have to refer
to the code in the integration file.
In case you want to go deeper, you could also take a look at the
endpoint_base gem.
We plan to improve integrations and the endpoint_base gem. However this is quite a lot of stuff, so contributions are very welcome.
After that we have to implement the perform?
method.
Cangaroo uses this method to understand if this job should be performed. In
our case we have to check if the type is a product
and if it’s a new product
(please refer to the README to see which variables can be
used inside the perform?
method):
# app/jobs/cangaroo/tweet_product.rb
...
def perform?
type == 'products' &&
payload['created_at'] == payload['updated_at']
end
...
Sometimes integrations don’t understand the data that we send to them, so we
have to transform
it. In our case we want to write a tweet with the product’s
name and link so we’ll use the transform
method to convert the product’s data
coming from the store into a JSON that will be sent to the integration.
Our transform
method looks like:
# app/jobs/cangaroo/tweet_product.rb
...
def transform
{
tweet: {
body: "Hey, we have a new awesome product #{payload['name']} http://localhost:3000/products/#{payload['permalink']}"
}
}
end
...
and then our job should look like:
# app/jobs/cangaroo/tweet_product.rb
class Cangaroo::TweetProductJob < Cangaroo::Job
connection 'twitter'
path '/send_tweet'
parameters {
consumer_key: 'your_twitter_consumer_key',
consumer_secret: 'your_twitter_consumer_secret',
access_token: 'your_twitter_access_token',
access_token_secret: 'your_twittwr_token_secret'
}
def transform
{
tweet: {
body: "Hey, we have a new awesome product #{payload['name']} http://localhost:3000/products/#{payload['permalink']}"
}
}
end
def perform?
type == 'products' &&
payload['created_at'] == payload['updated_at']
end
end
We are now lacking just the last thing: we have to inform Cangaroo about this job. This is an easy task: we just have to add our job to the right configuration, in this case Rails.configuration.cangaroo.jobs. To do so we need to add it in the cangaroo initializer:
# config/initializers/cangaroo.rb
...
Rails.configuration.cangaroo.jobs = [Cangaroo::TweetProductJob]
All done! Now is time to test our integration:
create a new product on Solidus then run from rails console
:
Spree::Wombat::Client.push_batches("Spree::Product")
And…if everything is working you should see a tweet about your new awesome product!!!
Take note that, at the moment, our project doesn’t use any queue system and Cangaroo jobs are just simple Rails jobs. You can therefore use any backend queue system that is currently supported by Rails.
Also, in this tutorial we pushed objects from the rails console. Conversely, in a real application you would probably want to use the proper rake task:
$ bundle exec rake wombat:push_it
I hope you now have a better understanding of how Cangaroo works and how to use integrations. There’s still a lot of work to be done, both on Cangaroo and the integrations. We’ll welcome anyone that wants to work on the projects with us.
You can find the code used for this tutorial on this repo.