shamu 0.0.4 → 0.0.5

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