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
How to Get Name of the Month in Ruby on Rails
Convert Hash to Openstruct Recursively
Mechanize How to Get Current Url
"Gem Update --System Is Disabled on Debian" Error
Ruby on Rails - Paperclip and Dynamic Parameters
Refactoring Activerecord Models with a Base Class Versus a Base Module
Custom Rails Configuration Section
I18N: Error Message Localization for Particular Model
Ruby on Rails - Is Params a Method or a Hash
How to Find Best Matching Element in Array of Numbers
(Ruby) How to Check Whether a Range Contains a Subset of Another Range
Unicode Characters in Ruby 1.9.3 Irb with Rvm
Ruby Operator Overloading Question
Require Lib in Rspec with Ruby 1.9.2 Brings "No Such File to Load"