Munson

Code Climate Test Coverage Build Status

A JSON API Spec client for Ruby

Installation

Add this line to your application's Gemfile:

gem 'munson'

And then execute:

$ bundle

Or install it yourself as:

$ gem install munson

Usage

Munson::Connection and configuring the default connection

Munson is designed to support multiple connections or API endpoints. A connection is a wrapper around Faraday::Connection that includes a few pieces of middleware for parsing and encoding requests and responses to JSON API Spec.

Munson.configure(url: 'http://api.example.com') do |c|
  c.use MyCustomMiddleware
end

Options can be any Faraday::Connection options

Additional connections can be created with:

my_connection = Munson::Connection.new(url: 'http://api2.example.com') do |c|
  c.use MoreMiddleware
  c.use AllTheMiddlewares
end

Munson::Agent

Munson::Agent provides a small 'DSL' to build requests and parse responses, while allowing additional configuration for a particular 'resource.'

Munson.configure url: 'http://api.example.com'

class Product
  def self.munson
    return @munson if @munson
    @munson = Munson::Agent.new(
      connection: Munson.default_connection, # || Munson::Connection.new(...)
      paginator: :offset,
      type: 'products'
    )
  end
end

Getting the faraday response

query = Product.munson.filter(min_price: 30, max_price: 65)
# its chainable
query.filter(category: 'Hats').filter(size: ['small', 'medium'])

query.to_params
#=> {:filter=>{:min_price=>"30", :max_price=>"65", :category=>"Hats", :size=>"small,medium"}}

Product.munson.get(params: query.to_params)

Filtering

query = Product.munson.filter(min_price: 30, max_price: 65)
# its chainable
query.filter(category: 'Hats').filter(size: ['small', 'medium'])

query.to_params
#=> {:filter=>{:min_price=>"30", :max_price=>"65", :category=>"Hats", :size=>"small,medium"}}

query.fetch #=> Some lovely data

Sorting

query = Product.munson.sort(created_at: :desc)
# its chainable
query.sort(:price) # defaults to ASC

query.to_params
#=> {:sort=>"-created_at,price"}

query.fetch #=> Some lovely data
query = Product.munson.includes(:manufacturer)
# its chainable
query.includes(:vendor)

query.to_params
#=> {:include=>"manufacturer,vendor"}

query.fetch #=> Some lovely data

Sparse Fieldsets

query = Product.munson.fields(products: [:name, :price])
# its chainable
query.includes(:manufacturer).fields(manufacturer: [:name])

query.to_params
#=> {:fields=>{:products=>"name,price", :manufacturer=>"name"}, :include=>"manufacturer"}

query.fetch #=> Some lovely data

All the things!

query = Product.munson.
  filter(min_price: 30, max_price: 65).
  includes(:manufacturer).
  sort(popularity: :desc, price: :asc).
  fields(product: ['name', 'price'], manufacturer: ['name', 'website']).
  page(number: 1, limit: 100)

query.to_params
#=> {:filter=>{:min_price=>"30", :max_price=>"65"}, :fields=>{:product=>"name,price", :manufacturer=>"name,website"}, :include=>"manufacturer", :sort=>"-popularity,price", :page=>{:limit=>10}}

query.fetch #=> Some lovely data

Fetching a single resource

Product.munson.find(1)

Paginating

A paged and offset paginator are included with Munson.

Using the offset paginator

class Product
  def self.munson
    return @munson if @munson
    @munson = Munson::Agent.new(
      paginator: :offset,
      type: 'products'
    )
  end
end

query = Product.munson.includes('manufacturer').page(offset: 10, limit: 25)
query.to_params
# => {:include=>"manufacturer", :page=>{:limit=>10, :offset=>10}}

query.fetch #=> Some lovely data

Using the paged paginator

class Product  
  def self.munson
    return @munson if @munson
    @munson = Munson::Agent.new(
      paginator: :paged,
      type: 'products'
    )
  end
end

query = Product.munson.includes('manufacturer').page(page: 10, size: 25)
query.to_params
# => {:include=>"manufacturer", :page=>{:page=>10, :size=>10}}

query.fetch #=> Some lovely data
Custom paginators

Since the JSON API Spec does not dictate how to paginate, Munson has been designed to make adding custom paginators pretty easy.

class CustomPaginator
  # @param [Hash] Hash of options like max/default page size
  def initialize(opts={})
  end

  # @param [Hash] Hash to set the 'limit' and 'offset' to be returned later by #to_params
  def set(params={})
  end

  # @return [Hash] Params to be merged into query builder.
  def to_params
    { page: {} }
  end
end

Munson::Resource

A munson resource provides a DSL in the including class for doing common JSON API queries on your ruby class.

It delegates a set of methods so that they dont have to be accessed through the munson class method and sets a few options based on the including class name.

It also will alter the response objects coming from #fetch and #find. Instead of returning a json hash like when using the bare Munson::Agent, Munson::Resource will pass the JSON Spec attributes and the ID as a hash into your class's initializer.

class Product
  include Munson::Resource
  register_munson_type :products
end

# Munson method is there, should you be looking for it.
Product.munson #=> Munson::Agent

Setting the type

This will cause Munson to return a hash instead of a class instance (Product).

class Product
  include Munson::Resource
  munson.type = :products
end

There are two ways to set the JSON API type when using a Munson::Resource

Registering the type

This will cause munson to return your model's datatype. Munson will register this in its type map dictionary and use the class to initialize a model

class Product
  include Munson::Resource
  register_munson_type :products
end

Filtering

query = Product.filter(min_price: 30, max_price: 65)
# its chainable
query.filter(category: 'Hats').filter(size: ['small', 'medium'])

query.to_params
#=> {:filter=>{:min_price=>"30", :max_price=>"65", :category=>"Hats", :size=>"small,medium"}}

query.fetch #=> Munson::Collection<Product,Product>

Sorting

query = Product.sort(created_at: :desc)
# its chainable
query.sort(:price) # defaults to ASC

query.to_params
#=> {:sort=>"-created_at,price"}

query.fetch #=> Munson::Collection<Product,Product>
query = Product.includes(:manufacturer)
# its chainable
query.includes(:vendor)

query.to_params
#=> {:include=>"manufacturer,vendor"}

query.fetch #=> Munson::Collection<Product,Product>

Sparse Fieldsets

query = Product.fields(products: [:name, :price])
# its chainable
query.includes(:manufacturer).fields(manufacturer: [:name])

query.to_params
#=> {:fields=>{:products=>"name,price", :manufacturer=>"name"}, :include=>"manufacturer"}

query.fetch #=> Munson::Collection<Product,Product>

All the things!

query = Product.
  filter(min_price: 30, max_price: 65).
  includes(:manufacturer).
  sort(popularity: :desc, price: :asc).
  fields(product: ['name', 'price'], manufacturer: ['name', 'website']).
  page(number: 1, limit: 100)

query.to_params
#=> {:filter=>{:min_price=>"30", :max_price=>"65"}, :fields=>{:product=>"name,price", :manufacturer=>"name,website"}, :include=>"manufacturer", :sort=>"-popularity,price", :page=>{:limit=>10}}

query.fetch #=> Munson::Collection<Product,Product>

Fetching a single resource

Product.find(1) #=> product

Paginating

A paged and offset paginator are included with Munson.

Using the offset paginator

class Product
  include Munson::Resource
  munson.paginator = :offset
  munson.paginator_options = {default: 10, max: 100}
end

query = Product.includes('manufacturer').page(offset: 10, limit: 25)
query.to_params
# => {:include=>"manufacturer", :page=>{:limit=>10, :offset=>10}}

query.fetch #=> Munson::Collection<Product,Product>

Using the paged paginator

class Product
  include Munson::Resource
  munson.paginator = :paged
  munson.paginator_options = {default: 10, max: 100}
end

query = Product.includes('manufacturer').page(page: 10, size: 25)
query.to_params
# => {:include=>"manufacturer", :page=>{:page=>10, :size=>10}}

query.fetch #=> Some lovely data

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/coryodaniel/munson.

Munson::Model

WIP see usage

Finding related resources could set instance methods that mix/override behavior of the class level agent...

/articles/1 Article.find(1) uses Article.munson

/articles/1/author article = Article.find(1) uses Article.munson article.author uses article.munson as a new connection that sets the base uri to /articles/1

Usage

address = Address.new
address.state = "FL"
address.save
address.state = "CA"
address.save
# Mind mutex adding method accessors on... dangerous, see her, consider storing them in an @attributes hash...
user = User.find(1)
address = user.addresses.build
address.save #posts on the relation

address = Address.new({...})
address = Address.new
address.assign_attributes
address.update_attributes
address.dirty?

Address.update(1, {}) #update without loading
Address.destroy(1) #Destroy without loading

address = Address.find(300)
address.destroy

Address.find(10)
Address.filter(zip_code: 90210).find(10)
Address.filter(zip_code: 90210).all
Address.filter(zip_code: 90210).fetch
Address.includes(:user, 'user.purchases').filter(active: true).all
Address.includes(:user, 'user.purchases').find(10)
Address.sort(city: :asc)
Address.sort(city: :desc)
Address.fields(:street1, :street2, {user: :name})
Address.fields(:street1, :street2).fields(user: :name)
addresses = Address.fields(:street1, :street2).fields(user: :name)
addresses.first.shipped_products.filter(min_total: 300.00)

Custom collection/member methods?