munson 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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