api_me 0.10.7 → 0.14.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19d6a0588c78c9c54147a063e94ad1bce087748819f1c422c847d351ce3df156
4
- data.tar.gz: 3dd3e873f6aa2518fee3a85d804d084822dac85dfa2a130c8d472a56d2cf7b5c
3
+ metadata.gz: b406ddb8c9fb3a865f90531fc62819b2e6d3870b45c2e11c20975ead90fb6345
4
+ data.tar.gz: 7a31d26119457065f33a499c0ff6913a3a0eed34c460ec878d60935458b23b76
5
5
  SHA512:
6
- metadata.gz: 1bd2149f84f9f7a76fe8fa39875b6a133ef19e707b634ded5c16607ca1007a5122593ea3b3cb8c8f757f6311828227ff7429f3ac915038fcac91a558ac1a787a
7
- data.tar.gz: 8438532c00323267eb7b4303af85948712b3821e5993b1d212441a7249cd3a00e9fedccdce32ef62b223cb7f716b21a48574390ba757636c93c352d5b4e58434
6
+ metadata.gz: 7d5fdc0406b3ba44ead6d00957966cfa3f734f62444594dd66ad3149ca115ebe916e828c8e3ba70cc3107537f67c58ef8e78fbaf6e0f463034be4737e793249c
7
+ data.tar.gz: 41267c7fb138975facca66c4f56929e7872459e6986f283a3f344efd4f4342f7423a07086724b685c7185967b029afd2fb4d66d8a7206211d4bc312dd8e547b5
data/lib/api_me.rb CHANGED
@@ -9,14 +9,19 @@ require 'api_me/version'
9
9
  require 'api_me/base_filter'
10
10
  require 'api_me/sorting'
11
11
  require 'api_me/pagination'
12
+ require 'api_me/csv_stream_writer'
12
13
 
13
14
  module ApiMe
14
15
  extend ActiveSupport::Concern
15
- include ::Pundit
16
16
 
17
17
  included do
18
+ include ::Pundit
19
+ include ActionController::Live
20
+
18
21
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
19
22
  rescue_from ActiveRecord::RecordInvalid, with: :handle_active_record_errors
23
+ rescue_from ActiveRecord::RecordNotDestroyed, with: :handle_active_record_errors
24
+ rescue_from ActiveRecord::ReadOnlyRecord, with: :handle_active_record_errors
20
25
  rescue_from ActiveRecord::RecordNotFound, with: :resource_not_found
21
26
  end
22
27
 
@@ -74,15 +79,39 @@ module ApiMe
74
79
  @sorted_scope = sort_scope(@filter_scope)
75
80
  @pagination_object = paginate_scope(@sorted_scope, page_params)
76
81
 
77
- render(
78
- json: @pagination_object.results,
79
- root: collection_root_key,
80
- each_serializer: serializer_klass,
81
- meta: {
82
- page: @pagination_object.page_meta,
83
- sort: sorting_meta(@filter_scope)
84
- }
85
- )
82
+ respond_to do |format|
83
+ format.csv do
84
+ @csv_includes_scope = csv_includes_scope(@sorted_scope)
85
+ response.headers['Content-Type'] = 'text/csv'
86
+ response.headers['Cache-Control'] = 'no-cache'
87
+ # Hack to work around https://github.com/rack/rack/issues/1619
88
+ response.headers['Last-Modified'] = Time.current.httpdate
89
+ # Disable gzip in nginx
90
+ response.headers['X-Accel-Buffering'] = 'no'
91
+ response.headers['Content-Disposition'] = "attachment; filename=\"#{csv_filename}\""
92
+
93
+ ::ApiMe::CsvStreamWriter.generate(response.stream) do |csv|
94
+ # headers
95
+ csv << csv_headers
96
+ @csv_includes_scope.find_each do |object|
97
+ csv << object.try('to_csv')
98
+ end
99
+ end
100
+ ensure
101
+ response.stream.close
102
+ end
103
+ format.all do
104
+ render(
105
+ json: @pagination_object.results,
106
+ root: collection_root_key,
107
+ each_serializer: serializer_klass,
108
+ meta: {
109
+ page: @pagination_object.page_meta,
110
+ sort: sorting_meta(@filter_scope)
111
+ }
112
+ )
113
+ end
114
+ end
86
115
  end
87
116
 
88
117
  def show
@@ -93,7 +122,7 @@ module ApiMe
93
122
  end
94
123
 
95
124
  def new
96
- render_errors(['new endpoint not supported'], 404)
125
+ render_errors(['new endpoint not supported'], :not_found)
97
126
  end
98
127
 
99
128
  def create
@@ -101,11 +130,11 @@ module ApiMe
101
130
  authorize_resource @object
102
131
  create_resource!
103
132
 
104
- render status: 201, json: @object, root: singular_root_key, serializer: serializer_klass
133
+ render status: :created, json: @object, root: singular_root_key, serializer: serializer_klass
105
134
  end
106
135
 
107
136
  def edit
108
- render_errors(['edit endpoint not supported'], 404)
137
+ render_errors(['edit endpoint not supported'], :not_found)
109
138
  end
110
139
 
111
140
  def update
@@ -114,7 +143,7 @@ module ApiMe
114
143
  authorize_resource @object
115
144
  update_resource!
116
145
 
117
- head 204
146
+ render status: :ok, json: @object, root: singular_root_key, serializer: serializer_klass
118
147
  end
119
148
 
120
149
  def destroy
@@ -122,11 +151,23 @@ module ApiMe
122
151
  authorize_resource @object
123
152
  destroy_resource!
124
153
 
125
- head 204
154
+ head :no_content
126
155
  end
127
156
 
128
157
  protected
129
158
 
159
+ def csv_filename
160
+ "#{model_klass.name.dasherize}-#{Time.zone.now.to_date.to_s(:default)}.csv"
161
+ end
162
+
163
+ def csv_headers
164
+ model_klass.respond_to?('csv_headers') ? model_klass.csv_headers : []
165
+ end
166
+
167
+ def csv_includes_scope(scope)
168
+ scope
169
+ end
170
+
130
171
  def singular_root_key
131
172
  model_klass.name.singularize.underscore
132
173
  end
@@ -147,7 +188,7 @@ module ApiMe
147
188
  params[:sort]
148
189
  end
149
190
 
150
- def render_errors(errors, status = 422)
191
+ def render_errors(errors, status = :unprocessable_entity)
151
192
  render(json: { errors: errors }, status: status)
152
193
  end
153
194
 
@@ -159,11 +200,11 @@ module ApiMe
159
200
 
160
201
  def user_not_authorized
161
202
  payload = { message: "User is not allowed to access #{params[:action]} on this resource" }
162
- render json: payload, status: 403
203
+ render json: payload, status: :forbidden
163
204
  end
164
205
 
165
206
  def resource_not_found
166
- head 404
207
+ head :not_found
167
208
  end
168
209
 
169
210
  def model_klass
@@ -225,7 +266,7 @@ module ApiMe
225
266
  end
226
267
 
227
268
  def find_resource
228
- @find_resource ||= model_klass.find_by_id!(params[:id])
269
+ @find_resource ||= model_klass.find_by!(id: params[:id])
229
270
  end
230
271
 
231
272
  def build_resource
@@ -233,11 +274,11 @@ module ApiMe
233
274
  end
234
275
 
235
276
  def create_resource!
236
- @object.save!(object_params)
277
+ @object.save!
237
278
  end
238
279
 
239
280
  def update_resource!
240
- @object.save!(object_params)
281
+ @object.save!
241
282
  end
242
283
 
243
284
  def destroy_resource!
@@ -0,0 +1,32 @@
1
+ require 'csv'
2
+ # frozen_string_literal: true
3
+
4
+ module ApiMe
5
+ class CsvStreamWriter
6
+ # @!attribute [r] stream
7
+ # @return [IO]
8
+ attr_reader :stream
9
+
10
+ # Provides a similar interface to CSV.generate but compatible with an IO stream
11
+ # @example
12
+ # CsvStreamWriter.generate(stream) do |csv|
13
+ # csv << ['foo', 'bar']
14
+ # end
15
+ #
16
+ # @param [IO]
17
+ # @yield [CsvStreamWriter] csv
18
+ def self.generate(stream)
19
+ yield new(stream)
20
+ end
21
+
22
+ # @param [IO]
23
+ def initialize(stream)
24
+ @stream = stream
25
+ end
26
+
27
+ # @param [Array<String>]
28
+ def <<(row)
29
+ stream.write CSV.generate_line(row)
30
+ end
31
+ end
32
+ end
@@ -2,13 +2,16 @@
2
2
 
3
3
  module ApiMe
4
4
  class Sorting
5
- attr_accessor :sort_criteria, :sort_reverse, :scope
5
+ attr_accessor :sort_criteria, :sort_reverse, :scope, :custom_sort_options
6
6
 
7
- def initialize(scope:, sort_params:)
7
+ def initialize(scope:, sort_params:, custom_sort_options: {})
8
8
  self.scope = scope
9
+
9
10
  return unless sort_params
11
+
10
12
  self.sort_criteria = sort_params[:criteria] || default_sort_criteria
11
13
  self.sort_reverse = sort_params[:reverse] || false
14
+ self.custom_sort_options = custom_sort_options
12
15
  end
13
16
 
14
17
  def results
@@ -39,7 +42,14 @@ module ApiMe
39
42
  end
40
43
 
41
44
  def sorted_scope(criteria)
42
- if sort_reverse === 'true'
45
+ criteria_key = criteria.to_sym
46
+ if custom_sort_options.key?(criteria_key)
47
+ if sort_reverse == 'true'
48
+ custom_sort_scope(criteria_key).order(Arel.sql("#{custom_sort_options[criteria_key][:column]} DESC"))
49
+ else
50
+ custom_sort_scope(criteria_key).order(Arel.sql("#{custom_sort_options[criteria_key][:column]} ASC"))
51
+ end
52
+ elsif sort_reverse == 'true'
43
53
  scope.order(criteria => :desc)
44
54
  else
45
55
  scope.order(criteria => :asc)
@@ -48,6 +58,10 @@ module ApiMe
48
58
 
49
59
  private
50
60
 
61
+ def custom_sort_scope(criteria)
62
+ custom_sort_options[criteria].key?(:joins) ? scope.joins(custom_sort_options[criteria][:joins]) : scope
63
+ end
64
+
51
65
  def default_sort_criteria
52
66
  'id'
53
67
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ApiMe
4
- VERSION = '0.10.7'
4
+ VERSION = '0.14.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api_me
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.7
4
+ version: 0.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Clopton
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-10-14 00:00:00.000000000 Z
12
+ date: 2021-02-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -162,6 +162,7 @@ files:
162
162
  - Rakefile
163
163
  - lib/api_me.rb
164
164
  - lib/api_me/base_filter.rb
165
+ - lib/api_me/csv_stream_writer.rb
165
166
  - lib/api_me/engine.rb
166
167
  - lib/api_me/model.rb
167
168
  - lib/api_me/pagination.rb
@@ -199,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
200
  version: '0'
200
201
  requirements: []
201
202
  rubyforge_project:
202
- rubygems_version: 2.7.6
203
+ rubygems_version: 2.7.6.2
203
204
  signing_key:
204
205
  specification_version: 4
205
206
  summary: Api Me