introspective_grape 0.4.1 → 0.5.2

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 (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
+