introspective_grape 0.4.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/lint.yml +23 -0
  3. data/.github/workflows/security.yml +32 -0
  4. data/.github/workflows/test.yml +18 -0
  5. data/.rubocop.yml +84 -1173
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +30 -0
  8. data/Gemfile +3 -5
  9. data/README.md +94 -63
  10. data/Rakefile +1 -6
  11. data/introspective_grape.gemspec +63 -53
  12. data/lib/introspective_grape/api.rb +166 -137
  13. data/lib/introspective_grape/camel_snake.rb +5 -3
  14. data/lib/introspective_grape/create_helpers.rb +3 -5
  15. data/lib/introspective_grape/doc.rb +19 -5
  16. data/lib/introspective_grape/filters.rb +98 -83
  17. data/lib/introspective_grape/formatter/camel_json.rb +2 -3
  18. data/lib/introspective_grape/helpers.rb +55 -48
  19. data/lib/introspective_grape/snake_params.rb +1 -2
  20. data/lib/introspective_grape/traversal.rb +56 -54
  21. data/lib/introspective_grape/validators.rb +23 -23
  22. data/lib/introspective_grape/version.rb +3 -1
  23. data/spec/dummy/.ruby-version +1 -0
  24. data/spec/dummy/Gemfile +5 -4
  25. data/spec/dummy/app/api/api_helpers.rb +1 -1
  26. data/spec/dummy/app/api/dummy/company_api.rb +10 -1
  27. data/spec/dummy/app/api/dummy/project_api.rb +1 -0
  28. data/spec/dummy/app/api/dummy/sessions.rb +1 -1
  29. data/spec/dummy/app/api/dummy_api.rb +8 -2
  30. data/spec/dummy/app/assets/config/manifest.js +4 -0
  31. data/spec/dummy/app/models/user.rb +1 -1
  32. data/spec/dummy/config/database.yml +1 -1
  33. data/spec/rails_helper.rb +1 -1
  34. data/spec/requests/company_api_spec.rb +9 -0
  35. metadata +153 -45
  36. data/.coveralls.yml +0 -2
  37. data/.travis.yml +0 -40
  38. data/bin/rails +0 -12
  39. data/gemfiles/Gemfile.rails.5.0.0 +0 -14
  40. data/gemfiles/Gemfile.rails.5.0.1 +0 -14
  41. data/gemfiles/Gemfile.rails.5.1.0 +0 -14
  42. data/gemfiles/Gemfile.rails.5.2.0 +0 -14
  43. data/gemfiles/Gemfile.rails.master +0 -14
@@ -1,113 +1,128 @@
1
- module IntrospectiveGrape::Filters
2
- #
3
- # Allow filters on all whitelisted model attributes (from api_params) and declare
4
- # customer filters for the index in a method.
5
- #
6
-
7
- def default_sort(*args)
8
- @default_sort ||= args
9
- end
10
-
11
- def custom_filter(*args)
12
- custom_filters( *args )
13
- end
1
+ module IntrospectiveGrape
2
+ module Filters
3
+ # Allow filters on all whitelisted model attributes (from api_params) and declare
4
+ # customer filters for the index in a method.
14
5
 
15
- def custom_filters(*args)
16
- @custom_filters ||= {}
17
- @custom_filters = Hash[*args].merge(@custom_filters) if args.present?
18
- @custom_filters
19
- end
6
+ def default_sort(*args)
7
+ @default_sort ||= args
8
+ end
20
9
 
21
- def filter_on(*args)
22
- filters( *args )
23
- end
10
+ def custom_filter(*args)
11
+ custom_filters( *args )
12
+ end
24
13
 
25
- def filters(*args)
26
- @filters ||= []
27
- @filters = @filters+args if args.present?
28
- @filters
29
- end
14
+ def custom_filters(*args)
15
+ @custom_filters ||= {}
16
+ @custom_filters = Hash[*args].merge(@custom_filters) if args.present?
17
+ @custom_filters
18
+ end
30
19
 
31
- def simple_filters(klass, model, api_params)
32
- @simple_filters ||= api_params.select {|p| p.is_a? Symbol }.select {|field|
33
- filters.include?(:all) || filters.include?(field)
34
- }.map { |field|
35
- (klass.param_type(model,field) == DateTime ? ["#{field}_start", "#{field}_end"] : field.to_s)
36
- }.flatten
37
- end
20
+ def filter_on(*args)
21
+ filters( *args )
22
+ end
38
23
 
39
- def timestamp_filter(klass,model,field)
40
- filter = field.sub(/_(end|start)\z/,'')
41
- if field =~ /_(end|start)\z/ && klass.param_type(model,filter) == DateTime
42
- filter
43
- else
44
- false
24
+ def filters(*args)
25
+ @filters ||= []
26
+ @filters += args if args.present?
27
+ @filters
45
28
  end
46
- end
47
29
 
48
- def identifier_filter(klass,model,field)
49
- if field.ends_with?('id') && klass.param_type(model,field) == Integer
50
- field
51
- else
52
- false
30
+ def simple_filters(klass, model, api_params)
31
+ @simple_filters ||= api_params.select {|p| p.is_a? Symbol }.select {|field|
32
+ filters.include?(:all) || filters.include?(field)
33
+ }.map {|field|
34
+ (klass.param_type(model, field) == DateTime ? ["#{field}_start", "#{field}_end"] : field.to_s)
35
+ }.flatten
53
36
  end
54
- end
55
37
 
56
- def declare_filter_params(dsl, klass, model, api_params)
57
- # Declare optional parameters for filtering parameters, create two parameters per
58
- # timestamp, a Start and an End, to apply a date range.
59
- simple_filters(klass, model, api_params).each do |field|
60
- if timestamp_filter(klass,model,field)
61
- terminal = field.ends_with?("_start") ? "initial" : "terminal"
62
- dsl.optional field, type: klass.param_type(model,field), description: "Constrain #{field} by #{terminal} date."
63
- elsif identifier_filter(klass,model,field)
64
- dsl.optional field, type: Array[Integer], coerce_with: ->(val) { val.split(',') }, description: "Filter by a comma separated list of integers."
38
+ def timestamp_filter(klass, model, field)
39
+ filter = field.sub(/_(end|start)\z/, '')
40
+ if field =~ /_(end|start)\z/ && klass.param_type(model, filter) == DateTime
41
+ filter
65
42
  else
66
- dsl.optional field, type: klass.param_type(model,field), description: "Filter on #{field} by value."
43
+ false
67
44
  end
68
45
  end
69
46
 
70
- custom_filters.each do |filter,details|
71
- dsl.optional filter, details
72
- end
47
+ def declare_filter_params(dsl, klass, model, api_params)
48
+ # Declare optional parameters for filtering parameters, create two parameters per
49
+ # timestamp, a Start and an End, to apply a date range.
50
+ simple_filters(klass, model, api_params).each do |field|
51
+ declare_simple_filter(dsl, klass, model, field)
52
+ end
73
53
 
74
- dsl.optional :filter, type: String, description: "JSON of conditions for query. If you're familiar with ActiveRecord's query conventions you can build more complex filters, e.g. against included child associations, e.g. {\"<association_name>_<parent>\":{\"field\":\"value\"}}" if filters.include?(:all) || filters.include?(:filter)
75
- end
54
+ custom_filters.each do |filter, details|
55
+ dsl.optional filter, details
56
+ end
76
57
 
58
+ dsl.optional :filter, type: String, description: filter_doc if special_filter_enabled?(filters)
59
+ end
77
60
 
78
- def apply_simple_filter(klass, model, params, records, field)
79
- return records if params[field].blank?
61
+ def declare_simple_filter(dsl, klass, model, field)
62
+ if timestamp_filter(klass, model, field)
63
+ dsl.optional field, type: klass.param_type(model, field), description: "Constrain #{field} by #{humanize_date_range(field)} date."
64
+ elsif identifier_filter?(model, field)
65
+ dsl.optional field, type: Array[String], coerce_with: ->(val) { val.split(',') }, description: 'Filter by a comma separated list of unique identifiers.'
66
+ else
67
+ dsl.optional field, type: klass.param_type(model, field), description: "Filter on #{field} by value."
68
+ end
69
+ end
80
70
 
81
- if timestamp_filter(klass,model,field)
82
- op = field.ends_with?("_start") ? ">=" : "<="
83
- records.where("#{timestamp_filter(klass,model,field)} #{op} ?", Time.zone.parse(params[field]))
84
- elsif model.respond_to?("#{field}=")
85
- records.send("#{field}=", params[field])
86
- else
87
- records.where(field => params[field])
71
+ def humanize_date_range(field)
72
+ field.ends_with?('_start') ? 'initial' : 'terminal'
88
73
  end
89
- end
90
74
 
91
- def apply_filter_params(klass, model, api_params, params, records)
92
- records = records.order(default_sort) if default_sort.present?
75
+ def identifier_filter?(model, field)
76
+ true if field.ends_with?('id') && %i(integer uuid).include?(model.columns_hash[field]&.type)
77
+ end
93
78
 
94
- simple_filters(klass, model, api_params).each do |field|
95
- records = apply_simple_filter(klass, model, params, records, field)
79
+ def special_filter_enabled?(filters)
80
+ filters.include?(:all) || filters.include?(:filter)
96
81
  end
97
82
 
98
- klass.custom_filters.each do |filter,_details|
99
- records = records.send(filter, params[filter])
83
+ def filter_doc
84
+ <<-STR
85
+ JSON of conditions for query. If you're familiar with ActiveRecord's query conventions you can build more complex filters, i.e. against included child associations, e.g.: {\"&lt;association_name&gt;_&lt;parent&gt;\":{\"field\":\"value\"}}
86
+ STR
100
87
  end
101
88
 
102
- if params[:filter].present?
103
- filters = JSON.parse( params[:filter].delete('\\') )
104
- filters.each do |key, value|
105
- records = records.where(key => value) if value.present?
89
+ def apply_simple_filter(klass, model, params, records, field)
90
+ return records if params[field].blank?
91
+
92
+ if timestamp_filter(klass, model, field)
93
+ op = field.ends_with?('_start') ? '>=' : '<='
94
+ records.where("#{timestamp_filter(klass, model, field)} #{op} ?", Time.zone.parse(params[field]))
95
+ elsif model.respond_to?("#{field}=")
96
+ records.send("#{field}=", params[field])
97
+ else
98
+ records.where(field => params[field])
106
99
  end
107
100
  end
108
101
 
109
- records.where( JSON.parse(params[:query]) ) if params[:query].present?
102
+ def apply_filter_params(klass, model, api_params, params, records)
103
+ records = records.order(default_sort) if default_sort.present?
110
104
 
111
- records
105
+ simple_filters(klass, model, api_params).each do |field|
106
+ records = apply_simple_filter(klass, model, params, records, field)
107
+ end
108
+
109
+ klass.custom_filters.each do |filter, _details|
110
+ records = records.send(filter, params[filter])
111
+ end
112
+
113
+ records = apply_filters(records, params[:filter])
114
+ records.where( JSON.parse(params[:query]) ) if params[:query].present?
115
+ records
116
+ end
117
+
118
+ def apply_filters(records, filters)
119
+ if filters.present?
120
+ filters = JSON.parse( filters.delete('\\') )
121
+ filters.each do |key, value|
122
+ records = records.where(key => value) if value.present?
123
+ end
124
+ end
125
+ records
126
+ end
112
127
  end
113
128
  end
@@ -9,9 +9,8 @@ module IntrospectiveGrape
9
9
  # We only need to parse(object.to_json) like this if it isn't already
10
10
  # a native hash (or array of them), i.e. we have to parse Grape::Entities
11
11
  # and other formatter facades:
12
- unless (object.is_a?(Array) && object.first.is_a?(Hash)) || object.is_a?(Hash)
13
- object = JSON.parse(object.to_json) if object.respond_to?(:to_json)
14
- end
12
+ has_hash = (object.is_a?(Array) && object.first.is_a?(Hash)) || object.is_a?(Hash)
13
+ object = JSON.parse(object.to_json) if object.respond_to?(:to_json) && !has_hash
15
14
  CamelSnakeKeys.camel_keys(object)
16
15
  end
17
16
 
@@ -1,63 +1,70 @@
1
- module IntrospectiveGrape::Helpers
2
- API_ACTIONS = [:index,:show,:create,:update,:destroy].freeze
3
- def authentication_method=(method)
4
- # IntrospectiveGrape::API.authentication_method=
5
- @authentication_method = method
6
- end
1
+ module IntrospectiveGrape
2
+ module Helpers
3
+ API_ACTIONS = %i(index show create update destroy).freeze
7
4
 
8
- def authentication_method(context)
9
- # Default to "authenticate!" or as grape docs once suggested, "authorize!"
10
- if @authentication_method
11
- @authentication_method
12
- elsif context.respond_to?('authenticate!')
13
- 'authenticate!'
14
- elsif context.respond_to?('authorize!')
15
- 'authorize!'
5
+ def authentication_method=(method)
6
+ # IntrospectiveGrape::API.authentication_method=
7
+ @authentication_method = method
16
8
  end
17
- end
18
-
19
- def paginate(args={})
20
- @pagination = args
21
- end
22
9
 
23
- def pagination
24
- @pagination
25
- end
10
+ def authentication_method(context)
11
+ # Default to "authenticate!" or as grape docs once suggested, "authorize!"
12
+ if @authentication_method
13
+ @authentication_method
14
+ elsif context.respond_to?('authenticate!')
15
+ 'authenticate!'
16
+ elsif context.respond_to?('authorize!')
17
+ 'authorize!'
18
+ end
19
+ end
26
20
 
27
- def exclude_actions(model, *args)
28
- @exclude_actions ||= {}; @exclude_actions[model.name] ||= []
29
- args.flatten!
30
- args = API_ACTIONS if args.include?(:all)
31
- args = [] if args.include?(:none)
21
+ def paginate(args={})
22
+ @pagination = args
23
+ end
32
24
 
33
- undefined_actions = args.compact-API_ACTIONS
34
- raise "#{model.name} defines invalid actions: #{undefined_actions}" if undefined_actions.present?
25
+ def pagination
26
+ @pagination
27
+ end
35
28
 
36
- @exclude_actions[model.name] = args.present? ? args.compact : @exclude_actions[model.name] || []
37
- end
29
+ def exclude_actions(model, *args)
30
+ args = all_or_none(args)
31
+ @exclude_actions ||= {}
32
+ @exclude_actions[model.name] ||= []
38
33
 
39
- def include_actions(model, *args)
40
- @exclude_actions ||= {}; @exclude_actions[model.name] ||= []
41
- @exclude_actions[model.name] = API_ACTIONS-exclude_actions(model, args)
42
- end
34
+ undefined_actions = args - API_ACTIONS
35
+ raise "#{model.name} defines invalid actions: #{undefined_actions}" if undefined_actions.present?
43
36
 
37
+ @exclude_actions[model.name] = args.present? ? args : @exclude_actions[model.name]
38
+ end
44
39
 
45
- def default_includes(model, *args)
46
- @default_includes ||= {}
47
- @default_includes[model.name] = args.present? ? args.flatten : @default_includes[model.name] || []
48
- end
40
+ def all_or_none(args=[])
41
+ args.flatten!&.compact!
42
+ args = API_ACTIONS if args.include?(:all)
43
+ args = [] if args.include?(:none)
44
+ args
45
+ end
49
46
 
50
- def whitelist(whitelist=nil)
51
- return @whitelist if !whitelist
52
- @whitelist = whitelist
53
- end
47
+ def include_actions(model, *args)
48
+ @exclude_actions ||= {}
49
+ @exclude_actions[model.name] ||= []
50
+ @exclude_actions[model.name] = API_ACTIONS - exclude_actions(model, args)
51
+ end
54
52
 
55
- def skip_presence_validations(fields=nil)
56
- return @skip_presence_fields||[] if !fields
57
- @skip_presence_fields = [fields].flatten
58
- end
53
+ def default_includes(model, *args)
54
+ @default_includes ||= {}
55
+ @default_includes[model.name] = args.present? ? args.flatten : @default_includes[model.name] || []
56
+ end
59
57
 
58
+ def whitelist(whitelist=nil)
59
+ return @whitelist unless whitelist
60
60
 
61
- end
61
+ @whitelist = whitelist
62
+ end
62
63
 
64
+ def skip_presence_validations(fields=nil)
65
+ return @skip_presence_fields || [] unless fields
63
66
 
67
+ @skip_presence_fields = [fields].flatten
68
+ end
69
+ end
70
+ end
@@ -1,12 +1,11 @@
1
1
  module IntrospectiveGrape
2
2
  module SnakeParams
3
-
4
3
  def snake_params_before_validation
5
4
  before_validation do
6
5
  # We have to snake case the Rack params then re-assign @params to the
7
6
  # request.params, because of the I-think-very-goofy-and-inexplicable
8
7
  # way Grape interacts with both independently of each other
9
- (CamelSnakeKeys.snake_keys(params)||{}).each do |k,v|
8
+ (CamelSnakeKeys.snake_keys(params) || {}).each do |k, v|
10
9
  request.delete_param(k.camelize(:lower))
11
10
  request.update_param(k, v)
12
11
  end
@@ -1,54 +1,56 @@
1
- module IntrospectiveGrape::Traversal
2
- # For deeply nested endpoints we want to present the record being affected, these
3
- # methods traverse down from the parent instance to the child model associations
4
- # of the deeply nested route.
5
-
6
- def find_leaves(routes, record, params)
7
- # Traverse down our route and find the leaf's siblings from its parent, e.g.
8
- # project/#/teams/#/team_users ~> project.find.teams.find.team_users
9
- # (the traversal of the intermediate nodes occurs in find_leaf())
10
- return record if routes.size < 2 # the leaf is the root
11
- record = find_leaf(routes, record, params)
12
- if record
13
- assoc = routes.last
14
- if assoc.many?
15
- leaves = record.send( assoc.reflection.name ).includes( default_includes(assoc.model) )
16
- verify_records_found(leaves, routes)
17
- leaves
18
- else
19
- # has_one associations don't return a CollectionProxy and so don't support
20
- # eager loading.
21
- record.send( assoc.reflection.name )
22
- end
23
- end
24
- end
25
-
26
- def verify_records_found(leaves, routes)
27
- unless (leaves.map(&:class) - [routes.last.model]).empty?
28
- raise ActiveRecord::RecordNotFound.new("Records contain the wrong models, they should all be #{routes.last.model.name}, found #{records.map(&:class).map(&:name).join(',')}")
29
- end
30
- end
31
-
32
- def find_leaf(routes, record, params)
33
- return record unless routes.size > 1
34
- # For deeply nested routes we need to search from the root of the API to the leaf
35
- # of its nested associations in order to guarantee the validity of the relationship,
36
- # the authorization on the parent model, and the sanity of passed parameters.
37
- routes[1..-1].each_with_index do |r|
38
- if record && params[r.key]
39
- ref = r.reflection
40
- record = record.send(ref.name).where( id: params[r.key] ).first if ref
41
- end
42
- end
43
-
44
- verify_record_found(routes, params, record)
45
- record
46
- end
47
-
48
- def verify_record_found(routes, params, record)
49
- if params[routes.last.key] && record.class != routes.last.model
50
- raise ActiveRecord::RecordNotFound.new("No #{routes.last.model.name} with ID '#{params[routes.last.key]}'")
51
- end
52
- end
53
-
54
- end
1
+ module IntrospectiveGrape
2
+ module Traversal
3
+ # For deeply nested endpoints we want to present the record being affected, these
4
+ # methods traverse down from the parent instance to the child model associations
5
+ # of the deeply nested route.
6
+
7
+ def find_leaves(routes, record, params)
8
+ # Traverse down our route and find the leaf's siblings from its parent, e.g.
9
+ # project/#/teams/#/team_users ~> project.find.teams.find.team_users
10
+ # (the traversal of the intermediate nodes occurs in find_leaf())
11
+ return record if routes.size < 2 # the leaf is the root
12
+
13
+ record = find_leaf(routes, record, params) || return
14
+
15
+ assoc = routes.last
16
+ if assoc.many?
17
+ leaves = record.send( assoc.reflection.name ).includes( default_includes(assoc.model) )
18
+ verify_records_found(leaves, routes)
19
+ leaves
20
+ else
21
+ # has_one associations don't return a CollectionProxy and so don't support
22
+ # eager loading.
23
+ record.send( assoc.reflection.name )
24
+ end
25
+ end
26
+
27
+ def verify_records_found(leaves, routes)
28
+ return if (leaves.map(&:class) - [routes.last.model]).empty?
29
+
30
+ raise ActiveRecord::RecordNotFound.new("Records contain the wrong models, they should all be #{routes.last.model.name}, found #{records.map(&:class).map(&:name).join(',')}")
31
+ end
32
+
33
+ def find_leaf(routes, record, params)
34
+ return record unless routes.size > 1
35
+
36
+ # For deeply nested routes we need to search from the root of the API to the leaf
37
+ # of its nested associations in order to guarantee the validity of the relationship,
38
+ # the authorization on the parent model, and the sanity of passed parameters.
39
+ routes[1..-1].each do |r|
40
+ if record && params[r.key]
41
+ ref = r.reflection
42
+ record = record.send(ref.name).where( id: params[r.key] ).first if ref
43
+ end
44
+ end
45
+
46
+ verify_record_found(routes, params, record)
47
+ record
48
+ end
49
+
50
+ def verify_record_found(routes, params, record)
51
+ return unless params[routes.last.key] && record.class != routes.last.model
52
+
53
+ raise ActiveRecord::RecordNotFound.new("No #{routes.last.model.name} with ID '#{params[routes.last.key]}'")
54
+ end
55
+ end
56
+ end
@@ -1,34 +1,34 @@
1
1
  require 'grape/validations'
2
- module Grape::Validators
3
-
4
- class Json < Grape::Validations::Base
5
- def validate_param!(field, params)
6
- begin
7
- JSON.parse( params[field] )
8
- rescue
9
- fail Grape::Exceptions::Validation, params: [@scope.full_name(field)], message: 'must be valid JSON!'
2
+ module Grape
3
+ module Validators
4
+ class Json < Grape::Validations::Base
5
+ def validate_param!(field, params)
6
+ begin
7
+ JSON.parse( params[field] )
8
+ rescue StandardError
9
+ raise Grape::Exceptions::Validation, params: [@scope.full_name(field)], message: 'must be valid JSON!'
10
+ end
10
11
  end
11
12
  end
12
- end
13
13
 
14
- class JsonArray < Grape::Validations::Base
15
- def validate_param!(field, params)
16
- begin
17
- raise unless JSON.parse( params[field] ).kind_of? Array
18
- rescue
19
- fail Grape::Exceptions::Validation, params: [@scope.full_name(field)], message: 'must be a valid JSON array!'
14
+ class JsonArray < Grape::Validations::Base
15
+ def validate_param!(field, params)
16
+ begin
17
+ raise unless JSON.parse( params[field] ).is_a? Array
18
+ rescue StandardError
19
+ raise Grape::Exceptions::Validation, params: [@scope.full_name(field)], message: 'must be a valid JSON array!'
20
+ end
20
21
  end
21
22
  end
22
- end
23
23
 
24
- class JsonHash < Grape::Validations::Base
25
- def validate_param!(field, params)
26
- begin
27
- raise unless JSON.parse( params[field] ).kind_of? Hash
28
- rescue
29
- fail Grape::Exceptions::Validation, params: [@scope.full_name(field)], message: 'must be a valid JSON hash!'
24
+ class JsonHash < Grape::Validations::Base
25
+ def validate_param!(field, params)
26
+ begin
27
+ raise unless JSON.parse( params[field] ).is_a? Hash
28
+ rescue StandardError
29
+ raise Grape::Exceptions::Validation, params: [@scope.full_name(field)], message: 'must be a valid JSON hash!'
30
+ end
30
31
  end
31
32
  end
32
33
  end
33
-
34
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module IntrospectiveGrape
2
- VERSION = "0.4.1".freeze
4
+ VERSION = '0.5.2'
3
5
  end
@@ -0,0 +1 @@
1
+ 2.6.0
data/spec/dummy/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source 'https://rubygems.org'
2
- gem 'rails'
2
+ gem 'rails', '5.2.6'
3
3
 
4
- #gem 'byebug'
4
+ gem 'byebug'
5
5
  gem 'camel_snake_keys'
6
6
 
7
7
  gem 'devise'
@@ -12,10 +12,11 @@ gem 'grape'
12
12
  gem 'grape-entity'
13
13
  gem 'grape-kaminari'
14
14
  gem 'grape-swagger'
15
- gem 'introspective_grape', git: 'https://github.com/buermann/introspective_grape', branch: 'grape-1.2'
15
+ gem 'grape-swagger-entity'
16
+ gem 'introspective_grape', path: '~/Dropbox/contrib/introspective_grape'
16
17
 
17
18
  gem 'paperclip'
18
19
  gem 'pundit'
19
20
 
20
21
  gem 'rack-cors'
21
- gem 'sqlite3', '< 1.4.0'
22
+ gem 'sqlite3'
@@ -2,7 +2,7 @@ module ApiHelpers
2
2
  def current_user
3
3
  params[:api_key].present? && @user = User.find_by_authentication_token(params[:api_key])
4
4
  # for testing in situ
5
- #@user = User.find_or_create_by(email: 'test@test.com', superuser: true, authentication_token: '1234567890', first_name: "First", last_name: "Last")
5
+ @user = User.find_or_create_by(email: 'test@test.com', superuser: true, authentication_token: '1234567890', first_name: "First", last_name: "Last")
6
6
  end
7
7
 
8
8
  def authenticate!
@@ -5,7 +5,7 @@ class Dummy::CompanyAPI < IntrospectiveGrape::API
5
5
 
6
6
  desc "Test default values in an extra endpoint"
7
7
  params do
8
- optional :boolean_default, type: Virtus::Attribute::Boolean, default: false
8
+ optional :boolean_default, type: Boolean, default: false
9
9
  optional :string_default, type: String, default: "foo"
10
10
  optional :integer_default, type: Integer, default: 123
11
11
  end
@@ -14,6 +14,15 @@ class Dummy::CompanyAPI < IntrospectiveGrape::API
14
14
  present params
15
15
  end
16
16
 
17
+ desc "Test kaminari pagination in a custom index"
18
+ params do
19
+ use :pagination
20
+ end
21
+ get '/paginated/list' do
22
+ authorize Company.new, :index?
23
+ companies = Company.all
24
+ present paginate(companies), using: CompanyEntity
25
+ end
17
26
  end
18
27
 
19
28
  class CompanyEntity < Grape::Entity
@@ -1,4 +1,5 @@
1
1
  class Dummy::ProjectAPI < IntrospectiveGrape::API
2
+ include Grape::Kaminari
2
3
 
3
4
  default_includes Project, :owner, :admins, :user_project_jobs, project_jobs: [:job], teams: [:team_users]
4
5
  default_includes Team, :team_users
@@ -1,4 +1,4 @@
1
- class Dummy::Sessions < Grape::API
1
+ class Dummy::Sessions < Grape::API #::Instance
2
2
 
3
3
  resource :sessions do
4
4
 
@@ -1,9 +1,14 @@
1
- class DummyAPI < Grape::API
1
+ require 'byebug'
2
+ require 'grape-kaminari'
3
+ class DummyAPI < Grape::API #::Instance
4
+ include Grape::Kaminari
5
+
2
6
  version 'v1', using: :path
3
7
  format :json
4
8
  formatter :json, IntrospectiveGrape::Formatter::CamelJson
5
9
  default_format :json
6
10
 
11
+
7
12
  include ErrorHandlers
8
13
  helpers PermissionsHelper
9
14
  helpers ApiHelpers
@@ -32,7 +37,8 @@ class DummyAPI < Grape::API
32
37
 
33
38
  # Mount every api endpoint under app/api/dummy/.
34
39
  Dir.glob(Rails.root+"app"+"api"+'dummy'+'*.rb').each do |f|
35
- api = "Dummy::#{File.basename(f, '.rb').camelize.sub(/Api$/,'API')}".constantize
40
+ api = "Dummy::#{File.basename(f, '.rb').camelize.sub(/Api$/,'API')}"
41
+ api = api.constantize
36
42
  mount api if api.respond_to? :endpoints
37
43
  end
38
44
 
@@ -0,0 +1,4 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../javascripts .js
3
+ //= link_directory ../stylesheets .css
4
+