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