Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

mccracken

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mccracken

  • 0.3.1
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

McCracken

Gem Version Code Climate Test Coverage Build Status

A JSON API client for Ruby

Installation

Add this line to your application's Gemfile:

gem 'mccracken'

And then execute:

$ bundle

Or install it yourself as:

$ gem install mccracken

Basic Usage

class Article < McCracken::Resource
  # This is done automatically if the tableize method is present
  self.type = :articles

  # how to process the JSON API ID. JSON API always uses strings, but McCracken defaults to :integer
  key_type :integer, #:string, ->(id){ }
  attribute :title, :string
end

Registering the JSONAPI 'type'

Calling Article.type = :articles registers the class Article as the handler for JSON API resources of the type articles.

When the ActiveSupport method tableize is present, this will be set automatically. A class can be bound to multiple types:

Article.type = :articles
McCracken.register_type(:posts, Article)

Querying

articles = Article.fetch # Get some articles
article = Article.find(9) # Find an article

query = Article.fields(:title).include(:author, :comments).sort(id: :desc).filter(published: true)
query.to_params #=> {:filter=>{:published=>"true"}, :fields=>{:articles=>"title"}, :include=>"author,comments", :sort=>"-id"}
query.to_query_string #=> "fields[articles]=title&filter[published]=true&include=author,comments&sort=-id"

query.fetch # Fetch articles w/ the given query string

The McCracken::Resource delegates a few methods to its underlying McCracken::Connection:

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 #=> McCracken::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 #=> McCracken::Collection<Product,Product>
query = Product.include(:manufacturer)

# its chainable
query.include(:vendor)

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

query.fetch #=> McCracken::Collection<Product,Product>
Sparse Fieldsets
query = Product.fields(products: [:name, :price])

# its chainable
query.include(:manufacturer).fields(manufacturer: [:name])

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

query.fetch #=> McCracken::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 #=> McCracken::Collection<Product,Product>
Fetching a single resource
Product.find(1) #=> product

Accessing McCracken internals

Every McCracken::Resource has an internally managed client. It is accessible via .mccracken

Article.mccracken #=> McCracken::Client
Article.mccracken.path #=> base path to use for this resource. Defaults to "/" + type; i.e., "/articles"
Article.mccracken.agent #=> McCracken::Agent: the agent wraps the McCracken::Connection. It performs the low level GET/POST/PUT/PATCH/DELETE methods
Article.mccracken.query #=> McCracken::Query: a chainable query building instance
Article.mccracken.connection #=> McCracken::Connection: Small wrapper around the Farady connection
Article.mccracken.connection.response_key_format #=> :dasherize, :camelize, nil
Article.mccracken.connection.url #=> This endpoints base URL http://api.example.com/
Article.mccracken.connection.faraday #=> The faraday object for this connection
Article.mccracken.connection = SomeNewConnectionYouPrefer
Article.mccracken.connection.configure(opts) do { |faraday_conn| } #=> Feel free to reconfigure me ;D

Persistence Resources

class Article < McCracken::Resource
  attribute :title, :string
  attribute :body, :string
  attribute :created_at, :time
end

Creating a new resource

article = Article.new
article.title = "This is a great read!"
article.save #=> Boolean: Will attempt to POST to /articles
article.errors?
article.errors #=> Array of errors

Updating a resource

article = Article.find(9)
article.title = "This is a great read!"
article.save #=> Boolean: Will attempt to PATCH to /articles
article.errors?
article.errors #=> Array of errors

Accessing Side Loaded Resources

Given the following relationship:

class Article < McCracken::Resource
  self.type = :articles
  has_one :author
  has_many :comments

  key_type :integer
  attribute :title, :string
end

class Person < McCracken::Resource
  self.type = :people
  has_many :articles

  attribute :first_name, String
  attribute :last_name, :string
  attribute :twitter, :string
  attribute :created_at, :time, default: ->{ Time.now }, serialize: ->(val){ val.to_s }
  attribute :post_count, :integer
end

class Comment < McCracken::Resource
  self.type = :comments
  has_one :author

  attribute :body, ->(val){ val.to_s }
  attribute :score, :float
  attribute :created_at, :time
  attribute :is_spam, :boolean
  attribute :mentions, :string, array: true
end

Note: When specifying relationships in McCracken, you are specifying the JSON API type name. You'll notice below that when .author is called it returns a person object. That it is because in the HTTP response, the relationship name is author but the resource type is people.

{
  "data": [{
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON API paints my bikeshed!"
    },
    "relationships": {
      "author": {
        "links": {
          "self": "http://example.com/articles/1/relationships/author",
          "related": "http://example.com/articles/1/author"
        },
        "data": { "type": "people", "id": "9" }
      }
    }
  }]
}

McCracken initializes objects for side loaded resources. Only 1 HTTP call is made.

article = Article.include(:author, :comments).find(9)
article.author #=> Person object
article.comments #=> McCracken::Collection<Comment>

article.author.first_name #=> Chauncy

Configuration

McCracken 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.

Setting the default connection:

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

Each McCracken::Resource has its own McCracken::Client. The client copies the default connection so its easy to set general configuration options, and overwrite them on a resource by resource basis.

McCracken.configure(url: 'http://api.example.com', response_key_format: :dasherize)

class Kitten < McCracken::Resource
  mccracken.url = "http://api.differentsite.com"
end

# Overwritten URL
Kitten.mccracken.connection.url #=> "http://api.differentsite.com"
# Copied key format
Kitten.mccracken.connection.response_key_format #=> :dasherize

McCracken.default_connection.url #=> "http://api.example.com"
McCracken.default_connection.response_key_format #=> :dasherize

Configuration Options

McCracken.configure(url: 'http://api.example.com', response_key_format: :dasherize) do |conn|
  conn.use SomeCoolFaradayMiddleware
end

Two special options can be passed into .configure:

  • url the base url for this endpoint
  • response_key_format the format of the JSONAPI response keys. Valid values are: :dasherize, :camelize, nil

Additinally any Faraday Connection options can be passed. [Faraday::Connection options](https://github.com/lostisland/faraday/blob/master/lib/faraday/connection.rb Faraday::Connection)

Advanced Usage

Custom Query Builder

Since the filter param's format isn't specified in the spec this implementation uses JSONAPI::Resource's implementation

To override, implement your own custom query builder inheriting from McCracken::Query. McCracken::Client takes a Query class to use. This method could be overwritten in your Resource:

class MyBuilder < McCracken::Query
  def filter_to_query_value
    # ... your fancier logic
  end
end

Article.mccracken.query_builder = MyBuilder
Article.mccracken.filter(:name => "Chauncy") #=> MyBuilder instance

Without inheriting from McCracken::Resource

If for some reason you cannot inherit from McCracken::Resource, you can still get a lot of JSONAPI parsing functionality

class Album
  # Just some attr accessors, NBD
  attr_accessor :id
  attr_accessor :title

  # Give Album a client to use
  def self.mccracken
    return @mccracken if @mccracken
    @mccracken = McCracken::Client.new
  end

  # Set the type, note, this is not being set on self
  mccracken.type = :albums

  # Register the type w/ mccracken
  McCracken.register_type(mccracken.type, self)

  # When you aren't inherited from McCracken::Resource, McCracken will pass a McCracken::Document to a static method called mccracken_initializer for you to initialze your record as you wish
  def self.mccracken_initializer(document)
    new(document.id, document.attributes[:title])
  end

  def initialize(id, title)
    @id    = id
    @title = title
  end
end
albums = Album.mccracken.include(:songs).fetch
albums.first.title #=> An album title!

Any ol' object (Register type, add mccracken_initializer)

As long as a class is registered with mccracken and it response to mccracken_initializer, McCracken will be able to initialize the object with or without a client

Extending the example above...

class Song
  attr_reader :name
  def self.mccracken_initializer(document)
    new(document.attributes[:name])
  end

  def initialize(name)
    @name = name
  end
end

McCracken.register_type :songs, Song
album = Album.mccracken.include(:songs).find(9)
album.songs #=> McCracken::Collection<Song>

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/jgnagy/mccracken. Base hotfixes off of the master branch and features off of the develop branch.

TODOS

  • Update Yard docs :D
  • Posting/Putting relationships
  • A few pending tests :/
  • Collection#next (queries for next page, if pagination present)
  • Related Documents/Resources taking advantage of underlying resource[links]
  • Error object to wrap an individual error
  • consider enumerable protocol on a query
  • Handle null/empty responses...
  • Pluggable pagination?
  • Query#find([...]) find multiple records

FAQs

Package last updated on 29 Apr 2017

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc