WP2AT Part 2: Working with WordPress & AirTable APIs
Tags: ruby, wordpress, airtable, api
Published: 2021-03-31 02:13:25

### Correction
In [part 1](https://dev.to/lizlaffitte/what-i-m-working-on-wp2at-a-ruby-gem-3kh9) I implied that wp2at isn't a CLI. That is incorrect. Just because it doesn't continuously run via a while loop until the user exits the app (like my very entertaining [Hogwarts CLI gem](https://github.com/LizLaffitte/hogwarts)), doesn't mean it's not a CLI. Part of learning and growing in development is sharpening your industry vocab and I work on that every day.

## Updating execute()
This week I worked on integrating the WordPress and AirTable APIs into wp2at  (aka: the fun stuff).

This is where we left off:
```Ruby
    def execute(args)
        command = args[0]
        options = args[1]
        @current_settings = Settings.exists? ? Settings.load : Settings.new
        case command
        when "userconfig"
            puts options ? add_username(options) : @current_settings.username
        when "blog" 
            options ? @current_settings.add_blog(options) : @current_settings.list_blogs 
        when "sync"
            api = API.new(@current_settings)
            if options
                api.ping(options)
            else
                api.list_blogs
            end
        else
            puts "That's not an option"
        end
    
    end

```

The "sync" when condition is where we are telling the gem to get the WordPress blog data and add it to AirTable. This is what that section of the execute method looks like right now:

```Ruby
 def execute(args)
        command = args[0]
        options = args[1]
        flags = args[2]
        @current_settings = Settings.exists? ? Settings.load : Settings.new
        case command
       ...
        when "sync"
            if options
                if @current_settings.blog_count < 1
                    puts "Add a blog by running blog with an argument of the blog name you'd like to add."
                end
                if @current_settings.at_api != ""
                    blog = @current_settings.blogs.find{|blog| blog.name == options}
                    api = API.new(@current_settings, blog)
                    api.ping(flags)
                else
                    puts "Add an AirTable API Key by running the command api-key and passing an API key."
                end
            else
                puts "Add a blog to be synced"
            end
        else
            puts "That's not an option"
        end
    
    end

```
I'm doing a little error handling here with some if statements. First, the method checks to see if there is a blog to retrieve data for, then it checks to see if there is an AirTable API key available. Without those two pieces, the gem can't do the one thing I want it to do. If the gem has all the necessary pieces, it finds the blog object the user wants to grab post data from, instantiates a new API object and passes it that blog object and the current settings. It then calls the API instance method ping. (Normally I would advise against this, but ignore the flags for now...)

## API Class
Let's take a look at the initialize method in the API class.

```Ruby
    @@wp_api = "/wp-json/wp/v2/posts?_fields=id,title,date,link&per_page=100&page="
    @@at_api = "https://api.airtable.com/v0/"

    def initialize(settings, blog, flags="")
        @current_settings = settings
        @blog = blog
        @@wp_api.prepend(blog.url)
        @@at_api += @blog.base_id + "/" + replace_space(@blog.table) 
    end

```

First, there are two class variable assignments. The base or domain of the AirTable API route will always be the same, while the subdirectories and query parameters will depend on the base and table values of the Blog object. On the other hand, the subdirectories and query params are constant for the WordPress API endpoint, while the domain will depend on the Blog object's url attribute.

When the class instantiates the new API object, it uses the Blog object it was passed  to concatenate and prepend to the @@at_api and @@wp_api class variables, respectively.

## Ping!
Let's look at the method our options class is calling: ping()
```Ruby
    def ping(flags)
        posts = collect_post_data()
        data =  prep_data(posts)
        add_to_at(data, @@at_api)
    end
``` 

This method calls collect_post_data, which gets the WordPress blog post data using the WordPress API. Then it calls prep_data to clean that data before passing it to add_to_at which adds it to AirTable.

```Ruby
def collect_post_data
        x = 1
        total = 2
        resp_array = []
        until x > total
            resp = HTTParty.get(@@wp_api + x.to_s)
            resp_array.push(resp.parsed_response)
            total = resp.headers["x-wp-totalpages"].to_i
            x += 1
        end
        resp_array.flatten
    end
```
This method sends a GET request to the WordPress API and adding that parsed response to resp_array. The WordPress API response will include ["x-wp-totalpages"] in the header. I'm using this to make sure I'm are getting all of the post data. Otherwise we would only get the first page of results, which WordPress limits to a max of 100 results.

```Ruby
def prep_data(results)
        records = []
        results.collect do |post|
            post["ID"] = post.delete("id")
            post["Title"] = post["title"].delete("rendered")
            post.delete("title")
            post["Date Published"] = post.delete("date")
            post["URL"] = post.delete("link")
            records.push({:fields => post})
        end
        records
    end
```

Prep_data iterates over the post data it was passed, changing key names to match the table header names in AirTable. It also adds a top-level key of :fields for each post, because that is what AirTable will expect in a post request.

```Ruby
def add_to_at(data, at_route)
        data.each_slice(10) do |slice| 
            at_response = HTTParty.post(at_route, 
            :body => {:records => slice}.to_json,
            :headers => {"Authorization" => "Bearer #{@current_settings.at_api}", "Content-Type" => "application/json"}
             )
            puts at_response
        end
    end
```

When creating new records, AirTable limits you to creating 10 records in each new request. That's why I use each_slice() here to send a post request with the cleaned post data in slices of 10. 

## Status
Right now, the gem can add blog data for any WordPress blog saved in the YAML file. However, multiple calls to sync the data don't account for the rows already in AirTable. Meaning that duplicate blog data is being added. The table header names are also very rigid at the moment. They must match what is hardcoded in the API class.

## Next Steps
Remember the flags? The next step will be allowing users to indicate whether they want to delete all the current post data and add all the post data again as new rows, or just update the table with missing data. I'd also like to add an option for retitling the AirTable table headers, saving the values alongside the other data in the YAML file.