pragma-decorator 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +36 -0
- data/README.md +13 -11
- data/lib/pragma/decorator/association/adapter/active_record.rb +142 -0
- data/lib/pragma/decorator/association/adapter/base.rb +88 -0
- data/lib/pragma/decorator/association/adapter/poro.rb +48 -0
- data/lib/pragma/decorator/association/adapter.rb +32 -0
- data/lib/pragma/decorator/association/{binding.rb → bond.rb} +32 -23
- data/lib/pragma/decorator/association/errors.rb +37 -0
- data/lib/pragma/decorator/association/reflection.rb +1 -1
- data/lib/pragma/decorator/association.rb +42 -5
- data/lib/pragma/decorator/base.rb +1 -3
- data/lib/pragma/decorator/collection.rb +38 -3
- data/lib/pragma/decorator/pagination/adapter/base.rb +80 -0
- data/lib/pragma/decorator/pagination/adapter/kaminari.rb +71 -0
- data/lib/pragma/decorator/pagination/adapter/will_paginate.rb +71 -0
- data/lib/pragma/decorator/pagination/adapter.rb +51 -0
- data/lib/pragma/decorator/pagination.rb +73 -24
- data/lib/pragma/decorator/timestamp.rb +11 -3
- data/lib/pragma/decorator/type.rb +43 -12
- data/lib/pragma/decorator/version.rb +1 -1
- data/lib/pragma/decorator.rb +9 -3
- metadata +12 -3
@@ -2,21 +2,58 @@
|
|
2
2
|
|
3
3
|
module Pragma
|
4
4
|
module Decorator
|
5
|
+
# Associations provide a way to define related records on your decorators.
|
6
|
+
#
|
7
|
+
# Once you define an association, it can be expanded by API clients through the +expand+ query
|
8
|
+
# parameter to load the full record.
|
9
|
+
#
|
10
|
+
# @example Defining and expanding an association
|
11
|
+
# class ArticleDecorator < Pragma::Decorator::Base
|
12
|
+
# belongs_to :author, decorator: API::V1::User::Decorator
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # This will return a hash whose `author` key is the ID of the article's author.
|
16
|
+
# ArticleDecorator.new(article).to_hash
|
17
|
+
#
|
18
|
+
# # This will return a hash whose `author` key is a hash representing the decorated
|
19
|
+
# # article's author.
|
20
|
+
# ArticleDecorator.new(article).to_hash(user_options: { expand: ['author'] })
|
5
21
|
module Association
|
6
22
|
def self.included(klass)
|
7
23
|
klass.extend ClassMethods
|
8
24
|
klass.include InstanceMethods
|
9
25
|
end
|
10
26
|
|
11
|
-
module ClassMethods #
|
27
|
+
module ClassMethods # :nodoc:
|
28
|
+
# Returns the associations defined on this decorator.
|
29
|
+
#
|
30
|
+
# @return [Hash{Symbol => Reflection}] the associations
|
12
31
|
def associations
|
13
32
|
@associations ||= {}
|
14
33
|
end
|
15
34
|
|
35
|
+
# Defines a +belongs_to+ association on this decorator.
|
36
|
+
#
|
37
|
+
# This will first create an association definition and then define a new property with the
|
38
|
+
# name of the association.
|
39
|
+
#
|
40
|
+
# This method supports all the usual options accepted by +#property+.
|
41
|
+
#
|
42
|
+
# @param property_name [Symbol] name of the association
|
43
|
+
# @param options [Hash] the association's options
|
16
44
|
def belongs_to(property_name, options = {})
|
17
45
|
define_association :belongs_to, property_name, options
|
18
46
|
end
|
19
47
|
|
48
|
+
# Defines a +has_one+ association on this decorator.
|
49
|
+
#
|
50
|
+
# This will first create an association definition and then define a new property with the
|
51
|
+
# name of the association.
|
52
|
+
#
|
53
|
+
# This method supports all the usual options accepted by +#property+.
|
54
|
+
#
|
55
|
+
# @param property_name [Symbol] name of the association
|
56
|
+
# @param options [Hash] the association's options
|
20
57
|
def has_one(property_name, options = {}) # rubocop:disable Naming/PredicateName
|
21
58
|
define_association :has_one, property_name, options
|
22
59
|
end
|
@@ -35,12 +72,12 @@ module Pragma
|
|
35
72
|
def create_association_property(_type, property_name, options)
|
36
73
|
property_options = options.dup.tap { |po| po.delete(:decorator) }.merge(
|
37
74
|
exec_context: :decorator,
|
38
|
-
as: property_name,
|
75
|
+
as: options[:as] || property_name,
|
39
76
|
getter: (lambda do |decorator:, user_options:, **_args|
|
40
|
-
|
77
|
+
Bond.new(
|
41
78
|
reflection: decorator.class.associations[property_name],
|
42
79
|
decorator: decorator
|
43
|
-
).render(user_options
|
80
|
+
).render(user_options)
|
44
81
|
end)
|
45
82
|
)
|
46
83
|
|
@@ -48,7 +85,7 @@ module Pragma
|
|
48
85
|
end
|
49
86
|
end
|
50
87
|
|
51
|
-
module InstanceMethods
|
88
|
+
module InstanceMethods # :nodoc:
|
52
89
|
def validate_expansion(expand)
|
53
90
|
check_parent_associations_are_expanded(expand)
|
54
91
|
check_expanded_associations_exist(expand)
|
@@ -7,9 +7,7 @@ module Pragma
|
|
7
7
|
module Decorator
|
8
8
|
# This is the base decorator that all your resource-specific decorators should extend from.
|
9
9
|
#
|
10
|
-
# It is already configured to render your resources
|
11
|
-
#
|
12
|
-
# @author Alessandro Desantis
|
10
|
+
# It is already configured to render your resources as JSON.
|
13
11
|
class Base < Roar::Decorator
|
14
12
|
feature Roar::JSON
|
15
13
|
|
@@ -2,6 +2,33 @@
|
|
2
2
|
|
3
3
|
module Pragma
|
4
4
|
module Decorator
|
5
|
+
# This module is used to represent collections of objects.
|
6
|
+
#
|
7
|
+
# It will wrap the collection in a +data+ property so that you can include meta-data about the
|
8
|
+
# collection at the root level.
|
9
|
+
#
|
10
|
+
# @example Using Collection to include a total count
|
11
|
+
# class ArticlesDecorator < Pragma::Decorator::Base
|
12
|
+
# include Pragma::Decorator::Collection
|
13
|
+
#
|
14
|
+
# decorate_with ArticleDecorator
|
15
|
+
#
|
16
|
+
# property :total_count, exec_context: :decorator
|
17
|
+
#
|
18
|
+
# def total_count
|
19
|
+
# represented.count
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # {
|
24
|
+
# # "data": [
|
25
|
+
# # { "...": "..." },
|
26
|
+
# # { "...": "..." },
|
27
|
+
# # { "...": "..." }
|
28
|
+
# # ],
|
29
|
+
# # "total_count": 150
|
30
|
+
# # }
|
31
|
+
# ArticlesDecorator.new(Article.all).to_hash
|
5
32
|
module Collection
|
6
33
|
def self.included(klass)
|
7
34
|
klass.include InstanceMethods
|
@@ -12,13 +39,21 @@ module Pragma
|
|
12
39
|
end
|
13
40
|
end
|
14
41
|
|
15
|
-
module InstanceMethods
|
42
|
+
module InstanceMethods # :nodoc:
|
43
|
+
# Overrides the type of the resource to be +list+, for compatibility with {Type}.
|
44
|
+
#
|
45
|
+
# @see Type
|
16
46
|
def type
|
17
|
-
'
|
47
|
+
'list'
|
18
48
|
end
|
19
49
|
end
|
20
50
|
|
21
|
-
module ClassMethods
|
51
|
+
module ClassMethods # :nodoc:
|
52
|
+
# Defines the decorator to use for each resource in the collection.
|
53
|
+
#
|
54
|
+
# @param decorator [Class] a decorator class
|
55
|
+
#
|
56
|
+
# @todo Accept a callable/block or document how to decorate polymorphic collections
|
22
57
|
def decorate_with(decorator)
|
23
58
|
collection :represented, as: :data, exec_context: :decorator, decorator: decorator
|
24
59
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Decorator
|
5
|
+
module Pagination
|
6
|
+
module Adapter
|
7
|
+
# This is the base pagination adapter.
|
8
|
+
#
|
9
|
+
# @abstract Subclass and override the abstract methods to implement an adapter
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class Base
|
13
|
+
# @!attribute [r] collection
|
14
|
+
# @return [Object] the collection this adapter is working with
|
15
|
+
attr_reader :collection
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Returns whether this adapter supports the given collection.
|
19
|
+
#
|
20
|
+
# @return [Boolean] whether the adapter supports the given collection
|
21
|
+
#
|
22
|
+
# @see Adapter.load_for
|
23
|
+
def supports?(_collection)
|
24
|
+
fail NotImplementedError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Initializes the adapter.
|
29
|
+
#
|
30
|
+
# @param collection [Object] the collection to work with
|
31
|
+
def initialize(collection)
|
32
|
+
@collection = collection
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the total number of entries in the collection.
|
36
|
+
#
|
37
|
+
# @return [Integer] the total number of entries in the collection
|
38
|
+
def total_entries
|
39
|
+
fail NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the number of entries per page in the collection.
|
43
|
+
#
|
44
|
+
# @return [Integer] the number of entries per page in the collection
|
45
|
+
def per_page
|
46
|
+
fail NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the total number of pages in the collection.
|
50
|
+
#
|
51
|
+
# @return [Integer] the total number of pages in the collection
|
52
|
+
def total_pages
|
53
|
+
fail NotImplementedError
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the number of the previous page, if any.
|
57
|
+
#
|
58
|
+
# @return [Integer|NilClass] the number of the previous page, if any
|
59
|
+
def previous_page
|
60
|
+
fail NotImplementedError
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the number of the current page.
|
64
|
+
#
|
65
|
+
# @return [Integer] the number of the current page
|
66
|
+
def current_page
|
67
|
+
fail NotImplementedError
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the number of the next page, if any.
|
71
|
+
#
|
72
|
+
# @return [Integer|NilClass] the number of the next page, if any
|
73
|
+
def next_page
|
74
|
+
fail NotImplementedError
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Decorator
|
5
|
+
module Pagination
|
6
|
+
module Adapter
|
7
|
+
# This adapter provides support for retireving pagination information from collections
|
8
|
+
# paginated with {https://github.com/kaminari/kaminari Kaminari}.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Kaminari < Base
|
12
|
+
class << self
|
13
|
+
# Returns whether this adapter supports the given collection.
|
14
|
+
#
|
15
|
+
# Esnures that the +Kaminari+ constant is defined and that the collection responds to
|
16
|
+
# +#prev_page+.
|
17
|
+
#
|
18
|
+
# @return [Boolean] whether the adapter supports the given collection
|
19
|
+
#
|
20
|
+
# @see Adapter.load_for
|
21
|
+
def supports?(collection)
|
22
|
+
Object.const_defined?('Kaminari') && collection.respond_to?(:prev_page)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the total number of entries in the collection.
|
27
|
+
#
|
28
|
+
# @return [Integer] the total number of entries in the collection
|
29
|
+
def total_entries
|
30
|
+
collection.total_count
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the number of entries per page in the collection.
|
34
|
+
#
|
35
|
+
# @return [Integer] the number of entries per page in the collection
|
36
|
+
def per_page
|
37
|
+
collection.limit_value
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the total number of pages in the collection.
|
41
|
+
#
|
42
|
+
# @return [Integer] the total number of pages in the collection
|
43
|
+
def total_pages
|
44
|
+
collection.total_pages
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the number of the previous page, if any.
|
48
|
+
#
|
49
|
+
# @return [Integer|NilClass] the number of the previous page, if any
|
50
|
+
def previous_page
|
51
|
+
collection.prev_page
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the number of the current page.
|
55
|
+
#
|
56
|
+
# @return [Integer] the number of the current page
|
57
|
+
def current_page
|
58
|
+
collection.current_page
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the number of the next page, if any.
|
62
|
+
#
|
63
|
+
# @return [Integer|NilClass] the number of the next page, if any
|
64
|
+
def next_page
|
65
|
+
collection.next_page
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Decorator
|
5
|
+
module Pagination
|
6
|
+
module Adapter
|
7
|
+
# This adapter provides support for retireving pagination information from collections
|
8
|
+
# paginated with {https://github.com/mislav/will_paginate will_paginate}.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class WillPaginate < Base
|
12
|
+
class << self
|
13
|
+
# Returns whether this adapter supports the given collection.
|
14
|
+
#
|
15
|
+
# Esnures that the +WillPaginate+ constant is defined and that the collection responds
|
16
|
+
# to +#previous_page+.
|
17
|
+
#
|
18
|
+
# @return [Boolean] whether the adapter supports the given collection
|
19
|
+
#
|
20
|
+
# @see Adapter.load_for
|
21
|
+
def supports?(collection)
|
22
|
+
Object.const_defined?('WillPaginate') && collection.respond_to?(:previous_page)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the total number of entries in the collection.
|
27
|
+
#
|
28
|
+
# @return [Integer] the total number of entries in the collection
|
29
|
+
def total_entries
|
30
|
+
collection.total_entries
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the number of entries per page in the collection.
|
34
|
+
#
|
35
|
+
# @return [Integer] the number of entries per page in the collection
|
36
|
+
def per_page
|
37
|
+
collection.per_page
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the total number of pages in the collection.
|
41
|
+
#
|
42
|
+
# @return [Integer] the total number of pages in the collection
|
43
|
+
def total_pages
|
44
|
+
collection.total_pages
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the number of the previous page, if any.
|
48
|
+
#
|
49
|
+
# @return [Integer|NilClass] the number of the previous page, if any
|
50
|
+
def previous_page
|
51
|
+
collection.previous_page
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the number of the current page.
|
55
|
+
#
|
56
|
+
# @return [Integer] the number of the current page
|
57
|
+
def current_page
|
58
|
+
collection.current_page
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the number of the next page, if any.
|
62
|
+
#
|
63
|
+
# @return [Integer|NilClass] the number of the next page, if any
|
64
|
+
def next_page
|
65
|
+
collection.next_page
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Decorator
|
5
|
+
module Pagination
|
6
|
+
# Adapters make pagination library-independent by providing support for multiple underlying
|
7
|
+
# libraries like Kaminari and will_paginate.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
module Adapter
|
11
|
+
# The list of supported adapters, in order of priority.
|
12
|
+
SUPPORTED_ADAPTERS = [Kaminari, WillPaginate].freeze
|
13
|
+
|
14
|
+
# Loads the adapter for the given collection.
|
15
|
+
#
|
16
|
+
# This will try {SUPPORTED_ADAPTERS} in order until it finds an adapter that supports the
|
17
|
+
# collection. When the adapter is found, it will return a new instance of it.
|
18
|
+
#
|
19
|
+
# @param collection [Object] the collection to load the adapter for
|
20
|
+
#
|
21
|
+
# @return [Adapter::Base]
|
22
|
+
#
|
23
|
+
# @see Adapter::Base.supports?
|
24
|
+
#
|
25
|
+
# @raise [AdapterError] if no adapter supports the collection
|
26
|
+
def self.load_for(collection)
|
27
|
+
adapter_klass = SUPPORTED_ADAPTERS.find do |klass|
|
28
|
+
klass.supports?(collection)
|
29
|
+
end
|
30
|
+
|
31
|
+
fail NoAdapterError unless adapter_klass
|
32
|
+
|
33
|
+
adapter_klass.new(collection)
|
34
|
+
end
|
35
|
+
|
36
|
+
# This error is raised when no adapter can be found for a collection.
|
37
|
+
class NoAdapterError < StandardError
|
38
|
+
# Initializes the adapter.
|
39
|
+
def initialize
|
40
|
+
message = <<~MESSAGE.tr("\n", ' ')
|
41
|
+
No adapter found for the collection. The available adapters are:
|
42
|
+
#{SUPPORTED_ADAPTERS.map { |a| a.to_s.split('::').last }.join(', ')}.
|
43
|
+
MESSAGE
|
44
|
+
|
45
|
+
super message
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -2,44 +2,93 @@
|
|
2
2
|
|
3
3
|
module Pragma
|
4
4
|
module Decorator
|
5
|
+
# Pagination provides support for including pagination metadata in your collection.
|
6
|
+
#
|
7
|
+
# It is particularly useful when used in conjunction with {Collection}.
|
8
|
+
#
|
9
|
+
# It supports both {https://github.com/kaminari/kaminari Kaminari} and
|
10
|
+
# {https://github.com/mislav/will_paginate will_paginate}.
|
11
|
+
#
|
12
|
+
# @example Including pagination metadata
|
13
|
+
# class ArticlesDecorator < Pragma::Decorator::Base
|
14
|
+
# include Pragma::Decorator::Collection
|
15
|
+
# include Pragma::Decorator::Pagination
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # {
|
19
|
+
# # "data": [
|
20
|
+
# # { "...": "..." },
|
21
|
+
# # { "...": "..." },
|
22
|
+
# # { "...": "..." }
|
23
|
+
# # ],
|
24
|
+
# # "total_entries": 150,
|
25
|
+
# # "per_page": 30,
|
26
|
+
# # "total_pages": 5,
|
27
|
+
# # "previous_page": 2,
|
28
|
+
# # "current_page": 3,
|
29
|
+
# # "next_page": 4
|
30
|
+
# # }
|
31
|
+
# ArticlesDecorator.new(Article.all).to_hash
|
5
32
|
module Pagination
|
6
|
-
module InstanceMethods
|
33
|
+
module InstanceMethods # :nodoc:
|
34
|
+
# Returns the current page of the collection.
|
35
|
+
#
|
36
|
+
# @return [Integer] current page number
|
37
|
+
#
|
38
|
+
# @see Adapter::Base#current_page
|
7
39
|
def current_page
|
8
|
-
|
40
|
+
adapter.current_page
|
9
41
|
end
|
10
42
|
|
43
|
+
# Returns the next page of the collection.
|
44
|
+
#
|
45
|
+
# @return [Integer|NilClass] next page number, if any
|
46
|
+
#
|
47
|
+
# @see Adapter::Base#next_page
|
11
48
|
def next_page
|
12
|
-
|
49
|
+
adapter.next_page
|
13
50
|
end
|
14
51
|
|
52
|
+
# Returns the number of items per page in the collection.
|
53
|
+
#
|
54
|
+
# @return [Integer] items per page
|
55
|
+
#
|
56
|
+
# @see Adapter::Base#per_page
|
15
57
|
def per_page
|
16
|
-
|
17
|
-
:per_page
|
18
|
-
else
|
19
|
-
:limit_value
|
20
|
-
end
|
21
|
-
|
22
|
-
represented.public_send(per_page_method)
|
58
|
+
adapter.per_page
|
23
59
|
end
|
24
60
|
|
61
|
+
# Returns the previous page of the collection.
|
62
|
+
#
|
63
|
+
# @return [Integer|NilClass] previous page number, if any
|
64
|
+
#
|
65
|
+
# @see Adapter::Base#previous_page
|
25
66
|
def previous_page
|
26
|
-
|
27
|
-
:previous_page
|
28
|
-
else
|
29
|
-
:prev_page
|
30
|
-
end
|
31
|
-
|
32
|
-
represented.public_send(previous_page_method)
|
67
|
+
adapter.previous_page
|
33
68
|
end
|
34
69
|
|
70
|
+
# Returns the total number of items in the collection.
|
71
|
+
#
|
72
|
+
# @return [Integer] number of items
|
73
|
+
#
|
74
|
+
# @see Adapter::Base#total_entries
|
35
75
|
def total_entries
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
76
|
+
adapter.total_entries
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the total number of pages in the collection.
|
80
|
+
#
|
81
|
+
# @return [Integer] number of pages
|
82
|
+
#
|
83
|
+
# @see Adapter::Base#total_pages
|
84
|
+
def total_pages
|
85
|
+
adapter.total_pages
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
41
89
|
|
42
|
-
|
90
|
+
def adapter
|
91
|
+
@adapter ||= Pagination::Adapter.load_for(represented)
|
43
92
|
end
|
44
93
|
end
|
45
94
|
|
@@ -49,7 +98,7 @@ module Pragma
|
|
49
98
|
klass.class_eval do
|
50
99
|
property :total_entries, exec_context: :decorator
|
51
100
|
property :per_page, exec_context: :decorator
|
52
|
-
property :total_pages
|
101
|
+
property :total_pages, exec_context: :decorator
|
53
102
|
property :previous_page, exec_context: :decorator
|
54
103
|
property :current_page, exec_context: :decorator
|
55
104
|
property :next_page, exec_context: :decorator
|
@@ -4,13 +4,21 @@ module Pragma
|
|
4
4
|
module Decorator
|
5
5
|
# Supports rendering timestamps as UNIX times.
|
6
6
|
#
|
7
|
-
# @
|
7
|
+
# @example Rendering a timestamp as UNIX time
|
8
|
+
# class ArticleDecorator < Pragma::Decorator::Base
|
9
|
+
# timestamp :created_at
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# # {
|
13
|
+
# # "created_at": 1515250106
|
14
|
+
# # }
|
15
|
+
# ArticleDecorator.new(article).to_hash
|
8
16
|
module Timestamp
|
9
17
|
def self.included(klass)
|
10
18
|
klass.extend ClassMethods
|
11
19
|
end
|
12
20
|
|
13
|
-
module ClassMethods #
|
21
|
+
module ClassMethods # :nodoc:
|
14
22
|
# Defines a timestamp property which will be rendered as UNIX time.
|
15
23
|
#
|
16
24
|
# @param name [Symbol] the name of the property
|
@@ -34,7 +42,7 @@ module Pragma
|
|
34
42
|
|
35
43
|
def create_timestamp_property(name, options = {})
|
36
44
|
property "_#{name}_timestamp", options.merge(
|
37
|
-
as: name,
|
45
|
+
as: options[:as] || name,
|
38
46
|
exec_context: :decorator
|
39
47
|
)
|
40
48
|
end
|
@@ -4,28 +4,59 @@ module Pragma
|
|
4
4
|
module Decorator
|
5
5
|
# Adds a +type+ property containing the machine-readable type of the represented object.
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# This is useful for the client to understand what kind of resource it's dealing with
|
8
|
+
# and trigger related logic.
|
9
|
+
#
|
10
|
+
# @example Including the resource's type
|
11
|
+
# class ArticleDecorator < Pragma::Decorator::Base
|
12
|
+
# include Pragma::Decorator::Type
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # {
|
16
|
+
# # "type": "article"
|
17
|
+
# # }
|
18
|
+
# ArticleDecorator.new(article).to_hash
|
8
19
|
module Type
|
9
|
-
|
10
|
-
|
11
|
-
|
20
|
+
class << self
|
21
|
+
def included(klass)
|
22
|
+
klass.class_eval do
|
23
|
+
property :type, exec_context: :decorator, render_nil: false
|
24
|
+
end
|
25
|
+
end
|
12
26
|
|
13
|
-
|
14
|
-
|
15
|
-
|
27
|
+
# Returns the type overrides.
|
28
|
+
#
|
29
|
+
# By default, +Array+ and +ActiveRecord::Relation+ are renamed to +list+.
|
30
|
+
#
|
31
|
+
# @return [Hash{String => String}] a hash of class-override pairs
|
32
|
+
def overrides
|
33
|
+
@overrides ||= {
|
34
|
+
'Array' => 'list',
|
35
|
+
'ActiveRecord::Relation' => 'list'
|
36
|
+
}
|
16
37
|
end
|
17
38
|
end
|
18
39
|
|
19
|
-
# Returns the type
|
40
|
+
# Returns the type to expose to API clients.
|
41
|
+
#
|
42
|
+
# If an override is present for the decorated class, returns the override, otherwise returns
|
43
|
+
# the underscored class.
|
44
|
+
#
|
45
|
+
# @return [String] type to expose
|
20
46
|
#
|
21
|
-
# @
|
47
|
+
# @see .overrides
|
22
48
|
def type
|
23
|
-
|
49
|
+
Pragma::Decorator::Type.overrides[decorated.class.name] ||
|
50
|
+
underscore_klass(decorated.class.name)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def underscore_klass(klass)
|
56
|
+
klass
|
24
57
|
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
25
58
|
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
26
59
|
.downcase
|
27
|
-
|
28
|
-
TYPE_OVERRIDES[type.to_sym] || type
|
29
60
|
end
|
30
61
|
end
|
31
62
|
end
|