jsonapi-serializable 0.1.1.beta4 → 0.1.1

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.
@@ -13,9 +13,9 @@ module JSONAPI
13
13
  @options = options.dup
14
14
  @klass = @options.delete(:class)
15
15
  @namespace = @options.delete(:namespace)
16
- @inferer = @options.delete(:inferer)
16
+ @inferrer = @options.delete(:inferrer)
17
17
  @exposures = @options.delete(:expose) || {}
18
- @exposures[:_resource_inferer] = namespace_inferer || @inferer
18
+ @exposures[:_resource_inferrer] = namespace_inferrer || @inferrer
19
19
  end
20
20
 
21
21
  def render
@@ -24,18 +24,21 @@ module JSONAPI
24
24
 
25
25
  private
26
26
 
27
+ # @api private
27
28
  def jsonapi_params
28
29
  @options
29
30
  end
30
31
 
32
+ # @api private
31
33
  def jsonapi_resources
32
- toplevel_inferer = @klass || @inferer
34
+ toplevel_inferrer = @klass || @inferrer
33
35
  JSONAPI::Serializable::ResourceBuilder.build(@objects,
34
36
  @exposures,
35
- toplevel_inferer)
37
+ toplevel_inferrer)
36
38
  end
37
39
 
38
- def namespace_inferer
40
+ # @api private
41
+ def namespace_inferrer
39
42
  return nil unless @namespace
40
43
  proc do |klass|
41
44
  names = klass.name.split('::')
@@ -61,10 +64,12 @@ module JSONAPI
61
64
 
62
65
  private
63
66
 
67
+ # @api private
64
68
  def jsonapi_params
65
69
  @options
66
70
  end
67
71
 
72
+ # @api private
68
73
  def jsonapi_errors
69
74
  @errors
70
75
  end
@@ -0,0 +1,73 @@
1
+ module JSONAPI
2
+ module Serializable
3
+ class Resource
4
+ # Mixin to handle resource attributes.
5
+ module Attributes
6
+ def self.prepended(klass)
7
+ super
8
+ klass.class_eval do
9
+ extend DSL
10
+ class << self
11
+ attr_accessor :attribute_blocks
12
+ end
13
+ self.attribute_blocks = {}
14
+ end
15
+ end
16
+
17
+ def initialize(*)
18
+ super
19
+ @_attributes = {}
20
+ end
21
+
22
+ # @see JSONAPI::Serializable::Resource#as_jsonapi
23
+ def as_jsonapi(fields: nil, include: [])
24
+ super.tap do |hash|
25
+ attrs =
26
+ requested_attributes(fields).each_with_object({}) do |(k, v), h|
27
+ h[k] = instance_eval(&v)
28
+ end
29
+ hash[:attributes] = attrs if attrs.any?
30
+ end
31
+ end
32
+
33
+ # @api private
34
+ def requested_attributes(fields)
35
+ self.class.attribute_blocks
36
+ .select { |k, _| fields.nil? || fields.include?(k) }
37
+ end
38
+
39
+ # DSL methods for declaring attributes.
40
+ module DSL
41
+ def inherited(klass)
42
+ super
43
+ klass.attribute_blocks = attribute_blocks.dup
44
+ end
45
+
46
+ # Declare an attribute for this resource.
47
+ #
48
+ # @param [Symbol] name The key of the attribute.
49
+ # @yieldreturn [Hash, String, nil] The block to compute the value.
50
+ #
51
+ # @example
52
+ # attribute(:name) { @object.name }
53
+ def attribute(name, _options = {}, &block)
54
+ block ||= proc { @object.public_send(name) }
55
+ attribute_blocks[name.to_sym] = block
56
+ end
57
+
58
+ # Declare a list of attributes for this resource.
59
+ #
60
+ # @param [Array] *args The attributes keys.
61
+ #
62
+ # @example
63
+ # attributes :title, :body, :date
64
+ def attributes(*args)
65
+ args.each do |attr|
66
+ attribute(attr)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,82 @@
1
+ module JSONAPI
2
+ module Serializable
3
+ class Resource
4
+ # Extension for handling conditional fields in serializable resources.
5
+ #
6
+ # @usage
7
+ # class SerializableUser < JSONAPI::Serializable::Resource
8
+ # prepend JSONAPI::Serializable::Resource::ConditionalFields
9
+ #
10
+ # attribute :email, if: -> { @current_user.admin? }
11
+ # has_many :friends, unless: -> { @object.private_profile? }
12
+ # end
13
+ #
14
+ module ConditionalFields
15
+ def self.prepended(klass)
16
+ klass.class_eval do
17
+ extend ClassMethods
18
+ class << self
19
+ attr_accessor :condition_blocks
20
+ end
21
+ self.condition_blocks ||= {}
22
+ end
23
+ end
24
+
25
+ # DSL extensions for conditional fields.
26
+ module ClassMethods
27
+ def inherited(klass)
28
+ super
29
+ klass.condition_blocks = condition_blocks.dup
30
+ end
31
+
32
+ # Handle the `if` and `unless` options for attributes.
33
+ #
34
+ # @example
35
+ # attribute :email, if: -> { @current_user.admin? }
36
+ #
37
+ def attribute(name, options = {}, &block)
38
+ super
39
+ _register_condition(name, options)
40
+ end
41
+
42
+ # Handle the `if` and `unless` options for relationships (has_one,
43
+ # belongs_to, has_many).
44
+ #
45
+ # @example
46
+ # has_many :friends, unless: -> { @object.private_profile? }
47
+ #
48
+ def relationship(name, options = {}, &block)
49
+ super
50
+ _register_condition(name, options)
51
+ end
52
+
53
+ # @api private
54
+ def _register_condition(name, options)
55
+ condition_blocks[name.to_sym] =
56
+ if options.key?(:if)
57
+ options[:if]
58
+ elsif options.key?(:unless)
59
+ proc { !instance_exec(&options[:unless]) }
60
+ end
61
+ end
62
+ end
63
+
64
+ # @api private
65
+ def requested_attributes(fields)
66
+ super.select { |k, _| _conditionally_included?(k) }
67
+ end
68
+
69
+ # @api private
70
+ def requested_relationships(fields)
71
+ super.select { |k, _| _conditionally_included?(k) }
72
+ end
73
+
74
+ # @api private
75
+ def _conditionally_included?(field)
76
+ condition = self.class.condition_blocks[field]
77
+ condition.nil? || instance_exec(&condition)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,48 @@
1
+ module JSONAPI
2
+ module Serializable
3
+ class Resource
4
+ # Mixin to handle resource id.
5
+ module Id
6
+ def self.prepended(klass)
7
+ super
8
+ klass.class_eval do
9
+ extend DSL
10
+ class << self
11
+ attr_accessor :id_block
12
+ end
13
+ end
14
+ end
15
+
16
+ def initialize(*)
17
+ super
18
+ @_id = instance_eval(&self.class.id_block).to_s
19
+ end
20
+
21
+ # @see JSONAPI::Serializable::Resource#as_jsonapi
22
+ def as_jsonapi(*)
23
+ super.tap do |hash|
24
+ hash[:id] = @_id
25
+ end
26
+ end
27
+
28
+ # DSL methods for declaring the resource id.
29
+ module DSL
30
+ def inherited(klass)
31
+ super
32
+ klass.id_block = id_block
33
+ end
34
+
35
+ # Declare the JSON API id of this resource.
36
+ #
37
+ # @yieldreturn [String] The id of the resource.
38
+ #
39
+ # @example
40
+ # id { @user.id.to_s }
41
+ def id(&block)
42
+ self.id_block = block
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,51 @@
1
+ module JSONAPI
2
+ module Serializable
3
+ class Resource
4
+ # Extension for handling automatic key transformations of
5
+ # attributes/relationships.
6
+ #
7
+ # @usage
8
+ # class SerializableUser < JSONAPI::Serializable::Resource
9
+ # prepend JSONAPI::Serializable::Resource::KeyTransform
10
+ # self.key_transform = proc { |key| key.camelize }
11
+ #
12
+ # attribute :user_name
13
+ # has_many :close_friends
14
+ # end
15
+ # # => will modify the serialized keys to `UserName` and `CloseFriends`.
16
+ module KeyTransform
17
+ def self.prepended(klass)
18
+ klass.class_eval do
19
+ extend ClassMethods
20
+ class << self
21
+ attr_accessor :key_transform
22
+ end
23
+ end
24
+ end
25
+
26
+ # DSL extensions for automatic key transformations.
27
+ module ClassMethods
28
+ def inherited(klass)
29
+ super
30
+ klass.key_transform = key_transform
31
+ end
32
+
33
+ # Handles automatic key transformation for attributes.
34
+ def attribute(name, options = {}, &block)
35
+ block ||= proc { @object.public_send(name) }
36
+ super(key_transform.call(name), options, &block)
37
+ end
38
+
39
+ # Handles automatic key transformation for relationships.
40
+ def relationship(name, options = {}, &block)
41
+ rel_block = proc do
42
+ data(options[:class]) { @object.public_send(name) }
43
+ instance_eval(&block) unless block.nil?
44
+ end
45
+ super(key_transform.call(name), options, &rel_block)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,67 @@
1
+ require 'jsonapi/serializable/link'
2
+
3
+ module JSONAPI
4
+ module Serializable
5
+ class Resource
6
+ # Mixin for handling resource links.
7
+ module Links
8
+ def self.prepended(klass)
9
+ super
10
+ klass.class_eval do
11
+ extend DSL
12
+ class << self
13
+ attr_accessor :link_blocks
14
+ end
15
+ self.link_blocks = {}
16
+ end
17
+ end
18
+
19
+ def initialize(*)
20
+ super
21
+ @_links = self.class.link_blocks.each_with_object({}) do |(k, v), h|
22
+ h[k] = Link.as_jsonapi(@_exposures, &v)
23
+ end
24
+ end
25
+
26
+ # @see JSONAPI::Serializable::Resource#as_jsonapi
27
+ def as_jsonapi(*)
28
+ super.tap do |hash|
29
+ hash[:links] = @_links if @_links.any?
30
+ end
31
+ end
32
+
33
+ # DSL methods for declaring resource links.
34
+ module DSL
35
+ def inherited(klass)
36
+ super
37
+ klass.link_blocks = link_blocks.dup
38
+ end
39
+
40
+ # Declare a link for this resource. The properties of the link are set
41
+ # by providing a block in which the DSL methods of
42
+ # +JSONAPI::Serializable::Link+ are called, or the value of the link
43
+ # is returned directly.
44
+ # @see JSONAPI::Serialiable::Link
45
+ #
46
+ # @param [Symbol] name The key of the link.
47
+ # @yieldreturn [Hash, String, nil] The block to compute the value, if
48
+ # any.
49
+ #
50
+ # @example
51
+ # link(:self) do
52
+ # "http://api.example.com/users/#{@user.id}"
53
+ # end
54
+ #
55
+ # @example
56
+ # link(:self) do
57
+ # href "http://api.example.com/users/#{@user.id}"
58
+ # meta is_self: true
59
+ # end
60
+ def link(name, &block)
61
+ link_blocks[name] = block
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,62 @@
1
+ module JSONAPI
2
+ module Serializable
3
+ class Resource
4
+ # Mixin for handling resource meta.
5
+ module Meta
6
+ def self.prepended(klass)
7
+ super
8
+ klass.class_eval do
9
+ extend DSL
10
+ class << self
11
+ attr_accessor :meta_val, :meta_block
12
+ end
13
+ end
14
+ end
15
+
16
+ def initialize(*)
17
+ super
18
+ @_meta = if self.class.meta_block
19
+ instance_eval(&self.class.meta_block)
20
+ else
21
+ self.class.meta_val
22
+ end
23
+ end
24
+
25
+ # @see JSONAPI::Serializable::Resource#as_jsonapi
26
+ def as_jsonapi(*)
27
+ super.tap do |hash|
28
+ hash[:meta] = @_meta unless @_meta.nil?
29
+ end
30
+ end
31
+
32
+ # DSL methods for declaring resource meta.
33
+ module DSL
34
+ def inherited(klass)
35
+ super
36
+ klass.meta_val = meta_val
37
+ klass.meta_block = meta_block
38
+ end
39
+
40
+ # @overload meta(value)
41
+ # Declare the meta information for this resource.
42
+ # @param [Hash] value The meta information hash.
43
+ #
44
+ # @example
45
+ # meta key: value
46
+ #
47
+ # @overload meta(&block)
48
+ # Declare the meta information for this resource.
49
+ # @yieldreturn [String] The meta information hash.
50
+ # @example
51
+ # meta do
52
+ # { key: value }
53
+ # end
54
+ def meta(value = nil, &block)
55
+ self.meta_val = value
56
+ self.meta_block = block
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,95 @@
1
+ require 'jsonapi/serializable/relationship'
2
+
3
+ module JSONAPI
4
+ module Serializable
5
+ class Resource
6
+ # Mixin to handle resource relationships.
7
+ module Relationships
8
+ def self.prepended(klass)
9
+ super
10
+ klass.class_eval do
11
+ extend DSL
12
+ class << self
13
+ attr_accessor :relationship_blocks
14
+ end
15
+ self.relationship_blocks = {}
16
+ end
17
+ end
18
+
19
+ def initialize(*)
20
+ super
21
+ @_relationships = self.class.relationship_blocks
22
+ .each_with_object({}) do |(k, v), h|
23
+ h[k] = Relationship.new(@_exposures, &v)
24
+ end
25
+ end
26
+
27
+ # @see JSONAPI::Serializable::Resource#as_jsonapi
28
+ def as_jsonapi(fields: nil, include: [])
29
+ super.tap do |hash|
30
+ rels = requested_relationships(fields)
31
+ .each_with_object({}) do |(k, v), h|
32
+ h[k] = v.as_jsonapi(include.include?(k))
33
+ end
34
+ hash[:relationships] = rels if rels.any?
35
+ end
36
+ end
37
+
38
+ # @api private
39
+ def requested_relationships(fields)
40
+ @_relationships
41
+ .select { |k, _| fields.nil? || fields.include?(k) }
42
+ end
43
+
44
+ # DSL methods for declaring relationships.
45
+ module DSL
46
+ def inherited(klass)
47
+ super
48
+ klass.relationship_blocks = relationship_blocks.dup
49
+ end
50
+
51
+ # Declare a relationship for this resource. The properties of the
52
+ # relationship are set by providing a block in which the DSL methods
53
+ # of +JSONAPI::Serializable::Relationship+ are called.
54
+ # @see JSONAPI::Serializable::Relationship
55
+ #
56
+ # @param [Symbol] name The key of the relationship.
57
+ #
58
+ # @example
59
+ # relationship :posts do
60
+ # resources { @user.posts.map { |p| PostResource.new(post: p) } }
61
+ # end
62
+ #
63
+ # @example
64
+ # relationship :author do
65
+ # resources do
66
+ # @post.author && UserResource.new(user: @post.author)
67
+ # end
68
+ # data do
69
+ # { type: 'users', id: @post.author_id }
70
+ # end
71
+ # link(:self) do
72
+ # "http://api.example.com/posts/#{@post.id}/relationships/author"
73
+ # end
74
+ # link(:related) do
75
+ # "http://api.example.com/posts/#{@post.id}/author"
76
+ # end
77
+ # meta do
78
+ # { author_online: @post.author.online? }
79
+ # end
80
+ # end
81
+ def relationship(name, options = {}, &block)
82
+ rel_block = proc do
83
+ data(options[:class]) { @object.public_send(name) }
84
+ instance_eval(&block) unless block.nil?
85
+ end
86
+ relationship_blocks[name.to_sym] = rel_block
87
+ end
88
+ alias has_many relationship
89
+ alias has_one relationship
90
+ alias belongs_to relationship
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,47 @@
1
+ module JSONAPI
2
+ module Serializable
3
+ class Resource
4
+ # Mixin to handle resource type.
5
+ module Type
6
+ def self.prepended(klass)
7
+ super
8
+ klass.class_eval do
9
+ extend DSL
10
+ class << self
11
+ attr_accessor :type_val
12
+ end
13
+ end
14
+ end
15
+
16
+ def initialize(*)
17
+ super
18
+ @_type = self.class.type_val || :unknown
19
+ end
20
+
21
+ # @see JSONAPI::Serializable::Resource#as_jsonapi
22
+ def as_jsonapi(*)
23
+ super.tap do |hash|
24
+ hash[:type] = @_type
25
+ end
26
+ end
27
+
28
+ # DSL methods for declaring the resource type.
29
+ module DSL
30
+ def inherited(klass)
31
+ super
32
+ klass.type_val = type_val
33
+ end
34
+
35
+ # Declare the JSON API type of this resource.
36
+ # @param [String] value The value of the type.
37
+ #
38
+ # @example
39
+ # type 'users'
40
+ def type(value)
41
+ self.type_val = value.to_sym
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,12 +1,45 @@
1
- require 'jsonapi/serializable/abstract_resource'
2
- require 'jsonapi/serializable/resource_dsl'
1
+ require 'jsonapi/serializable/resource/id'
2
+ require 'jsonapi/serializable/resource/type'
3
+ require 'jsonapi/serializable/resource/meta'
4
+ require 'jsonapi/serializable/resource/links'
5
+ require 'jsonapi/serializable/resource/attributes'
6
+ require 'jsonapi/serializable/resource/relationships'
3
7
 
4
8
  module JSONAPI
5
9
  module Serializable
6
- class Resource < AbstractResource
7
- include ResourceDSL
10
+ class Resource
11
+ prepend Id
12
+ prepend Type
13
+ prepend Meta
14
+ prepend Links
15
+ prepend Attributes
16
+ prepend Relationships
8
17
 
18
+ # Default the value of id.
9
19
  id { @object.public_send(:id).to_s }
20
+
21
+ def initialize(exposures = {})
22
+ exposures.each { |k, v| instance_variable_set("@#{k}", v) }
23
+ @_exposures = exposures
24
+ end
25
+
26
+ def as_jsonapi(*)
27
+ {}
28
+ end
29
+
30
+ def jsonapi_type
31
+ @_type
32
+ end
33
+
34
+ def jsonapi_id
35
+ @_id
36
+ end
37
+
38
+ def jsonapi_related(include)
39
+ @_relationships
40
+ .select { |k, _| include.include?(k) }
41
+ .each_with_object({}) { |(k, v), h| h[k] = v.related_resources }
42
+ end
10
43
  end
11
44
  end
12
45
  end
@@ -37,10 +37,12 @@ module JSONAPI
37
37
 
38
38
  private
39
39
 
40
+ # @api private
40
41
  def serializable_params
41
42
  @expose.merge(object: @object)
42
43
  end
43
44
 
45
+ # @api private
44
46
  # rubocop:disable Metrics/MethodLength
45
47
  def serializable_class
46
48
  klass =
@@ -58,6 +60,7 @@ module JSONAPI
58
60
  end
59
61
  # rubocop:enable Metrics/MethodLength
60
62
 
63
+ # @api private
61
64
  def reify_class(klass)
62
65
  if klass.is_a?(Class)
63
66
  klass