{"id":13879607,"url":"https://github.com/coryodaniel/munson","last_synced_at":"2025-03-17T01:30:50.245Z","repository":{"id":46035689,"uuid":"71813429","full_name":"coryodaniel/munson","owner":"coryodaniel","description":"JSON API Spec client for Ruby","archived":false,"fork":false,"pushed_at":"2021-11-18T15:53:13.000Z","size":95,"stargazers_count":18,"open_issues_count":7,"forks_count":10,"subscribers_count":7,"default_branch":"develop","last_synced_at":"2025-02-27T15:38:07.542Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coryodaniel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-24T17:23:10.000Z","updated_at":"2023-12-15T12:07:38.000Z","dependencies_parsed_at":"2022-09-13T17:51:21.117Z","dependency_job_id":null,"html_url":"https://github.com/coryodaniel/munson","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coryodaniel%2Fmunson","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coryodaniel%2Fmunson/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coryodaniel%2Fmunson/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coryodaniel%2Fmunson/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coryodaniel","download_url":"https://codeload.github.com/coryodaniel/munson/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243835942,"owners_count":20355611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-06T08:02:26.625Z","updated_at":"2025-03-17T01:30:49.841Z","avatar_url":"https://github.com/coryodaniel.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Munson\n\n[![Gem Version](https://badge.fury.io/rb/munson.svg)](https://badge.fury.io/rb/munson)\n[![Code Climate](https://codeclimate.com/github/coryodaniel/munson/badges/gpa.svg)](https://codeclimate.com/github/coryodaniel/munson)\n[![Test Coverage](https://codeclimate.com/github/coryodaniel/munson/badges/coverage.svg)](https://codeclimate.com/github/coryodaniel/munson/coverage)\n[![Build Status](https://travis-ci.org/coryodaniel/munson.svg?branch=develop)](https://travis-ci.org/coryodaniel/munson)\n\nA JSON API client for Ruby\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'munson'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install munson\n\n## Basic Usage\n\n```ruby\nclass Article \u003c Munson::Resource\n  # This is done automatically if the tableize method is present\n  self.type = :articles\n\n  # how to process the JSON API ID. JSON API always uses strings, but Munson defaults to :integer\n  key_type :integer, #:string, -\u003e(id){ }\n  attribute :title, :string\nend\n```\n\n### Registering the JSONAPI 'type'\n\nCalling ```Article.type = :articles``` registers the class ```Article``` as the handler for JSON API resources of the type ```articles```.\n\nWhen the ActiveSupport method ```tableize``` is present, this will be set automatically. A class can be bound to multiple types:\n\n```ruby\nArticle.type = :articles\nMunson.register_type(:posts, Article)\n```\n\n### Querying\n```ruby\narticles = Article.fetch # Get some articles\narticle = Article.find(9) # Find an article\n\nquery = Article.fields(:title).include(:author, :comments).sort(id: :desc).filter(published: true)\nquery.to_params #=\u003e {:filter=\u003e{:published=\u003e\"true\"}, :fields=\u003e{:articles=\u003e\"title\"}, :include=\u003e\"author,comments\", :sort=\u003e\"-id\"}\nquery.to_query_string #=\u003e \"fields[articles]=title\u0026filter[published]=true\u0026include=author,comments\u0026sort=-id\"\n\nquery.fetch # Fetch articles w/ the given query string\n```\n\nThe Munson::Resource delegates a few methods to its underlying Munson::Connection:\n\n#### Filtering\n\n```ruby\nquery = Product.filter(min_price: 30, max_price: 65)\n\n# its chainable\nquery.filter(category: 'Hats').filter(size: ['small', 'medium'])\n\nquery.to_params\n#=\u003e {:filter=\u003e{:min_price=\u003e\"30\", :max_price=\u003e\"65\", :category=\u003e\"Hats\", :size=\u003e\"small,medium\"}}\n\nquery.fetch #=\u003e Munson::Collection\u003cProduct,Product\u003e\n```\n\n#### Sorting\n\n```ruby\nquery = Product.sort(created_at: :desc)\n\n# its chainable\nquery.sort(:price) # defaults to ASC\n\nquery.to_params\n#=\u003e {:sort=\u003e\"-created_at,price\"}\n\nquery.fetch #=\u003e Munson::Collection\u003cProduct,Product\u003e\n```\n\n#### Including (Side loading related resources)\n\n```ruby\nquery = Product.include(:manufacturer)\n\n# its chainable\nquery.include(:vendor)\n\nquery.to_params\n#=\u003e {:include=\u003e\"manufacturer,vendor\"}\n\nquery.fetch #=\u003e Munson::Collection\u003cProduct,Product\u003e\n```\n\n#### Sparse Fieldsets\n\n```ruby\nquery = Product.fields(products: [:name, :price])\n\n# its chainable\nquery.include(:manufacturer).fields(manufacturer: [:name])\n\nquery.to_params\n#=\u003e {:fields=\u003e{:products=\u003e\"name,price\", :manufacturer=\u003e\"name\"}, :include=\u003e\"manufacturer\"}\n\nquery.fetch #=\u003e Munson::Collection\u003cProduct,Product\u003e\n```\n\n#### All the things!\n```ruby\nquery = Product.\n  filter(min_price: 30, max_price: 65).\n  includes(:manufacturer).\n  sort(popularity: :desc, price: :asc).\n  fields(product: ['name', 'price'], manufacturer: ['name', 'website']).\n  page(number: 1, limit: 100)\n\nquery.to_params\n\n#=\u003e {:filter=\u003e{:min_price=\u003e\"30\", :max_price=\u003e\"65\"}, :fields=\u003e{:product=\u003e\"name,price\", :manufacturer=\u003e\"name,website\"}, :include=\u003e\"manufacturer\", :sort=\u003e\"-popularity,price\", :page=\u003e{:limit=\u003e10}}\n\nquery.fetch #=\u003e Munson::Collection\u003cProduct,Product\u003e\n```\n\n#### Fetching a single resource\n\n```ruby\nProduct.find(1) #=\u003e product\n```\n\n### Accessing Munson internals\nEvery Munson::Resource has an internally managed client. It is accessible via ```.munson```\n\n```ruby\nArticle.munson #=\u003e Munson::Client\nArticle.munson.path #=\u003e base path to use for this resource. Defaults to \"/\" + type; i.e., \"/articles\"\nArticle.munson.agent #=\u003e Munson::Agent: the agent wraps the Munson::Connection. It performs the low level GET/POST/PUT/PATCH/DELETE methods\nArticle.munson.query #=\u003e Munson::Query: a chainable query building instance\nArticle.munson.connection #=\u003e Munson::Connection: Small wrapper around the Farady connection\nArticle.munson.connection.response_key_format #=\u003e :dasherize, :camelize, nil\nArticle.munson.connection.url #=\u003e This endpoints base URL http://api.example.com/\nArticle.munson.connection.faraday #=\u003e The faraday object for this connection\nArticle.munson.connection = SomeNewConnectionYouPrefer\nArticle.munson.connection.configure(opts) do { |faraday_conn| } #=\u003e Feel free to reconfigure me ;D\n```\n\n### Persistence Resources\n```ruby\nclass Article \u003c Munson::Resource\n  attribute :title, :string\n  attribute :body, :string\n  attribute :created_at, :time\nend\n```\n\nCreating a new resource\n\n```ruby\narticle = Article.new\narticle.title = \"This is a great read!\"\narticle.save #=\u003e Boolean: Will attempt to POST to /articles\narticle.errors?\narticle.errors #=\u003e Array of errors\n```\n\nUpdating a resource\n\n```ruby\narticle = Article.find(9)\narticle.title = \"This is a great read!\"\narticle.save #=\u003e Boolean: Will attempt to PATCH to /articles\narticle.errors?\narticle.errors #=\u003e Array of errors\n```\n\n### Accessing Side Loaded Resources\n\nGiven the following relationship:\n\n```ruby\nclass Article \u003c Munson::Resource\n  self.type = :articles\n  has_one :author\n  has_many :comments\n\n  key_type :integer\n  attribute :title, :string\nend\n\nclass Person \u003c Munson::Resource\n  self.type = :people\n  has_many :articles\n\n  attribute :first_name, String\n  attribute :last_name, :string\n  attribute :twitter, :string\n  attribute :created_at, :time, default: -\u003e{ Time.now }, serialize: -\u003e(val){ val.to_s }\n  attribute :post_count, :integer\nend\n\nclass Comment \u003c Munson::Resource\n  self.type = :comments\n  has_one :author\n\n  attribute :body, -\u003e(val){ val.to_s }\n  attribute :score, :float\n  attribute :created_at, :time\n  attribute :is_spam, :boolean\n  attribute :mentions, :string, array: true\nend\n```\n\n**Note:** When specifying relationships in Munson, 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```.\n\n```json\n{\n  \"data\": [{\n    \"type\": \"articles\",\n    \"id\": \"1\",\n    \"attributes\": {\n      \"title\": \"JSON API paints my bikeshed!\"\n    },\n    \"relationships\": {\n      \"author\": {\n        \"links\": {\n          \"self\": \"http://example.com/articles/1/relationships/author\",\n          \"related\": \"http://example.com/articles/1/author\"\n        },\n        \"data\": { \"type\": \"people\", \"id\": \"9\" }\n      }\n    }\n  }]\n}\n```\n\nMunson initializes objects for side loaded resources. Only 1 HTTP call is made.\n\n```ruby\narticle = Article.include(:author, :comments).find(9)\narticle.author #=\u003e Person object\narticle.comments #=\u003e Munson::Collection\u003cComment\u003e\n\narticle.author.first_name #=\u003e Chauncy\n```\n\n\n\n## Configuration\n\nMunson 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.\n\nSetting the default connection:\n\n```ruby\nMunson.configure(url: 'http://api.example.com') do |c|\n  c.use MyCustomMiddleware\n  c.use AllTheMiddlewares\nend\n```\n\nEach Munson::Resource has its own Munson::Client. The client *copies* the default connection so its easy to set general configuration options, and overwrite them on a resource by resource basis.\n\n```ruby\nMunson.configure(url: 'http://api.example.com', response_key_format: :dasherize)\n\nclass Kitten \u003c Munson::Resource\n  munson.url = \"http://api.differentsite.com\"\nend\n\n# Overwritten URL\nKitten.munson.connection.url #=\u003e \"http://api.differentsite.com\"\n# Copied key format\nKitten.munson.connection.response_key_format #=\u003e :dasherize\n\nMunson.default_connection.url #=\u003e \"http://api.example.com\"\nMunson.default_connection.response_key_format #=\u003e :dasherize\n```\n\n### Configuration Options\n\n```ruby\nMunson.configure(url: 'http://api.example.com', response_key_format: :dasherize) do |conn|\n  conn.use SomeCoolFaradayMiddleware\nend\n```\n\nTwo special options can be passed into ```.configure```:\n* ```url``` the base url for this endpoint\n* ```response_key_format``` the format of the JSONAPI response keys. Valid values are: ```:dasherize```, ```:camelize```, ```nil```\n\nAdditinally any Faraday Connection options can be passed. [Faraday::Connection options](https://github.com/lostisland/faraday/blob/master/lib/faraday/connection.rb Faraday::Connection)\n\n\n## Advanced Usage\n\n### Custom Query Builder\n\nSince the filter param's format isn't specified in the [spec](http://jsonapi.org/format/#fetching-filtering)\nthis implementation uses [JSONAPI::Resource's implementation](https://github.com/cerebris/jsonapi-resources#filters)\n\nTo override, implement your own custom query builder inheriting from ```Munson::Query```.\n```Munson::Client``` takes a Query class to use. This method could be overwritten in your Resource:\n\n```ruby\nclass MyBuilder \u003c Munson::Query\n  def filter_to_query_value\n    # ... your fancier logic\n  end\nend\n\nArticle.munson.query_builder = MyBuilder\nArticle.munson.filter(:name =\u003e \"Chauncy\") #=\u003e MyBuilder instance\n```\n\n### Without inheriting from Munson::Resource\n\nIf for some reason you cannot inherit from Munson::Resource, you can still get a lot of JSONAPI parsing functionality\n\n```ruby\nclass Album\n  # Just some attr accessors, NBD\n  attr_accessor :id\n  attr_accessor :title\n\n  # Give Album a client to use\n  def self.munson\n    return @munson if @munson\n    @munson = Munson::Client.new\n  end\n\n  # Set the type, note, this is not being set on self\n  munson.type = :albums\n\n  # Register the type w/ munson\n  Munson.register_type(munson.type, self)\n\n  # When you aren't inherited from Munson::Resource, Munson will pass a Munson::Document to a static method called munson_initializer for you to initialze your record as you wish\n  def self.munson_initializer(document)\n    new(document.id, document.attributes[:title])\n  end\n\n  def initialize(id, title)\n    @id    = id\n    @title = title\n  end\nend\n```\n\n```ruby\nalbums = Album.munson.include(:songs).fetch\nalbums.first.title #=\u003e An album title!\n```\n\n### Any ol' object (Register type, add munson_initializer)\n\nAs long as a class is registered with munson and it response to munson_initializer, Munson will be able to initialize the object with or without a client\n\nExtending the example above...\n\n```ruby\nclass Song\n  attr_reader :name\n  def self.munson_initializer(document)\n    new(document.attributes[:name])\n  end\n\n  def initialize(name)\n    @name = name\n  end\nend\n\nMunson.register_type :songs, Song\n```\n\n```ruby\nalbum = Album.munson.include(:songs).find(9)\nalbum.songs #=\u003e Munson::Collection\u003cSong\u003e\n```\n\n## Development\n\nAfter 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.\n\nTo 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](https://rubygems.org).\n\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/coryodaniel/munson. Base hotfixes off of the `master` branch and features off of the `develop` branch.\n\n\n## TODOS\n* [ ] Update Yard docs :D\n* [ ] Posting/Putting relationships\n* [ ] A few pending tests :/\n* [ ] Collection#next (queries for next page, if pagination present)\n* [ ] Related Documents/Resources taking advantage of underlying resource[links]\n* [ ] Error object to wrap an individual error\n* [ ] consider enumerable protocol on a query\n* [ ] Handle null/empty responses...\n* [ ] Pluggable pagination?\n* [ ] Query#find([...]) find multiple records\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoryodaniel%2Fmunson","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoryodaniel%2Fmunson","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoryodaniel%2Fmunson/lists"}