pragma-decorator 2.0.0 → 2.1.0
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 +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
|