introspective_grape 0.0.4 → 0.1.9

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +1164 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +4 -2
  7. data/CHANGELOG.md +58 -0
  8. data/Gemfile +5 -3
  9. data/README.md +70 -17
  10. data/introspective_grape.gemspec +8 -8
  11. data/lib/.DS_Store +0 -0
  12. data/lib/introspective_grape/api.rb +177 -216
  13. data/lib/introspective_grape/camel_snake.rb +28 -59
  14. data/lib/introspective_grape/filters.rb +66 -0
  15. data/lib/introspective_grape/formatter/camel_json.rb +14 -0
  16. data/lib/introspective_grape/helpers.rb +63 -0
  17. data/lib/introspective_grape/traversal.rb +54 -0
  18. data/lib/introspective_grape/version.rb +1 -1
  19. data/lib/introspective_grape.rb +11 -0
  20. data/spec/.DS_Store +0 -0
  21. data/spec/dummy/Gemfile +5 -3
  22. data/spec/dummy/app/api/.DS_Store +0 -0
  23. data/spec/dummy/app/api/api_helpers.rb +5 -6
  24. data/spec/dummy/app/api/dummy/chat_api.rb +1 -2
  25. data/spec/dummy/app/api/dummy/company_api.rb +16 -1
  26. data/spec/dummy/app/api/dummy/location_api.rb +3 -3
  27. data/spec/dummy/app/api/dummy/project_api.rb +1 -0
  28. data/spec/dummy/app/api/dummy/sessions.rb +4 -8
  29. data/spec/dummy/app/api/dummy/user_api.rb +3 -1
  30. data/spec/dummy/app/api/dummy_api.rb +6 -6
  31. data/spec/dummy/app/api/error_handlers.rb +2 -2
  32. data/spec/dummy/app/models/chat_user.rb +1 -1
  33. data/spec/dummy/app/models/image.rb +2 -2
  34. data/spec/dummy/app/models/role.rb +1 -1
  35. data/spec/dummy/app/models/user/chatter.rb +6 -6
  36. data/spec/dummy/app/models/user_project_job.rb +3 -3
  37. data/spec/dummy/config/application.rb +1 -1
  38. data/spec/dummy/db/migrate/20150824215701_create_images.rb +3 -3
  39. data/spec/dummy/db/schema.rb +1 -1
  40. data/spec/models/image_spec.rb +1 -1
  41. data/spec/models/role_spec.rb +5 -5
  42. data/spec/models/user_location_spec.rb +2 -2
  43. data/spec/models/user_project_job_spec.rb +1 -1
  44. data/spec/rails_helper.rb +3 -1
  45. data/spec/requests/company_api_spec.rb +28 -0
  46. data/spec/requests/location_api_spec.rb +19 -2
  47. data/spec/requests/project_api_spec.rb +34 -3
  48. data/spec/requests/sessions_api_spec.rb +1 -1
  49. data/spec/requests/user_api_spec.rb +24 -3
  50. data/spec/support/blueprints.rb +3 -3
  51. data/spec/support/location_helper.rb +26 -21
  52. data/spec/support/request_helpers.rb +1 -3
  53. metadata +58 -28
  54. data/spec/dummy/app/api/active_record_helpers.rb +0 -17
@@ -1,10 +1,15 @@
1
1
  require 'action_controller'
2
+ require 'grape-kaminari'
2
3
 
3
4
  module IntrospectiveGrape
4
- # Allow files to be uploaded through ActionController:
5
- ActionController::Parameters::PERMITTED_SCALAR_TYPES.push Rack::Multipart::UploadedFile, ActionController::Parameters
6
-
7
5
  class API < Grape::API
6
+ extend IntrospectiveGrape::Helpers
7
+ extend IntrospectiveGrape::Filters
8
+ extend IntrospectiveGrape::Traversal
9
+
10
+ # Allow files to be uploaded through ActionController:
11
+ ActionController::Parameters::PERMITTED_SCALAR_TYPES.push Rack::Multipart::UploadedFile, ActionController::Parameters
12
+
8
13
  # Generate uniform RESTful APIs for an ActiveRecord Model:
9
14
  #
10
15
  # class <Some API Controller> < IntrospectiveGrape::API
@@ -33,33 +38,21 @@ module IntrospectiveGrape
33
38
  #
34
39
 
35
40
  class << self
36
- PLURAL_REFLECTIONS = [ ActiveRecord::Reflection::HasManyReflection, ActiveRecord::Reflection::HasManyReflection]
37
-
38
- Pg2Ruby = {
39
- # mapping of activerecord/postgres 'type's to ruby data classes, where they differ
40
- datetime: DateTime
41
- }
42
-
43
- def exclude_actions(model, *args)
44
- @exclude_actions ||= {}
45
- @@api_actions ||= [:index,:show,:create,:update,:destroy,nil]
46
- raise "#{model.name} defines invalid exclude_actions: #{args-@@api_actions}" if (args.flatten-@@api_actions).present?
47
- @exclude_actions[model.name] = args.present? ? args.flatten : @exclude_actions[model.name] || []
48
- end
49
-
50
- def default_includes(model, *args)
51
- @default_includes ||= {}
52
- @default_includes[model.name] = args.present? ? args.flatten : @default_includes[model.name] || []
53
- end
41
+ PLURAL_REFLECTIONS = [ ActiveRecord::Reflection::HasManyReflection, ActiveRecord::Reflection::HasManyReflection].freeze
42
+ # mapping of activerecord/postgres 'type's to ruby data classes, where they differ
43
+ Pg2Ruby = { datetime: DateTime }.freeze
54
44
 
55
45
  def inherited(child)
56
46
  super(child)
57
47
  child.before do
48
+ # Ensure that a user is logged in.
49
+ self.send(IntrospectiveGrape::API.authentication_method(self))
50
+ end
51
+
52
+ child.after_validation do
58
53
  # Convert incoming camel case params to snake case: grape will totally blow this
59
- # if the params hash is not a Hashie::Mash, so make it one of those:
60
- @params = Hashie::Mash.new(snake_keys(params))
61
- # ensure that a user is logged in
62
- authorize!
54
+ # if the params hash does not come back as a Hashie::Mash.
55
+ @params = (params||Hashie::Mash.new).with_snake_keys if IntrospectiveGrape.config.camelize_parameters
63
56
  end
64
57
  end
65
58
 
@@ -93,8 +86,7 @@ module IntrospectiveGrape
93
86
  # As routes are nested keep track of the routes, we are preventing siblings from
94
87
  # appending to the routes array here:
95
88
  routes = build_routes(routes, model)
96
-
97
- define_routes(routes,whitelist)
89
+ define_routes(routes, whitelist)
98
90
 
99
91
  resource routes.first.name.pluralize do
100
92
  # yield to append additional routes under the root namespace
@@ -102,13 +94,12 @@ module IntrospectiveGrape
102
94
  end
103
95
  end
104
96
 
105
- def define_routes(routes, whitelist)
106
- define_endpoint(routes, whitelist)
107
-
97
+ def define_routes(routes, api_params)
98
+ define_endpoints(routes, api_params)
108
99
  # recursively define endpoints
109
100
  model = routes.last.model || return
110
101
 
111
- whitelist.select{|a| a.kind_of?(Hash) }.each do |nested|
102
+ api_params.select{|a| a.kind_of?(Hash) }.each do |nested|
112
103
  # Recursively add RESTful nested routes for every nested model:
113
104
  nested.each do |r,fields|
114
105
  # Look at model.reflections to find the association's class name:
@@ -120,162 +111,179 @@ module IntrospectiveGrape
120
111
  end
121
112
 
122
113
  next_routes = build_routes(routes, relation, reflection_name)
123
-
124
114
  define_routes(next_routes, fields)
125
115
  end
126
116
  end
127
117
  end
128
118
 
129
- def build_routes(routes, model, reflection_name=nil)
130
- routes = routes.clone
131
- # routes: the existing routes array passed from the parent
132
- # model: the model being manipulated in this leaf
133
- # reflection_name: the association name from the original strong_params declaration
134
- #
135
- # Constructs an array representation of the route's models and their associations,
136
- # a /root/:rootId/branch/:branchId/leaf/:leafId path would have flat array like
137
- # [root,branch,leaf] representing the path structure and its models, used to
138
- # manipulate ActiveRecord relationships and params hashes and so on.
139
- parent_model = routes.last.try(:model)
140
- return routes if model == parent_model
141
119
 
142
- name = reflection_name || model.name.underscore
143
- reflection = parent_model && parent_model.reflections[reflection_name]
144
- many = parent_model && PLURAL_REFLECTIONS.include?( reflection.class ) ? true : false
145
- routes.push OpenStruct.new(name: name, param: "#{name}_attributes", model: model, many?: many, key: "#{name.singularize}_id".to_sym, swaggerKey: "#{name.singularize.camelize(:lower)}Id", reflection: reflection)
146
- end
147
-
148
- def define_endpoint(routes,api_params)
149
- # Inside the Grape DSL "self" will derefernece to its Endpoint classes,
150
- # so save a reference to our API class:
151
- klass = self
120
+ def define_endpoints(routes,api_params)
152
121
  # De-reference these as local variables from their class scope, or when we make
153
122
  # calls to the API they will be whatever they were last set to by the recursive
154
123
  # calls to "nest_routes".
155
124
  routes = routes.clone
156
125
  api_params = api_params.clone
157
126
 
158
- root = routes.first
159
127
  model = routes.last.model || return
160
- name = routes.last.name.singularize
128
+
161
129
  # We define the param keys for ID fields in camelcase for swagger's URL substitution,
162
130
  # they'll come back in snake case in the params hash, the API as a whole is agnostic:
163
- swaggerKey = routes.last.swaggerKey
164
-
165
- namespace = routes[0..-2].map{|p| "#{p.name.pluralize}/:#{p.swaggerKey}/" }.join + name.pluralize
131
+ namespace = routes[0..-2].map{|p| "#{p.name.pluralize}/:#{p.swaggerKey}/" }.join + routes.last.name.pluralize
166
132
 
167
133
  resource namespace do
134
+ convert_nested_params_hash(self, routes)
135
+ define_restful_api(self, routes, model, api_params)
136
+ end
137
+ end
168
138
 
169
- after_validation do
170
- # After Grape validates its parameters:
171
- # 1) Find the root model instance for the API if its passed (implicitly either
172
- # an update/destroy on the root node or it's a nested route
173
- # 2) For nested endpoints convert the params hash into Rails-compliant nested
174
- # attributes, to be passed to the root instance for update. This keeps
175
- # behavior consistent between bulk and single record updates.
176
- if params[root.key]
177
- default_includes = routes.size > 1 ? [] : root.model.default_includes
178
- @model = root.model.includes( default_includes ).find(params[root.key])
179
- end
180
-
181
- if routes.size > 1
182
- nested_attributes = klass.build_nested_attributes(routes[1..-1], params.except(root.key,:api_key) )
183
-
184
- @params.merge!( nested_attributes ) if nested_attributes.kind_of?(Hash)
185
- end
186
-
187
- end
188
-
189
- unless model.exclude_actions.include?(:index)
190
- desc "list #{name.pluralize}" do
191
- detail "returns list of all #{name.pluralize}"
192
- end
193
- get '/' do
194
- # Invoke the policy for the action, defined in the policy classes for the model:
195
- authorize root.model.new, :index?
196
-
197
- # Nested route indexes need to be scoped by the API's top level policy class:
198
- records = policy_scope( root.model.includes(klass.default_includes(root.model)) )
139
+ def define_restful_api(dsl, routes, model, api_params)
140
+ # declare index, show, update, create, and destroy methods:
141
+ API_ACTIONS.each do |action|
142
+ send("define_#{action}", dsl, routes, model, api_params) unless exclude_actions(model).include?(action)
143
+ end
144
+ end
199
145
 
200
- records = records.map{|r| klass.find_leaves( routes, r, params ) }.flatten.compact.uniq
146
+ def define_index(dsl, routes, model, api_params)
147
+ include Grape::Kaminari
148
+ root = routes.first
149
+ klass = routes.first.klass
150
+ name = routes.last.name.pluralize
151
+ simple_filters(klass, model, api_params)
201
152
 
202
- present records, with: "#{klass}::#{model}Entity".constantize
203
- end
204
- end
153
+ dsl.desc "list #{name}" do
154
+ detail "returns list of all #{name}"
155
+ end
156
+ dsl.params do
157
+ klass.declare_filter_params(self, klass, model, api_params)
158
+ end
159
+ if klass.pagination
160
+ paginate per_page: klass.pagination[:per_page]||25, max_per_page: klass.pagination[:max_per_page], offset: klass.pagination[:offset]||0
161
+ end
162
+ dsl.get '/' do
163
+ # Invoke the policy for the action, defined in the policy classes for the model:
164
+ authorize root.model.new, :index?
165
+
166
+ # Nested route indexes need to be scoped by the API's top level policy class:
167
+ records = policy_scope( root.model.includes(klass.default_includes(root.model)) )
168
+
169
+ records = klass.apply_filter_params(klass, model, api_params, params, records)
170
+ records = records.where( JSON.parse(params[:query]) ) if params[:query].present?
171
+ records = records.map{|r| klass.find_leaves( routes, r, params ) }.flatten.compact.uniq
172
+ # paginate the records using Kaminari
173
+ records = paginate(Kaminari.paginate_array(records)) if klass.pagination
174
+ present records, with: "#{klass}::#{model}Entity".constantize
175
+ end
176
+ end
205
177
 
178
+ def define_show(dsl, routes, model, _api_params)
179
+ name = routes.last.name.singularize
180
+ klass = routes.first.klass
181
+ dsl.desc "retrieve a #{name}" do
182
+ detail "returns details on a #{name}"
183
+ end
184
+ dsl.get ":#{routes.last.swaggerKey}" do
185
+ authorize @model, :show?
186
+ present klass.find_leaf(routes, @model, params), with: "#{klass}::#{model}Entity".constantize
187
+ end
188
+ end
206
189
 
207
- unless model.exclude_actions.include?(:show)
208
- desc "retrieve a #{name}" do
209
- detail "returns details on a #{name}"
210
- end
211
- get ":#{swaggerKey}" do
212
- authorize @model, :show?
213
- present klass.find_leaf(routes, @model, params), with: "#{klass}::#{model}Entity".constantize
214
- end
190
+ def define_create(dsl, routes, model, api_params)
191
+ name = routes.last.name.singularize
192
+ klass = routes.first.klass
193
+ root = routes.first
194
+ dsl.desc "create a #{name}" do
195
+ detail "creates a new #{name} record"
196
+ end
197
+ dsl.params do
198
+ klass.generate_params(self, klass, :create, model, api_params)
199
+ end
200
+ dsl.post do
201
+ if @model
202
+ @model.update_attributes( safe_params(params).permit(klass.whitelist) )
203
+ else
204
+ @model = root.model.new( safe_params(params).permit(klass.whitelist) )
215
205
  end
206
+ authorize @model, :create?
207
+ @model.save!
208
+ present klass.find_leaves(routes, @model.reload, params), with: "#{klass}::#{model}Entity".constantize
209
+ end
210
+ end
216
211
 
212
+ def define_update(dsl, routes, model, api_params)
213
+ klass = routes.first.klass
214
+ name = routes.last.name.singularize
215
+ dsl.desc "update a #{name}" do
216
+ detail "updates the details of a #{name}"
217
+ end
218
+ dsl.params do
219
+ klass.generate_params(self, klass, :update, model, api_params)
220
+ end
221
+ dsl.put ":#{routes.last.swaggerKey}" do
222
+ authorize @model, :update?
217
223
 
218
- unless model.exclude_actions.include?(:create)
219
- desc "create a #{name}" do
220
- detail "creates a new #{name} record"
221
- end
222
- params do
223
- klass.generate_params(self, klass, :create, model, api_params)
224
- end
225
- post do
226
- if (@model)
227
- @model.update_attributes( safe_params(params).permit(klass.whitelist) )
228
- else
229
- @model = root.model.new( safe_params(params).permit(klass.whitelist) )
230
- end
231
- authorize @model, :create?
232
- @model.save!
233
- present klass.find_leaves(routes, @model, params), with: "#{klass}::#{model}Entity".constantize
234
- end
235
- end
236
-
224
+ @model.update_attributes!( safe_params(params).permit(klass.whitelist) )
237
225
 
238
- unless model.exclude_actions.include?(:update)
239
- desc "update a #{name}" do
240
- detail "updates the details of a #{name}"
241
- end
242
- params do
243
- klass.generate_params(self, klass, :update, model, api_params)
244
- end
245
- put ":#{swaggerKey}" do
246
- authorize @model, :update?
226
+ present klass.find_leaf(routes, @model.reload, params), with: "#{klass}::#{model}Entity".constantize
227
+ end
228
+ end
247
229
 
248
- @model.update_attributes!( safe_params(params).permit(klass.whitelist) )
230
+ def define_destroy(dsl, routes, _model, _api_params)
231
+ klass = routes.first.klass
232
+ name = routes.last.name.singularize
233
+ dsl.desc "destroy a #{name}" do
234
+ detail "destroys the details of a #{name}"
235
+ end
236
+ dsl.delete ":#{routes.last.swaggerKey}" do
237
+ authorize @model, :destroy?
238
+ present status: (klass.find_leaf(routes, @model, params).destroy ? true : false)
239
+ end
240
+ end
249
241
 
250
- present klass.find_leaf(routes, @model, params), with: "#{klass}::#{model}Entity".constantize
251
- end
242
+ def convert_nested_params_hash(dsl, routes)
243
+ root = routes.first
244
+ klass = root.klass
245
+ dsl.after_validation do
246
+ # After Grape validates its parameters:
247
+ # 1) Find the root model instance for the API if its passed (implicitly either
248
+ # an update/destroy on the root node or it's a nested route
249
+ # 2) For nested endpoints convert the params hash into Rails-compliant nested
250
+ # attributes, to be passed to the root instance for update. This keeps
251
+ # behavior consistent between bulk and single record updates.
252
+ if params[root.key]
253
+ default_includes = routes.size > 1 ? [] : klass.default_includes(root.model)
254
+ @model = root.model.includes( default_includes ).find(params[root.key])
252
255
  end
253
256
 
254
-
255
- unless model.exclude_actions.include?(:destroy)
256
- desc "destroy a #{name}" do
257
- detail "destroys the details of a #{name}"
258
- end
259
- delete ":#{swaggerKey}" do
260
- authorize @model, :destroy?
261
- present status: (klass.find_leaf(routes, @model, params).destroy ? true : false)
262
- end
257
+ if routes.size > 1
258
+ nested_attributes = klass.build_nested_attributes(routes[1..-1], params.except(root.key,:api_key) )
259
+ @params.merge!( nested_attributes ) if nested_attributes.kind_of?(Hash)
263
260
  end
264
-
265
261
  end
266
262
  end
267
263
 
268
264
 
269
- def whitelist(whitelist=nil)
270
- return @whitelist if !whitelist
271
- @whitelist = whitelist
272
- end
265
+ def build_routes(routes, model, reflection_name=nil)
266
+ routes = routes.clone
267
+ # routes: the existing routes array passed from the parent
268
+ # model: the model being manipulated in this leaf
269
+ # reflection_name: the association name from the original strong_params declaration
270
+ #
271
+ # Constructs an array representation of the route's models and their associations,
272
+ # a /root/:rootId/branch/:branchId/leaf/:leafId path would have flat array like
273
+ # [root,branch,leaf] representing the path structure and its models, used to
274
+ # manipulate ActiveRecord relationships and params hashes and so on.
275
+ parent_model = routes.last.try(:model)
276
+ return routes if model == parent_model
277
+
278
+ name = reflection_name || model.name.underscore
279
+ reflection = parent_model.try(:reflections).try(:fetch,reflection_name)
280
+ many = parent_model && PLURAL_REFLECTIONS.include?( reflection.class ) ? true : false
281
+ swaggerKey = IntrospectiveGrape.config.camelize_parameters ? "#{name.singularize.camelize(:lower)}Id" : "#{name.singularize}_id"
273
282
 
274
- def skip_presence_validations(fields=nil)
275
- return @skip_presence_fields||[] if !fields
276
- @skip_presence_fields = [fields].flatten
283
+ routes.push OpenStruct.new(klass: self, name: name, param: "#{name}_attributes", model: model, many?: many, key: "#{name.singularize}_id".to_sym, swaggerKey: swaggerKey, reflection: reflection)
277
284
  end
278
285
 
286
+
279
287
  def build_nested_attributes(routes,hash)
280
288
  # Recursively re-express the flat attributes hash from nested routes as nested
281
289
  # attributes that can be used to perform an update on the root model.
@@ -298,47 +306,6 @@ module IntrospectiveGrape
298
306
  end
299
307
 
300
308
 
301
- def find_leaves(routes, record, params)
302
- # Traverse down our route and find the leaf's siblings from its parent, e.g.
303
- # project/#/teams/#/team_users ~> project.find.teams.find.team_users.
304
- # (the traversal of the intermediate nodes occurs in find_leaf())
305
- return record if routes.size < 2 # the leaf is the root
306
- if record = find_leaf(routes, record, params)
307
- assoc = routes.last
308
- if assoc.many? && leaves = record.send( assoc.reflection.name ).includes( default_includes(assoc.model) )
309
- if (leaves.map(&:class) - [routes.last.model]).size > 0
310
- raise ActiveRecord::RecordNotFound.new("Records contain the wrong models, they should all be #{routes.last.model.name}, found #{records.map(&:class).map(&:name).join(',')}")
311
- end
312
-
313
- leaves
314
- else
315
- # has_one associations don't return a CollectionProxy and so don't support
316
- # eager loading.
317
- record.send( assoc.reflection.name )
318
- end
319
- end
320
- end
321
-
322
- def find_leaf(routes, record, params)
323
- return record unless routes.size > 1
324
- # For deeply nested routes we need to search from the root of the API to the leaf
325
- # of its nested associations in order to guarantee the validity of the relationship,
326
- # the authorization on the parent model, and the sanity of passed parameters.
327
- routes[1..-1].each_with_index do |r|
328
- if record && params[r.key]
329
- if ref = r.reflection
330
- record = record.send(ref.name).where( id: params[r.key] ).first
331
- end
332
- end
333
- end
334
-
335
- if params[routes.last.key] && record.class != routes.last.model
336
- raise ActiveRecord::RecordNotFound.new("No #{routes.last.model.name} with ID '#{params[routes.last.key]}'")
337
- end
338
-
339
- record
340
- end
341
-
342
309
 
343
310
  def generate_params(dsl, klass, action, model, fields)
344
311
  # We'll be doing a recursive walk (to handle nested attributes) down the
@@ -355,17 +322,15 @@ module IntrospectiveGrape
355
322
  # infer Grape's parameters
356
323
 
357
324
  (fields-[:id]).each do |field|
358
- if ( field.kind_of?(Hash) )
325
+ if field.kind_of?(Hash)
359
326
  generate_nested_params(dsl,klass,action,model,field)
327
+ elsif (action==:create && klass.param_required?(model,field) )
328
+ # All params are optional on an update, only require them during creation.
329
+ # Updating a record with new child models will have to rely on ActiveRecord
330
+ # validations:
331
+ dsl.requires field, type: klass.param_type(model,field)
360
332
  else
361
- if (action==:create && klass.param_required?(model,field) )
362
- # All params are optional on an update, only require them during creation.
363
- # Updating a record with new child models will have to rely on ActiveRecord
364
- # validations:
365
- dsl.requires field, type: klass.param_type(model,field)
366
- else
367
- dsl.optional field, type: klass.param_type(model,field)
368
- end
333
+ dsl.optional field, type: klass.param_type(model,field)
369
334
  end
370
335
  end
371
336
  end
@@ -374,32 +339,29 @@ module IntrospectiveGrape
374
339
  fields.each do |r,v|
375
340
  # Look at model.reflections to find the association's class name:
376
341
  reflection = r.to_s.sub(/_attributes$/,'') # the reflection name
377
- relation = begin
378
- model.reflections[reflection].class_name.constantize # its class
379
- rescue
380
- model
381
- end
342
+ relation = begin model.reflections[reflection].class_name.constantize rescue model end
382
343
 
383
344
  if is_file_attachment?(model,r)
384
345
  # Handle Carrierwave file upload fields
385
- if s = [:filename, :type, :name, :tempfile, :head]-v && s.present?
346
+ s = [:filename, :type, :name, :tempfile, :head]-v
347
+ if s.present?
386
348
  Rails.logger.warn "Missing required file upload parameters #{s} for uploader field #{r}"
387
349
  end
388
350
  elsif PLURAL_REFLECTIONS.include?( model.reflections[reflection].class )
389
351
  # In case you need a refresher on how these work:
390
352
  # http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
391
- dsl.optional r, type: Array do |dsl|
392
- klass.generate_params(dsl,klass,action,relation,v)
393
- klass.add_destroy_param(dsl,model,reflection) unless action==:create
353
+ dsl.optional r, type: Array do |dl|
354
+ klass.generate_params(dl,klass,action,relation,v)
355
+ klass.add_destroy_param(dl,model,reflection) unless action==:create
394
356
  end
395
357
  else
396
358
  # TODO: handle any remaining correctly. Presently defaults to a Hash
397
359
  # http://www.rubydoc.info/github/rails/rails/ActiveRecord/Reflection
398
360
  # ThroughReflection, HasOneReflection,
399
361
  # HasAndBelongsToManyReflection, BelongsToReflection
400
- dsl.optional r, type: Hash do |dsl|
401
- klass.generate_params(dsl,klass,action,relation,v)
402
- klass.add_destroy_param(dsl,model,reflection) unless action==:create
362
+ dsl.optional r, type: Hash do |dl|
363
+ klass.generate_params(dl,klass,action,relation,v)
364
+ klass.add_destroy_param(dl,model,reflection) unless action==:create
403
365
  end
404
366
  end
405
367
  end
@@ -407,8 +369,8 @@ module IntrospectiveGrape
407
369
 
408
370
  def is_file_attachment?(model,field)
409
371
  model.try(:uploaders) && model.uploaders[field.to_sym] || # carrierwave
410
- model.try(:paperclip_definitions) && model.paperclip_definitions[field.to_sym] || # paperclip
411
- model.send(:new).try(field).kind_of?(Paperclip::Attachment)
372
+ (model.try(:attachment_definitions) && model.attachment_definitions[field.to_sym]) ||
373
+ defined?(Paperclip::Attachment) && model.send(:new).try(field).kind_of?(Paperclip::Attachment)
412
374
  end
413
375
 
414
376
  def param_type(model,f)
@@ -416,8 +378,8 @@ module IntrospectiveGrape
416
378
  f = f.to_s
417
379
  db_type = (model.try(:columns_hash)||{})[f].try(:type)
418
380
 
419
- # Look for an override class from the model, check Pg2Ruby, use the database type,
420
- # or fail over to a String:
381
+ # Check if it's a file attachment, look for an override class from the model,
382
+ # check Pg2Ruby, use the database type, or fail over to a String:
421
383
  ( is_file_attachment?(model,f) && Rack::Multipart::UploadedFile ) ||
422
384
  (model.try(:attribute_param_types)||{})[f] ||
423
385
  Pg2Ruby[db_type] ||
@@ -441,5 +403,4 @@ module IntrospectiveGrape
441
403
 
442
404
  end
443
405
  end
444
-
445
406
  end
@@ -1,71 +1,40 @@
1
1
  require 'grape-swagger'
2
2
  require 'active_support' #/core_ext/module/aliasing'
3
- module IntrospectiveGrape::CamelSnake
4
- def snake_keys(data)
5
- if data.kind_of? Array
6
- data.map { |v| snake_keys(v) }
7
- elsif data.kind_of? Hash
8
- Hash[data.map {|k, v| [k.to_s.underscore, snake_keys(v)] }]
9
- else
10
- data
11
- end
12
- end
13
-
14
- def camel_keys(data)
15
- if data.kind_of? Array
16
- data.map { |v| camel_keys(v) }
17
- elsif data.kind_of?(Hash)
18
- Hash[data.map {|k, v| [k.to_s.camelize(:lower), camel_keys(v)] }]
19
- else
20
- data
21
- end
22
- end
23
- end
3
+ require 'camel_snake_keys'
24
4
 
25
- # Duck type Grape's built in JSON formatter to convert all snake case hash keys
26
- # to camel case.
27
- module Grape
28
- module Formatter
29
- module Json
5
+ if IntrospectiveGrape.config.camelize_parameters
6
+ # Camelize the parameters in the swagger documentation.
7
+ if Gem::Version.new( GrapeSwagger::VERSION ) <= Gem::Version.new('0.11.0')
8
+ Grape::API.class_eval do
30
9
  class << self
31
- include IntrospectiveGrape::CamelSnake
32
- def call(object, env)
33
- if object.respond_to?(:to_json)
34
- camel_keys(JSON.parse(object.to_json)).to_json
35
- else
36
- camel_keys(MultiJson.dump(object)).to_json
37
- end
38
- end
39
- end
40
- end
41
- end
42
- end
43
-
44
- # Camelize the parameters in the swagger documentation.
45
- module Grape
46
- class API
47
- class << self
48
- private
49
- def create_documentation_class_with_camelized
50
- doc = create_documentation_class_without_camelized
51
- doc.class_eval do
52
- class << self
53
- def parse_params_with_camelized(params, path, method, options = {})
54
- parsed_params = parse_params_without_camelized(params, path, method)
55
- parsed_params.each_with_index do |param|
56
- param[:name] = param[:name]
57
- .camelize(:lower)
58
- .gsub(/\[Destroy\]/,'[_destroy]')
10
+ private
11
+ def create_documentation_class_with_camelized
12
+ doc = create_documentation_class_without_camelized
13
+ doc.class_eval do
14
+ class << self
15
+ def parse_params_with_camelized(params, path, method, _options = {})
16
+ parsed_params = parse_params_without_camelized(params, path, method)
17
+ parsed_params.each_with_index do |param|
18
+ param[:name] = param[:name]
19
+ .camelize(:lower)
20
+ .gsub(/Destroy/,'_destroy')
21
+ end
22
+ parsed_params
59
23
  end
60
- parsed_params
61
- end
62
24
 
63
- alias_method_chain :parse_params, :camelized
25
+ alias_method_chain :parse_params, :camelized
26
+ end
64
27
  end
28
+ doc
65
29
  end
66
- doc
30
+ alias_method_chain :create_documentation_class, :camelized
67
31
  end
68
- alias_method_chain :create_documentation_class, :camelized
69
32
  end
33
+ else Gem::Version.new( GrapeSwagger::VERSION ) > Gem::Version.new('0.11.0')
34
+ # Grape::Swagger 0.20.xx is not yet compatible with Grape >0.14 and will alter
35
+ # the way it parses params, so will not be compatible with introspective_grape,
36
+ # and produces swagger docs for SwaggerUI 2.1.4 that don't appear to be
37
+ # backwards compatible swagger.js 2.0.41, so this is pending.
70
38
  end
39
+
71
40
  end