api_me 0.10.6 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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