jsonapi-realizer 1.0.0 → 2.0.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 +53 -47
- data/lib/jsonapi/realizer.rb +30 -8
- data/lib/jsonapi/realizer/action.rb +86 -12
- data/lib/jsonapi/realizer/action/create.rb +13 -8
- data/lib/jsonapi/realizer/action/create_spec.rb +61 -10
- data/lib/jsonapi/realizer/action/index.rb +20 -0
- data/lib/jsonapi/realizer/action/index_spec.rb +42 -0
- data/lib/jsonapi/realizer/action/show.rb +21 -0
- data/lib/jsonapi/realizer/action/show_spec.rb +48 -0
- data/lib/jsonapi/realizer/action/update.rb +12 -12
- data/lib/jsonapi/realizer/action/update_spec.rb +60 -10
- data/lib/jsonapi/realizer/adapter.rb +78 -17
- data/lib/jsonapi/realizer/adapter/active_record.rb +35 -13
- data/lib/jsonapi/realizer/adapter/memory.rb +34 -15
- data/lib/jsonapi/realizer/adapter_spec.rb +6 -2
- data/lib/jsonapi/realizer/resource.rb +35 -53
- data/lib/jsonapi/realizer/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38b5321aa71d6cd99b39ddb9fb9a3e09679239a8eed202403a82fe0191d1eb06
|
4
|
+
data.tar.gz: e0ff4d86639cef636a852196af3cca83d10e335eda4bbaf322e7b79f63da56f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6bfa065c4d7b383210a0be470f5d95e497da056e4df74beb10ced42920d6e66c09d9b9025c2397a98bad14268d330faaa021320b8fbae0d8144be5d77c3d095b
|
7
|
+
data.tar.gz: 9e2c5b864c0b3d067a88863bdc23f3bf839ecb3bb1da93b0423948f02e0b2dc8b8789cecb4bd1d677055ac0c83d5f25eb5c50b6bed7a60e8044dd2c299dfca3e
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@ This library handles incoming [json:api](https://www.jsonapi.org) payloads and t
|
|
10
10
|
A successful JSON:API request can be annotated as:
|
11
11
|
|
12
12
|
```
|
13
|
-
JSONAPIRequest -> (
|
13
|
+
JSONAPIRequest -> (BusinessLayer -> JSONAPIRequest -> (Record | Array<Record>)) -> JSONAPIResponse
|
14
14
|
```
|
15
15
|
|
16
16
|
The `jsonapi-serializers` library provides this shape:
|
@@ -22,12 +22,14 @@ JSONAPIRequest -> (Record | Array<Record>) -> JSONAPIResponse
|
|
22
22
|
But it leaves fetching/createing/updating/destroying the records up to you! This is where jsonapi-realizer comes into play, as it provides this shape:
|
23
23
|
|
24
24
|
```
|
25
|
-
|
25
|
+
BusinessLayer -> JSONAPIRequest -> (Record | Array<Record>)
|
26
26
|
```
|
27
27
|
|
28
28
|
|
29
29
|
## Using
|
30
30
|
|
31
|
+
In order to use this library you'll want to have some models:
|
32
|
+
|
31
33
|
``` ruby
|
32
34
|
class Photo < ApplicationRecord
|
33
35
|
belongs_to :photographer, class_name: "Profile"
|
@@ -36,13 +38,15 @@ end
|
|
36
38
|
class Profile < ApplicationRecord
|
37
39
|
has_many :photos
|
38
40
|
end
|
41
|
+
```
|
42
|
+
|
43
|
+
*They don't have to be ActiveRecord* models, but we have built-in support for that library (adapter-based). Second you'll need some realizers:
|
39
44
|
|
45
|
+
``` ruby
|
40
46
|
class PhotoRealizer
|
41
47
|
include JSONAPI::Realizer::Resource
|
42
48
|
|
43
|
-
adapter :active_record
|
44
|
-
|
45
|
-
represents :photos, class_name: "Photo"
|
49
|
+
register :photos, class_name: "Photo", adapter: :active_record
|
46
50
|
|
47
51
|
has_one :photographer, as: :profiles
|
48
52
|
|
@@ -53,9 +57,7 @@ end
|
|
53
57
|
class ProfileRealizer
|
54
58
|
include JSONAPI::Realizer::Resource
|
55
59
|
|
56
|
-
adapter :active_record
|
57
|
-
|
58
|
-
represents :profiles, class_name: "Profile"
|
60
|
+
register :profiles, class_name: "Profile", adapter: :active_record
|
59
61
|
|
60
62
|
has_many :photos, as: :photos
|
61
63
|
|
@@ -63,6 +65,14 @@ class ProfileRealizer
|
|
63
65
|
end
|
64
66
|
```
|
65
67
|
|
68
|
+
You can define special properties on attributes and relationships realizers:
|
69
|
+
|
70
|
+
``` ruby
|
71
|
+
has_many :doctors, as: :users, includable: false
|
72
|
+
|
73
|
+
has :title, selectable: false
|
74
|
+
```
|
75
|
+
|
66
76
|
Once you've designed your resources, we just need to use them! In this example, we'll use controllers from Rails:
|
67
77
|
|
68
78
|
``` ruby
|
@@ -71,11 +81,24 @@ class PhotosController < ApplicationController
|
|
71
81
|
validate_parameters!
|
72
82
|
authenticate_session!
|
73
83
|
|
74
|
-
|
84
|
+
realization = JSONAPI::Realizer.create(params, headers: request.headers)
|
85
|
+
|
86
|
+
ProcessPhotosService.new(realization.model)
|
87
|
+
|
88
|
+
render json: JSONAPI::Serializer.serialize(record)
|
89
|
+
end
|
90
|
+
|
91
|
+
def index
|
92
|
+
validate_parameters!
|
93
|
+
authenticate_session!
|
94
|
+
|
95
|
+
realization = JSONAPI::Realizer.index(params, headers: request.headers, type: :photos)
|
75
96
|
|
76
|
-
|
97
|
+
# See: pundit for `authorize()`
|
98
|
+
authorize realization.models
|
77
99
|
|
78
|
-
|
100
|
+
# See: pundit for `policy_scope()`
|
101
|
+
render json: JSONAPI::Serializer.serialize(policy_scope(record), is_collection: true)
|
79
102
|
end
|
80
103
|
end
|
81
104
|
```
|
@@ -89,9 +112,14 @@ There are two core adapters:
|
|
89
112
|
|
90
113
|
An adapter must provide the following interfaces:
|
91
114
|
|
92
|
-
0. `find_via`,
|
93
|
-
0. `
|
94
|
-
0. `
|
115
|
+
0. `find_via`, describes how to find the model
|
116
|
+
0. `find_many_via`, describes how to find many models
|
117
|
+
0. `assign_attributes_via`, describes how to write a set of properties
|
118
|
+
0. `assign_relationships_via`, describes how to write a set of relationships
|
119
|
+
0. `create_via`, describes how to create the model
|
120
|
+
0. `update_via`, describes how to update the model
|
121
|
+
0. `includes_via`, describes how to eager include related models
|
122
|
+
0. `sparse_fields_via`, describes how to only return certain fields
|
95
123
|
|
96
124
|
You can also provide custom adapter interfaces:
|
97
125
|
|
@@ -99,43 +127,20 @@ You can also provide custom adapter interfaces:
|
|
99
127
|
class PhotoRealizer
|
100
128
|
include JSONAPI::Realizer::Resource
|
101
129
|
|
102
|
-
adapter
|
103
|
-
find_via do |model_class, id|
|
104
|
-
model_class.where { id == id or slug == id }.first
|
105
|
-
end
|
106
|
-
|
107
|
-
write_attributes_via do |model, attributes|
|
108
|
-
model.update_columns(attributes)
|
109
|
-
end
|
130
|
+
register :photos, class_name: "Photo", adapter: :active_record
|
110
131
|
|
111
|
-
|
112
|
-
|
113
|
-
Rails.cache.write(model.cache_key, model)
|
114
|
-
end
|
132
|
+
adapter.find_via do |model_class, id|
|
133
|
+
model_class.where { id == id or slug == id }.first
|
115
134
|
end
|
116
135
|
|
117
|
-
|
118
|
-
|
119
|
-
has_one :photographer, as: :profiles
|
120
|
-
|
121
|
-
has :title
|
122
|
-
has :src
|
123
|
-
end
|
124
|
-
```
|
125
|
-
|
126
|
-
If you want, you can use both the regular adapters and some custom pieces:
|
127
|
-
|
128
|
-
``` ruby
|
129
|
-
class PhotoRealizer
|
130
|
-
include JSONAPI::Realizer::Resource
|
131
|
-
|
132
|
-
adapter :active_record do
|
133
|
-
find_via do |model_class, id|
|
134
|
-
model_class.where { id == id or slug == id }.first
|
135
|
-
end
|
136
|
+
adapter.assign_attributes_via do |model, attributes|
|
137
|
+
model.update_columns(attributes)
|
136
138
|
end
|
137
139
|
|
138
|
-
|
140
|
+
adapter.create_via do |model|
|
141
|
+
model.save!
|
142
|
+
Rails.cache.write(model.cache_key, model)
|
143
|
+
end
|
139
144
|
|
140
145
|
has_one :photographer, as: :profiles
|
141
146
|
|
@@ -144,11 +149,12 @@ class PhotoRealizer
|
|
144
149
|
end
|
145
150
|
```
|
146
151
|
|
152
|
+
|
147
153
|
## Installing
|
148
154
|
|
149
155
|
Add this line to your application's Gemfile:
|
150
156
|
|
151
|
-
gem "jsonapi-realizer", "
|
157
|
+
gem "jsonapi-realizer", "2.0.0"
|
152
158
|
|
153
159
|
And then execute:
|
154
160
|
|
data/lib/jsonapi/realizer.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "ostruct"
|
2
2
|
require "active_support/concern"
|
3
|
+
require "active_support/core_ext/enumerable"
|
3
4
|
|
4
5
|
module JSONAPI
|
5
6
|
module Realizer
|
@@ -8,23 +9,44 @@ module JSONAPI
|
|
8
9
|
require_relative "realizer/adapter"
|
9
10
|
require_relative "realizer/resource"
|
10
11
|
|
11
|
-
def self.register(resource_class
|
12
|
-
@mapping ||=
|
13
|
-
@mapping
|
14
|
-
model_class: model_class,
|
15
|
-
type: type,
|
12
|
+
def self.register(resource_class:, model_class:, adapter:, type:)
|
13
|
+
@mapping ||= Set.new
|
14
|
+
@mapping << OpenStruct.new({
|
16
15
|
resource_class: resource_class,
|
16
|
+
model_class: model_class,
|
17
|
+
adapter: adapter,
|
18
|
+
type: type.dasherize,
|
17
19
|
attributes: OpenStruct.new({}),
|
18
20
|
relationships: OpenStruct.new({})
|
19
21
|
})
|
20
22
|
end
|
21
23
|
|
22
|
-
def self.
|
23
|
-
@mapping
|
24
|
+
def self.resource_mapping
|
25
|
+
@mapping.index_by(&:resource_class)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.type_mapping
|
29
|
+
@mapping.index_by(&:type)
|
24
30
|
end
|
25
31
|
|
26
32
|
def self.create(payload, headers:)
|
27
|
-
Create.new(payload: payload, headers: headers)
|
33
|
+
enact(Create.new(payload: payload, headers: headers))
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.update(payload, headers:)
|
37
|
+
enact(Update.new(payload: payload, headers: headers))
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.show(payload, headers:, type:)
|
41
|
+
enact(Show.new(payload: payload, headers: headers, type: type))
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.index(payload, headers:, type:)
|
45
|
+
enact(Index.new(payload: payload, headers: headers, type: type))
|
46
|
+
end
|
47
|
+
|
48
|
+
private_class_method def self.inact(action)
|
49
|
+
action.tap(&:call)
|
28
50
|
end
|
29
51
|
end
|
30
52
|
end
|
@@ -3,37 +3,111 @@ module JSONAPI
|
|
3
3
|
class Action
|
4
4
|
require_relative "action/create"
|
5
5
|
require_relative "action/update"
|
6
|
+
require_relative "action/show"
|
7
|
+
require_relative "action/index"
|
6
8
|
|
7
|
-
attr_reader :
|
8
|
-
attr_reader :data
|
9
|
-
attr_reader :resource
|
9
|
+
attr_reader :payload
|
10
10
|
|
11
|
-
def initialize
|
11
|
+
def initialize
|
12
12
|
raise NoMethodError, "must implement this function"
|
13
13
|
end
|
14
14
|
|
15
|
-
def call
|
16
|
-
|
15
|
+
def call; end
|
16
|
+
|
17
|
+
private def model_class
|
18
|
+
resource_class.model_class
|
17
19
|
end
|
18
20
|
|
19
21
|
private def resource_class
|
20
|
-
|
22
|
+
configuration.resource_class
|
21
23
|
end
|
22
24
|
|
23
|
-
private def
|
24
|
-
|
25
|
+
private def adapter
|
26
|
+
configuration.adapter
|
27
|
+
end
|
28
|
+
|
29
|
+
private def relation_after_inclusion(relation)
|
30
|
+
if includes.any?
|
31
|
+
resource_class.include_via_call(relation, includes)
|
32
|
+
else
|
33
|
+
relation
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private def relation_after_fields(relation)
|
38
|
+
if includes.any?
|
39
|
+
resource_class.sparse_fields_call(relation, fields)
|
40
|
+
else
|
41
|
+
relation
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private def relation
|
46
|
+
relation_after_fields(
|
47
|
+
relation_after_inclusion(
|
48
|
+
model_class
|
49
|
+
)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
private def data
|
54
|
+
payload.fetch("data", {})
|
25
55
|
end
|
26
56
|
|
27
57
|
private def id
|
28
|
-
data.fetch("id", nil)
|
58
|
+
data.fetch("id", nil) || payload.fetch("id", nil)
|
29
59
|
end
|
30
60
|
|
31
61
|
private def type
|
32
|
-
data.fetch("type")
|
62
|
+
(@type || data.fetch("type")).to_s.dasherize
|
33
63
|
end
|
34
64
|
|
35
65
|
private def attributes
|
36
|
-
data.
|
66
|
+
data.
|
67
|
+
fetch("attributes", {}).
|
68
|
+
transform_keys(&:underscore).
|
69
|
+
select(&resource_class.method(:valid_attribute?))
|
70
|
+
end
|
71
|
+
|
72
|
+
private def relationships
|
73
|
+
data.
|
74
|
+
fetch("relationships", {}).
|
75
|
+
transform_keys(&:underscore).
|
76
|
+
select(&resource_class.method(:valid_relationship?)).
|
77
|
+
transform_values(&method(:as_relationship))
|
78
|
+
end
|
79
|
+
|
80
|
+
private def as_relationship(value)
|
81
|
+
data = value.fetch("data")
|
82
|
+
mapping = JSONAPI::Realizer.type_mapping.fetch(data.fetch("type"))
|
83
|
+
mapping.adapter.find_via_call(
|
84
|
+
mapping.model_class,
|
85
|
+
data.fetch("id")
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
private def includes
|
90
|
+
payload.
|
91
|
+
fetch("include", []).
|
92
|
+
# "carts.cart-items,carts.cart-items.product,carts.billing-information,payments"
|
93
|
+
map { |path| path.split(/\s*,\s*/) }.
|
94
|
+
# ["carts.cart-items", "carts.cart-items.product", "carts.billing-information", "payments"]
|
95
|
+
map { |path| path.gsub("-", "_") }.
|
96
|
+
# ["carts.cart_items", "carts.cart_items.product", "carts.billing_information", "payments"]
|
97
|
+
map { |path| path.split(".") }.
|
98
|
+
# [["carts", "cart_items"], ["carts", "cart_items", "product"], ["carts", "billing_information"], ["payments"]]
|
99
|
+
select(&resource_class.method(:valid_includes?))
|
100
|
+
end
|
101
|
+
|
102
|
+
private def fields
|
103
|
+
payload.
|
104
|
+
fetch("fields", []).
|
105
|
+
split(/\s*,\s*/).
|
106
|
+
select(&resource_class.method(:valid_sparse_field?))
|
107
|
+
end
|
108
|
+
|
109
|
+
private def configuration
|
110
|
+
JSONAPI::Realizer.type_mapping.fetch(type)
|
37
111
|
end
|
38
112
|
end
|
39
113
|
end
|
@@ -2,18 +2,23 @@ module JSONAPI
|
|
2
2
|
module Realizer
|
3
3
|
class Action
|
4
4
|
class Create < Action
|
5
|
+
attr_accessor :resource
|
6
|
+
|
5
7
|
def initialize(payload:, headers:)
|
6
|
-
@
|
7
|
-
@
|
8
|
+
@payload = payload
|
9
|
+
@headers = headers
|
10
|
+
@resource = resource_class.new(relation.new)
|
8
11
|
end
|
9
12
|
|
10
13
|
def call
|
11
|
-
resource.model
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
adapter.assign_attributes_via_call(resource.model, {id: id}) if id
|
15
|
+
adapter.assign_attributes_via_call(resource.model, attributes)
|
16
|
+
adapter.assign_relationships_via_call(resource.model, relationships)
|
17
|
+
adapter.create_via_call(resource.model)
|
18
|
+
end
|
19
|
+
|
20
|
+
def model
|
21
|
+
resource.model
|
17
22
|
end
|
18
23
|
end
|
19
24
|
end
|
@@ -6,6 +6,10 @@ RSpec.describe JSONAPI::Realizer::Action::Create do
|
|
6
6
|
describe "#call" do
|
7
7
|
subject { action.call }
|
8
8
|
|
9
|
+
context "with no top-level data" do
|
10
|
+
|
11
|
+
end
|
12
|
+
|
9
13
|
context "with a good payload and good headers" do
|
10
14
|
let(:payload) do
|
11
15
|
{
|
@@ -14,16 +18,18 @@ RSpec.describe JSONAPI::Realizer::Action::Create do
|
|
14
18
|
"type" => "photos",
|
15
19
|
"attributes" => {
|
16
20
|
"title" => "Ember Hamster",
|
21
|
+
"alt-text" => "A hamster logo.",
|
17
22
|
"src" => "http://example.com/images/productivity.png"
|
18
23
|
},
|
19
24
|
"relationships" => {
|
20
|
-
"photographer" => {
|
21
|
-
"data" => { "type" => "people", "id" => "4b8a0af6-953d-4729-8b9a-1fa4eb18f3c9" }
|
25
|
+
"active-photographer" => {
|
26
|
+
"data" => { "type" => "photographer-people", "id" => "4b8a0af6-953d-4729-8b9a-1fa4eb18f3c9" }
|
22
27
|
}
|
23
28
|
}
|
24
29
|
}
|
25
30
|
}
|
26
31
|
end
|
32
|
+
|
27
33
|
let(:headers) do
|
28
34
|
{
|
29
35
|
"Content-Type" => "application/vnd.api+json",
|
@@ -38,20 +44,65 @@ RSpec.describe JSONAPI::Realizer::Action::Create do
|
|
38
44
|
}
|
39
45
|
end
|
40
46
|
|
41
|
-
it "
|
42
|
-
|
47
|
+
it "is the right model" do
|
48
|
+
subject
|
49
|
+
|
50
|
+
expect(action.model).to be_a_kind_of(Photo)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "assigns the id attribute" do
|
54
|
+
subject
|
55
|
+
|
56
|
+
expect(action.model).to have_attributes(id: "550e8400-e29b-41d4-a716-446655440000")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "assigns the title attribute" do
|
60
|
+
subject
|
61
|
+
|
62
|
+
expect(action.model).to have_attributes(title: "Ember Hamster")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "assigns the alt_text attribute" do
|
66
|
+
subject
|
67
|
+
|
68
|
+
expect(action.model).to have_attributes(alt_text: "A hamster logo.")
|
69
|
+
end
|
70
|
+
|
71
|
+
it "assigns the src attribute" do
|
72
|
+
subject
|
73
|
+
|
74
|
+
expect(action.model).to have_attributes(src: "http://example.com/images/productivity.png")
|
43
75
|
end
|
44
76
|
|
45
|
-
it "assigns the
|
46
|
-
|
77
|
+
it "assigns the updated_at attribute" do
|
78
|
+
subject
|
79
|
+
|
80
|
+
expect(action.model).to have_attributes(updated_at: a_kind_of(Time))
|
47
81
|
end
|
48
82
|
|
49
|
-
it "
|
50
|
-
|
83
|
+
it "assigns the active_photographer attribute" do
|
84
|
+
subject
|
85
|
+
|
86
|
+
expect(action.model).to have_attributes(active_photographer: a_kind_of(People))
|
51
87
|
end
|
52
88
|
|
53
|
-
it "
|
54
|
-
expect {
|
89
|
+
it "creates the new record" do
|
90
|
+
expect {
|
91
|
+
subject
|
92
|
+
}.to change {
|
93
|
+
Photo::STORE
|
94
|
+
}.from(
|
95
|
+
{}
|
96
|
+
).to(
|
97
|
+
{
|
98
|
+
"550e8400-e29b-41d4-a716-446655440000" => hash_including(
|
99
|
+
id: "550e8400-e29b-41d4-a716-446655440000",
|
100
|
+
title: "Ember Hamster",
|
101
|
+
alt_text: "A hamster logo.",
|
102
|
+
src: "http://example.com/images/productivity.png"
|
103
|
+
)
|
104
|
+
}
|
105
|
+
)
|
55
106
|
end
|
56
107
|
end
|
57
108
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Realizer
|
3
|
+
class Action
|
4
|
+
class Index < Action
|
5
|
+
attr_accessor :resources
|
6
|
+
|
7
|
+
def initialize(payload:, headers:, type:)
|
8
|
+
@payload = payload
|
9
|
+
@headers = headers
|
10
|
+
@type = type
|
11
|
+
@resources = adapter.find_many_via_call(relation).map(&resource_class.method(:new))
|
12
|
+
end
|
13
|
+
|
14
|
+
def models
|
15
|
+
resources.map(&:model)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe JSONAPI::Realizer::Action::Index do
|
4
|
+
let(:action) { described_class.new(payload: payload, headers: headers, type: :photos) }
|
5
|
+
|
6
|
+
describe "#models" do
|
7
|
+
subject { action.models }
|
8
|
+
|
9
|
+
context "with no top-level data" do
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with a good payload and good headers" do
|
14
|
+
let(:payload) do
|
15
|
+
{}
|
16
|
+
end
|
17
|
+
let(:headers) do
|
18
|
+
{
|
19
|
+
"Content-Type" => "application/vnd.api+json",
|
20
|
+
"Accept" => "application/vnd.api+json"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
before do
|
25
|
+
Photo::STORE["550e8400-e29b-41d4-a716-446655440000"] = {
|
26
|
+
id: "550e8400-e29b-41d4-a716-446655440000",
|
27
|
+
title: "Ember Hamster",
|
28
|
+
src: "http://example.com/images/productivity.png"
|
29
|
+
}
|
30
|
+
Photo::STORE["d09ae4c6-0fc3-4c42-8fe8-6029530c3bed"] = {
|
31
|
+
id: "d09ae4c6-0fc3-4c42-8fe8-6029530c3bed",
|
32
|
+
title: "Ember Fox",
|
33
|
+
src: "http://example.com/images/productivity-2.png"
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns a list of photos" do
|
38
|
+
expect(subject).to include(a_kind_of(Photo), a_kind_of(Photo))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Realizer
|
3
|
+
class Action
|
4
|
+
class Show < Action
|
5
|
+
|
6
|
+
attr_accessor :resource
|
7
|
+
|
8
|
+
def initialize(payload:, headers:, type:)
|
9
|
+
@payload = payload
|
10
|
+
@headers = headers
|
11
|
+
@type = type
|
12
|
+
@resource = resource_class.new(adapter.find_via_call(relation, id))
|
13
|
+
end
|
14
|
+
|
15
|
+
def model
|
16
|
+
resource.model
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe JSONAPI::Realizer::Action::Show do
|
4
|
+
let(:action) { described_class.new(payload: payload, headers: headers, type: :photos) }
|
5
|
+
|
6
|
+
describe "#model" do
|
7
|
+
subject { action.model }
|
8
|
+
|
9
|
+
context "with no top-level data" do
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with a good payload and good headers" do
|
14
|
+
let(:payload) do
|
15
|
+
{
|
16
|
+
"id" => "d09ae4c6-0fc3-4c42-8fe8-6029530c3bed"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
let(:headers) do
|
20
|
+
{
|
21
|
+
"Content-Type" => "application/vnd.api+json",
|
22
|
+
"Accept" => "application/vnd.api+json"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
before do
|
27
|
+
Photo::STORE["550e8400-e29b-41d4-a716-446655440000"] = {
|
28
|
+
id: "550e8400-e29b-41d4-a716-446655440000",
|
29
|
+
title: "Ember Hamster",
|
30
|
+
src: "http://example.com/images/productivity.png"
|
31
|
+
}
|
32
|
+
Photo::STORE["d09ae4c6-0fc3-4c42-8fe8-6029530c3bed"] = {
|
33
|
+
id: "d09ae4c6-0fc3-4c42-8fe8-6029530c3bed",
|
34
|
+
title: "Ember Fox",
|
35
|
+
src: "http://example.com/images/productivity-2.png"
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns a photo model" do
|
40
|
+
expect(subject).to be_a_kind_of(Photo)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns the photos attributes" do
|
44
|
+
expect(subject).to have_attributes(title: "Ember Fox", src: "http://example.com/images/productivity-2.png")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -2,22 +2,22 @@ module JSONAPI
|
|
2
2
|
module Realizer
|
3
3
|
class Action
|
4
4
|
class Update < Action
|
5
|
+
attr_accessor :resource
|
6
|
+
|
5
7
|
def initialize(payload:, headers:)
|
6
|
-
@
|
7
|
-
@
|
8
|
-
|
9
|
-
resource_class.model_class,
|
10
|
-
id
|
11
|
-
)
|
12
|
-
)
|
8
|
+
@payload = payload
|
9
|
+
@headers = headers
|
10
|
+
@resource = resource_class.new(adapter.find_via_call(relation, id))
|
13
11
|
end
|
14
12
|
|
15
13
|
def call
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
adapter.assign_attributes_via_call(resource.model, attributes)
|
15
|
+
adapter.assign_relationships_via_call(resource.model, relationships)
|
16
|
+
adapter.update_via_call(resource.model)
|
17
|
+
end
|
18
|
+
|
19
|
+
def model
|
20
|
+
resource.model
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -6,6 +6,10 @@ RSpec.describe JSONAPI::Realizer::Action::Update do
|
|
6
6
|
describe "#call" do
|
7
7
|
subject { action.call }
|
8
8
|
|
9
|
+
context "with no top-level data" do
|
10
|
+
|
11
|
+
end
|
12
|
+
|
9
13
|
context "with a good payload and good headers" do
|
10
14
|
let(:payload) do
|
11
15
|
{
|
@@ -14,11 +18,12 @@ RSpec.describe JSONAPI::Realizer::Action::Update do
|
|
14
18
|
"type" => "photos",
|
15
19
|
"attributes" => {
|
16
20
|
"title" => "Ember Hamster 2",
|
21
|
+
"alt-text" => "A hamster logo.",
|
17
22
|
"src" => "http://example.com/images/productivity-2.png"
|
18
23
|
},
|
19
24
|
"relationships" => {
|
20
|
-
"photographer" => {
|
21
|
-
"data" => { "type" => "people", "id" => "4b8a0af6-953d-4729-8b9a-1fa4eb18f3c9" }
|
25
|
+
"active-photographer" => {
|
26
|
+
"data" => { "type" => "photographer-people", "id" => "4b8a0af6-953d-4729-8b9a-1fa4eb18f3c9" }
|
22
27
|
}
|
23
28
|
}
|
24
29
|
}
|
@@ -43,20 +48,65 @@ RSpec.describe JSONAPI::Realizer::Action::Update do
|
|
43
48
|
}
|
44
49
|
end
|
45
50
|
|
46
|
-
it "
|
47
|
-
|
51
|
+
it "is the right model" do
|
52
|
+
subject
|
53
|
+
|
54
|
+
expect(action.model).to be_a_kind_of(Photo)
|
48
55
|
end
|
49
56
|
|
50
|
-
it "assigns the
|
51
|
-
|
57
|
+
it "assigns the title attribute" do
|
58
|
+
subject
|
59
|
+
|
60
|
+
expect(action.model).to have_attributes(title: "Ember Hamster 2")
|
52
61
|
end
|
53
62
|
|
54
|
-
it "
|
55
|
-
|
63
|
+
it "assigns the alt_text attribute" do
|
64
|
+
subject
|
65
|
+
|
66
|
+
expect(action.model).to have_attributes(alt_text: "A hamster logo.")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "assigns the src attribute" do
|
70
|
+
subject
|
71
|
+
|
72
|
+
expect(action.model).to have_attributes(src: "http://example.com/images/productivity-2.png")
|
56
73
|
end
|
57
74
|
|
58
|
-
it "
|
59
|
-
|
75
|
+
it "assigns the updated_at attribute" do
|
76
|
+
subject
|
77
|
+
|
78
|
+
expect(action.model).to have_attributes(updated_at: a_kind_of(Time))
|
79
|
+
end
|
80
|
+
|
81
|
+
it "assigns the active_photographer attribute" do
|
82
|
+
subject
|
83
|
+
|
84
|
+
expect(action.model).to have_attributes(active_photographer: a_kind_of(People))
|
85
|
+
end
|
86
|
+
|
87
|
+
it "updates the record" do
|
88
|
+
expect {
|
89
|
+
subject
|
90
|
+
}.to change {
|
91
|
+
Photo::STORE
|
92
|
+
}.from(
|
93
|
+
{
|
94
|
+
"550e8400-e29b-41d4-a716-446655440000" => hash_including(
|
95
|
+
id: "550e8400-e29b-41d4-a716-446655440000",
|
96
|
+
title: "Ember Hamster",
|
97
|
+
src: "http://example.com/images/productivity.png"
|
98
|
+
)
|
99
|
+
}
|
100
|
+
).to(
|
101
|
+
{
|
102
|
+
"550e8400-e29b-41d4-a716-446655440000" => hash_including(
|
103
|
+
id: "550e8400-e29b-41d4-a716-446655440000",
|
104
|
+
title: "Ember Hamster 2",
|
105
|
+
alt_text: "A hamster logo.",
|
106
|
+
src: "http://example.com/images/productivity-2.png"
|
107
|
+
)
|
108
|
+
}
|
109
|
+
)
|
60
110
|
end
|
61
111
|
end
|
62
112
|
end
|
@@ -1,32 +1,93 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
module Realizer
|
3
|
-
|
3
|
+
class Adapter
|
4
4
|
require_relative "adapter/active_record"
|
5
5
|
require_relative "adapter/memory"
|
6
6
|
|
7
7
|
MAPPINGS = {
|
8
|
-
memory: JSONAPI::Realizer::Adapter::
|
9
|
-
active_record: JSONAPI::Realizer::Adapter::
|
8
|
+
memory: JSONAPI::Realizer::Adapter::MEMORY,
|
9
|
+
active_record: JSONAPI::Realizer::Adapter::ACTIVE_RECORD,
|
10
10
|
}
|
11
11
|
|
12
|
-
def
|
13
|
-
if
|
14
|
-
|
15
|
-
resource.include(JSONAPI::Realizer::Adapter::MAPPINGS.fetch(interface))
|
16
|
-
else
|
17
|
-
raise ArgumentError, "you've given an invalid adapter alias: #{interface}, we support #{JSONAPI::Realizer::Adapter::MAPPINGS.keys}"
|
18
|
-
end
|
12
|
+
def initialize(interface)
|
13
|
+
if JSONAPI::Realizer::Adapter::MAPPINGS.key?(interface.to_sym)
|
14
|
+
instance_eval(&JSONAPI::Realizer::Adapter::MAPPINGS.fetch(interface.to_sym))
|
19
15
|
else
|
20
|
-
|
16
|
+
raise ArgumentError, "you've given an invalid adapter alias: #{interface}, we support #{JSONAPI::Realizer::Adapter::MAPPINGS.keys}"
|
21
17
|
end
|
22
18
|
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
raise ArgumentError, "need to provide a Adapter.find_via interface" unless instance_variable_defined?(:@find_via_call)
|
20
|
+
raise ArgumentError, "need to provide a Adapter.find_many_via_call interface" unless instance_variable_defined?(:@find_many_via_call)
|
21
|
+
raise ArgumentError, "need to provide a Adapter.assign_attributes_via interface" unless instance_variable_defined?(:@assign_attributes_via_call)
|
22
|
+
raise ArgumentError, "need to provide a Adapter.assign_relationships_via interface" unless instance_variable_defined?(:@assign_relationships_via_call)
|
23
|
+
raise ArgumentError, "need to provide a Adapter.create_via interface" unless instance_variable_defined?(:@create_via_call)
|
24
|
+
raise ArgumentError, "need to provide a Adapter.update_via interface" unless instance_variable_defined?(:@update_via_call)
|
25
|
+
raise ArgumentError, "need to provide a Adapter.sparse_fields interface" unless instance_variable_defined?(:@sparse_fields_call)
|
26
|
+
raise ArgumentError, "need to provide a Adapter.include_via interface" unless instance_variable_defined?(:@include_via_call)
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_via(&callback)
|
30
|
+
@find_via_call = callback
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_many_via(&callback)
|
34
|
+
@find_many_via_call = callback
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_via(&callback)
|
38
|
+
@create_via_call = callback
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_via(&callback)
|
42
|
+
@update_via_call = callback
|
43
|
+
end
|
44
|
+
|
45
|
+
def assign_attributes_via(&callback)
|
46
|
+
@assign_attributes_via_call = callback
|
47
|
+
end
|
48
|
+
|
49
|
+
def assign_relationships_via(&callback)
|
50
|
+
@assign_relationships_via_call = callback
|
51
|
+
end
|
52
|
+
|
53
|
+
def sparse_fields(&callback)
|
54
|
+
@sparse_fields_call = callback
|
55
|
+
end
|
56
|
+
|
57
|
+
def include_via(&callback)
|
58
|
+
@include_via_call = callback
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_via_call(model_class, id)
|
62
|
+
@find_via_call.call(model_class, id)
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_many_via_call(model_class)
|
66
|
+
@find_many_via_call.call(model_class)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_via_call(model)
|
70
|
+
@create_via_call.call(model)
|
71
|
+
end
|
72
|
+
|
73
|
+
def update_via_call(model)
|
74
|
+
@update_via_call.call(model)
|
75
|
+
end
|
76
|
+
|
77
|
+
def assign_attributes_via_call(model, attributes)
|
78
|
+
@assign_attributes_via_call.call(model, attributes)
|
79
|
+
end
|
80
|
+
|
81
|
+
def assign_relationships_via_call(model, relationships)
|
82
|
+
@assign_relationships_via_call.call(model, relationships)
|
83
|
+
end
|
84
|
+
|
85
|
+
def sparse_fields_call(model_class, fields)
|
86
|
+
@sparse_fields_call.call(model_class, fields)
|
87
|
+
end
|
26
88
|
|
27
|
-
|
28
|
-
|
29
|
-
raise ArgumentError, "need to provide a Adapter.save_via interface" unless resource.instance_variable_defined?(:@save_via_call)
|
89
|
+
def include_via_call(model_class, includes)
|
90
|
+
@include_via_call.call(model_class, includes)
|
30
91
|
end
|
31
92
|
end
|
32
93
|
end
|
@@ -1,21 +1,43 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
module Realizer
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class Adapter
|
4
|
+
ACTIVE_RECORD = Proc.new do
|
5
|
+
find_many_via do |model_class|
|
6
|
+
model_class.all
|
7
|
+
end
|
8
|
+
|
9
|
+
find_via do |model_class, id|
|
10
|
+
model_class.find(id)
|
11
|
+
end
|
6
12
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
13
|
+
assign_attributes_via do |model, attributes|
|
14
|
+
model.assign_attributes(attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
assign_relationships_via do |model, relationships|
|
18
|
+
model.assign_attributes(relationships)
|
19
|
+
end
|
11
20
|
|
12
|
-
|
13
|
-
|
14
|
-
|
21
|
+
sparse_fields do |model_class, fields|
|
22
|
+
model_class.select(fields)
|
23
|
+
end
|
24
|
+
|
25
|
+
include_via do |model_class, includes|
|
26
|
+
model_class.includes(includes.map(&(recursively_nest = -> (chains) do
|
27
|
+
if chains.size == 1
|
28
|
+
chains.first
|
29
|
+
else
|
30
|
+
{chains.first => recursively_nest.call(chains.drop(1))}
|
31
|
+
end
|
32
|
+
end)))
|
33
|
+
end
|
34
|
+
|
35
|
+
create_via do |model|
|
36
|
+
model.create!
|
37
|
+
end
|
15
38
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
39
|
+
update_via do |model|
|
40
|
+
model.update!
|
19
41
|
end
|
20
42
|
end
|
21
43
|
end
|
@@ -1,24 +1,43 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
module Realizer
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class Adapter
|
4
|
+
MEMORY = Proc.new do
|
5
|
+
find_many_via do |model_class|
|
6
|
+
model_class.all
|
7
|
+
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
find_via do |model_class, id|
|
10
|
+
model_class.fetch(id)
|
11
|
+
end
|
12
|
+
|
13
|
+
assign_attributes_via do |model, attributes|
|
14
|
+
model.assign_attributes(attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
assign_relationships_via do |model, relationships|
|
18
|
+
model.assign_attributes(relationships)
|
19
|
+
end
|
20
|
+
|
21
|
+
sparse_fields do |model_class, fields|
|
22
|
+
model_class
|
23
|
+
end
|
11
24
|
|
12
|
-
|
13
|
-
|
25
|
+
include_via do |model_class, includes|
|
26
|
+
model_class
|
27
|
+
end
|
28
|
+
|
29
|
+
create_via do |model|
|
30
|
+
model.assign_attributes(id: model.id || SecureRandom.uuid)
|
31
|
+
model.assign_attributes(updated_at: Time.now)
|
32
|
+
model.class.const_get("STORE")[model.id] = model.class.const_get("ATTRIBUTES").inject({}) do |hash, key|
|
33
|
+
hash.merge({ key => model.public_send(key) })
|
14
34
|
end
|
35
|
+
end
|
15
36
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
hash.merge({ key => model.public_send(key) })
|
21
|
-
end
|
37
|
+
update_via do |model|
|
38
|
+
model.assign_attributes(updated_at: Time.now)
|
39
|
+
model.class.const_get("STORE")[model.id] = model.class.const_get("ATTRIBUTES").inject({}) do |hash, key|
|
40
|
+
hash.merge({ key => model.public_send(key) })
|
22
41
|
end
|
23
42
|
end
|
24
43
|
end
|
@@ -23,11 +23,15 @@ RSpec.describe JSONAPI::Realizer::Adapter do
|
|
23
23
|
|
24
24
|
end
|
25
25
|
|
26
|
-
context "when the
|
26
|
+
context "when the assign_attributes_via interface isn't defined" do
|
27
27
|
|
28
28
|
end
|
29
29
|
|
30
|
-
context "when the
|
30
|
+
context "when the create_via interface isn't defined" do
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when the update_via interface isn't defined" do
|
31
35
|
|
32
36
|
end
|
33
37
|
end
|
@@ -1,19 +1,17 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
module Realizer
|
3
3
|
class Resource
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
4
|
attr_reader :model
|
7
5
|
|
8
6
|
def initialize(model)
|
9
7
|
@model = model
|
10
8
|
end
|
11
9
|
|
12
|
-
def
|
13
|
-
|
10
|
+
private def attribute(name)
|
11
|
+
attributes.public_send(name.to_sym)
|
14
12
|
end
|
15
13
|
|
16
|
-
def
|
14
|
+
private def relationship(name)
|
17
15
|
relationships.public_send(name.to_sym)
|
18
16
|
end
|
19
17
|
|
@@ -25,15 +23,6 @@ module JSONAPI
|
|
25
23
|
configuration.relationships
|
26
24
|
end
|
27
25
|
|
28
|
-
private def as_relationship(value)
|
29
|
-
data = value.fetch("data")
|
30
|
-
mapping = JSONAPI::Realizer.mapping.fetch(data.fetch("type"))
|
31
|
-
mapping.resource_class.find_via_call(
|
32
|
-
mapping.model_class,
|
33
|
-
data.fetch("id")
|
34
|
-
)
|
35
|
-
end
|
36
|
-
|
37
26
|
private def model_class
|
38
27
|
configuration.model_class
|
39
28
|
end
|
@@ -42,60 +31,48 @@ module JSONAPI
|
|
42
31
|
self.class.configuration
|
43
32
|
end
|
44
33
|
|
45
|
-
def
|
46
|
-
attributes.
|
47
|
-
end
|
48
|
-
|
49
|
-
def valid_relationship?(name, value)
|
50
|
-
relationships.respond_to?(name.to_sym)
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.represents(type, class_name:)
|
54
|
-
@configuration = JSONAPI::Realizer.register(self, class_name.constantize, type.to_s)
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.adapter(interface, &block)
|
58
|
-
JSONAPI::Realizer::Adapter.adapt(self, interface, &block)
|
34
|
+
def self.attribute(name)
|
35
|
+
attributes.public_send(name.to_sym)
|
59
36
|
end
|
60
37
|
|
61
|
-
def self.
|
62
|
-
|
38
|
+
def self.relationship(name)
|
39
|
+
relationships.public_send(name.to_sym)
|
63
40
|
end
|
64
41
|
|
65
|
-
def self.
|
66
|
-
|
42
|
+
def self.valid_attribute?(name, value)
|
43
|
+
attributes.respond_to?(name.to_sym)
|
67
44
|
end
|
68
45
|
|
69
|
-
def self.
|
70
|
-
|
46
|
+
def self.valid_relationship?(name, value)
|
47
|
+
relationships.respond_to?(name.to_sym)
|
71
48
|
end
|
72
49
|
|
73
|
-
def self.
|
74
|
-
|
50
|
+
def self.valid_sparse_field?(name)
|
51
|
+
attribute(name).fetch(:selectable)
|
75
52
|
end
|
76
53
|
|
77
|
-
def self.
|
78
|
-
|
54
|
+
def self.valid_includes?(name)
|
55
|
+
relationship(name).fetch(:includable)
|
79
56
|
end
|
80
57
|
|
81
|
-
def self.
|
82
|
-
|
58
|
+
def self.has(name, selectable: true)
|
59
|
+
attributes.public_send("#{name}=", OpenStruct.new({name: name, selectable: selectable}))
|
83
60
|
end
|
84
61
|
|
85
|
-
def self.
|
86
|
-
relationships.public_send("#{name}=", OpenStruct.new({name: name, as: as}))
|
62
|
+
def self.has_related(name, as: name, includable: true)
|
63
|
+
relationships.public_send("#{name}=", OpenStruct.new({name: name, as: as, includable: includable}))
|
87
64
|
end
|
88
65
|
|
89
|
-
def self.
|
90
|
-
|
66
|
+
def self.has_one(name, as: name, includable: true)
|
67
|
+
has_related(name, as: name, includable: includable)
|
91
68
|
end
|
92
69
|
|
93
|
-
def self.
|
94
|
-
|
70
|
+
def self.has_many(name, as: name, includable: true)
|
71
|
+
has_related(name, as: name, includable: includable)
|
95
72
|
end
|
96
73
|
|
97
|
-
def self.
|
98
|
-
|
74
|
+
def self.adapter
|
75
|
+
configuration.adapter
|
99
76
|
end
|
100
77
|
|
101
78
|
def self.attributes
|
@@ -110,12 +87,17 @@ module JSONAPI
|
|
110
87
|
configuration.model_class
|
111
88
|
end
|
112
89
|
|
90
|
+
def self.register(type, class_name:, adapter:)
|
91
|
+
JSONAPI::Realizer.register(
|
92
|
+
resource_class: self,
|
93
|
+
model_class: class_name.constantize,
|
94
|
+
adapter: JSONAPI::Realizer::Adapter.new(adapter),
|
95
|
+
type: type.to_s
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
113
99
|
def self.configuration
|
114
|
-
|
115
|
-
@configuration
|
116
|
-
else
|
117
|
-
raise ArgumentError, "you need to have the resource configured"
|
118
|
-
end
|
100
|
+
JSONAPI::Realizer.resource_mapping.fetch(self)
|
119
101
|
end
|
120
102
|
end
|
121
103
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-realizer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kurtis Rainbolt-Greene
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-03-
|
11
|
+
date: 2018-03-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -122,6 +122,10 @@ files:
|
|
122
122
|
- lib/jsonapi/realizer/action.rb
|
123
123
|
- lib/jsonapi/realizer/action/create.rb
|
124
124
|
- lib/jsonapi/realizer/action/create_spec.rb
|
125
|
+
- lib/jsonapi/realizer/action/index.rb
|
126
|
+
- lib/jsonapi/realizer/action/index_spec.rb
|
127
|
+
- lib/jsonapi/realizer/action/show.rb
|
128
|
+
- lib/jsonapi/realizer/action/show_spec.rb
|
125
129
|
- lib/jsonapi/realizer/action/update.rb
|
126
130
|
- lib/jsonapi/realizer/action/update_spec.rb
|
127
131
|
- lib/jsonapi/realizer/adapter.rb
|