jsonapi-materializer 1.0.0.rc.pre.1 → 1.0.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.
@@ -1,102 +1,103 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require("spec_helper")
2
4
 
3
5
  RSpec.describe(JSONAPI::Materializer::Collection) do
4
- let(:described_class) {ArticleMaterializer::Collection}
5
- let(:policy) {Class.new { def read_attribute?(name); true end}}
6
- let(:collection) {described_class.new(:object => object, :includes => [["comments"], ["author"]], :context => {:policy => policy.new})}
6
+ let(:described_class) { ArticleMaterializer::Collection }
7
+ let(:collection) { described_class.new(object:, includes: [["comments"], ["author"]]) }
7
8
 
8
9
  describe("#as_json") do
9
- subject {collection.as_json.deep_stringify_keys}
10
+ subject { collection.as_json.deep_stringify_keys }
10
11
 
11
12
  before do
12
- Account.create!(:id => 9, :name => "Dan Gebhardt", :twitter => "dgeb")
13
- Account.create!(:id => 2, :name => "DHH", :twitter => "DHH")
14
- Article.create!(:id => 1, :title => "JSON API paints my bikeshed!", :account => Account.find(9))
15
- Article.create!(:id => 2, :title => "Rails is Omakase", :account => Account.find(9))
16
- Article.create!(:id => 3, :title => "What is JSON:API?", :account => Account.find(9))
17
- Comment.create!(:id => 5, :body => "First!", :article => Article.find(1), :account => Account.find(2))
18
- Comment.create!(:id => 12, :body => "I like XML better", :article => Article.find(1), :account => Account.find(9))
13
+ Account.create!(id: 9, name: "Dan Gebhardt", twitter: "dgeb")
14
+ Account.create!(id: 2, name: "DHH", twitter: "DHH")
15
+ Article.create!(id: 1, title: "JSON API paints my bikeshed!", account: Account.find(9))
16
+ Article.create!(id: 2, title: "Rails is Omakase", account: Account.find(9))
17
+ Article.create!(id: 3, title: "What is JSON:API?", account: Account.find(9))
18
+ Comment.create!(id: 5, body: "First!", article: Article.find(1), account: Account.find(2))
19
+ Comment.create!(id: 12, body: "I like XML better", article: Article.find(1), account: Account.find(9))
19
20
  end
20
21
 
21
22
  context("when the list has items") do
22
- let(:object) {Kaminari.paginate_array(Article.all).page(1).per(1)}
23
+ let(:object) { Kaminari.paginate_array(Article.all).page(1).per(1) }
23
24
 
24
25
  it("has a data key at root with the resources") do
25
26
  expect(subject.fetch("data")).to(eq([{
26
- "id" => "1",
27
- "type" => "articles",
28
- "attributes" => {
29
- "title" => "JSON API paints my bikeshed!"
30
- },
31
- "relationships" => {
32
- "author" => {
33
- "data" => {"id" => "9", "type" => "people"},
34
- "links" => {
35
- "self" => "http://example.com/articles/1/relationships/author",
36
- "related" => "http://example.com/articles/1/author"
37
- }
38
- },
39
- "comments" => {
40
- "data" => [
41
- {"id" => "5", "type" => "comments"},
42
- {"id" => "12", "type" => "comments"}
43
- ],
44
- "links" => {
45
- "self" => "http://example.com/articles/1/relationships/comments",
46
- "related" => "http://example.com/articles/1/comments"
47
- }
48
- }
49
- },
50
- "links" => {
51
- "self" => "http://example.com/articles/1"
52
- }
53
- }]))
27
+ "id" => "1",
28
+ "type" => "articles",
29
+ "attributes" => {
30
+ "title" => "JSON API paints my bikeshed!"
31
+ },
32
+ "relationships" => {
33
+ "author" => {
34
+ "data" => { "id" => "9", "type" => "people" },
35
+ "links" => {
36
+ "self" => "http://example.com/articles/1/relationships/author",
37
+ "related" => "http://example.com/articles/1/author"
38
+ }
39
+ },
40
+ "comments" => {
41
+ "data" => [
42
+ { "id" => "5", "type" => "comments" },
43
+ { "id" => "12", "type" => "comments" }
44
+ ],
45
+ "links" => {
46
+ "self" => "http://example.com/articles/1/relationships/comments",
47
+ "related" => "http://example.com/articles/1/comments"
48
+ }
49
+ }
50
+ },
51
+ "links" => {
52
+ "self" => "http://example.com/articles/1"
53
+ }
54
+ }]))
54
55
  end
55
56
 
56
57
  it("has a links key at root with pagination") do
57
58
  expect(subject.fetch("links")).to(eq(
58
- "self" => "http://example.com/articles",
59
- "next" => "http://example.com/articles?page[offset]=2&page[limit]=1",
60
- "last" => "http://example.com/articles?page[offset]=3&page[limit]=1"
61
- ))
59
+ "self" => "http://example.com/articles",
60
+ "next" => "http://example.com/articles?page[offset]=2&page[limit]=1",
61
+ "last" => "http://example.com/articles?page[offset]=3&page[limit]=1"
62
+ ))
62
63
  end
63
64
 
64
65
  it("has a included key at root with included models") do
65
66
  expect(subject.fetch("included")).to(include(
66
- {
67
- "id" => "5",
68
- "type" => "comments",
69
- "attributes"=>{"body"=>"First!"},
70
- "relationships" => {
71
- "author" => {"data" => {"id" => "2", "type" => "people"}, "links" => {"self" => "http://example.com/comments/5/relationships/author", "related" => "http://example.com/comments/5/author"}},
72
- "article" => {"data" => {"id" => "1", "type" => "articles"}, "links" => {"self" => "http://example.com/comments/5/relationships/article", "related" => "http://example.com/comments/5/article"}}
73
- },
74
- "links" => {"self" => "http://example.com/comments/5"}
75
- },
76
- {
77
- "id" => "12",
78
- "type" => "comments",
79
- "attributes"=>{"body"=>"I like XML better"},
80
- "relationships" => {
81
- "author" => {"data" => {"id" => "9", "type" => "people"}, "links" => {"self" => "http://example.com/comments/12/relationships/author", "related" => "http://example.com/comments/12/author"}},
82
- "article" => {"data" => {"id" => "1", "type" => "articles"}, "links" => {"self" => "http://example.com/comments/12/relationships/article", "related" => "http://example.com/comments/12/article"}}
83
- },
84
- "links" => {"self" => "http://example.com/comments/12"}
85
- },
86
- {
87
- "id" => "9",
88
- "type" => "people",
89
- "attributes"=>{"name"=>"Dan Gebhardt"},
90
- "relationships" => {
91
- "comments" => {"data" => [{"id" => "12", "type" => "comments"}], "links" => {"self" => "http://example.com/people/9/relationships/comments", "related" => "http://example.com/people/9/comments"}},
92
- "articles" => {
93
- "data" => [{"id" => "1", "type" => "articles"}, {"id" => "2", "type" => "articles"}, {"id" => "3", "type" => "articles"}],
94
- "links" => {"self" => "http://example.com/people/9/relationships/articles", "related" => "http://example.com/people/9/articles"}
95
- }
96
- },
97
- "links" => {"self" => "http://example.com/people/9"}
98
- }
99
- ))
67
+ {
68
+ "id" => "5",
69
+ "type" => "comments",
70
+ "attributes" => { "body" => "First!" },
71
+ "relationships" => {
72
+ "author" => { "data" => { "id" => "2", "type" => "people" }, "links" => { "self" => "http://example.com/comments/5/relationships/author", "related" => "http://example.com/comments/5/author" } },
73
+ "article" => { "data" => { "id" => "1", "type" => "articles" }, "links" => { "self" => "http://example.com/comments/5/relationships/article", "related" => "http://example.com/comments/5/article" } }
74
+ },
75
+ "links" => { "self" => "http://example.com/comments/5" }
76
+ },
77
+ {
78
+ "id" => "12",
79
+ "type" => "comments",
80
+ "attributes" => { "body" => "I like XML better" },
81
+ "relationships" => {
82
+ "author" => { "data" => { "id" => "9", "type" => "people" }, "links" => { "self" => "http://example.com/comments/12/relationships/author", "related" => "http://example.com/comments/12/author" } },
83
+ "article" => { "data" => { "id" => "1", "type" => "articles" }, "links" => { "self" => "http://example.com/comments/12/relationships/article", "related" => "http://example.com/comments/12/article" } }
84
+ },
85
+ "links" => { "self" => "http://example.com/comments/12" }
86
+ },
87
+ {
88
+ "id" => "9",
89
+ "type" => "people",
90
+ "attributes" => { "name" => "Dan Gebhardt" },
91
+ "relationships" => {
92
+ "comments" => { "data" => [{ "id" => "12", "type" => "comments" }], "links" => { "self" => "http://example.com/people/9/relationships/comments", "related" => "http://example.com/people/9/comments" } },
93
+ "articles" => {
94
+ "data" => [{ "id" => "1", "type" => "articles" }, { "id" => "2", "type" => "articles" }, { "id" => "3", "type" => "articles" }],
95
+ "links" => { "self" => "http://example.com/people/9/relationships/articles", "related" => "http://example.com/people/9/articles" }
96
+ }
97
+ },
98
+ "links" => { "self" => "http://example.com/people/9" }
99
+ }
100
+ ))
100
101
  end
101
102
  end
102
103
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  class Configuration
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  module Controller
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  class Error
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  class Error
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  class Error
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  class Error
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  class Error < StandardError
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  module Resource
@@ -7,12 +9,10 @@ module JSONAPI
7
9
  attr_accessor(:owner)
8
10
  attr_accessor(:name)
9
11
  attr_accessor(:from)
10
- attr_accessor(:visible)
11
12
 
12
13
  validates_presence_of(:owner)
13
14
  validates_presence_of(:name)
14
15
  validates_presence_of(:from)
15
- validate(:visible_callable)
16
16
 
17
17
  def initialize(**keyword_arguments)
18
18
  super(**keyword_arguments)
@@ -24,25 +24,9 @@ module JSONAPI
24
24
  subject.object.public_send(from)
25
25
  end
26
26
 
27
- def visible?(subject)
28
- return visible if [true, false].include?(visible)
29
- return subject.send(visible, self) if visible.is_a?(Symbol)
30
- return visible.call(self) if visible.respond_to?(:call)
31
-
32
- true
33
- end
34
-
35
27
  private def materializer_class
36
28
  class_name.constantize
37
29
  end
38
-
39
- private def visible_callable
40
- return if [true, false].include?(visible)
41
- return if visible.is_a?(Symbol)
42
- return if visible.respond_to?(:call)
43
-
44
- errors.add(:visible, "not callable or boolean")
45
- end
46
30
  end
47
31
  end
48
32
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  module Resource
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  module Resource
@@ -9,14 +11,12 @@ module JSONAPI
9
11
  attr_accessor(:type)
10
12
  attr_accessor(:from)
11
13
  attr_accessor(:class_name)
12
- attr_accessor(:visible)
13
14
 
14
15
  validates_presence_of(:owner)
15
16
  validates_presence_of(:name)
16
17
  validates_presence_of(:type)
17
18
  validates_presence_of(:from)
18
19
  validates_presence_of(:class_name)
19
- validate(:visible_callable)
20
20
 
21
21
  def initialize(**keyword_arguments)
22
22
  super(**keyword_arguments)
@@ -27,40 +27,28 @@ module JSONAPI
27
27
  def for(subject)
28
28
  @for ||= {}
29
29
  @for[checksum(subject)] ||= case type
30
- when :many then
31
- unlessing(fetch_relation(subject), -> {subject.includes.any? {|included| included.include?(from.to_s)} || fetch_relation(subject).loaded?}) do |subject|
32
- subject.select(:id)
33
- end.map do |related_object|
34
- materializer_class.new(
35
- **subject.raw,
36
- :object => related_object
37
- )
38
- end
39
- when :one then
40
- if fetch_relation(subject).present?
41
- materializer_class.new(
42
- **subject.raw,
43
- :object => fetch_relation(subject)
44
- )
45
- end
46
- end
47
- end
48
-
49
- def visible?(subject)
50
- return visible if [true, false].include?(visible)
51
- return subject.send(visible, self) if visible.is_a?(Symbol)
52
- return visible.call(self) if visible.respond_to?(:call)
53
-
54
- true
55
- end
56
-
57
- private def fetch_relation(subject)
58
- @fetch_relationship ||= {}
59
- @fetch_relationship[checksum(subject)] ||= subject.object.public_send(from)
30
+ when :many
31
+ unlessing(fetch_relation(subject), -> { subject.includes.any? { |included| included.include?(from.to_s) } || fetch_relation(subject).loaded? }) do |subject|
32
+ subject.select(:id)
33
+ end.map do |related_object|
34
+ materializer_class.new(
35
+ **subject.raw,
36
+ object: related_object
37
+ )
38
+ end
39
+ when :one
40
+ if fetch_relation(subject).present?
41
+ materializer_class.new(
42
+ **subject.raw,
43
+ object: fetch_relation(subject)
44
+ )
45
+ end
46
+ end
60
47
  end
48
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
61
49
 
62
50
  def using(parent)
63
- Resource::Relationship.new(:related => self, :parent => parent)
51
+ Resource::Relationship.new(related: self, parent:)
64
52
  end
65
53
 
66
54
  def many?
@@ -71,23 +59,20 @@ module JSONAPI
71
59
  type == :one
72
60
  end
73
61
 
74
- private def materializer_class
75
- class_name.constantize
62
+ private def fetch_relation(subject)
63
+ @fetch_relationship ||= {}
64
+ @fetch_relationship[checksum(subject)] ||= subject.object.public_send(from)
76
65
  end
77
66
 
78
- private def visible_callable
79
- return if [true, false].include?(visible)
80
- return if visible.is_a?(Symbol)
81
- return if visible.respond_to?(:call)
82
-
83
- errors.add(:visible, "not callable or boolean")
67
+ private def materializer_class
68
+ class_name.constantize
84
69
  end
85
70
 
86
71
  private def unlessing(object, proc)
87
- unless proc.call()
88
- yield(object)
89
- else
72
+ if proc.call
90
73
  object
74
+ else
75
+ yield(object)
91
76
  end
92
77
  end
93
78
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Materializer
3
5
  module Resource
@@ -18,12 +20,12 @@ module JSONAPI
18
20
 
19
21
  def as_json(*)
20
22
  {
21
- :data => data,
22
- :links => {
23
- :self => links_self,
24
- :related => links_related
23
+ data:,
24
+ links: {
25
+ self: links_self,
26
+ related: links_related
25
27
  },
26
- :meta => {}
28
+ meta: {}
27
29
  }.transform_values(&:presence).compact
28
30
  end
29
31
 
@@ -33,31 +35,32 @@ module JSONAPI
33
35
  ).pattern
34
36
  end
35
37
 
36
- private def links_related
38
+ def links_related
37
39
  Addressable::Template.new(
38
40
  "#{parent.links_self}/#{related.name}"
39
41
  ).pattern
40
42
  end
41
43
 
42
- private def data
44
+ def data
43
45
  return if related_parent_materializer.blank?
44
46
 
45
47
  @data ||= if related.many?
46
- related_parent_materializer.map do |child|
47
- {
48
- :id => child.attribute("id").for(child).to_s,
49
- :type => child.type.to_s
50
- }
51
- end
52
- else
53
- {
54
- :id => related_parent_materializer.attribute("id").for(related_parent_materializer).to_s,
55
- :type => related_parent_materializer.type.to_s
56
- }
57
- end
48
+ related_parent_materializer.map do |child|
49
+ {
50
+ id: child.attribute("id").for(child).to_s,
51
+ type: child.type.to_s
52
+ }
53
+ end
54
+ else
55
+ {
56
+ id: related_parent_materializer.attribute("id").for(related_parent_materializer).to_s,
57
+ type: related_parent_materializer.type.to_s
58
+ }
59
+ end
58
60
  end
61
+ # rubocop:enable Metrics/AbcSize
59
62
 
60
- private def related_parent_materializer
63
+ def related_parent_materializer
61
64
  related.for(parent)
62
65
  end
63
66
  end