api_me 0.10.6 → 0.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64522583b827ed64ae2f0d8d8f12ec7c2afbacbad196bad0061c9079ec622fba
4
- data.tar.gz: 4f3fd8c3740418a5e211526a2d9bd76ab16dbaa26aec211e62d84b4ca37a268f
3
+ metadata.gz: 9646af7af8c9ba9da48b8655d133eaea222e669a916435eb86670c2ab19aa848
4
+ data.tar.gz: c580cea45bc61f08bb8cadb103db8090b7d87f7e560d7859d990dfa256735f6a
5
5
  SHA512:
6
- metadata.gz: 77493e6cd629345dde520b41432c78c37fecfc331a88ffd1e9042d8cc94460b2951c5395330dec1202a2d20f7edfe84dd45835622959a01a976394166a71f702
7
- data.tar.gz: 29726f428276b1ac44422216a06a89035d98f2a1da7aee9feb5bc95a0761517cb8e3dd08b261a79135155978d2e303b288c1e23617c7d509834b1a9cea770dae
6
+ metadata.gz: 8e5bf1017206e3569e3998b419e4cd310ba80ab6787f2d71e317bc3880e99a1f32e29d3eb4d4355dc357a9c1429a1aa2f35e8a7fc6003da0faa65f0726e59189
7
+ data.tar.gz: 482033012c3342badf0c2b1fbeb099a04b32d217ec50af2d4940bc1bb33bd96c6e26bc5d6ef7ddebeefeac7314415e022e998f40196d97c2656b7e899b33c8f9
@@ -9,6 +9,7 @@ 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
@@ -17,6 +18,8 @@ module ApiMe
17
18
  included do
18
19
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
19
20
  rescue_from ActiveRecord::RecordInvalid, with: :handle_active_record_errors
21
+ rescue_from ActiveRecord::RecordNotDestroyed, with: :handle_active_record_errors
22
+ rescue_from ActiveRecord::ReadOnlyRecord, with: :handle_active_record_errors
20
23
  rescue_from ActiveRecord::RecordNotFound, with: :resource_not_found
21
24
  end
22
25
 
@@ -74,15 +77,39 @@ module ApiMe
74
77
  @sorted_scope = sort_scope(@filter_scope)
75
78
  @pagination_object = paginate_scope(@sorted_scope, page_params)
76
79
 
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
- )
80
+ respond_to do |format|
81
+ format.csv do
82
+ @csv_includes_scope = csv_includes_scope(@sorted_scope)
83
+ response.headers['Content-Type'] = 'text/csv'
84
+ response.headers['Cache-Control'] = 'no-cache'
85
+ # Hack to work around https://github.com/rack/rack/issues/1619
86
+ response.headers['Last-Modified'] = Time.current.httpdate
87
+ # Disable gzip in nginx
88
+ response.headers['X-Accel-Buffering'] = 'no'
89
+ response.headers['Content-Disposition'] = "attachment; filename=\"#{csv_filename}\""
90
+
91
+ ::ApiMe::CsvStreamWriter.generate(response.stream) do |csv|
92
+ # headers
93
+ csv << csv_headers
94
+ @csv_includes_scope.find_each do |object|
95
+ csv << object.try('to_csv')
96
+ end
97
+ end
98
+ ensure
99
+ response.stream.close
100
+ end
101
+ format.all do
102
+ render(
103
+ json: @pagination_object.results,
104
+ root: collection_root_key,
105
+ each_serializer: serializer_klass,
106
+ meta: {
107
+ page: @pagination_object.page_meta,
108
+ sort: sorting_meta(@filter_scope)
109
+ }
110
+ )
111
+ end
112
+ end
86
113
  end
87
114
 
88
115
  def show
@@ -93,10 +120,7 @@ module ApiMe
93
120
  end
94
121
 
95
122
  def new
96
- @object = model_klass.new
97
- authorize_resource @object
98
-
99
- render_errors(['new endpoint not supported'], 404)
123
+ render_errors(['new endpoint not supported'], :not_found)
100
124
  end
101
125
 
102
126
  def create
@@ -104,22 +128,20 @@ module ApiMe
104
128
  authorize_resource @object
105
129
  create_resource!
106
130
 
107
- render status: 201, json: @object, root: singular_root_key, serializer: serializer_klass
131
+ render status: :created, json: @object, root: singular_root_key, serializer: serializer_klass
108
132
  end
109
133
 
110
134
  def edit
111
- @object = find_resource
112
- authorize_resource @object
113
-
114
- render_errors(['edit endpoint not supported'], 404)
135
+ render_errors(['edit endpoint not supported'], :not_found)
115
136
  end
116
137
 
117
138
  def update
118
139
  @object = find_resource
140
+ @object.assign_attributes(object_params)
119
141
  authorize_resource @object
120
142
  update_resource!
121
143
 
122
- head 204
144
+ render status: :ok, json: @object, root: singular_root_key, serializer: serializer_klass
123
145
  end
124
146
 
125
147
  def destroy
@@ -127,11 +149,23 @@ module ApiMe
127
149
  authorize_resource @object
128
150
  destroy_resource!
129
151
 
130
- head 204
152
+ head :no_content
131
153
  end
132
154
 
133
155
  protected
134
156
 
157
+ def csv_filename
158
+ "#{model_klass.name.dasherize}-#{Time.zone.now.to_date.to_s(:default)}.csv"
159
+ end
160
+
161
+ def csv_headers
162
+ model_klass.respond_to?('csv_headers') ? model_klass.csv_headers : []
163
+ end
164
+
165
+ def csv_includes_scope(scope)
166
+ scope
167
+ end
168
+
135
169
  def singular_root_key
136
170
  model_klass.name.singularize.underscore
137
171
  end
@@ -152,7 +186,7 @@ module ApiMe
152
186
  params[:sort]
153
187
  end
154
188
 
155
- def render_errors(errors, status = 422)
189
+ def render_errors(errors, status = :unprocessable_entity)
156
190
  render(json: { errors: errors }, status: status)
157
191
  end
158
192
 
@@ -164,11 +198,11 @@ module ApiMe
164
198
 
165
199
  def user_not_authorized
166
200
  payload = { message: "User is not allowed to access #{params[:action]} on this resource" }
167
- render json: payload, status: 403
201
+ render json: payload, status: :forbidden
168
202
  end
169
203
 
170
204
  def resource_not_found
171
- head 404
205
+ head :not_found
172
206
  end
173
207
 
174
208
  def model_klass
@@ -230,7 +264,7 @@ module ApiMe
230
264
  end
231
265
 
232
266
  def find_resource
233
- @find_resource ||= model_klass.find_by_id!(params[:id])
267
+ @find_resource ||= model_klass.find_by!(id: params[:id])
234
268
  end
235
269
 
236
270
  def build_resource
@@ -238,11 +272,11 @@ module ApiMe
238
272
  end
239
273
 
240
274
  def create_resource!
241
- @object.save!(object_params)
275
+ @object.save!
242
276
  end
243
277
 
244
278
  def update_resource!
245
- @object.update!(object_params)
279
+ @object.save!
246
280
  end
247
281
 
248
282
  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.6'
4
+ VERSION = '0.14.0'
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.6
4
+ version: 0.14.0
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-08-20 00:00:00.000000000 Z
12
+ date: 2020-12-22 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