Frenchy
Frenchy is an opinionated modeling framework for consuming HTTP+JSON API endpoints as ActiveModel-like objects. It deals with making requests, converting responses, type conversion, struct nesting, model decorating and instrumentation. Frenchy is used in production at Dotabuff serving millions of requests per day.
Installation
Add this line to your application's Gemfile:
gem "frenchy"
And then execute:
$ bundle install
Usage
Frenchy supports multiple back-end services. If you're using Rails, register them in an initializer:
# config/initializer/frenchy.rb
Frenchy.register_service :dodgeball, host: "http://127.0.0.1:3000"
Let's say we want to track the players on dodgeball team. Players have nicknames returned as a nested response:
class Player
# Include Frenchy::Model to get field macros and instance attribute methods
include Frenchy::Model
# Include Frenchy::Resource to get resource macro and class finder methods
include Frenchy::Resource
# Declare which service the model belongs to and specify your named API endpoints
resource service: "dodgeball", endpoints: {
one: { path: "/v1/players/:id" },
many: { path: "/v1/players", many: true },
team: { path: "/v1/teams/:team_id/players", many: true }
}
# You can supply a primary key field, which really just uses it for to_param.
key :id
# Define fields which create named attributes and deal with typecasting.
# Valid built-in types: string, integer, float, bool, time, array, hash
field :id, type: "integer"
field :name, type: "string"
field :win_rate, type: "float"
field :free_agent, type: "bool"
# You can also supply types of any class that can be instantiated by sending
# a hash of attributes to the "new" class method. If you specify the "many"
# option, we'll expect that the server returns an array and will properly treat
# the response as a collection.
field :nicknames, type: "nickname", many: true
end
class Nickname
include Frenchy::Model
field :name, type: "string"
field :insulting, type: "bool"
end
# GET /v1/players/1
# Expects response '{"id": N, ...}'
# Returns a single Player object
p = Player.find(1)
# GET /v1/players/?ids=1,2,3
# Expects response '[{"id": N, ...}, ...]'
# Returns multiple Player objects
Player.find_many([1,2,3])
# GET /v1/teams/3/players?injured=true
# Expects response '[{"id": N, ...}, ...]'
# Returns multiple Player objects
Player.find_with_endpoint(:team, team_id: 3, injured: true)
Decorators
Frenchy loves decorating! Call the .decorate
method on your Frenchy models for fun and profit. Under the covers it will find an appropriately named decorator (ex. PlayerDecorator
) and call decorate(self)
on it.
You can also call decorate on a collection of Frenchy models (as may be returned if you supply many: true
).
Instrumentation
Frenchy knows you like to monitor things, so requests are instrumented. You can do something like this:
# in an initializer...
ActiveSupport::Notifications.subscribe /request.frenchy/ do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
# Generates something along the lines of:
# [StatsD] dodgeball.player.one.count:1|c
# [StatsD] dodgeball.player.one.runtime:528.78|c
label = "#{event.payload[:service]}.#{event.payload[:model]}.#{event.payload[:endpoint]}"
StatsD.increment "#{label}.count", 1
StatsD.increment "#{label}.runtime", event.duration
end
Frenchy also provides Rails controller logging and instrumentation just like ActiveRecord:
Dodgeball (14.49ms) GET /v1/players/3
...
Completed 200 OK in 56.6ms (Views: 49.9ms | Frenchy: 14.49ms | ActiveRecord: 0.9ms)
Mascot
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
License
Copyright 2014 Jason Coene. Frenchy is released under the MIT license. See LICENSE.txt for details.