• Follow us

Integrate Solidus with external services using Cangaroo

Alessio Alessio Rocco #Code
20 minutes Read
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:

  1. Add Cangaroo to our Solidus application.
  2. Setup solidus_cangaroo extension to send products to Cangaroo
  3. Add Twitter Integration
  4. Add the TweetProduct Cangaroo Job

To follow this tutorial you need:

  1. A working Solidus store (please refer to this article to deploy Solidus to Heroku).
  2. 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’s localhost: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 Deploy 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.

Need help with your Solidus store?

Let's talk
Related posts
#Code

Why Solidus?

Spree or Solidus? Which OSS project Nebulab, as an agency involved in Ruby on Rails eCommerce development, should invest on? This blog post tells the story about...

Join the Conversation