Liz Laffitte
Posted on March 31, 2021
Correction
In part 1 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), 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:
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:
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.
@@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()
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.
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.
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.
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.
Posted on March 31, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.