shamu 0.0.4 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7dbbdb9cc8883493b91766988eafe575fdc6d7b4
4
- data.tar.gz: c4c2e75d33b28dd7759d656d60c5b1dd444a3dc7
3
+ metadata.gz: 607eb630404b0977965af426db12d63821be992c
4
+ data.tar.gz: 70638f256144a1b3b20984b5bb3950aee54cc76e
5
5
  SHA512:
6
- metadata.gz: ff33cde446d56d6a4d0930f0cd74d00b9a893af315bf2dcdcfb70f0289f33d53cdcfa93c229df4672c54947d1ec409cd8fb195913607be34b8432d972fc49b8d
7
- data.tar.gz: e47d92497b0485d0cb356550d932aacce2f2a8a47177b2f3e51f7ff960ed2d990301f00a176751ca4b4ff9861edbcaa784de6da7bacad103b4f92a1be907de12
6
+ metadata.gz: 365c41405a527220f21112099997ddf023c1c7bfc8b93ddc1cdf7e6275302c103adf2bf79266a2a22661e576d095ed6899f49cda35a2293907ad28b7b18ed40c
7
+ data.tar.gz: 6d657880be0470a380ba0c1a31718bf4877866c940a07e5c6780cd7c550c31db8a7992730bd3340e24cff610c9c1ac24e4723580f1a005c1674aa06f547b128f
data/Gemfile CHANGED
@@ -6,6 +6,7 @@ gemspec
6
6
  group :test do
7
7
  gem "activerecord", "~> 4.2.5"
8
8
  gem "actionpack", "~> 4.2.5"
9
+ gem "responders", "~> 2.1.2"
9
10
  gem "kaminari", "~> 0.16.3", require: false
10
11
 
11
12
  gem "byebug"
@@ -70,6 +70,9 @@ module Shamu
70
70
  include Shamu::Attributes::Equality
71
71
  include Shamu::ToModelIdExtension::Models
72
72
 
73
+ extend ActiveModel::Naming
74
+ include ActiveModel::Conversion
75
+
73
76
  # @!attribute
74
77
  # @return [Object] id of the entity.
75
78
  #
@@ -4,69 +4,29 @@ module Shamu
4
4
  # Used by a {Serilaizer} to write fields and relationships
5
5
  class BaseBuilder
6
6
 
7
- # ============================================================================
8
- # @!group Attributes
9
- #
10
-
11
- # @!attribute
12
- # @return [Context] the JSON serialization context.
13
- attr_reader :context
14
-
15
- #
16
- # @!endgroup Attributes
17
-
18
-
19
7
  # @param [Context] context the current serialization context.
20
8
  def initialize( context )
21
9
  @context = context
22
10
  @output = {}
23
11
  end
24
12
 
25
- # Write a resource linkage info.
26
- #
27
- # @param [String] type of the resource.
28
- # @param [Object] id of the resource.
29
- # @return [void]
30
- def identifier( type, id = nil )
31
- output[:type] = type.to_s
32
- output[:id] = id.to_s
33
- end
34
-
35
- # Write a link to another resource.
36
- #
37
- # @param [String,Symbol] name of the link.
38
- # @param [String] url
39
- # @param [Hash] meta optional additional meta information.
40
- # @return [void]
41
- def link( name, url, meta: nil )
42
- links = ( output[:links] ||= {} )
43
-
44
- if meta # rubocop:disable Style/ConditionalAssignment
45
- links[ name.to_sym ] = { href: url, meta: meta }
46
- else
47
- links[ name.to_sym ] = url
48
- end
49
- end
50
-
51
- # Add a meta field.
52
- # @param [String,Symbol] name of the meta field.
53
- # @param [Object] vlaue that can be converted to a JSON primitive type.
54
- # @return [void]
55
- def meta( name, value )
56
- meta = ( output[:meta] ||= {} )
57
- meta[ name.to_sym ] = value
58
- end
13
+ include BuilderMethods::Link
14
+ include BuilderMethods::Meta
59
15
 
60
16
  # @return [Hash] the results output as JSON safe hash.
61
17
  def compile
62
- fail JsonApi::IncompleteResourceError unless output[:type]
63
18
  output
64
19
  end
65
20
 
66
21
  private
67
22
 
68
- attr_reader :output
23
+ # @!attribute
24
+ # @return [Hash] output hash.
25
+ attr_reader :output
69
26
 
27
+ # @!attribute
28
+ # @return [Context] the JSON serialization context.
29
+ attr_reader :context
70
30
  end
71
31
  end
72
32
  end
@@ -0,0 +1,34 @@
1
+ module Shamu
2
+ module JsonApi
3
+ module BuilderMethods
4
+ module Identifier
5
+ # Write a resource linkage info.
6
+ #
7
+ # @param [String] type of the resource.
8
+ # @param [Object] id of the resource.
9
+ # @return [self]
10
+ def identifier( type, id = nil )
11
+ output[:type] = @type = type.to_s
12
+ output[:id] = id.to_s
13
+
14
+ self
15
+ end
16
+
17
+ # (see BaseBuilder#compile)
18
+ def compile
19
+ require_identifier!
20
+ super
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :type
26
+
27
+ def require_identifier!
28
+ fail IncompleteResourceError unless type
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ module Shamu
2
+ module JsonApi
3
+ module BuilderMethods
4
+ module Link
5
+ # Write a link to another resource.
6
+ #
7
+ # @param [String,Symbol] name of the link.
8
+ # @param [String] url
9
+ # @param [Hash] meta optional additional meta information.
10
+ # @return [self]
11
+ def link( name, url, meta: nil )
12
+ links = ( output[:links] ||= {} )
13
+
14
+ if meta # rubocop:disable Style/ConditionalAssignment
15
+ links[ name.to_sym ] = { href: url, meta: meta }
16
+ else
17
+ links[ name.to_sym ] = url
18
+ end
19
+
20
+ self
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ module Shamu
2
+ module JsonApi
3
+ module BuilderMethods
4
+ module Meta
5
+ # Add a meta field.
6
+ # @param [String,Symbol] name of the meta field.
7
+ # @param [Object] vlaue that can be converted to a JSON primitive type.
8
+ # @return [self]
9
+ def meta( name, value )
10
+ meta = ( output[:meta] ||= {} )
11
+ meta[ name.to_sym ] = value
12
+
13
+ self
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module Shamu
2
+ module JsonApi
3
+ module BuilderMethods
4
+ require "shamu/json_api/builder_methods/link"
5
+ require "shamu/json_api/builder_methods/meta"
6
+ require "shamu/json_api/builder_methods/identifier"
7
+ end
8
+ end
9
+ end
@@ -30,9 +30,9 @@ module Shamu
30
30
  # obtained by calling {#find_presenter}.
31
31
  #
32
32
  # @param [Object] resource to be serialized.
33
- # @param [Presenter] presenter to use to serialize the object. If
34
- # not provided a default {Presenter} will be chosen.
35
- # @return [resource]
33
+ # @param [Class] presenter tpresenter {Presenter} class to use to
34
+ # serialize the `resource`. If not provided a default {Presenter} will
35
+ # be chosen.
36
36
  # @yield (builder)
37
37
  # @yieldparam [ResourceBuilder] builder to write embedded resource to.
38
38
  def include_resource( resource, presenter = nil, &block )
@@ -84,7 +84,7 @@ module Shamu
84
84
  # - Fails with a {NoPresenter} error if a presenter cannot be found.
85
85
  #
86
86
  # @param [Object] resource to present.
87
- # @return [Presenter]
87
+ # @return [Class] the {Presenter} class to use.
88
88
  # @raise [NoPresenter] if a presenter cannot be found.
89
89
  def find_presenter( resource )
90
90
  presenter = presenters[ resource.class ]
@@ -92,7 +92,7 @@ module Shamu
92
92
 
93
93
  fail NoPresenter.new( resource, namespaces ) unless presenter
94
94
 
95
- presenter.is_a?( Class ) ? presenter.new : presenter
95
+ presenter
96
96
  end
97
97
 
98
98
 
@@ -10,7 +10,6 @@ module Shamu
10
10
  def translation_scope
11
11
  super.dup.insert( 1, :json_api )
12
12
  end
13
-
14
13
  end
15
14
 
16
15
  # Raised if an {ResourceBuilder#identifier} was not built.
@@ -20,12 +19,6 @@ module Shamu
20
19
  end
21
20
  end
22
21
 
23
- class IdentifierRequiredError < Error
24
- def initialize( message = :identifier_required )
25
- super
26
- end
27
- end
28
-
29
22
  class NoPresenter < Error
30
23
  def initialize( resource, namespaces )
31
24
  super translate( :no_presenter, class: resource.class, namespaces: namespaces )
@@ -7,65 +7,76 @@ module Shamu
7
7
  class ErrorBuilder
8
8
 
9
9
  def initialize
10
- @output = { id: SecureRandom.uuid }
10
+ @output = {}
11
11
  end
12
12
 
13
+ include BuilderMethods::Link
14
+ include BuilderMethods::Meta
15
+
13
16
  # @param [String] id unique id for this occurrence of the error.
17
+ # @return [self]
14
18
  def id( id )
15
19
  output[:id] = id
16
- end
17
-
18
- # Summary of the error.
19
- # @param [Integer] http_status code.
20
- # @param [String,Symbol] code application specific code for the error.
21
- # @param [String] human friendly title for the error.
22
- def summary( http_status, code = nil, title = nil )
23
- code ||= ::Rack::Util::HTTP_STATUS_CODES[ code ].to_s.underscore
24
-
25
- output[:status] = http_status.to_s
26
- output[:code] = code.to_s
27
- output[:title] = title || code.to_s.titleize
20
+ self
28
21
  end
29
22
 
30
23
  # Summarize an exception as an error.
31
24
  # @param [Exception] exception
32
- # @param [Integer] http_status code. Default 400.
33
- def exception( exception, http_status = nil )
34
- http_status ||= 500
35
-
25
+ # @return [self]
26
+ def exception( exception )
36
27
  name = exception.class.name.demodulize.gsub( /Error$/, "" )
37
- summary http_status, name.underscore, name.titleize
28
+ code name.underscore
29
+ title name.titleize
38
30
  detail exception.message
31
+
32
+ self
33
+ end
34
+
35
+ # Set an HTTP status code related to the error.
36
+ # @param [Symbol,Integer] status code.
37
+ # @return [self]
38
+ def http_status( status )
39
+ status = ::Rack::Utils.status_code( status ) if status.is_a? Symbol
40
+ output[:status] = status.to_s
41
+ self
42
+ end
43
+
44
+ # Set an application specific error code.
45
+ # @return [self]
46
+ def code( code )
47
+ output[:code] = code.to_s
48
+ self
49
+ end
50
+
51
+ # Set a short human readable title of the error.
52
+ # @return [self]
53
+ def title( title )
54
+ output[:title] = title.to_s
55
+ self
39
56
  end
40
57
 
41
58
  # @return [String] message details about the error.
59
+ # @return [self]
42
60
  def detail( message )
43
61
  output[:detail] = message
62
+ self
44
63
  end
45
64
 
46
- # Write a link to error information.
47
- #
48
- # @param [String,Symbol] name of the link.
49
- # @param [String] url
50
- # @param [Hash] meta optional additional meta information.
51
- # @return [void]
52
- def link( name, url, meta: nil )
53
- links = ( output[:links] ||= {} )
54
-
55
- if meta # rubocop:disable Style/ConditionalAssignment
56
- links[ name.to_sym ] = { href: url, meta: meta }
57
- else
58
- links[ name.to_sym ] = url
59
- end
65
+ # JSON pointer to the associated document in the request that was the
66
+ # source of the pointer.
67
+ # @return [self]
68
+ def pointer( pointer )
69
+ output[:source] ||= {}
70
+ output[:source][:pointer] = pointer
71
+ self
60
72
  end
61
73
 
62
- # Add a meta field.
63
- # @param [String,Symbol] name of the meta field.
64
- # @param [Object] value that can be converted to a JSON primitive type.
65
- # @return [void]
66
- def meta( name, value )
67
- meta = ( output[:meta] ||= {} )
68
- meta[ name.to_sym ] = value
74
+ # The name of the parameter that caused the error.
75
+ # @return [self]
76
+ def parameter( name )
77
+ output[:source] ||= {}
78
+ output[:source][:parameter] = name
79
+ self
69
80
  end
70
81
 
71
82
  # @return [Hash] the results output as JSON safe hash.
@@ -1,18 +1,50 @@
1
1
  module Shamu
2
2
  module JsonApi
3
3
 
4
- # Present an object to a JSON API {ResourceBuilder builder}.
4
+ # Presenters are responsible for projecting an {Entities::Entity} or PORO
5
+ # to a well-formatted JSON API {ResourceBuilder builder} response.
6
+ #
7
+ # {Presenter} delegates all of the {ResourceBuilder} methods for convenient
8
+ # syntax.
9
+ #
10
+ # ```
11
+ # class UserPresenter < ApplicationPresenter
12
+ # def present
13
+ # identifier :user, resource.id
14
+ #
15
+ # attributes name: resource.name,
16
+ # email: resource.email
17
+ #
18
+ # relationship( :address ) do |rel|
19
+ # rel.identifier :address, resource.address_id
20
+ # rel.link :related, user_address_url( resource, resource.address_id )
21
+ # end
22
+ # end
23
+ # end
24
+ # ```
5
25
  class Presenter
6
26
 
27
+ # @param [Object] resource to presenter.
28
+ # @param [ResourceBuilder] builder used to build the JSON API response.
29
+ def initialize( resource, builder )
30
+ @resource = resource
31
+ @builder = builder
32
+ end
33
+
7
34
  # Serialize the `resource` to the `builder`.
8
35
  #
9
- # @param [Object] resource to present.
10
- # @param [ResourceBuilder] builder to write to.
11
36
  # @return [void]
12
- def present( resource, builder )
37
+ def present
13
38
  fail NotImplementedError
14
39
  end
15
40
 
41
+ private
42
+
43
+ delegate :relationship, :attribute, :attributes, :link, :identifier, :meta, to: :builder
44
+
45
+ attr_reader :resource
46
+ attr_reader :builder
47
+
16
48
  end
17
49
  end
18
50
  end
@@ -6,11 +6,25 @@ module Shamu
6
6
  # Build a relationship from one resource to another.
7
7
  class RelationshipBuilder < BaseBuilder
8
8
 
9
+
9
10
  # (see Context#include_resource)
10
- def include_resource( resource, serializer = nil, &block )
11
- context.include_resource resource, serializer, &block
11
+ def include_resource( resource, presenter = nil, &block )
12
+ context.include_resource resource, presenter, &block
12
13
  end
13
14
 
15
+ include BuilderMethods::Identifier
16
+
17
+ # Write a resource linkage info.
18
+ #
19
+ # @param [String] type of the resource.
20
+ # @param [Object] id of the resource.
21
+ # @return [void]
22
+ def identifier( type, id = nil )
23
+ output[:data] ||= {}
24
+ output[:data][:type] = @type = type.to_s
25
+ output[:data][:id] = id.to_s
26
+ self
27
+ end
14
28
  end
15
29
  end
16
30
  end
@@ -6,6 +6,8 @@ module Shamu
6
6
  # Used by a {Serilaizer} to write fields and relationships
7
7
  class ResourceBuilder < BaseBuilder
8
8
 
9
+ include BuilderMethods::Identifier
10
+
9
11
  # @overload attribute( attributes )
10
12
  # @param [Hash] attributes to write.
11
13
  # @overload attribute( name, value )
@@ -26,6 +28,7 @@ module Shamu
26
28
  end
27
29
  end
28
30
  end
31
+ alias_method :attributes, :attribute
29
32
 
30
33
  # Build a relationship reference.
31
34
  #
@@ -54,21 +57,8 @@ module Shamu
54
57
  relationships[ name.to_sym ] = builder.compile
55
58
  end
56
59
 
57
- # (see BaseBuilder#identifier)
58
- def identifier( * )
59
- super.tap do
60
- @type = output[:type]
61
- end
62
- end
63
-
64
60
  private
65
61
 
66
- attr_reader :type
67
-
68
- def require_identifier!
69
- fail IdentifierRequiredError unless @type
70
- end
71
-
72
62
  def add_attribute( name, value )
73
63
  return unless context.include_field?( type, name )
74
64
 
@@ -34,31 +34,50 @@ module Shamu
34
34
  self
35
35
  end
36
36
 
37
- # @overload error( exception, http_status = nil )
37
+ # @overload error( exception )
38
38
  # @param (see ErrorBuilder#exception)
39
39
  # @overload error( &block )
40
40
  # @yield (builder)
41
41
  # @yieldparam [ErrorBuilder] builder used to describe the error.
42
42
  #
43
43
  # @return [self]
44
- def error( exception = nil, http_status = nil, &block )
44
+ def error( error = nil, &block )
45
45
  builder = ErrorBuilder.new
46
46
 
47
- if block_given?
48
- yield builder
49
- elsif exception.is_a?( Exception )
50
- builder.exception( exception, http_status )
51
- else
52
- http_status ||= 500
53
- builder.summary http_status, http_status.to_s, exception
47
+ if error.is_a?( Exception )
48
+ builder.exception( error )
49
+ elsif error
50
+ builder.title error
54
51
  end
55
52
 
53
+ yield builder if block_given?
54
+
56
55
  errors = ( output[:errors] ||= [] )
57
56
  errors << builder.compile
58
57
 
59
58
  self
60
59
  end
61
60
 
61
+ # Write ActiveModel validation errors to the response.
62
+ #
63
+ # @param [Hash<Symbol,String>] errors map of attributes to errors.
64
+ # @yield ( builder, attr, message )
65
+ # @yieldparam [ErrorBuilder] builder the builder for this error message.
66
+ # @yieldparam [String] attr the attribute with a validation error.
67
+ # @yieldparam [String] message the error message.
68
+ # @return [self]
69
+ def validation_errors( errors, &block )
70
+ errors.each do |attr, message|
71
+ error message do |builder|
72
+ path = "/data"
73
+ path << "/attributes/#{ attr }" unless attr == :base
74
+ builder.pointer path
75
+
76
+ yield builder, attr, message if block_given?
77
+ end
78
+ end
79
+ end
80
+
62
81
  # (see BaseBuilder#compile)
63
82
  def compile
64
83
  @compiled ||= begin
@@ -95,17 +114,15 @@ module Shamu
95
114
  to_json
96
115
  end
97
116
 
98
- # Responses don't have identifiers
99
- undef :identifier
100
-
101
117
  private
102
118
 
103
119
  def build_resource( resource, presenter, &block )
104
120
  presenter = context.find_presenter( resource ) if !presenter && !block_given?
121
+ builder = ResourceBuilder.new( context )
105
122
 
106
- builder = ResourceBuilder.new( context )
107
123
  if presenter
108
- presenter.present( resource, builder )
124
+ instance = presenter.new( resource, builder )
125
+ instance.present
109
126
  else
110
127
  yield builder
111
128
  end
@@ -1,7 +1,11 @@
1
1
  module Shamu
2
2
  # {include:file:lib/shamu/json_api/README.md}
3
3
  module JsonApi
4
+
5
+ MIME_TYPE = "application/vnd.api+json".freeze
6
+
4
7
  require "shamu/json_api/context"
8
+ require "shamu/json_api/builder_methods"
5
9
  require "shamu/json_api/relationship_builder"
6
10
  require "shamu/json_api/resource_builder"
7
11
  require "shamu/json_api/response"
@@ -29,5 +29,4 @@ en:
29
29
  json_api:
30
30
  errors:
31
31
  incomplete_resource: "`identifier` was not called to define the type and id of the resource."
32
- identifier_resource: "`identifier` must be called before defining any fields."
33
32
  no_presenter: No presenter available for %{class} objects. Looked in %{namespaces}.