DEV Community

Cover image for Integrate Strapi in Rails or how I decided to build my first gem strapi_ruby
saint-james-fr
saint-james-fr

Posted on

Integrate Strapi in Rails or how I decided to build my first gem strapi_ruby

TL;DR:

This article introduces the strapi_ruby gem, a solution for integrating headless CMS Strapi with Ruby on Rails seamlessly. Strapi is an ideal choice for a CMS, but using it effectively in a Rails environment and keeping your code DRY can be tricky. If you're a Rails developer willing to use Strapi, this is for you!

Strapi: modern, easy and flexible

I came across a problem. I'm a Fullstack developer and I have worked with Ruby On Rails since a year, and despite the joy I try to transfer to my clients for using Rails, I often heard the need for a CMS like Wordpress. In a world dominated by content strategies, articles, videos, tutorials or interviews often lay the foundation for better referencing within a business.

In the Rails world, some gems exist but nothing actually gets high enough to compare to the Wordpress standards needed by the client or really combine in Rails.

I discovered the Strapi headless CMS solution and I just loved how good it is. Users and developers get a proper UI, lot of tweaking possibilities and nobody will mess up some Wordpress configuration or introduce nasty plugins, or worse, forget to update Wordpress, theme and plugins. Forget about all that.

Using a headless solution means end-users will have a separate interface to manage content, and actually, that's what everybody wants as a real-world solution.

Using Strapi in a Rails environment means you decided to deliver the content using a Server-Side Rendering strategy - which means having your Rails controller prepare the articles to the views. This could be a better SEO strategy than fetching it client-side with Javascript. But this is clearly post-rationalisation: I was doing front-end for a week now on the project and I wanted to write some Ruby.

Let's get motivated

So, that's it, let's use Strapi. Having in mind some vague architectural concepts, I coded my best - which was not that good - and tried to have classes and modules organized but this ended up a bit too close to the needs of the client and not enough modular.

Looking at the code six months later and waiting for work to show up, I decided to externalise the service and its ideas into a gem, maybe influenced by the Hacktoberfest which is happening right now. I love the idea that freelance status also offers more time to participate in Open-Source projects or build stuff for the community.

I set up some objectives like having an API as clean and smooth as possible, writing tests, better organizing my code, and of course offering all options offered by Strapi REST API parameters.

I did love the rush of dopamine when starting the project but sooner or later, you have to make choices about your project. I set myself a deadline of 6 days and I must say, the final form and architecture became good enough at day 3. It helped me understand a few things :

  • You don't have to get it right immediately. It's okay to change the design if you see what could go wrong. Better late than never!
  • I thought it was an amazing thing to extract some code you wrote months before you can see how you improved and that's motivating - and it's for the good cause.
  • Looking for inspiration, I did check some gems repos (not related to Strapi), and also some gems that would interact with Strapi and Jekyll. I would highly recommend this if you wanna start to build a gem. Just open one and see how it's set up. Of course, I often heard this advice but I thought it was really interesting to do it while building a gem.

Back to the gem

Anyway created the strapi_ruby gem, so it could be as easy as passing options to a simple API.

Also, I wanted my gem to even more ease the situation by converting what selected fields from Markdown to HTML or converting ISO formatted time to regular Ruby DateTime instances.

So let's dive in!

Installation

First, we need to download, install and configure the gem

# Gemfile

# ...
gem 'strapi_ruby'
Enter fullscreen mode Exit fullscreen mode
bundle
bundle exec rake strapi_ruby:config
Enter fullscreen mode Exit fullscreen mode

It will generate a config file in an initializer. Fill your Strapi server URI and token, using environment variables or Rails credentials.

# config/initializer/strapi_ruby.rb

StrapiRuby.configure do |config|
  config.strapi_server_uri = ENV["STRAPI_SERVER_URI"]
  config.strapi_token = ENV["STRAPI_SERVER_TOKEN"]
end
Enter fullscreen mode Exit fullscreen mode

That's it, I'm ready to fetch some data! Let's get some articles for our home page.

Fetching articles collection

# pages_controller.rb
def home
@articles = StrapiRuby.get(resource: :articles)
end
Enter fullscreen mode Exit fullscreen mode

What we get is an OpenStruct instance similar to the JSON answer of Strapi, you can access data by dot notation. Each sub fields have also been converted to Openstruct so you can navigate easily.

# home.html.erb

<%= @articles.data.first.attributes.title %>
Enter fullscreen mode Exit fullscreen mode

Gracefully degrading when errors happen

But we'd rather access these in our views. And there's a simple reason for that. Let's say you reference a specific article and this one is deleted. This will trigger an StrapiRuby::ClientError and you don't want this to block your application. We need to escape an empty answer.

# home.html.erb

<% StrapiRuby.escape_empty_answer(@articles) do %>
  <ul>
    <% @articles.data.each do |article| %>
      <li>
        <%= article.attributes.title %>
      </li>
    <% end %>
  </ul>
<% end %>
Enter fullscreen mode Exit fullscreen mode

The block will return an empty string and the error will be logged in the console if there is any error.

Handling errors should be an essential part of your gem if you know it will trigger runtime exceptions. I found it to be almost half of the job, especially in the last days of my challenge.

Debugging

Same thing about debugging. This is what we do every day at some point.

Outside the block, you can still access the answer and get information about the error. Imagine requesting a non-existing resource because you typoed it.

# pages_controller.rb

def home
@articles = StrapiRuby.get(resource: :articless)

@articles.data # => nil
@articleS.meta # => nil
@articles .error.message # => "The Strapi servers answer with an error status 404. Not Found"
@article.endpoint # => "URL_OF_MY_SERVER/articless"
end
Enter fullscreen mode Exit fullscreen mode

This is what happens in the gem.

Begin
# some code for getting the answer ...
Rescue ClientError e=>
# return OpenStruct.new(data: nil, meta: nil, error: OpenStruct.new(message: e.message), endpoint: @endpoint)
end
Enter fullscreen mode Exit fullscreen mode

What do you think about this solution? I would love to know more how someone else would handle this and the graceful degradation.

Build queries with ease

You can access all Strapi V4 parameters jut as populate, fields, sort, filters, locale, and pagination...

Using it in Strapi with Javascript is easy enough using Strapi interactive query builder. Or have a look at the Strapi excellent documentation.

Strapi use the qs javascript library which traverses a JS object to build a query. strapi_ruby makes it as easy with an equivalent function.

# Using $in operator to match multiples values
StrapiRuby.get(resource: :restaurants,
               populate: :*,
               filters: {
                 id: {
                   "$in" => ["3", "6", "8"],
                 },
               })
# StrapiRuby.endpoint
"YOUR_SERVER/restaurants?populate=*&filters[id][$in][0]=3&filters[id][$in][1]=6&filters[id][$in][2]=8"
Enter fullscreen mode Exit fullscreen mode

As you'd expect, some JS syntax must be translated into Ruby though.
We need to use strings or symbols as values, we can pack them into arrays that's no issue. For operators of filters, we need to pass them as strings because of the $ character.

# Complex filtering with $and and $or
StrapiRuby.get(resource: :books,
               filters: {
                 "$or" => [
                   {
                     date: {
                       "$eq" => "2020-01-01",
                     },
                   },
                   {
                     date: {
                       "$eq" => "2020-01-02",
                     },
                   },
                 ],
                 author: {
                   name: {
                     "$eq" => "Kai doe",
                   },
                 },
               })
# StrapiRuby.endpoint
"YOUR_SERVER/books?filters[$or][0][date][$eq]=2020-01-01&filters[$or][1][date][$eq]=2020-01-02&filters[author][name][$eq]=Kai%20doe"
Enter fullscreen mode Exit fullscreen mode

Convert Markdown to HTML

Strapi is transferring Rich Content as Markdown so you can fully leverage its CMS potential. Convert with ease the fields you want using this option in the config file or in your options.

# This way
StrapiRuby.configure do |config|
  config.convert_to_html: [:body]
end

# Or this way

StrapiRuby.get(resouce: :restaurants, convert_to_html: [:menu, :description])
Enter fullscreen mode Exit fullscreen mode

Real-World Scenarios

Let's consider a few scenarios:

  • Blog Websites: For a blog site built with Ruby on Rails, using Strapi as the content management system is a wise choice. With strapi_ruby, you can quickly fetch, render, and display blog posts in a Rails application while keeping your codebase clean.

  • E-commerce Stores: In e-commerce applications, product details and descriptions are critical. The gem simplifies the retrieval of product information from Strapi, ensuring an up-to-date and error-free product catalogue.

  • Actually, any services that need separation from content and views. Let the creators create content!

Conclusion

I hope you found this article informative and that it encourages you to build yourself your own gem or participate in Open-Source projects.

Don't hesitate to visit the repo for the full code, I'd love some advice. Feel free to connect, comment, and help me improve as you just read my first article - and this was my first gem!

Thank you for reading, and happy coding 😎

Top comments (0)