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