jsonapi-serializable 0.1.1.beta4 → 0.1.1

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