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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +5 -0
- data/Guardfile +70 -0
- data/README.md +356 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/munson/agent.rb +129 -0
- data/lib/munson/collection.rb +4 -0
- data/lib/munson/connection.rb +99 -0
- data/lib/munson/middleware/encode_json_api.rb +43 -0
- data/lib/munson/middleware/json_parser.rb +22 -0
- data/lib/munson/model.rb +15 -0
- data/lib/munson/paginator/offset_paginator.rb +44 -0
- data/lib/munson/paginator/paged_paginator.rb +45 -0
- data/lib/munson/paginator.rb +6 -0
- data/lib/munson/query_builder.rb +226 -0
- data/lib/munson/resource.rb +26 -0
- data/lib/munson/response_mapper.rb +54 -0
- data/lib/munson/version.rb +3 -0
- data/lib/munson.rb +70 -0
- data/munson.gemspec +34 -0
- metadata +250 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
module Munson
|
2
|
+
# Faraday::Connection wrapper for making JSON API Requests
|
3
|
+
#
|
4
|
+
# @attr_reader [Faraday::Connection] faraday connection object
|
5
|
+
# @attr_reader [Hash] options
|
6
|
+
class Connection
|
7
|
+
# @private
|
8
|
+
attr_reader :faraday, :options
|
9
|
+
|
10
|
+
FARADAY_OPTIONS = [:request, :proxy, :ssl, :builder, :url,
|
11
|
+
:parallel_manager, :params, :headers, :builder_class].freeze
|
12
|
+
|
13
|
+
# Create a new connection. A connection serves as a thin wrapper around a
|
14
|
+
# a faraday connection that includes two pieces of middleware for handling
|
15
|
+
# JSON API Spec
|
16
|
+
#
|
17
|
+
# @param [Hash] opts {Munson::Connection} configuration options
|
18
|
+
# @param [Proc] block to yield to Faraday::Connection
|
19
|
+
# @see https://github.com/lostisland/faraday/blob/master/lib/faraday/connection.rb Faraday::Connection
|
20
|
+
#
|
21
|
+
# @example Creating a new connection
|
22
|
+
# my_connection = Munson::Connection.new url: "http://api.example.com" do |c|
|
23
|
+
# c.use Your::Custom::Middleware
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# class User
|
27
|
+
# include Munson::Resource
|
28
|
+
# munson.connection = my_connection
|
29
|
+
# end
|
30
|
+
def initialize(opts, &block)
|
31
|
+
configure(opts, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
# JSON API Spec GET request
|
35
|
+
#
|
36
|
+
# @example making a GET request
|
37
|
+
# @connection.get(path: 'addresses', params: {include: 'user'}, headers: {'X-API-Token' => '2kewl'})
|
38
|
+
#
|
39
|
+
# @option [Hash,nil] params: nil query params
|
40
|
+
# @option [String] path: nil to GET
|
41
|
+
# @option [Hash] headers: nil HTTP Headers
|
42
|
+
# @return [Faraday::Response]
|
43
|
+
def get(path: nil, params: nil, headers: nil)
|
44
|
+
faraday.get do |request|
|
45
|
+
request.headers.merge!(headers) if headers
|
46
|
+
request.url path.to_s, params
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# JSON API Spec POST request
|
51
|
+
#
|
52
|
+
# @option [Hash,nil] body: {} query params
|
53
|
+
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#type
|
54
|
+
# @option [Hash] headers: nil HTTP Headers
|
55
|
+
# @option [Type] http_method: :post describe http_method: :post
|
56
|
+
# @return [Faraday::Response]
|
57
|
+
def post(body: {}, path: nil, headers: nil, http_method: :post)
|
58
|
+
connection.faraday.send(http_method) do |request|
|
59
|
+
request.headers.merge!(headers) if headers
|
60
|
+
request.url path.to_s
|
61
|
+
request.body = body
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Configure the connection
|
67
|
+
#
|
68
|
+
# @param [Hash] opts {Munson::Connection} configuration options
|
69
|
+
# @return Faraday::Connection
|
70
|
+
#
|
71
|
+
# @example Setting up the default API connection
|
72
|
+
# Munson::Connection.new url: "http://api.example.com"
|
73
|
+
#
|
74
|
+
# @example A custom middleware added to the default list
|
75
|
+
# class MyTokenAuth < Faraday::Middleware
|
76
|
+
# def call(env)
|
77
|
+
# env[:request_headers]["X-API-Token"] = "SECURE_TOKEN"
|
78
|
+
# @app.call(env)
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# Munson::Connection.new url: "http://api.example.com" do |c|
|
83
|
+
# c.use MyTokenAuth
|
84
|
+
# end
|
85
|
+
def configure(opts={}, &block)
|
86
|
+
@options = opts
|
87
|
+
|
88
|
+
faraday_options = @options.reject { |key, value| !FARADAY_OPTIONS.include?(key.to_sym) }
|
89
|
+
@faraday = Faraday.new(faraday_options) do |conn|
|
90
|
+
yield conn if block_given?
|
91
|
+
|
92
|
+
conn.use Munson::Middleware::EncodeJsonApi
|
93
|
+
conn.use Munson::Middleware::JsonParser
|
94
|
+
|
95
|
+
conn.adapter Faraday.default_adapter
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Munson
|
2
|
+
module Middleware
|
3
|
+
class EncodeJsonApi < Faraday::Middleware
|
4
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
5
|
+
ACCEPT = 'Accept'.freeze
|
6
|
+
MIME_TYPE = 'application/vnd.api+json'.freeze
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
env[:request_headers][ACCEPT] ||= MIME_TYPE
|
10
|
+
match_content_type(env) do |data|
|
11
|
+
env[:body] = encode data
|
12
|
+
end
|
13
|
+
@app.call env
|
14
|
+
end
|
15
|
+
|
16
|
+
def encode(data)
|
17
|
+
::JSON.dump data
|
18
|
+
end
|
19
|
+
|
20
|
+
def match_content_type(env)
|
21
|
+
if process_request?(env)
|
22
|
+
env[:request_headers][CONTENT_TYPE] ||= MIME_TYPE
|
23
|
+
yield env[:body] unless env[:body].respond_to?(:to_str)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_request?(env)
|
28
|
+
type = request_type(env)
|
29
|
+
has_body?(env) and (type.empty? or type == MIME_TYPE)
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_body?(env)
|
33
|
+
body = env[:body] and !(body.respond_to?(:to_str) and body.empty?)
|
34
|
+
end
|
35
|
+
|
36
|
+
def request_type(env)
|
37
|
+
type = env[:request_headers][CONTENT_TYPE].to_s
|
38
|
+
type = type.split(';', 2).first if type.index(';')
|
39
|
+
type
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Munson
|
2
|
+
module Middleware
|
3
|
+
class JsonParser < Faraday::Middleware
|
4
|
+
def call(request_env)
|
5
|
+
@app.call(request_env).on_complete do |response_env|
|
6
|
+
response_env[:raw_body] = response_env[:body]
|
7
|
+
response_env[:body] = parse(response_env[:body])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def parse(body)
|
14
|
+
unless body.strip.empty?
|
15
|
+
::JSON.parse(body, symbolize_names: true)
|
16
|
+
else
|
17
|
+
{}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/munson/model.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# module Munson
|
2
|
+
# module Model
|
3
|
+
# extend ActiveSupport::Concern
|
4
|
+
#
|
5
|
+
# included do
|
6
|
+
# self.include Munson::Resource
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# class_methods do
|
10
|
+
# def has_many(*);end;
|
11
|
+
# def has_one(*);end;
|
12
|
+
# def belongs_to(*);end;
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
# end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Munson
|
2
|
+
module Paginator
|
3
|
+
class OffsetPaginator
|
4
|
+
def initialize(options={})
|
5
|
+
@max_limit = options[:max]
|
6
|
+
@default_limit = options[:default]
|
7
|
+
end
|
8
|
+
|
9
|
+
def set(opts={})
|
10
|
+
limit(opts[:limit]) if opts[:limit]
|
11
|
+
offset(opts[:offset]) if opts[:offset]
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_params
|
15
|
+
{
|
16
|
+
page: {
|
17
|
+
limit: @limit || @default_limit || 10,
|
18
|
+
offset: @offset
|
19
|
+
}.compact
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Set limit of resources per page
|
26
|
+
#
|
27
|
+
# @param [Fixnum] num number of resources per page
|
28
|
+
def limit(num)
|
29
|
+
if @max_limit && num > @max_limit
|
30
|
+
@limit = @max_limit
|
31
|
+
else
|
32
|
+
@limit = num
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Set offset
|
37
|
+
#
|
38
|
+
# @param [Fixnum] num pages to offset
|
39
|
+
def offset(num)
|
40
|
+
@offset = num
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Munson
|
2
|
+
module Paginator
|
3
|
+
class PagedPaginator
|
4
|
+
def initialize(options={})
|
5
|
+
@max_size = options[:max]
|
6
|
+
@default_size = options[:default]
|
7
|
+
end
|
8
|
+
|
9
|
+
def set(opts={})
|
10
|
+
number(opts[:number]) if opts[:number]
|
11
|
+
size(opts[:size]) if opts[:size]
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_params
|
15
|
+
{
|
16
|
+
page: {
|
17
|
+
size: @size || @default_size || 10,
|
18
|
+
number: @number
|
19
|
+
}.compact
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Set number of resources per page
|
26
|
+
#
|
27
|
+
# @param [Fixnum] num number of resources per page
|
28
|
+
def size(num)
|
29
|
+
if @max_size && num > @max_size
|
30
|
+
@size = @max_size
|
31
|
+
else
|
32
|
+
@size = num
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Set page number
|
37
|
+
#
|
38
|
+
# @param [Fixnum] num page number
|
39
|
+
def number(num)
|
40
|
+
@number = num
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module Munson
|
2
|
+
class QueryBuilder
|
3
|
+
attr_reader :query
|
4
|
+
attr_reader :paginator
|
5
|
+
attr_reader :agent
|
6
|
+
class UnsupportedSortDirectionError < StandardError; end;
|
7
|
+
class PaginatorNotSet < StandardError; end;
|
8
|
+
class AgentNotSet < StandardError; end;
|
9
|
+
|
10
|
+
# Description of method
|
11
|
+
#
|
12
|
+
# @param [Class] paginator: nil instantiated paginator
|
13
|
+
# @param [Class] agent: nil instantiated agent to use for fetching results
|
14
|
+
# @return [Type] description of returned object
|
15
|
+
def initialize(paginator: nil, agent: nil)
|
16
|
+
@paginator = paginator
|
17
|
+
@agent = agent
|
18
|
+
|
19
|
+
@query = {
|
20
|
+
include: [],
|
21
|
+
fields: [],
|
22
|
+
filter: [],
|
23
|
+
sort: []
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String] query as a query string
|
28
|
+
def to_query_string
|
29
|
+
to_params.to_query
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
to_query
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_params
|
37
|
+
str = {}
|
38
|
+
str[:filter] = filter_to_query_value unless @query[:filter].empty?
|
39
|
+
str[:fields] = fields_to_query_value unless @query[:fields].empty?
|
40
|
+
str[:include] = includes_to_query_value unless @query[:include].empty?
|
41
|
+
str[:sort] = sort_to_query_value unless @query[:sort].empty?
|
42
|
+
|
43
|
+
str.merge!(paginator.to_params) if paginator
|
44
|
+
|
45
|
+
str
|
46
|
+
end
|
47
|
+
|
48
|
+
# Fetches resources using {Munson::Agent}
|
49
|
+
#
|
50
|
+
# @return [Array] Array of resources
|
51
|
+
def fetch
|
52
|
+
if @agent
|
53
|
+
response = @agent.get(params: to_params)
|
54
|
+
resources = ResponseMapper.new(response).resources
|
55
|
+
Collection.new(resources)
|
56
|
+
else
|
57
|
+
raise AgentNotSet, "Agent was not set. QueryBuilder#new(agent:)"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def paging?
|
62
|
+
!!paginator
|
63
|
+
end
|
64
|
+
|
65
|
+
# Paginator proxy
|
66
|
+
#
|
67
|
+
# @return [Class,nil] paginator if set
|
68
|
+
def page(opts={})
|
69
|
+
if paging?
|
70
|
+
paginator.set(opts)
|
71
|
+
self
|
72
|
+
else
|
73
|
+
raise PaginatorNotSet, "Paginator was not set. QueryBuilder#new(paginator:)"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Chainably include related resources.
|
78
|
+
#
|
79
|
+
# @example including a resource
|
80
|
+
# Munson::QueryBuilder.new.includes(:user)
|
81
|
+
#
|
82
|
+
# @example including a related resource
|
83
|
+
# Munson::QueryBuilder.new.includes("user.addresses")
|
84
|
+
#
|
85
|
+
# @example including multiple resources
|
86
|
+
# Munson::QueryBuilder.new.includes("user.addresses", "user.images")
|
87
|
+
#
|
88
|
+
# @param [Array<String,Symbol>] *args relationships to include
|
89
|
+
# @return [Munson::QueryBuilder] self for chaining queries
|
90
|
+
#
|
91
|
+
# @see http://jsonapi.org/format/#fetching-includes JSON API Including Relationships
|
92
|
+
def includes(*args)
|
93
|
+
@query[:include] += args
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
# Chainably sort results
|
98
|
+
# @note Default order is ascending
|
99
|
+
#
|
100
|
+
# @example sorting by a single field
|
101
|
+
# Munsun::QueryBuilder.new.sort(:created_at)
|
102
|
+
#
|
103
|
+
# @example sorting by a multiple fields
|
104
|
+
# Munsun::QueryBuilder.new.sort(:created_at, :age)
|
105
|
+
#
|
106
|
+
# @example specifying sort direction
|
107
|
+
# Munsun::QueryBuilder.new.sort(:created_at, age: :desc)
|
108
|
+
#
|
109
|
+
# @example specifying sort direction
|
110
|
+
# Munsun::QueryBuilder.new.sort(score: :desc, :created_at)
|
111
|
+
#
|
112
|
+
# @param [Hash<Symbol,Symbol>, Symbol] *args fields to sort by
|
113
|
+
# @return [Munson::QueryBuilder] self for chaining queries
|
114
|
+
#
|
115
|
+
# @see http://jsonapi.org/format/#fetching-sorting JSON API Sorting Spec
|
116
|
+
def sort(*args)
|
117
|
+
validate_sort_args(args.select{|arg| arg.is_a?(Hash)})
|
118
|
+
@query[:sort] += args
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
# Hash resouce_name: [array of attribs]
|
123
|
+
def fields(*args)
|
124
|
+
@query[:fields] += args
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
def filter(*args)
|
129
|
+
@query[:filter] += args
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.includes(*args)
|
134
|
+
new.includes(*args)
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.sort(*args)
|
138
|
+
new.sort(*args)
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.fields(*args)
|
142
|
+
new.fields(*args)
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.filter(*args)
|
146
|
+
new.filter(*args)
|
147
|
+
end
|
148
|
+
|
149
|
+
protected
|
150
|
+
|
151
|
+
def sort_to_query_value
|
152
|
+
@query[:sort].map{|item|
|
153
|
+
if item.is_a?(Hash)
|
154
|
+
item.to_a.map{|name,dir|
|
155
|
+
dir.to_sym == :desc ? "-#{name}" : name.to_s
|
156
|
+
}
|
157
|
+
else
|
158
|
+
item.to_s
|
159
|
+
end
|
160
|
+
}.join(',')
|
161
|
+
end
|
162
|
+
|
163
|
+
def fields_to_query_value
|
164
|
+
@query[:fields].inject({}) do |acc, hash_arg|
|
165
|
+
hash_arg.each do |k,v|
|
166
|
+
acc[k] ||= []
|
167
|
+
v.is_a?(Array) ?
|
168
|
+
acc[k] += v :
|
169
|
+
acc[k] << v
|
170
|
+
|
171
|
+
acc[k].map(&:to_s).uniq!
|
172
|
+
end
|
173
|
+
|
174
|
+
acc
|
175
|
+
end.map { |k, v| [k, v.join(',')] }.to_h
|
176
|
+
end
|
177
|
+
|
178
|
+
def includes_to_query_value
|
179
|
+
@query[:include].map(&:to_s).sort.join(',')
|
180
|
+
end
|
181
|
+
|
182
|
+
# Since the filter query param's format isn't specified in the [spec](http://jsonapi.org/format/#fetching-filtering)
|
183
|
+
# this implemenation uses (JSONAPI::Resource's implementation](https://github.com/cerebris/jsonapi-resources#filters)
|
184
|
+
#
|
185
|
+
# To override, implement your own CustomQueryBuilder inheriting from {Munson::QueryBuilder}
|
186
|
+
# {Munson::Agent} takes a QueryBuilder class to use. This method could be overriden in your custom class
|
187
|
+
#
|
188
|
+
# @example Custom Query Builder
|
189
|
+
# class MyBuilder < Munson::QueryBuilder
|
190
|
+
# def filter_to_query_value
|
191
|
+
# # ... your fancier logic
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# class Article
|
196
|
+
# def self.munson
|
197
|
+
# return @munson if @munson
|
198
|
+
# @munson = Munson::Agent.new(
|
199
|
+
# query_builder: MyBuilder
|
200
|
+
# path: 'products'
|
201
|
+
# )
|
202
|
+
# end
|
203
|
+
# end
|
204
|
+
#
|
205
|
+
def filter_to_query_value
|
206
|
+
@query[:filter].reduce({}) do |acc, hash_arg|
|
207
|
+
hash_arg.each do |k,v|
|
208
|
+
acc[k] ||= []
|
209
|
+
v.is_a?(Array) ? acc[k] += v : acc[k] << v
|
210
|
+
acc[k].uniq!
|
211
|
+
end
|
212
|
+
acc
|
213
|
+
end.map { |k, v| [k, v.join(',')] }.to_h
|
214
|
+
end
|
215
|
+
|
216
|
+
def validate_sort_args(hashes)
|
217
|
+
hashes.each do |hash|
|
218
|
+
hash.each do |k,v|
|
219
|
+
if !%i(desc asc).include?(v.to_sym)
|
220
|
+
raise UnsupportedSortDirectionError, "Unknown direction '#{v}'. Use :asc or :desc"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Munson
|
2
|
+
module Resource
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
def self.munson
|
7
|
+
return @munson if @munson
|
8
|
+
@munson = Munson::Agent.new
|
9
|
+
@munson
|
10
|
+
end
|
11
|
+
|
12
|
+
self.munson.type = name.demodulize.tableize
|
13
|
+
Munson.register_type(self.munson.type, self)
|
14
|
+
end
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
[:includes, :sort, :filter, :fields, :fetch, :find, :page].each do |method|
|
18
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
19
|
+
def #{method}(*args)
|
20
|
+
munson.#{method}(*args)
|
21
|
+
end
|
22
|
+
RUBY
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Munson
|
2
|
+
class ResponseMapper
|
3
|
+
class UnsupportedDatatype < StandardError;end;
|
4
|
+
|
5
|
+
def initialize(response)
|
6
|
+
@data = response.body[:data]
|
7
|
+
@includes = response.body[:include]
|
8
|
+
end
|
9
|
+
|
10
|
+
def resources
|
11
|
+
if data_is_collection?
|
12
|
+
map_data(@data)
|
13
|
+
else
|
14
|
+
raise StandardError, "Called #resources, but response was a single resource. Use ResponseMapper#resource"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def resource
|
19
|
+
if data_is_resource?
|
20
|
+
map_data(@data)
|
21
|
+
else
|
22
|
+
raise StandardError, "Called #resource, but response was a collection of resources. Use ResponseMapper#resources"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def data_is_resource?
|
29
|
+
@data.is_a?(Hash)
|
30
|
+
end
|
31
|
+
|
32
|
+
def data_is_collection?
|
33
|
+
@data.is_a?(Array)
|
34
|
+
end
|
35
|
+
|
36
|
+
def map_data(data)
|
37
|
+
if data_is_collection?
|
38
|
+
@data.map{ |datum| map_resource(datum) }
|
39
|
+
elsif data_is_resource?
|
40
|
+
map_resource(@data)
|
41
|
+
else
|
42
|
+
raise UnsupportedDatatype, "No mapping rule for #{data.class}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def map_resource(resource)
|
47
|
+
if klass = Munson.lookup_type(resource[:type])
|
48
|
+
klass.new(resource[:attributes].merge(id: resource[:id]))
|
49
|
+
else
|
50
|
+
resource
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/munson.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
require "active_support/inflector"
|
5
|
+
require "active_support/core_ext/hash"
|
6
|
+
|
7
|
+
require 'faraday'
|
8
|
+
require 'faraday_middleware'
|
9
|
+
|
10
|
+
require "munson/version"
|
11
|
+
|
12
|
+
require "munson/middleware/encode_json_api"
|
13
|
+
require "munson/middleware/json_parser"
|
14
|
+
|
15
|
+
require 'munson/collection'
|
16
|
+
require 'munson/paginator'
|
17
|
+
require 'munson/response_mapper'
|
18
|
+
require 'munson/query_builder'
|
19
|
+
require 'munson/connection'
|
20
|
+
require 'munson/agent'
|
21
|
+
require 'munson/resource'
|
22
|
+
|
23
|
+
module Munson
|
24
|
+
@registered_types = {}
|
25
|
+
class << self
|
26
|
+
# Configure the default connection.
|
27
|
+
#
|
28
|
+
# @param [Hash] opts {Munson::Connection} configuration options
|
29
|
+
# @param [Proc] block to yield to Faraday::Connection
|
30
|
+
# @return [Munson::Connection] the default connection
|
31
|
+
|
32
|
+
# @see https://github.com/lostisland/faraday/blob/master/lib/faraday/connection.rb Faraday::Connection
|
33
|
+
# @see Munson::Connection
|
34
|
+
def configure(opts={}, &block)
|
35
|
+
@default_connection = Munson::Connection.new(opts, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# The default connection
|
39
|
+
#
|
40
|
+
# @return [Munson::Connection, nil] the default connection if configured
|
41
|
+
def default_connection
|
42
|
+
defined?(@default_connection) ? @default_connection : nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Register a JSON Spec resource type to a class
|
46
|
+
# This is used in Faraday response middleware to package the JSON into a domain model
|
47
|
+
#
|
48
|
+
# @example Mapping a type
|
49
|
+
# Munson.register_type("addresses", Address)
|
50
|
+
#
|
51
|
+
# @param [#to_s] type JSON Spec type
|
52
|
+
# @param [Class] klass to map to
|
53
|
+
def register_type(type, klass)
|
54
|
+
@registered_types[type] = klass
|
55
|
+
end
|
56
|
+
|
57
|
+
# Lookup a class by JSON Spec type name
|
58
|
+
#
|
59
|
+
# @param [#to_s] type JSON Spec type
|
60
|
+
# @return [Class] domain model
|
61
|
+
def lookup_type(type)
|
62
|
+
@registered_types[type]
|
63
|
+
end
|
64
|
+
|
65
|
+
# @private
|
66
|
+
def flush_types!
|
67
|
+
@registered_types = {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/munson.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'munson/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "munson"
|
8
|
+
spec.version = Munson::VERSION
|
9
|
+
spec.authors = ["Cory O'Daniel"]
|
10
|
+
spec.email = ["cory@coryodaniel.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A JSON API Spec client for Ruby}
|
13
|
+
spec.homepage = "http://github.com/coryodaniel/munson"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = "exe"
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "faraday"
|
21
|
+
spec.add_dependency "faraday_middleware"
|
22
|
+
spec.add_dependency "activesupport"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency 'rspec-mocks'
|
28
|
+
spec.add_development_dependency "guard-rspec"
|
29
|
+
spec.add_development_dependency "pry"
|
30
|
+
spec.add_development_dependency "pry-byebug"
|
31
|
+
spec.add_development_dependency 'yard'
|
32
|
+
spec.add_development_dependency 'yard-activesupport-concern'
|
33
|
+
spec.add_development_dependency 'webmock'
|
34
|
+
end
|