munson 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 77ac2773bff30e5728d4a9e4233c8d4249e549de
4
+ data.tar.gz: 401d3dd17993b1b6fe4bc37ad9389304bbcb8afb
5
+ SHA512:
6
+ metadata.gz: 1b1ae060b21129c271dd32475f84305b8481577b9c0aa7831352aa3cdfae3918103cf79ed02dd4a1040f63916286cac7a111c7cd9914e5187d57caf9ebcd8ccd
7
+ data.tar.gz: 3d0b4b589d45b5df66f9203e3fed3a3745c5addf09e63d4db172a13d0020ba815162d68891f30132635e9e84e3ddff2b899078e485fabf4cc37d11f2cc92fe11
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.6
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in munson.gemspec
4
+ gemspec
5
+ gem "codeclimate-test-reporter", group: :test, require: nil
data/Guardfile ADDED
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.("routing/#{m[1]}_routing"),
51
+ rspec.spec.("controllers/#{m[1]}_controller"),
52
+ rspec.spec.("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ # Rails config changes
57
+ watch(rails.spec_helper) { rspec.spec_dir }
58
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # Capybara features specs
62
+ watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
63
+ watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
64
+
65
+ # Turnip features and steps
66
+ watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ end
70
+ end
data/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # Munson
2
+
3
+ [![Code Climate](https://codeclimate.com/github/coryodaniel/munson/badges/gpa.svg)](https://codeclimate.com/github/coryodaniel/munson)
4
+ [![Test Coverage](https://codeclimate.com/github/coryodaniel/munson/badges/coverage.svg)](https://codeclimate.com/github/coryodaniel/munson/coverage)
5
+
6
+ A JSON API Spec client for Ruby
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'munson'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install munson
23
+
24
+ ## Usage
25
+
26
+ ### Munson::Connection and configuring the default connection
27
+
28
+ 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.
29
+
30
+ ```ruby
31
+ Munson.configure(url: 'http://api.example.com') do |c|
32
+ c.use MyCustomMiddleware
33
+ end
34
+ ```
35
+
36
+ Options can be any [Faraday::Connection options](https://github.com/lostisland/faraday/blob/master/lib/faraday/connection.rb Faraday::Connection)
37
+
38
+ Additional connections can be created with:
39
+ ```ruby
40
+ my_connection = Munson::Connection.new(url: 'http://api2.example.com') do |c|
41
+ c.use MoreMiddleware
42
+ c.use AllTheMiddlewares
43
+ end
44
+ ```
45
+
46
+ ### Munson::Agent
47
+
48
+ Munson::Agent provides a small 'DSL' to build requests and parse responses,
49
+ while allowing additional configuration for a particular 'resource.'
50
+
51
+
52
+ ```ruby
53
+ Munson.configure url: 'http://api.example.com'
54
+
55
+ class Product
56
+ def self.munson
57
+ return @munson if @munson
58
+ @munson = Munson::Agent.new(
59
+ connection: Munson.default_connection, # || Munson::Connection.new(...)
60
+ paginator: :offset,
61
+ path: 'products'
62
+ )
63
+ end
64
+ end
65
+ ```
66
+
67
+ #### Getting the faraday response
68
+ ```ruby
69
+ query = Product.munson.filter(min_price: 30, max_price: 65)
70
+ # its chainable
71
+ query.filter(category: 'Hats').filter(size: ['small', 'medium'])
72
+
73
+ query.to_params
74
+ #=> {:filter=>{:min_price=>"30", :max_price=>"65", :category=>"Hats", :size=>"small,medium"}}
75
+
76
+ Product.munson.get(params: query.to_params)
77
+ ```
78
+
79
+ #### Filtering
80
+
81
+ ```ruby
82
+ query = Product.munson.filter(min_price: 30, max_price: 65)
83
+ # its chainable
84
+ query.filter(category: 'Hats').filter(size: ['small', 'medium'])
85
+
86
+ query.to_params
87
+ #=> {:filter=>{:min_price=>"30", :max_price=>"65", :category=>"Hats", :size=>"small,medium"}}
88
+
89
+ query.fetch #=> Some lovely data
90
+ ```
91
+
92
+ #### Sorting
93
+
94
+ ```ruby
95
+ query = Product.munson.sort(created_at: :desc)
96
+ # its chainable
97
+ query.sort(:price) # defaults to ASC
98
+
99
+ query.to_params
100
+ #=> {:sort=>"-created_at,price"}
101
+
102
+ query.fetch #=> Some lovely data
103
+ ```
104
+
105
+ #### Including (Side loading related resources)
106
+
107
+ ```ruby
108
+ query = Product.munson.includes(:manufacturer)
109
+ # its chainable
110
+ query.includes(:vendor)
111
+
112
+ query.to_params
113
+ #=> {:include=>"manufacturer,vendor"}
114
+
115
+ query.fetch #=> Some lovely data
116
+ ```
117
+
118
+ #### Sparse Fieldsets
119
+
120
+ ```ruby
121
+ query = Product.munson.fields(products: [:name, :price])
122
+ # its chainable
123
+ query.includes(:manufacturer).fields(manufacturer: [:name])
124
+
125
+ query.to_params
126
+ #=> {:fields=>{:products=>"name,price", :manufacturer=>"name"}, :include=>"manufacturer"}
127
+
128
+ query.fetch #=> Some lovely data
129
+ ```
130
+
131
+ #### All the things!
132
+ ```ruby
133
+ query = Product.munson.
134
+ filter(min_price: 30, max_price: 65).
135
+ includes(:manufacturer).
136
+ sort(popularity: :desc, price: :asc).
137
+ fields(product: ['name', 'price'], manufacturer: ['name', 'website']).
138
+ page(number: 1, limit: 100)
139
+
140
+ query.to_params
141
+ #=> {:filter=>{:min_price=>"30", :max_price=>"65"}, :fields=>{:product=>"name,price", :manufacturer=>"name,website"}, :include=>"manufacturer", :sort=>"-popularity,price", :page=>{:limit=>10}}
142
+
143
+ query.fetch #=> Some lovely data
144
+ ```
145
+
146
+ #### Fetching a single resource
147
+
148
+ ```ruby
149
+ Product.munson.find(1)
150
+ ```
151
+
152
+ #### Paginating
153
+
154
+ A paged and offset paginator are included with Munson.
155
+
156
+ Using the ```offset``` paginator
157
+ ```ruby
158
+ class Product
159
+ def self.munson
160
+ return @munson if @munson
161
+ @munson = Munson::Agent.new(
162
+ paginator: :offset,
163
+ path: 'products'
164
+ )
165
+ end
166
+ end
167
+
168
+ query = Product.munson.includes('manufacturer').page(offset: 10, limit: 25)
169
+ query.to_params
170
+ # => {:include=>"manufacturer", :page=>{:limit=>10, :offset=>10}}
171
+
172
+ query.fetch #=> Some lovely data
173
+ ```
174
+
175
+ Using the ```paged``` paginator
176
+ ```ruby
177
+ class Product
178
+ def self.munson
179
+ return @munson if @munson
180
+ @munson = Munson::Agent.new(
181
+ paginator: :paged,
182
+ path: 'products'
183
+ )
184
+ end
185
+ end
186
+
187
+ query = Product.munson.includes('manufacturer').page(page: 10, size: 25)
188
+ query.to_params
189
+ # => {:include=>"manufacturer", :page=>{:page=>10, :size=>10}}
190
+
191
+ query.fetch #=> Some lovely data
192
+ ```
193
+
194
+ ##### Custom paginators
195
+ Since the JSON API Spec does not dictate [how to paginate](http://jsonapi.org/format/#fetching-pagination), Munson has been designed to make adding custom paginators pretty easy.
196
+
197
+ ```ruby
198
+ class CustomPaginator
199
+ # @param [Hash] Hash of options like max/default page size
200
+ def initialize(opts={})
201
+ end
202
+
203
+ # @param [Hash] Hash to set the 'limit' and 'offset' to be returned later by #to_params
204
+ def set(params={})
205
+ end
206
+
207
+ # @return [Hash] Params to be merged into query builder.
208
+ def to_params
209
+ { page: {} }
210
+ end
211
+ end
212
+
213
+ ```
214
+
215
+ ### Munson::Resource
216
+
217
+ A munson resource provides a DSL in the including class for doing common JSON API queries on your ruby class.
218
+
219
+ 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.
220
+
221
+ It also will alter the response objects coming from #fetch and #find. Instead of returning a json hash like
222
+ 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.
223
+
224
+ ```ruby
225
+ class Product
226
+ include Munson::Resource
227
+ end
228
+
229
+ # Munson method is there, should you be looking for it.
230
+ Product.munson #=> Munson::Agent
231
+ ```
232
+
233
+ Changing the type name:
234
+ ```ruby
235
+ class Product
236
+ include Munson::Resource
237
+ munson.type = "things"
238
+ end
239
+ ```
240
+
241
+ #### Filtering
242
+
243
+ ```ruby
244
+ query = Product.filter(min_price: 30, max_price: 65)
245
+ # its chainable
246
+ query.filter(category: 'Hats').filter(size: ['small', 'medium'])
247
+
248
+ query.to_params
249
+ #=> {:filter=>{:min_price=>"30", :max_price=>"65", :category=>"Hats", :size=>"small,medium"}}
250
+
251
+ query.fetch #=> Munson::Collection<Product,Product>
252
+ ```
253
+
254
+ #### Sorting
255
+
256
+ ```ruby
257
+ query = Product.sort(created_at: :desc)
258
+ # its chainable
259
+ query.sort(:price) # defaults to ASC
260
+
261
+ query.to_params
262
+ #=> {:sort=>"-created_at,price"}
263
+
264
+ query.fetch #=> Munson::Collection<Product,Product>
265
+ ```
266
+
267
+ #### Including (Side loading related resources)
268
+
269
+ ```ruby
270
+ query = Product.includes(:manufacturer)
271
+ # its chainable
272
+ query.includes(:vendor)
273
+
274
+ query.to_params
275
+ #=> {:include=>"manufacturer,vendor"}
276
+
277
+ query.fetch #=> Munson::Collection<Product,Product>
278
+ ```
279
+
280
+ #### Sparse Fieldsets
281
+
282
+ ```ruby
283
+ query = Product.fields(products: [:name, :price])
284
+ # its chainable
285
+ query.includes(:manufacturer).fields(manufacturer: [:name])
286
+
287
+ query.to_params
288
+ #=> {:fields=>{:products=>"name,price", :manufacturer=>"name"}, :include=>"manufacturer"}
289
+
290
+ query.fetch #=> Munson::Collection<Product,Product>
291
+ ```
292
+
293
+ #### All the things!
294
+ ```ruby
295
+ query = Product.
296
+ filter(min_price: 30, max_price: 65).
297
+ includes(:manufacturer).
298
+ sort(popularity: :desc, price: :asc).
299
+ fields(product: ['name', 'price'], manufacturer: ['name', 'website']).
300
+ page(number: 1, limit: 100)
301
+
302
+ query.to_params
303
+ #=> {:filter=>{:min_price=>"30", :max_price=>"65"}, :fields=>{:product=>"name,price", :manufacturer=>"name,website"}, :include=>"manufacturer", :sort=>"-popularity,price", :page=>{:limit=>10}}
304
+
305
+ query.fetch #=> Munson::Collection<Product,Product>
306
+ ```
307
+
308
+ #### Fetching a single resource
309
+
310
+ ```ruby
311
+ Product.find(1) #=> product
312
+ ```
313
+
314
+ #### Paginating
315
+
316
+ A paged and offset paginator are included with Munson.
317
+
318
+ Using the ```offset``` paginator
319
+ ```ruby
320
+ class Product
321
+ include Munson::Resource
322
+ munson.paginator = :offset
323
+ munson.paginator_options = {default: 10, max: 100}
324
+ end
325
+
326
+ query = Product.includes('manufacturer').page(offset: 10, limit: 25)
327
+ query.to_params
328
+ # => {:include=>"manufacturer", :page=>{:limit=>10, :offset=>10}}
329
+
330
+ query.fetch #=> Munson::Collection<Product,Product>
331
+ ```
332
+
333
+ Using the ```paged``` paginator
334
+ ```ruby
335
+ class Product
336
+ include Munson::Resource
337
+ munson.paginator = :paged
338
+ munson.paginator_options = {default: 10, max: 100}
339
+ end
340
+
341
+ query = Product.includes('manufacturer').page(page: 10, size: 25)
342
+ query.to_params
343
+ # => {:include=>"manufacturer", :page=>{:page=>10, :size=>10}}
344
+
345
+ query.fetch #=> Some lovely data
346
+ ```
347
+
348
+ ## Development
349
+
350
+ 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.
351
+
352
+ 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](https://rubygems.org).
353
+
354
+ ## Contributing
355
+
356
+ Bug reports and pull requests are welcome on GitHub at https://github.com/coryodaniel/munson.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "munson"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,129 @@
1
+ module Munson
2
+ class Agent
3
+ extend Forwardable
4
+ def_delegators :query, :includes, :sort, :filter, :fields, :fetch, :page
5
+
6
+ attr_writer :connection
7
+
8
+ attr_accessor :type
9
+ attr_accessor :query_builder
10
+
11
+ attr_reader :paginator
12
+ attr_accessor :paginator_options
13
+
14
+ # Creates a new Munson::Agent
15
+ #
16
+ # @param [Hash] opts={} describe opts={}
17
+ # @option opts [Munson::Connection] :connection to use
18
+ # @option opts [#to_s, Munson::Paginator] :paginator to use on query builder
19
+ # @option opts [Class] :query_builder provide a custom query builder, defaults to {Munson::QueryBuilder}
20
+ # @option opts [#to_s] :type JSON Spec type. Type will be added to the base path set in the Faraday::Connection
21
+ def initialize(opts={})
22
+ @connection = opts[:connection]
23
+ @type = opts[:type]
24
+
25
+ @query_builder = opts[:query_builder].is_a?(Class) ?
26
+ opts[:query_builder] : Munson::QueryBuilder
27
+
28
+ self.paginator = opts[:paginator]
29
+ @paginator_options = opts[:paginator_options]
30
+ end
31
+
32
+ def paginator=(pager)
33
+ if pager.is_a?(Symbol)
34
+ @paginator = "Munson::Paginator::#{pager.to_s.classify}Paginator".constantize
35
+ else
36
+ @paginator = pager
37
+ end
38
+ end
39
+
40
+ # Munson::QueryBuilder factory
41
+ #
42
+ # @example creating a query
43
+ # @agent.includes('user').sort(age: :desc)
44
+ #
45
+ # @return [Munson::QueryBuilder] a query builder
46
+ def query
47
+ if paginator
48
+ query_pager = paginator.new(paginator_options || {})
49
+ @query_builder.new paginator: query_pager, agent: self
50
+ else
51
+ @query_builder.new agent: self
52
+ end
53
+ end
54
+
55
+ # Connection that will be used for HTTP requests
56
+ #
57
+ # @return [Munson::Connection] current connection of Munson::Agent or Munson.default_connection if not set
58
+ def connection
59
+ return @connection if @connection
60
+ Munson.default_connection
61
+ end
62
+
63
+ def find(id, headers: nil, params: nil)
64
+ path = [type, id].join('/')
65
+ response = get(path: path, headers: headers, params: params)
66
+ ResponseMapper.new(response).resource
67
+ end
68
+
69
+ # JSON API Spec GET request
70
+ #
71
+ # @option [Hash,nil] params: nil query params
72
+ # @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#type
73
+ # @option [Hash] headers: nil HTTP Headers
74
+ # @return [Faraday::Response]
75
+ def get(params: nil, path: nil, headers: nil)
76
+ connection.get(
77
+ path: (path || type),
78
+ params: params,
79
+ headers: headers
80
+ )
81
+ end
82
+
83
+ # JSON API Spec POST request
84
+ #
85
+ # @option [Hash,nil] body: {} query params
86
+ # @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#type
87
+ # @option [Hash] headers: nil HTTP Headers
88
+ # @option [Type] http_method: :post describe http_method: :post
89
+ # @return [Faraday::Response]
90
+ def post(body: {}, path: nil, headers: nil, http_method: :post)
91
+ connection.post(
92
+ path: (path || type),
93
+ body: body,
94
+ headers: headers,
95
+ http_method: http_method
96
+ )
97
+ end
98
+
99
+ # JSON API Spec PATCH request
100
+ #
101
+ # @option [Hash,nil] body: nil query params
102
+ # @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#type
103
+ # @option [Hash] headers: nil HTTP Headers
104
+ # @return [Faraday::Response]
105
+ def patch(body: nil, path: nil, headers: nil)
106
+ post(body, path: path, headers: headers, http_method: :patch)
107
+ end
108
+
109
+ # JSON API Spec PUT request
110
+ #
111
+ # @option [Hash,nil] body: nil query params
112
+ # @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#type
113
+ # @option [Hash] headers: nil HTTP Headers
114
+ # @return [Faraday::Response]
115
+ def put(body: nil, path: nil, headers: nil)
116
+ post(body, path: path, headers: headers, http_method: :put)
117
+ end
118
+
119
+ # JSON API Spec DELETE request
120
+ #
121
+ # @option [Hash,nil] body: nil query params
122
+ # @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#type
123
+ # @option [Hash] headers: nil HTTP Headers
124
+ # @return [Faraday::Response]
125
+ def delete(body: nil, path: nil, headers: nil)
126
+ post(body, path: path, headers: headers, http_method: :delete)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,4 @@
1
+ module Munson
2
+ class Collection < ::Array
3
+ end
4
+ end