Using Activerecord Interface for Models Backed by External API in Ruby on Rails

Using ActiveRecord interface for Models backed by external API in Ruby on Rails

Remember that Rails is still just Ruby underneath.

You could represent the external API as instantiated classes within your application.

class Event
def self.find(id)
#...External http call to get some JSON...#
new(json_from_api)
end

def initialize(json)
#...set up your object here...#
end

def attendees
#...external http call to get some JSON and then assemble it
#...into an array of other objects
end
end

So you end up writing local abstractions to create ruby objects from api calls, you can probably also mix in ActiveModel, or Virtus into it, so you can use hash assignment of attributes, and validations for forms etc.

Take a look at an API abstraction I did for the TfL feed for the tube. service_disruption

General direction rails external API

I'd check your API license and calculate anticipated volume - they probably throttle you to x connections / second/hour. If you can live within those limitations I don't see any reason not to use the API. You can put in place some common sense caching to prevent unnecessary calls (untested):

$(document).on('change', '#make_id', function() {
writeModels(retrieve_model(this.value));
});

var modelCache = {};

function retrieve_model(make) {
if (modelCache[make]) {
return modelCache[make]; //return cached value
}
$.get('https://api.edmunds.com/api/vehicle/v2/' + make + '/models? fmt=json&api_key=' + edmunds_api_key, {
context: this, //needed to ensure "make" is correct in the complete callback
complete: function(data) {
var modelArray = data.models;
console.log(data.models);
modelCache[make] = data.models; //populate cache
}
});
}

function writeModels(modelArray) {
$('#model_id').empty();
$.each(modelArray, function(index, value) {
$('#model_id').append('<option>' + value.name + '</option>');
});
}

In rails, how can I incorporate external api calls tied to model create/update that do not trigger upon db:seed?

Don't send it unless you're in production:

def api_post

if Rails.env.production?
ms = RestClient.new()
ms.post :category, self.to_json(:root => true)
end

end

This will skip it for development and test environments. The if check can be moved around as desired (perhaps to be around the after_commit instead, if it suits).

Is there a good pattern for combining ActiveRecord results with API data?

Since you are unifying these data for display purposes, it's better to use a helper to arrange your data structure for any view that needs it.

Controller:

@model_records = ARmodel.find_my_scope
@api_records = ApiGem.gather_users

Helper:

def all_users
ar_prop_filter = [:username, :first_name, :last_name, :current_project]
api_prop_filter = ['ranking', 'postCount', 'username', 'first_name', 'last_name']

# reduce to your hashes
model_set = @model_records.map{|rec| ar_prop_filter.inject({}){|acc, f| acc[f] = rec.send(f)} }
api_set = @api_records.map{|rec| api_prop_filter.inject({}){|acc, f| acc[f.to_sym] = rec[f]} }

# add the API data to the AR data, using the AR key
model_set.map! do |m_rec|
api_set[m_rec[:username]].each do |k, v|
m_rec[k] = v
end
end

# add API data that is not represented in the AR data
model_set += api_set.reject{|k, v| model_set.keys.include? k}

return model_set
end

This method has inefficiencies, and assumes different data between sources, leading to gaps that you wold need to anneal or validate for your views


Remember to think of best-practices for MVC models - otherwise it begs the question why you would do it.

This is principally a problem because an active record object is not a simple hash. It is an instantiation of a class, and merging data into it could lead to unexpected results. If you use a library to access and API, that library may instantiate objects to – which probably will Lead to similar problems.

If you actually have a case that demands doing things just as described, then you would probably do best to represent cast each of the API and active record objects as hashes, and then .merge them together. Remember that you could have key space collisions when doing this, and lose data.

Remember that when converting to hashes you will have no simple nor performant way to read save this data back to the active record or the API source.


Cheers

Rails ActiveRecord::Relation object using including models

Have you tried MyFirstModel.joins(:my_second_models)? Check out details joins in the API here.

EDIT: Single Table Inheritance is a better solution to this problem. See comments below.

Ruby on rails active record queries which one is efficient

It's actually much more common and simplier to use delegation.

class Book < ApplicationRecord
belongs_to :author
delegate :name, to: :author, prefix: true, allow_nil: true
end

class Author < ApplicationRecord
has_many :books
def name
"#{first_name.titleize} #(last_name.titleize}"
end
end

As to performance, if you join the authors at the time of the book query you end up doing a single query.

@books = Book.joins(:author)

Now when you iterate through @books and you call individually book.author_name no SQL query needs to be made to the authors table.



Related Topics



Leave a reply



Submit