munson 0.2.0 → 0.3.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 +4 -4
- data/README.md +234 -246
- data/bin/console +2 -10
- data/lib/munson/agent.rb +35 -69
- data/lib/munson/attribute.rb +76 -0
- data/lib/munson/client.rb +43 -0
- data/lib/munson/collection.rb +19 -1
- data/lib/munson/connection.rb +61 -14
- data/lib/munson/document.rb +140 -0
- data/lib/munson/key_formatter.rb +87 -0
- data/lib/munson/middleware/encode_json_api.rb +10 -2
- data/lib/munson/middleware/json_parser.rb +11 -5
- data/lib/munson/query.rb +218 -0
- data/lib/munson/resource.rb +174 -20
- data/lib/munson/response_mapper.rb +92 -32
- data/lib/munson/version.rb +1 -1
- data/lib/munson.rb +30 -22
- metadata +7 -7
- data/lib/munson/model.rb +0 -15
- data/lib/munson/paginator/offset_paginator.rb +0 -45
- data/lib/munson/paginator/paged_paginator.rb +0 -47
- data/lib/munson/paginator.rb +0 -6
- data/lib/munson/query_builder.rb +0 -226
data/bin/console
CHANGED
@@ -2,13 +2,5 @@
|
|
2
2
|
|
3
3
|
require "bundler/setup"
|
4
4
|
require "munson"
|
5
|
-
|
6
|
-
|
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
|
5
|
+
require "pry"
|
6
|
+
Pry.start
|
data/lib/munson/agent.rb
CHANGED
@@ -1,55 +1,12 @@
|
|
1
1
|
module Munson
|
2
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
3
|
# Creates a new Munson::Agent
|
15
4
|
#
|
16
|
-
# @param [
|
17
|
-
# @
|
18
|
-
|
19
|
-
|
20
|
-
|
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.lookup_paginator(pager)
|
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
|
5
|
+
# @param [#to_s] path to JSON API Resource. Path will be added to the base path set in the Faraday::Connection
|
6
|
+
# @param [Munson::Connection] connection to use
|
7
|
+
def initialize(path, connection: nil)
|
8
|
+
@path = path
|
9
|
+
@connection = connection
|
53
10
|
end
|
54
11
|
|
55
12
|
# Connection that will be used for HTTP requests
|
@@ -60,36 +17,42 @@ module Munson
|
|
60
17
|
Munson.default_connection
|
61
18
|
end
|
62
19
|
|
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
20
|
# JSON API Spec GET request
|
70
21
|
#
|
71
22
|
# @option [Hash,nil] params: nil query params
|
72
|
-
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#
|
23
|
+
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#default_path
|
73
24
|
# @option [Hash] headers: nil HTTP Headers
|
25
|
+
# @option [String,Fixnum] id: nil ID to append to @path (provided in #new) when accessing a resource. If :path and :id are both specified, :path wins
|
74
26
|
# @return [Faraday::Response]
|
75
|
-
def get(params: nil, path: nil, headers: nil)
|
27
|
+
def get(params: nil, path: nil, headers: nil, id: nil)
|
76
28
|
connection.get(
|
77
|
-
path: (path
|
29
|
+
path: negotiate_path(path, id),
|
78
30
|
params: params,
|
79
31
|
headers: headers
|
80
32
|
)
|
81
33
|
end
|
82
34
|
|
35
|
+
def negotiate_path(path = nil, id = nil)
|
36
|
+
if path
|
37
|
+
path
|
38
|
+
elsif id
|
39
|
+
[@path, id].join('/')
|
40
|
+
else
|
41
|
+
@path
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
83
45
|
# JSON API Spec POST request
|
84
46
|
#
|
85
47
|
# @option [Hash,nil] body: {} query params
|
86
|
-
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#
|
48
|
+
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#default_path
|
87
49
|
# @option [Hash] headers: nil HTTP Headers
|
88
50
|
# @option [Type] http_method: :post describe http_method: :post
|
51
|
+
# @option [String,Fixnum] id: nil ID to append to default path when accessing a resource. If :path and :id are both specified, :path wins
|
89
52
|
# @return [Faraday::Response]
|
90
|
-
def post(body: {}, path: nil, headers: nil, http_method: :post)
|
53
|
+
def post(body: {}, path: nil, headers: nil, http_method: :post, id: nil)
|
91
54
|
connection.post(
|
92
|
-
path: (path
|
55
|
+
path: negotiate_path(path, id),
|
93
56
|
body: body,
|
94
57
|
headers: headers,
|
95
58
|
http_method: http_method
|
@@ -99,31 +62,34 @@ module Munson
|
|
99
62
|
# JSON API Spec PATCH request
|
100
63
|
#
|
101
64
|
# @option [Hash,nil] body: nil query params
|
102
|
-
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#
|
65
|
+
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#default_path
|
103
66
|
# @option [Hash] headers: nil HTTP Headers
|
67
|
+
# @option [String,Fixnum] id: nil ID to append to default path when accessing a resource. If :path and :id are both specified, :path wins
|
104
68
|
# @return [Faraday::Response]
|
105
|
-
def patch(body: nil, path: nil, headers: nil)
|
106
|
-
post(body, path: path, headers: headers, http_method: :patch)
|
69
|
+
def patch(body: nil, path: nil, headers: nil, id: nil)
|
70
|
+
post(body: body, path: path, headers: headers, http_method: :patch, id: id)
|
107
71
|
end
|
108
72
|
|
109
73
|
# JSON API Spec PUT request
|
110
74
|
#
|
111
75
|
# @option [Hash,nil] body: nil query params
|
112
|
-
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#
|
76
|
+
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#default_path
|
113
77
|
# @option [Hash] headers: nil HTTP Headers
|
78
|
+
# @option [String,Fixnum] id: nil ID to append to default path when accessing a resource. If :path and :id are both specified, :path wins
|
114
79
|
# @return [Faraday::Response]
|
115
|
-
def put(body: nil, path: nil, headers: nil)
|
116
|
-
post(body, path: path, headers: headers, http_method: :put)
|
80
|
+
def put(body: nil, path: nil, headers: nil, id: nil)
|
81
|
+
post(body: body, path: path, headers: headers, http_method: :put, id: id)
|
117
82
|
end
|
118
83
|
|
119
84
|
# JSON API Spec DELETE request
|
120
85
|
#
|
121
86
|
# @option [Hash,nil] body: nil query params
|
122
|
-
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#
|
87
|
+
# @option [String] path: nil path to GET, defaults to Faraday::Connection url + Agent#default_path
|
123
88
|
# @option [Hash] headers: nil HTTP Headers
|
89
|
+
# @option [String,Fixnum] id: nil ID to append to default path when accessing a resource. If :path and :id are both specified, :path wins
|
124
90
|
# @return [Faraday::Response]
|
125
|
-
def delete(body: nil, path: nil, headers: nil)
|
126
|
-
post(body, path: path, headers: headers, http_method: :delete)
|
91
|
+
def delete(body: nil, path: nil, headers: nil, id: nil)
|
92
|
+
post(body: body, path: path, headers: headers, http_method: :delete, id: id)
|
127
93
|
end
|
128
94
|
end
|
129
95
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Munson
|
2
|
+
class Attribute
|
3
|
+
attr_reader :name
|
4
|
+
attr_reader :cast_type
|
5
|
+
attr_reader :options
|
6
|
+
|
7
|
+
def initialize(name, cast_type, options={})
|
8
|
+
options[:default] ||= nil
|
9
|
+
options[:array] ||= false
|
10
|
+
@name = name
|
11
|
+
@cast_type = cast_type
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# Process a raw JSON value
|
16
|
+
def process(value)
|
17
|
+
value.nil? ? default_value : cast(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Super naive casting!
|
21
|
+
def cast(value)
|
22
|
+
return (@options[:array] ? [] : nil) if value.nil?
|
23
|
+
value.is_a?(Array) ?
|
24
|
+
value.map { |v| cast_value(v) } :
|
25
|
+
cast_value(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def cast_value(value)
|
29
|
+
return nil if value.nil?
|
30
|
+
|
31
|
+
case cast_type
|
32
|
+
when Proc
|
33
|
+
cast_type.call(value)
|
34
|
+
when :string, :to_s, String
|
35
|
+
value.to_s
|
36
|
+
when :integer, :to_i, Fixnum
|
37
|
+
value.to_i
|
38
|
+
when :bigdecimal
|
39
|
+
BigDecimal.new(value.to_s)
|
40
|
+
when :float, :to_f, Float
|
41
|
+
value.to_f
|
42
|
+
when :date, Date
|
43
|
+
Date.parse(value) rescue nil
|
44
|
+
when :time, Time
|
45
|
+
Time.parse(value) rescue nil
|
46
|
+
else
|
47
|
+
value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# Serializes the value back to JSON datatype
|
53
|
+
#
|
54
|
+
def serialize(value)
|
55
|
+
case options[:serialize]
|
56
|
+
when Proc
|
57
|
+
options[:serialize].call(value)
|
58
|
+
when Symbol
|
59
|
+
value.send(options[:serialize])
|
60
|
+
else
|
61
|
+
value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def default_value
|
66
|
+
case @options[:default]
|
67
|
+
when Proc
|
68
|
+
@options[:default].call
|
69
|
+
when nil
|
70
|
+
@options[:array] ? [] : nil
|
71
|
+
else
|
72
|
+
@options[:default].clone
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Munson
|
2
|
+
class Client
|
3
|
+
extend Forwardable
|
4
|
+
def_delegators :query, :include, :sort, :filter, :fields, :fetch, :page, :find
|
5
|
+
def_delegators :connection, :url=, :url, :response_key_format, :response_key_format=
|
6
|
+
|
7
|
+
attr_writer :path
|
8
|
+
attr_writer :query_builder
|
9
|
+
attr_accessor :type
|
10
|
+
|
11
|
+
def initialize(opts={})
|
12
|
+
opts.each do |k,v|
|
13
|
+
setter = "#{k}="
|
14
|
+
send(setter,v) if respond_to?(setter)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def query
|
19
|
+
(@query_builder || Query).new(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def agent
|
23
|
+
Agent.new(path, connection: connection)
|
24
|
+
end
|
25
|
+
|
26
|
+
def path
|
27
|
+
@path || "/#{type}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def configure(&block)
|
31
|
+
yield(self)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def connection
|
36
|
+
@connection ||= Munson.default_connection.clone
|
37
|
+
end
|
38
|
+
|
39
|
+
def connection=(connection)
|
40
|
+
@connection = connection
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/munson/collection.rb
CHANGED
@@ -1,4 +1,22 @@
|
|
1
1
|
module Munson
|
2
|
-
class Collection
|
2
|
+
class Collection
|
3
|
+
include Enumerable
|
4
|
+
extend Forwardable
|
5
|
+
def_delegator :@collection, :last
|
6
|
+
|
7
|
+
attr_reader :meta
|
8
|
+
attr_reader :jsonapi
|
9
|
+
attr_reader :links
|
10
|
+
|
11
|
+
def initialize(collection=[], errors: nil, meta: nil, jsonapi: nil, links: nil)
|
12
|
+
@collection = collection
|
13
|
+
@meta = meta
|
14
|
+
@jsonapi = jsonapi
|
15
|
+
@links = links
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(&block)
|
19
|
+
@collection.each(&block)
|
20
|
+
end
|
3
21
|
end
|
4
22
|
end
|
data/lib/munson/connection.rb
CHANGED
@@ -7,6 +7,7 @@ module Munson
|
|
7
7
|
# @private
|
8
8
|
attr_reader :faraday, :options
|
9
9
|
|
10
|
+
CONNECTION_OPTIONS = [:response_key_format].freeze
|
10
11
|
FARADAY_OPTIONS = [:request, :proxy, :ssl, :builder, :url,
|
11
12
|
:parallel_manager, :params, :headers, :builder_class].freeze
|
12
13
|
|
@@ -14,21 +15,35 @@ module Munson
|
|
14
15
|
# a faraday connection that includes two pieces of middleware for handling
|
15
16
|
# JSON API Spec
|
16
17
|
#
|
17
|
-
# @param [Hash]
|
18
|
+
# @param [Hash] args {Munson::Connection} configuration arguments
|
18
19
|
# @param [Proc] block to yield to Faraday::Connection
|
19
20
|
# @see https://github.com/lostisland/faraday/blob/master/lib/faraday/connection.rb Faraday::Connection
|
20
21
|
#
|
22
|
+
# @example Setting the key format
|
23
|
+
# $my_connection = Munson::Connection.new response_key_format: :dasherize, url: "http://api.example.com" do |c|
|
24
|
+
# c.use Your::Custom::Middleware
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# $my_connection = Munson::Connection.new response_key_format: :camelize, url: "http://api.example.com" do |c|
|
28
|
+
# c.use Your::Custom::Middleware
|
29
|
+
# end
|
30
|
+
#
|
21
31
|
# @example Creating a new connection
|
22
|
-
# my_connection = Munson::Connection.new url: "http://api.example.com" do |c|
|
32
|
+
# $my_connection = Munson::Connection.new url: "http://api.example.com" do |c|
|
23
33
|
# c.use Your::Custom::Middleware
|
24
34
|
# end
|
25
35
|
#
|
26
|
-
# class User
|
27
|
-
#
|
28
|
-
# munson.connection = my_connection
|
36
|
+
# class User < Munson::Resource
|
37
|
+
# self.type = :users
|
38
|
+
# munson.connection = $my_connection
|
29
39
|
# end
|
30
|
-
def initialize(
|
31
|
-
configure(
|
40
|
+
def initialize(args, &block)
|
41
|
+
configure(args, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Clones a connection
|
45
|
+
def clone
|
46
|
+
Connection.new(options.dup, &@block)
|
32
47
|
end
|
33
48
|
|
34
49
|
# JSON API Spec GET request
|
@@ -43,7 +58,7 @@ module Munson
|
|
43
58
|
def get(path: nil, params: nil, headers: nil)
|
44
59
|
faraday.get do |request|
|
45
60
|
request.headers.merge!(headers) if headers
|
46
|
-
request.url path.to_s, params
|
61
|
+
request.url path.to_s, externalize_keys(params)
|
47
62
|
end
|
48
63
|
end
|
49
64
|
|
@@ -55,14 +70,13 @@ module Munson
|
|
55
70
|
# @option [Type] http_method: :post describe http_method: :post
|
56
71
|
# @return [Faraday::Response]
|
57
72
|
def post(body: {}, path: nil, headers: nil, http_method: :post)
|
58
|
-
|
73
|
+
faraday.send(http_method) do |request|
|
59
74
|
request.headers.merge!(headers) if headers
|
60
75
|
request.url path.to_s
|
61
76
|
request.body = body
|
62
77
|
end
|
63
78
|
end
|
64
79
|
|
65
|
-
|
66
80
|
# Configure the connection
|
67
81
|
#
|
68
82
|
# @param [Hash] opts {Munson::Connection} configuration options
|
@@ -82,18 +96,51 @@ module Munson
|
|
82
96
|
# Munson::Connection.new url: "http://api.example.com" do |c|
|
83
97
|
# c.use MyTokenAuth
|
84
98
|
# end
|
85
|
-
def configure(
|
86
|
-
|
99
|
+
def configure(args={}, &block)
|
100
|
+
# Cache these for #clone method
|
101
|
+
@options = args
|
102
|
+
@block = block
|
87
103
|
|
88
104
|
faraday_options = @options.reject { |key, value| !FARADAY_OPTIONS.include?(key.to_sym) }
|
89
105
|
@faraday = Faraday.new(faraday_options) do |conn|
|
90
106
|
yield conn if block_given?
|
91
107
|
|
92
|
-
conn.
|
93
|
-
conn.
|
108
|
+
conn.request :"Munson::Middleware::EncodeJsonApi", key_formatter
|
109
|
+
conn.response :"Munson::Middleware::JsonParser", key_formatter
|
94
110
|
|
95
111
|
conn.adapter Faraday.default_adapter
|
96
112
|
end
|
97
113
|
end
|
114
|
+
|
115
|
+
(CONNECTION_OPTIONS + FARADAY_OPTIONS).each do |option_writer|
|
116
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
117
|
+
# Set the option value and reconfigure this connection
|
118
|
+
def #{option_writer}=(value)
|
119
|
+
@options[:#{option_writer}] = value
|
120
|
+
configure(@options, &@block)
|
121
|
+
@options[:#{option_writer}]
|
122
|
+
end
|
123
|
+
RUBY
|
124
|
+
end
|
125
|
+
|
126
|
+
def url
|
127
|
+
faraday.url_prefix.to_s
|
128
|
+
end
|
129
|
+
|
130
|
+
def response_key_format
|
131
|
+
@options[:response_key_format]
|
132
|
+
end
|
133
|
+
|
134
|
+
private def key_formatter
|
135
|
+
response_key_format ? Munson::KeyFormatter.new(response_key_format) : nil
|
136
|
+
end
|
137
|
+
|
138
|
+
private def externalize_keys(value)
|
139
|
+
if response_key_format && value.is_a?(Hash)
|
140
|
+
key_formatter.externalize(value)
|
141
|
+
else
|
142
|
+
value
|
143
|
+
end
|
144
|
+
end
|
98
145
|
end
|
99
146
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module Munson
|
2
|
+
class Document
|
3
|
+
attr_accessor :id
|
4
|
+
attr_reader :type
|
5
|
+
|
6
|
+
def initialize(jsonapi_document)
|
7
|
+
@id = jsonapi_document[:data][:id]
|
8
|
+
@type = jsonapi_document[:data][:type].to_sym
|
9
|
+
@jsonapi_document = jsonapi_document
|
10
|
+
|
11
|
+
if jsonapi_document[:data] && jsonapi_document[:data][:attributes]
|
12
|
+
@original_attributes = jsonapi_document[:data][:attributes].clone
|
13
|
+
@attributes = jsonapi_document[:data][:attributes].clone
|
14
|
+
else
|
15
|
+
@original_attributes = {}
|
16
|
+
@attributes = {}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Hash] hash for persisting this JSON API Resource via POST/PATCH/PUT
|
21
|
+
def payload
|
22
|
+
doc = { data: { type: @type } }
|
23
|
+
if id
|
24
|
+
doc[:data][:id] = id
|
25
|
+
doc[:data][:attributes] = changed
|
26
|
+
else
|
27
|
+
doc[:data][:attributes] = attributes
|
28
|
+
end
|
29
|
+
doc
|
30
|
+
end
|
31
|
+
|
32
|
+
def data
|
33
|
+
@jsonapi_document[:data]
|
34
|
+
end
|
35
|
+
|
36
|
+
def included
|
37
|
+
@jsonapi_document[:included] || []
|
38
|
+
end
|
39
|
+
|
40
|
+
def attributes
|
41
|
+
@attributes
|
42
|
+
end
|
43
|
+
|
44
|
+
def attributes=(attrs)
|
45
|
+
@attributes.merge!(attrs)
|
46
|
+
end
|
47
|
+
|
48
|
+
def changes
|
49
|
+
attributes.reduce({}) do |memo, (k,v)|
|
50
|
+
if @original_attributes[k] != attributes[k]
|
51
|
+
memo[k] = [@original_attributes[k], attributes[k]]
|
52
|
+
end
|
53
|
+
memo
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def changed
|
58
|
+
attributes.reduce({}) do |memo, (k,v)|
|
59
|
+
if @original_attributes[k] != attributes[k]
|
60
|
+
memo[k] = attributes[k]
|
61
|
+
end
|
62
|
+
memo
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def save(agent)
|
67
|
+
response = if id
|
68
|
+
agent.patch(id: id.to_s, body: payload)
|
69
|
+
else
|
70
|
+
agent.post(body: payload)
|
71
|
+
end
|
72
|
+
|
73
|
+
Munson::Document.new(response.body)
|
74
|
+
end
|
75
|
+
|
76
|
+
def url
|
77
|
+
links[:self]
|
78
|
+
end
|
79
|
+
|
80
|
+
def [](key)
|
81
|
+
attributes[key]
|
82
|
+
end
|
83
|
+
|
84
|
+
def errors
|
85
|
+
data[:errors] || []
|
86
|
+
end
|
87
|
+
|
88
|
+
# Raw relationship hashes
|
89
|
+
def relationships
|
90
|
+
data[:relationships] || {}
|
91
|
+
end
|
92
|
+
|
93
|
+
def links
|
94
|
+
data[:links] || {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def meta
|
98
|
+
data[:meta] || {}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Initialized {Munson::Document} from #relationships
|
102
|
+
# @param [Symbol] name of relationship
|
103
|
+
def relationship(name)
|
104
|
+
if relationship_data(name).is_a?(Array)
|
105
|
+
relationship_data(name).map { |meta_data| find_included_item(meta_data) }
|
106
|
+
elsif relationship_data(name).is_a?(Hash)
|
107
|
+
find_included_item(relationship_data(name))
|
108
|
+
else
|
109
|
+
raise RelationshipNotFound, <<-ERR
|
110
|
+
The relationship `#{name}` was called, but does not exist on the document.
|
111
|
+
Relationships available are: #{relationships.keys.join(',')}
|
112
|
+
ERR
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def relationship_data(name)
|
117
|
+
relationships[name] ? relationships[name][:data] : nil
|
118
|
+
end
|
119
|
+
|
120
|
+
# @param [Hash] relationship from JSONAPI relationships hash
|
121
|
+
# @return [Munson::Document,nil] the included relationship, if found
|
122
|
+
private def find_included_item(relationship)
|
123
|
+
resource = included.find do |included_resource|
|
124
|
+
included_resource[:type] == relationship[:type] &&
|
125
|
+
included_resource[:id] == relationship[:id]
|
126
|
+
end
|
127
|
+
|
128
|
+
if resource
|
129
|
+
Document.new(data: resource, included: included)
|
130
|
+
else
|
131
|
+
raise RelationshipNotIncludedError, <<-ERR
|
132
|
+
The relationship `#{relationship[:type]}` was called,
|
133
|
+
but it was not included in the request.
|
134
|
+
|
135
|
+
Try adding `include=#{relationship[:type]}` to your query.
|
136
|
+
ERR
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class Munson::KeyFormatter
|
2
|
+
# @param [Symbol] resource_key_format
|
3
|
+
def initialize(resource_key_format)
|
4
|
+
@format = resource_key_format
|
5
|
+
end
|
6
|
+
|
7
|
+
# Converts underscored keys to `format`
|
8
|
+
def externalize(hash)
|
9
|
+
deep_transform_keys(hash) do |key|
|
10
|
+
if @format == :dasherize
|
11
|
+
dasherize(key)
|
12
|
+
elsif @format == :camelize
|
13
|
+
camelize(key)
|
14
|
+
else
|
15
|
+
raise UnrecognizedKeyFormatter, <<-ERR
|
16
|
+
No key formatter found for `#{@format}`.
|
17
|
+
|
18
|
+
Valid :key_format values are `:camelize` and `:underscore`.
|
19
|
+
You may also provide a hash of lambdas `{format:->(key){}, unformat:->(key){}}`
|
20
|
+
ERR
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Converts keys formatted in `format` to underscore
|
26
|
+
def internalize(hash)
|
27
|
+
deep_transform_keys(hash) do |key|
|
28
|
+
if @format == :dasherize
|
29
|
+
undasherize(key)
|
30
|
+
elsif @format == :camelize
|
31
|
+
underscore(key)
|
32
|
+
else
|
33
|
+
raise UnrecognizedKeyFormatter, <<-ERR
|
34
|
+
No key formatter found for `#{@format}`.
|
35
|
+
|
36
|
+
Valid :key_format values are `:camelize` and `:underscore`.
|
37
|
+
You may also provide a hash of lambdas `{format:->(key){}, unformat:->(key){}}`
|
38
|
+
ERR
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def deep_transform_keys(hash, &block)
|
45
|
+
result = {}
|
46
|
+
hash.each do |key, value|
|
47
|
+
result[yield(key)] = map_value(value, &block)
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
def map_value(value, &block)
|
53
|
+
case value
|
54
|
+
when Hash
|
55
|
+
deep_transform_keys(value, &block)
|
56
|
+
when Array
|
57
|
+
value.map { |v| map_value(v, &block) }
|
58
|
+
else
|
59
|
+
value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def camelize(key)
|
64
|
+
string = key.to_s
|
65
|
+
string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
|
66
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
|
67
|
+
string.to_sym
|
68
|
+
end
|
69
|
+
|
70
|
+
def underscore(camel_cased_word)
|
71
|
+
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
|
72
|
+
word = camel_cased_word.to_s
|
73
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
|
74
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
|
75
|
+
word.tr!("-".freeze, "_".freeze)
|
76
|
+
word.downcase!
|
77
|
+
word.to_sym
|
78
|
+
end
|
79
|
+
|
80
|
+
def undasherize(key)
|
81
|
+
key.to_s.tr('-'.freeze, '_'.freeze).to_sym
|
82
|
+
end
|
83
|
+
|
84
|
+
def dasherize(key)
|
85
|
+
key.to_s.tr('_'.freeze, '-'.freeze).to_sym
|
86
|
+
end
|
87
|
+
end
|