api_presenter 0.2.3 → 0.2.4
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/CHANGELOG.md +4 -0
- data/lib/api_presenter.rb +5 -200
- data/lib/api_presenter/base.rb +196 -0
- data/lib/api_presenter/parsers/parse_include_params.rb +27 -0
- data/lib/api_presenter/version.rb +1 -1
- metadata +3 -2
- data/lib/api_presenter/parse_include_params.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a3e0a34d5d50f902760195ffd58a409d3231a15
|
4
|
+
data.tar.gz: 8a4261472f7ba030c7ff72e2bed5d9c3171b98d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e3491e2c26ac8affb10ba11eaeef277be47b4b9666f205a6c9f5a07da5178b204ffcc28961e10df10811abb6a98ff5cc0e0968e0d213911f07f96d1094a94fa
|
7
|
+
data.tar.gz: e426cce2a716fc7cdcb4e5066cd9b9a51509a5c339886031c27159fe0a6698c07f08f107f939cd6c0a071637bd7f4ee942f61e2876d139757f42383e157ed367
|
data/CHANGELOG.md
CHANGED
data/lib/api_presenter.rb
CHANGED
@@ -1,205 +1,10 @@
|
|
1
1
|
require 'pundit'
|
2
|
+
module ApiPresenter; end
|
2
3
|
|
3
|
-
|
4
|
-
class Base
|
5
|
-
|
6
|
-
attr_reader :current_user, :params, :relation
|
7
|
-
|
8
|
-
# @example
|
9
|
-
# @presenter = PostPresenter.call(
|
10
|
-
# current_user: current_user,
|
11
|
-
# relation: relation,
|
12
|
-
# params: params
|
13
|
-
# )
|
14
|
-
#
|
15
|
-
# @param (see #initialize)
|
16
|
-
#
|
17
|
-
# @return [ApiPresenter::Base]
|
18
|
-
#
|
19
|
-
def self.call(**kwargs)
|
20
|
-
new(kwargs).call
|
21
|
-
end
|
22
|
-
|
23
|
-
# @param current_user [User] Optional. current_user context.
|
24
|
-
# @param relation [ActiveRecord::Relation, Array] Relation or array-wrapped record(s) to present
|
25
|
-
# @param params [Hash] Controller params
|
26
|
-
# @option params [Boolean] :count Optional. If true, return count only.
|
27
|
-
# @option params [String, Array] :include Optional. Associated resources to include.
|
28
|
-
# @option params [Boolean] :policies Optional. If true, resolve polciies for relation.
|
29
|
-
#
|
30
|
-
def initialize(current_user: nil, relation:, params: {})
|
31
|
-
@current_user = current_user
|
32
|
-
@relation = relation
|
33
|
-
@params = params
|
34
|
-
end
|
35
|
-
|
36
|
-
# @return [ApiPresenter::Base]
|
37
|
-
#
|
38
|
-
def call
|
39
|
-
return self if count_only?
|
40
|
-
initialize_resolvers
|
41
|
-
call_resolvers
|
42
|
-
self
|
43
|
-
end
|
44
|
-
|
45
|
-
# Primary collection, empty if count requested
|
46
|
-
#
|
47
|
-
# @return [ActiveRecord::Relation, Array<ActiveRecord::Base>]
|
48
|
-
#
|
49
|
-
def collection
|
50
|
-
count_only? ? [] : relation
|
51
|
-
end
|
52
|
-
|
53
|
-
# Count of primary collection
|
54
|
-
#
|
55
|
-
# @note Delegate to Kaminari's `total_count` property, or regular count if not a paginated relation
|
56
|
-
#
|
57
|
-
# @return [Integer]
|
58
|
-
#
|
59
|
-
def total_count
|
60
|
-
relation.respond_to?(:total_count) ? relation.total_count : relation.count
|
61
|
-
end
|
62
|
-
|
63
|
-
# Policies for the primary collection
|
64
|
-
#
|
65
|
-
# @example
|
66
|
-
# [
|
67
|
-
# { post_id: 1, update: true, destroy: true },
|
68
|
-
# { post_id: 2, update: false, destroy: false }
|
69
|
-
# ]
|
70
|
-
#
|
71
|
-
# @return [<Array<Hash>]
|
72
|
-
#
|
73
|
-
def policies
|
74
|
-
@policies_resolver ? @policies_resolver.resolved_policies : {}
|
75
|
-
end
|
76
|
-
|
77
|
-
# Class names of included collections
|
78
|
-
#
|
79
|
-
# @example
|
80
|
-
# [:categories, :sub_categories, :users]
|
81
|
-
#
|
82
|
-
# @return [Array<Symbol>]
|
83
|
-
#
|
84
|
-
def included_collection_names
|
85
|
-
@included_collection_names ||= ParseIncludeParams.call(params[:include])
|
86
|
-
end
|
87
|
-
|
88
|
-
# Map of included collection names and loaded record
|
89
|
-
#
|
90
|
-
# @example
|
91
|
-
# {
|
92
|
-
# categories: [#<Category id:1>],
|
93
|
-
# sub_categories: [#<SubCategory id:1>],
|
94
|
-
# users: [#<User id:1>, #<User id:2]
|
95
|
-
# }
|
96
|
-
#
|
97
|
-
# @return [Hash]
|
98
|
-
#
|
99
|
-
def included_collections
|
100
|
-
@included_collections_resolver ? @included_collections_resolver.resolved_collections : {}
|
101
|
-
end
|
102
|
-
|
103
|
-
# Preload additional records with the relation
|
104
|
-
#
|
105
|
-
# @note Called by resolvers, but can also be called if additional data is required that does
|
106
|
-
# not need to be loaded as an included collection, and for some reason cannot be chained
|
107
|
-
# onto the original relation.
|
108
|
-
#
|
109
|
-
# @param associations [Symbol, Array<Symbol>]
|
110
|
-
#
|
111
|
-
def preload(associations)
|
112
|
-
@relation = @relation.preload(associations)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Hash map that defines the sources for included collection names
|
116
|
-
#
|
117
|
-
# @example
|
118
|
-
# def associations_map
|
119
|
-
# {
|
120
|
-
# categories: { associations: { sub_category: :category } },
|
121
|
-
# sub_categories: { associations: :sub_category },
|
122
|
-
# users: { associations: [:creator, :publisher] }
|
123
|
-
# }
|
124
|
-
# end
|
125
|
-
#
|
126
|
-
# @abstract
|
127
|
-
#
|
128
|
-
# @return [Hash]
|
129
|
-
#
|
130
|
-
def associations_map
|
131
|
-
{}
|
132
|
-
end
|
133
|
-
|
134
|
-
# Policy methods to resolve for the primary relation
|
135
|
-
#
|
136
|
-
# @example Single
|
137
|
-
# def policy_methods
|
138
|
-
# :update
|
139
|
-
# end
|
140
|
-
#
|
141
|
-
# @example Multiple
|
142
|
-
# def policy_methods
|
143
|
-
# [:update, :destroy]
|
144
|
-
# end
|
145
|
-
#
|
146
|
-
# @abstract
|
147
|
-
#
|
148
|
-
# @return [Symbol, Array<Symbol>]
|
149
|
-
#
|
150
|
-
def policy_methods
|
151
|
-
[]
|
152
|
-
end
|
153
|
-
|
154
|
-
# Policy associations to preload to optimize policy resolution
|
155
|
-
#
|
156
|
-
# @example Single
|
157
|
-
# def policy_associations
|
158
|
-
# :user_profile
|
159
|
-
# end
|
160
|
-
#
|
161
|
-
# @example Multiple
|
162
|
-
# def policy_associations
|
163
|
-
# [:user_profile, :company]
|
164
|
-
# end
|
165
|
-
#
|
166
|
-
# @abstract
|
167
|
-
#
|
168
|
-
# @return [Symbol, Array<Symbol>]
|
169
|
-
#
|
170
|
-
def policy_associations
|
171
|
-
[]
|
172
|
-
end
|
173
|
-
|
174
|
-
private
|
175
|
-
|
176
|
-
def count_only?
|
177
|
-
@count_only ||= !!params[:count]
|
178
|
-
end
|
179
|
-
|
180
|
-
def resolve_policies?
|
181
|
-
@resolve_policies ||= current_user && !!params[:policies]
|
182
|
-
end
|
183
|
-
|
184
|
-
def resolve_included_collctions?
|
185
|
-
included_collection_names.any?
|
186
|
-
end
|
187
|
-
|
188
|
-
def initialize_resolvers
|
189
|
-
@policies_resolver = Resolvers::PoliciesResolver.new(self) if resolve_policies?
|
190
|
-
@included_collections_resolver = Resolvers::IncludedCollectionsResolver.new(self) if resolve_included_collctions?
|
191
|
-
end
|
192
|
-
|
193
|
-
def call_resolvers
|
194
|
-
@policies_resolver.call if @policies_resolver
|
195
|
-
@included_collections_resolver.call if @included_collections_resolver
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
require 'api_presenter/parse_include_params'
|
201
|
-
require 'api_presenter/version'
|
4
|
+
require 'api_presenter/base'
|
202
5
|
require 'api_presenter/concerns/presentable'
|
6
|
+
require 'api_presenter/parsers/parse_include_params'
|
203
7
|
require 'api_presenter/resolvers/base'
|
204
|
-
require 'api_presenter/resolvers/policies_resolver'
|
205
8
|
require 'api_presenter/resolvers/included_collections_resolver'
|
9
|
+
require 'api_presenter/resolvers/policies_resolver'
|
10
|
+
require 'api_presenter/version'
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module ApiPresenter
|
2
|
+
class Base
|
3
|
+
|
4
|
+
attr_reader :current_user, :params, :relation
|
5
|
+
|
6
|
+
# @example
|
7
|
+
# @presenter = PostPresenter.call(
|
8
|
+
# current_user: current_user,
|
9
|
+
# relation: relation,
|
10
|
+
# params: params
|
11
|
+
# )
|
12
|
+
#
|
13
|
+
# @param (see #initialize)
|
14
|
+
#
|
15
|
+
# @return [ApiPresenter::Base]
|
16
|
+
#
|
17
|
+
def self.call(**kwargs)
|
18
|
+
new(kwargs).call
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param current_user [User] Optional. current_user context.
|
22
|
+
# @param relation [ActiveRecord::Relation, Array] Relation or array-wrapped record(s) to present
|
23
|
+
# @param params [Hash] Controller params
|
24
|
+
# @option params [Boolean] :count Optional. If true, return count only.
|
25
|
+
# @option params [String, Array] :include Optional. Associated resources to include.
|
26
|
+
# @option params [Boolean] :policies Optional. If true, resolve polciies for relation.
|
27
|
+
#
|
28
|
+
def initialize(current_user: nil, relation:, params: {})
|
29
|
+
@current_user = current_user
|
30
|
+
@relation = relation
|
31
|
+
@params = params
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [ApiPresenter::Base]
|
35
|
+
#
|
36
|
+
def call
|
37
|
+
return self if count_only?
|
38
|
+
initialize_resolvers
|
39
|
+
call_resolvers
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
# Primary collection, empty if count requested
|
44
|
+
#
|
45
|
+
# @return [ActiveRecord::Relation, Array<ActiveRecord::Base>]
|
46
|
+
#
|
47
|
+
def collection
|
48
|
+
count_only? ? [] : relation
|
49
|
+
end
|
50
|
+
|
51
|
+
# Count of primary collection
|
52
|
+
#
|
53
|
+
# @note Delegate to Kaminari's `total_count` property, or regular count if not a paginated relation
|
54
|
+
#
|
55
|
+
# @return [Integer]
|
56
|
+
#
|
57
|
+
def total_count
|
58
|
+
relation.respond_to?(:total_count) ? relation.total_count : relation.count
|
59
|
+
end
|
60
|
+
|
61
|
+
# Policies for the primary collection
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# [
|
65
|
+
# { post_id: 1, update: true, destroy: true },
|
66
|
+
# { post_id: 2, update: false, destroy: false }
|
67
|
+
# ]
|
68
|
+
#
|
69
|
+
# @return [<Array<Hash>]
|
70
|
+
#
|
71
|
+
def policies
|
72
|
+
@policies_resolver ? @policies_resolver.resolved_policies : {}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Class names of included collections
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# [:categories, :sub_categories, :users]
|
79
|
+
#
|
80
|
+
# @return [Array<Symbol>]
|
81
|
+
#
|
82
|
+
def included_collection_names
|
83
|
+
@included_collection_names ||= Parsers::ParseIncludeParams.call(params[:include])
|
84
|
+
end
|
85
|
+
|
86
|
+
# Map of included collection names and loaded record
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# {
|
90
|
+
# categories: [#<Category id:1>],
|
91
|
+
# sub_categories: [#<SubCategory id:1>],
|
92
|
+
# users: [#<User id:1>, #<User id:2]
|
93
|
+
# }
|
94
|
+
#
|
95
|
+
# @return [Hash]
|
96
|
+
#
|
97
|
+
def included_collections
|
98
|
+
@included_collections_resolver ? @included_collections_resolver.resolved_collections : {}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Preload additional records with the relation
|
102
|
+
#
|
103
|
+
# @note Called by resolvers, but can also be called if additional data is required that does
|
104
|
+
# not need to be loaded as an included collection, and for some reason cannot be chained
|
105
|
+
# onto the original relation.
|
106
|
+
#
|
107
|
+
# @param associations [Symbol, Array<Symbol>]
|
108
|
+
#
|
109
|
+
def preload(associations)
|
110
|
+
@relation = @relation.preload(associations)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Hash map that defines the sources for included collection names
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# def associations_map
|
117
|
+
# {
|
118
|
+
# categories: { associations: { sub_category: :category } },
|
119
|
+
# sub_categories: { associations: :sub_category },
|
120
|
+
# users: { associations: [:creator, :publisher] }
|
121
|
+
# }
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# @abstract
|
125
|
+
#
|
126
|
+
# @return [Hash]
|
127
|
+
#
|
128
|
+
def associations_map
|
129
|
+
{}
|
130
|
+
end
|
131
|
+
|
132
|
+
# Policy methods to resolve for the primary relation
|
133
|
+
#
|
134
|
+
# @example Single
|
135
|
+
# def policy_methods
|
136
|
+
# :update
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# @example Multiple
|
140
|
+
# def policy_methods
|
141
|
+
# [:update, :destroy]
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# @abstract
|
145
|
+
#
|
146
|
+
# @return [Symbol, Array<Symbol>]
|
147
|
+
#
|
148
|
+
def policy_methods
|
149
|
+
[]
|
150
|
+
end
|
151
|
+
|
152
|
+
# Policy associations to preload to optimize policy resolution
|
153
|
+
#
|
154
|
+
# @example Single
|
155
|
+
# def policy_associations
|
156
|
+
# :user_profile
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# @example Multiple
|
160
|
+
# def policy_associations
|
161
|
+
# [:user_profile, :company]
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# @abstract
|
165
|
+
#
|
166
|
+
# @return [Symbol, Array<Symbol>]
|
167
|
+
#
|
168
|
+
def policy_associations
|
169
|
+
[]
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def count_only?
|
175
|
+
@count_only ||= !!params[:count]
|
176
|
+
end
|
177
|
+
|
178
|
+
def resolve_policies?
|
179
|
+
@resolve_policies ||= current_user && !!params[:policies]
|
180
|
+
end
|
181
|
+
|
182
|
+
def resolve_included_collctions?
|
183
|
+
included_collection_names.any?
|
184
|
+
end
|
185
|
+
|
186
|
+
def initialize_resolvers
|
187
|
+
@policies_resolver = Resolvers::PoliciesResolver.new(self) if resolve_policies?
|
188
|
+
@included_collections_resolver = Resolvers::IncludedCollectionsResolver.new(self) if resolve_included_collctions?
|
189
|
+
end
|
190
|
+
|
191
|
+
def call_resolvers
|
192
|
+
@policies_resolver.call if @policies_resolver
|
193
|
+
@included_collections_resolver.call if @included_collections_resolver
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ApiPresenter
|
2
|
+
module Parsers
|
3
|
+
|
4
|
+
# Parses values into array of acceptable association map keys:
|
5
|
+
# * Removes blanks and dups
|
6
|
+
# * Underscores camel-cased keys
|
7
|
+
# * Converts to symbol
|
8
|
+
#
|
9
|
+
# @param values [String, Array<String>, Array<Symbol>] Comma-delimited string or array
|
10
|
+
#
|
11
|
+
# @return [Array<Symbol>]
|
12
|
+
#
|
13
|
+
class ParseIncludeParams
|
14
|
+
def self.call(values)
|
15
|
+
return [] if values.blank?
|
16
|
+
|
17
|
+
array = values.is_a?(Array) ? values.dup : values.split(',')
|
18
|
+
array.select!(&:present?)
|
19
|
+
array.map! { |value| value.try(:underscore) || value }
|
20
|
+
array.uniq!
|
21
|
+
array.map!(&:to_sym)
|
22
|
+
|
23
|
+
array
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api_presenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yuval Kordov
|
@@ -129,8 +129,9 @@ files:
|
|
129
129
|
- bin/console
|
130
130
|
- bin/setup
|
131
131
|
- lib/api_presenter.rb
|
132
|
+
- lib/api_presenter/base.rb
|
132
133
|
- lib/api_presenter/concerns/presentable.rb
|
133
|
-
- lib/api_presenter/parse_include_params.rb
|
134
|
+
- lib/api_presenter/parsers/parse_include_params.rb
|
134
135
|
- lib/api_presenter/resolvers/base.rb
|
135
136
|
- lib/api_presenter/resolvers/included_collections_resolver.rb
|
136
137
|
- lib/api_presenter/resolvers/policies_resolver.rb
|
@@ -1,25 +0,0 @@
|
|
1
|
-
module ApiPresenter
|
2
|
-
|
3
|
-
# Parses values into array of acceptable association map keys:
|
4
|
-
# * Removes blanks and dups
|
5
|
-
# * Underscores camel-cased keys
|
6
|
-
# * Converts to symbol
|
7
|
-
#
|
8
|
-
# @param values [String, Array<String>, Array<Symbol>] Comma-delimited string or array
|
9
|
-
#
|
10
|
-
# @return [Array<Symbol>]
|
11
|
-
#
|
12
|
-
class ParseIncludeParams
|
13
|
-
def self.call(values)
|
14
|
-
return [] if values.blank?
|
15
|
-
|
16
|
-
array = values.is_a?(Array) ? values.dup : values.split(',')
|
17
|
-
array.select!(&:present?)
|
18
|
-
array.map! { |value| value.try(:underscore) || value }
|
19
|
-
array.uniq!
|
20
|
-
array.map!(&:to_sym)
|
21
|
-
|
22
|
-
array
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|