Reading Instagram JSON with Ruby Controller and passing to View, using HTTParty gem

Viewed 261

I'm trying to learn processing JSON with Ruby. I went through quite a few tutorial but got confused even more, so I try to learn by doing it.

Here, I'm trying to get user's data from Instagram and display in my View. I have access to the JSON below but how do I reach certain fields and username or loop posts?

# users_controller.rb
require 'httparty'

class UsersController < ApplicationController
    include HTTParty

    def show
        @user = User.find(params[:id])
        fetch_instagram("elonofficiall")
    end

    private
    def fetch_instagram(instagram_username)
        url = "https://www.instagram.com/#{instagram_username}/?__a=1"
        @data = HTTParty.get(url)
        return @data
    end
end

# show.html.erb
# the code below is just to try if I get anything
<%= @data %>
<% @data.each do |data| %>
  <p><%= data %></p>
<% end %>

https://www.instagram.com/elonofficiall/?__a=1

enter image description here

2 Answers

First off don't do HTTP calls straight from your controller.

Instead create a separate class that "talks" to the instagram API:

# app/clients/instagram_client.rb
class InstagramClient
  include HTTParty
  base_uri 'https://www.instagram.com'
  format :json

  def initialize(**options)
    @options = options
  end

  def user(username, **opts)
    options = @options.reverse_merge(
      '__a' => 1 # wtf is this param?
    ).reverse_merge(opts)

    response = self.class.get("/#{username}", options)
    if response.success?
      extract_user(response)
    else
      Rails.logger.error("Fetching Instagram feed failed: HTTP #{response.code}")
      nil
    end
  end

  private 
  def extract_user(json)
    attributes = response.dig('graphql', 'user')&.slice('id', 'biography')
    attributes ? InstagramUser.new(attributes) : nil
  end
end

And an class that normalizes the API response for consumption in your application:

# app/models/instagram_user.rb
class InstagramUser
  include ActiveModel::Model
  include ActiveModel::Attributes
  attribute :id
  attribute :biography
  attribute :username
  # etc
end

This is just a straight up Rails model that is not persisted in the database. ActiveModel::Model and ActiveModel::Attributes let you pass a hash of attributes for mass assignment just like you do with ActiveRecord backed models.

This gives you an object you can simply test by:

InstagramClient.new.feed('elonofficiall')

And you would integrate it into your controller like so:

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    fetch_instagram(@user.instagram_username)
  end

  private
  def fetch_instagram(instagram_username)
    @instagram_user = InstagramClient.new.user(@user.instagram_username)
  end
end

Mixing HTTParty into your controller is a straight up bad idea as controllers are tricky to test and the controller already has the responsibility of responding to client requests and does not need more.

Also processing a "raw" JSON response in your view is not a great practice as it creates a hard coupling between the external API and your application and adds far to much complexity to your views. Normalize the data first in a separate object.

<% if @instagram_user %>
  <p><%= @instagram_user.username %></p>
<% end %>

If you want to actually get the media items from the users instagram feed you need to make another HTTP request to GET https://graph.facebook.com/{ig-user-id}/media. See the official API documentation.

Best to take it out of Rails first off. That only convolutes things. Many devs reply Rails === Ruby. It is not.

Write a simple ruby script and test her out:

rec = HTTParty.get(url)
puts rec.headers                    # content-type is 'text/html' and should be 'application/json'
res = rec.parsed_response           # getting error
puts res.class

The issue with Instagram is that I think it's forcing a bad content-header back to the user. It might be a security layer. I can't seem to force the reply into json or a hash either. So that's why httparty is not working for me.

Related