haveapi 0.18.2 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/haveapi.gemspec +2 -1
  3. data/lib/haveapi/action.rb +72 -31
  4. data/lib/haveapi/authentication/base.rb +1 -1
  5. data/lib/haveapi/authentication/basic/provider.rb +2 -2
  6. data/lib/haveapi/authentication/chain.rb +4 -4
  7. data/lib/haveapi/authentication/oauth2/config.rb +52 -14
  8. data/lib/haveapi/authentication/oauth2/provider.rb +98 -17
  9. data/lib/haveapi/authentication/oauth2/revoke_endpoint.rb +36 -0
  10. data/lib/haveapi/authentication/token/config.rb +1 -0
  11. data/lib/haveapi/authorization.rb +19 -12
  12. data/lib/haveapi/client_examples/js_client.rb +11 -1
  13. data/lib/haveapi/client_examples/php_client.rb +43 -1
  14. data/lib/haveapi/context.rb +21 -2
  15. data/lib/haveapi/example.rb +9 -9
  16. data/lib/haveapi/hooks.rb +23 -23
  17. data/lib/haveapi/metadata.rb +1 -1
  18. data/lib/haveapi/model_adapter.rb +14 -14
  19. data/lib/haveapi/model_adapters/active_record.rb +20 -20
  20. data/lib/haveapi/output_formatter.rb +4 -4
  21. data/lib/haveapi/output_formatters/base.rb +1 -1
  22. data/lib/haveapi/parameters/resource.rb +22 -22
  23. data/lib/haveapi/parameters/typed.rb +7 -7
  24. data/lib/haveapi/params.rb +24 -22
  25. data/lib/haveapi/resource.rb +9 -3
  26. data/lib/haveapi/resources/action_state.rb +16 -16
  27. data/lib/haveapi/route.rb +3 -2
  28. data/lib/haveapi/server.rb +113 -98
  29. data/lib/haveapi/spec/mock_action.rb +7 -7
  30. data/lib/haveapi/spec/spec_methods.rb +8 -8
  31. data/lib/haveapi/tasks/yard.rb +2 -2
  32. data/lib/haveapi/validator.rb +13 -13
  33. data/lib/haveapi/validator_chain.rb +6 -6
  34. data/lib/haveapi/validators/acceptance.rb +2 -2
  35. data/lib/haveapi/validators/confirmation.rb +4 -4
  36. data/lib/haveapi/validators/exclusion.rb +4 -4
  37. data/lib/haveapi/validators/format.rb +4 -4
  38. data/lib/haveapi/validators/inclusion.rb +3 -3
  39. data/lib/haveapi/validators/length.rb +1 -1
  40. data/lib/haveapi/validators/numericality.rb +3 -3
  41. data/lib/haveapi/validators/presence.rb +2 -2
  42. data/lib/haveapi/version.rb +1 -1
  43. data/lib/haveapi/views/version_page/auth_body.erb +6 -4
  44. data/lib/haveapi/views/version_page/resource_body.erb +2 -0
  45. data/lib/haveapi.rb +1 -0
  46. data/spec/authorization_spec.rb +28 -28
  47. data/spec/envelope_spec.rb +4 -4
  48. data/spec/parameters/typed_spec.rb +3 -3
  49. data/spec/params_spec.rb +2 -2
  50. data/spec/validators/acceptance_spec.rb +2 -2
  51. data/spec/validators/confirmation_spec.rb +4 -4
  52. data/spec/validators/exclusion_spec.rb +2 -2
  53. data/spec/validators/format_spec.rb +5 -5
  54. data/spec/validators/inclusion_spec.rb +8 -8
  55. data/spec/validators/presence_spec.rb +1 -1
  56. metadata +19 -4
@@ -51,7 +51,17 @@ api.authenticate("token", {
51
51
  END
52
52
 
53
53
  when :oauth2
54
- '// OAuth2 is not supported by HaveAPI JavaScript client.'
54
+ <<END
55
+ #{init}
56
+ // The JavaScript client must be configured with OAuth2 access token, it does not
57
+ // support the authorization procedure to obtain a new access token.
58
+ var accessToken = {
59
+ access_token: "the access token"
60
+ };
61
+
62
+ // The client is authenticated immediately, no need for a callback
63
+ api.authenticate("oauth2", {access_token: accessToken});
64
+ END
55
65
  end
56
66
  end
57
67
 
@@ -35,7 +35,49 @@ $api->authenticate("token", ["token" => $savedToken]);
35
35
  END
36
36
 
37
37
  when :oauth2
38
- '// OAuth2 is not supported by HaveAPI PHP client.'
38
+ <<END
39
+ // OAuth2 requires session
40
+ session_start();
41
+
42
+ // Client instance
43
+ #{init}
44
+ // Check if we already have an access token
45
+ if (isset($_SESSION["access_token"])) {
46
+ // We're already authenticated, reuse the existing access token
47
+ $api->authenticate("oauth2", ["access_token" => $_SESSION["access_token"]]);
48
+
49
+ } else {
50
+ // Follow the OAuth2 authorization process to get an access token using
51
+ // authorization code
52
+ $api->authenticate("oauth2", [
53
+ // Client id and secret are given by the API server
54
+ "client_id" => "your client id",
55
+ "client_secret" => "your client secret",
56
+
57
+ // This example code should run on the URL below
58
+ "redirect_uri" => "https://your-client.tld/oauth2-callback",
59
+
60
+ // Scopes are specific to the API implementation
61
+ "scope" => "all",
62
+ ]);
63
+
64
+ $provider = $api->getAuthenticationProvider();
65
+
66
+ // We don't have authorization code yet, request one
67
+ if (!isset($_GET['code'])) {
68
+ // Redirect the user to the authorization endpoint
69
+ $provider->requestAuthorizationCode();
70
+ exit;
71
+
72
+ } else {
73
+ // Request access token using the token endpoint
74
+ $provider->requestAccessToken();
75
+
76
+ // Store the access token in the session
77
+ $_SESSION['access_token'] = $provider->jsonSerialize();
78
+ }
79
+ }
80
+ END
39
81
  end
40
82
  end
41
83
 
@@ -1,12 +1,12 @@
1
1
  module HaveAPI
2
2
  class Context
3
3
  attr_accessor :server, :version, :request, :resource, :action, :path, :args,
4
- :params, :current_user, :authorization, :endpoint,
4
+ :params, :current_user, :authorization, :endpoint, :resource_path,
5
5
  :action_instance, :action_prepare, :layout
6
6
 
7
7
  def initialize(server, version: nil, request: nil, resource: [], action: nil,
8
8
  path: nil, args: nil, params: nil, user: nil,
9
- authorization: nil, endpoint: nil)
9
+ authorization: nil, endpoint: nil, resource_path: [])
10
10
  @server = server
11
11
  @version = version
12
12
  @request = request
@@ -18,6 +18,7 @@ module HaveAPI
18
18
  @current_user = user
19
19
  @authorization = authorization
20
20
  @endpoint = endpoint
21
+ @resource_path = resource_path
21
22
  end
22
23
 
23
24
  def resolved_path
@@ -71,6 +72,24 @@ module HaveAPI
71
72
  path_for(action, call_path_params(action, obj))
72
73
  end
73
74
 
75
+ def path_params_from_args
76
+ ret = {}
77
+ return ret if args.nil?
78
+
79
+ my_args = args.clone
80
+
81
+ path.scan(/\{([a-zA-Z\-_]+)\}/) do |match|
82
+ path_param = match.first
83
+ ret[path_param] = my_args.shift
84
+ end
85
+
86
+ ret
87
+ end
88
+
89
+ def action_scope
90
+ resource_path.map(&:downcase).join('.') + '#' + action.action_name.underscore
91
+ end
92
+
74
93
  private
75
94
  def resolve_arg!(path, arg)
76
95
  path.sub!(/\{[a-zA-Z\-_]+\}/, arg.to_s)
@@ -58,15 +58,15 @@ module HaveAPI
58
58
  def describe(context)
59
59
  if provided?
60
60
  {
61
- title: @title,
62
- comment: @comment,
63
- path_params: @path_params,
64
- request: filter_input_params(context, @request),
65
- response: filter_output_params(context, @response),
66
- status: @status.nil? ? true : @status,
67
- message: @message,
68
- errors: @errors,
69
- http_status: @http_status || 200,
61
+ title: @title,
62
+ comment: @comment,
63
+ path_params: @path_params,
64
+ request: filter_input_params(context, @request),
65
+ response: filter_output_params(context, @response),
66
+ status: @status.nil? ? true : @status,
67
+ message: @message,
68
+ errors: @errors,
69
+ http_status: @http_status || 200,
70
70
  }
71
71
  else
72
72
  {}
data/lib/haveapi/hooks.rb CHANGED
@@ -79,15 +79,15 @@ module HaveAPI
79
79
  module Hooks
80
80
  INSTANCE_VARIABLE = '@_haveapi_hooks'
81
81
 
82
- # Register a hook defined by +klass+ with +name+.
83
- # +klass+ is an instance of Class, that is class name, not it's instance.
84
- # +opts+ is a hash and can have following keys:
85
- # - desc - why this hook exists, when it's called
86
- # - context - the context in which given blocks are called
87
- # - args - hash of block positional arguments
88
- # - kwargs - hash of block keyword arguments
89
- # - initial - hash of initial values
90
- # - ret - hash of return values
82
+ # Register a hook defined by `klass` with `name`.
83
+ # @param klass [Class] an instance of Class, that is class name, not it's instance
84
+ # @param opts [Hash]
85
+ # @option opts [String] :desc why this hook exists, when it's called
86
+ # @option opts [String] :context the context in which given blocks are called
87
+ # @option opts [Hash] :args hash of block positional arguments
88
+ # @option opts [Hash] :kwargs hash of block keyword arguments
89
+ # @option opts [Hash] :initial - hash of initial values
90
+ # @option opts [Hash] :ret hash of return values
91
91
  def self.register_hook(klass, name, opts = {})
92
92
  classified = hook_classify(klass)
93
93
  opts[:listeners] = []
@@ -101,13 +101,13 @@ module HaveAPI
101
101
  @hooks
102
102
  end
103
103
 
104
- # Connect class hook defined in +klass+ with +name+ to +block+.
105
- # +klass+ is a class name.
104
+ # Connect class hook defined in `klass` with `name` to `block`.
105
+ # `klass` is a class name.
106
106
  def self.connect_hook(klass, name, &block)
107
107
  @hooks[hook_classify(klass)][name][:listeners] << block
108
108
  end
109
109
 
110
- # Connect instance hook from instance +klass+ with +name+ to +block+.
110
+ # Connect instance hook from instance `klass` with `name` to `block`.
111
111
  def self.connect_instance_hook(instance, name, &block)
112
112
  hooks = instance.instance_variable_get(INSTANCE_VARIABLE)
113
113
 
@@ -120,11 +120,11 @@ module HaveAPI
120
120
  hooks[name][:listeners] << block
121
121
  end
122
122
 
123
- # Call all blocks that are connected to hook in +klass+ with +name+.
123
+ # Call all blocks that are connected to hook in `klass` with `name`.
124
124
  # +klass+ may be a class name or an object instance.
125
- # If +where+ is set, the blocks are executed in it with instance_exec.
126
- # +args+ is an array of arguments given to all blocks. The first argument
127
- # to all block is always a return value from previous block or +initial+,
125
+ # If `where` is set, the blocks are executed in it with instance_exec.
126
+ # `args` is an array of arguments given to all blocks. The first argument
127
+ # to all block is always a return value from previous block or `initial`,
128
128
  # which defaults to an empty hash.
129
129
  #
130
130
  # Blocks are executed one by one in the order they were connected.
@@ -193,17 +193,17 @@ module HaveAPI
193
193
  # Classes that define hooks must include this module.
194
194
  module Hookable
195
195
  module ClassMethods
196
- # Register a hook named +name+.
196
+ # Register a hook named `name`.
197
197
  def has_hook(name, opts = {})
198
198
  Hooks.register_hook(self.to_s, name, opts)
199
199
  end
200
200
 
201
- # Connect +block+ to registered hook with +name+.
201
+ # Connect `block` to registered hook with `name`.
202
202
  def connect_hook(name, &block)
203
203
  Hooks.connect_hook(self.to_s, name, &block)
204
204
  end
205
205
 
206
- # Call all hooks for +name+. see Hooks.call_for.
206
+ # Call all hooks for `name`. see {Hooks.call_for}.
207
207
  def call_hooks(*args, **kwargs)
208
208
  Hooks.call_for(self.to_s, *args, **kwargs)
209
209
  end
@@ -228,7 +228,7 @@ module HaveAPI
228
228
  Hooks.call_for(self.class, name, where, args: args, kwargs: kwargs, initial: initial)
229
229
  end
230
230
 
231
- # Call hooks for different +klass+.
231
+ # Call hooks for different `klass`.
232
232
  def call_hooks_as_for(klass, *args, **kwargs)
233
233
  ret = call_instance_hooks_as_for(klass, *args, **kwargs)
234
234
 
@@ -236,17 +236,17 @@ module HaveAPI
236
236
  call_class_hooks_as_for(klass.class, *args, **kwargs)
237
237
  end
238
238
 
239
- # Call only instance hooks for different +klass+.
239
+ # Call only instance hooks for different `klass`.
240
240
  def call_instance_hooks_as_for(klass, *args, **kwargs)
241
241
  Hooks.call_for(klass, *args, **kwargs)
242
242
  end
243
243
 
244
- # Call only class hooks for different +klass+.
244
+ # Call only class hooks for different `klass`.
245
245
  def call_class_hooks_as_for(klass, *args, **kwargs)
246
246
  Hooks.call_for(klass, *args, **kwargs)
247
247
  end
248
248
 
249
- # Connect instance level hook +name+ to +block+.
249
+ # Connect instance level hook `name` to `block`.
250
250
  def connect_hook(name, &block)
251
251
  Hooks.connect_instance_hook(self, name, &block)
252
252
  end
@@ -6,7 +6,7 @@ module HaveAPI
6
6
 
7
7
  def self.describe
8
8
  {
9
- namespace: namespace
9
+ namespace: namespace
10
10
  }
11
11
  end
12
12
 
@@ -2,8 +2,8 @@ module HaveAPI
2
2
  # Model adapters are used to automate handling of action
3
3
  # input/output.
4
4
  #
5
- # Adapters are chosen based on the +model+ set on a HaveAPI::Resource.
6
- # If no +model+ is specified, ModelAdapters::Hash is used as a default
5
+ # Adapters are chosen based on the `model` set on a HaveAPI::Resource.
6
+ # If no `model` is specified, ModelAdapters::Hash is used as a default
7
7
  # adapter.
8
8
  #
9
9
  # All model adapters are based on this class.
@@ -17,7 +17,7 @@ module HaveAPI
17
17
  ModelAdapter.adapters << Kernel.const_get(self.to_s)
18
18
  end
19
19
 
20
- # Returns an adapter suitable for +layout+ and +obj+.
20
+ # Returns an adapter suitable for `layout` and `obj`.
21
21
  # Adapters are iterated over and the first to return true to handle?()
22
22
  # is returned.
23
23
  def for(layout, obj)
@@ -41,21 +41,21 @@ module HaveAPI
41
41
  self::Output.new(*args)
42
42
  end
43
43
 
44
- # Override this method to load validators from +model+
45
- # to +params+.
44
+ # Override this method to load validators from `model`
45
+ # to `params`.
46
46
  def load_validators(model, params)
47
47
 
48
48
  end
49
49
 
50
50
  # Called when mounting the API. Model adapters may use this method
51
- # to add custom meta parameters to +action+. +direction+ is one of
52
- # +:input+ and +:output+.
51
+ # to add custom meta parameters to `action`. `direction` is one of
52
+ # `:input` and `:output`.
53
53
  def used_by(direction, action)
54
54
  case direction
55
- when :input
56
- self::Input.used_by(action)
57
- when :output
58
- self::Output.used_by(action)
55
+ when :input
56
+ self::Input.used_by(action)
57
+ when :output
58
+ self::Output.used_by(action)
59
59
  end
60
60
  end
61
61
  end
@@ -72,12 +72,12 @@ module HaveAPI
72
72
  end
73
73
 
74
74
  # Return true if input parameters contain parameter
75
- # with +name+.
75
+ # with `name`.
76
76
  def has_param?(name)
77
77
  @input.has_key?(name)
78
78
  end
79
79
 
80
- # Return parameter with +name+.
80
+ # Return parameter with `name`.
81
81
  def [](name)
82
82
  @input[name]
83
83
  end
@@ -101,7 +101,7 @@ module HaveAPI
101
101
  end
102
102
 
103
103
  # Return true if input parameters contain parameter
104
- # with +name+.
104
+ # with `name`.
105
105
  def has_param?(name)
106
106
 
107
107
  end
@@ -21,7 +21,7 @@ module HaveAPI::ModelAdapters
21
21
  module InstanceMethods
22
22
  # Helper method that sets correct ActiveRecord includes
23
23
  # according to the meta includes sent by the user.
24
- # +q+ is the model or partial AR query. If not set,
24
+ # `q` is the model or partial AR query. If not set,
25
25
  # action's model class is used instead.
26
26
  def with_includes(q = nil)
27
27
  q ||= self.class.model
@@ -179,14 +179,14 @@ END
179
179
  end
180
180
 
181
181
  {
182
- path_params: params.is_a?(Array) ? params : [params],
183
- resolved: true
182
+ path_params: params.is_a?(Array) ? params : [params],
183
+ resolved: true
184
184
  }
185
185
  end
186
186
 
187
187
  protected
188
- # Return representation of an associated resource +param+
189
- # with its instance in +val+.
188
+ # Return representation of an associated resource `param`
189
+ # with its instance in `val`.
190
190
  #
191
191
  # By default, it returns an unresolved resource, which contains
192
192
  # only object id and label. Resource will be resolved
@@ -204,11 +204,11 @@ END
204
204
  pass_includes = includes_pass_on_to(param.name)
205
205
 
206
206
  show = res_show.new(
207
- push_ins.request,
208
- push_ins.version,
209
- {},
210
- nil,
211
- @context
207
+ push_ins.request,
208
+ push_ins.version,
209
+ {},
210
+ nil,
211
+ @context
212
212
  )
213
213
  show.meta[:includes] = pass_includes
214
214
 
@@ -231,17 +231,17 @@ END
231
231
 
232
232
  else
233
233
  {
234
- param.value_id => val.send(res_output[param.value_id].db_name),
235
- param.value_label => val.send(res_output[param.value_label].db_name),
236
- _meta: {
237
- :path_params => args.is_a?(Array) ? args : [args],
238
- :resolved => false
239
- }
234
+ param.value_id => val.send(res_output[param.value_id].db_name),
235
+ param.value_label => val.send(res_output[param.value_label].db_name),
236
+ _meta: {
237
+ :path_params => args.is_a?(Array) ? args : [args],
238
+ :resolved => false
239
+ }
240
240
  }
241
241
  end
242
242
  end
243
243
 
244
- # Should an association with +name+ be resolved?
244
+ # Should an association with `name` be resolved?
245
245
  def includes_include?(name)
246
246
  includes = @context.action_instance.meta[:includes]
247
247
  return unless includes
@@ -324,7 +324,7 @@ END
324
324
 
325
325
  handle ::ActiveModel::Validations::ExclusionValidator do |v|
326
326
  opts = {
327
- values: v.options[:in].map { |v| v }
327
+ values: v.options[:in].map { |v| v }
328
328
  }
329
329
  opts[:message] = v.options[:message] if v.options[:message]
330
330
 
@@ -333,7 +333,7 @@ END
333
333
 
334
334
  handle ::ActiveModel::Validations::FormatValidator do |v|
335
335
  opts = {
336
- rx: v.options[:with]
336
+ rx: v.options[:with]
337
337
  }
338
338
  opts[:message] = v.options[:message] if v.options[:message]
339
339
 
@@ -342,7 +342,7 @@ END
342
342
 
343
343
  handle ::ActiveModel::Validations::InclusionValidator do |v|
344
344
  opts = {
345
- values: v.options[:in].map { |v| v }
345
+ values: v.options[:in].map { |v| v }
346
346
  }
347
347
  opts[:message] = v.options[:message] if v.options[:message]
348
348
 
@@ -49,10 +49,10 @@ module HaveAPI
49
49
  ret = {}
50
50
  ret[:version] = HaveAPI::PROTOCOL_VERSION if version
51
51
  ret.update({
52
- status: status,
53
- response: response,
54
- message: message,
55
- errors: errors
52
+ status: status,
53
+ response: response,
54
+ message: message,
55
+ errors: errors
56
56
  })
57
57
  ret
58
58
  end
@@ -13,7 +13,7 @@ module HaveAPI::OutputFormatters
13
13
 
14
14
  def handle?(type)
15
15
  @types.detect do |t|
16
- File.fnmatch(type, t)
16
+ File.fnmatch(type, t)
17
17
  end
18
18
  end
19
19
  end
@@ -43,35 +43,35 @@ module HaveAPI::Parameters
43
43
 
44
44
  def describe(context)
45
45
  val_path = context.path_for(
46
- @resource::Show,
47
- context.endpoint && context.action_prepare && context.layout == :object && context.call_path_params(context.action, context.action_prepare)
46
+ @resource::Show,
47
+ context.endpoint && context.action_prepare && context.layout == :object && context.call_path_params(context.action, context.action_prepare)
48
48
  )
49
49
  val_method = @resource::Index.http_method.to_s.upcase
50
50
 
51
51
  choices_path = context.path_for(
52
- @choices,
53
- context.endpoint && context.layout == :object && context.call_path_params(context.action, context.action_prepare)
52
+ @choices,
53
+ context.endpoint && context.layout == :object && context.call_path_params(context.action, context.action_prepare)
54
54
  )
55
55
  choices_method = @choices.http_method.to_s.upcase
56
56
 
57
57
  {
58
- required: required?,
59
- label: @label,
60
- description: @desc,
61
- type: 'Resource',
62
- resource: @resource_path,
63
- value_id: @value_id,
64
- value_label: @value_label,
65
- value: context.action_prepare && {
66
- path: val_path,
67
- method: val_method,
68
- help: "#{val_path}?method=#{val_method}",
69
- },
70
- choices: {
71
- path: choices_path,
72
- method: choices_method,
73
- help: "#{choices_path}?method=#{choices_method}"
74
- }
58
+ required: required?,
59
+ label: @label,
60
+ description: @desc,
61
+ type: 'Resource',
62
+ resource: @resource_path,
63
+ value_id: @value_id,
64
+ value_label: @value_label,
65
+ value: context.action_prepare && {
66
+ path: val_path,
67
+ method: val_method,
68
+ help: "#{val_path}?method=#{val_method}",
69
+ },
70
+ choices: {
71
+ path: choices_path,
72
+ method: choices_method,
73
+ help: "#{choices_path}?method=#{choices_method}"
74
+ }
75
75
  }
76
76
  end
77
77
 
@@ -93,7 +93,7 @@ module HaveAPI::Parameters
93
93
 
94
94
  def clean(raw)
95
95
  ::HaveAPI::ModelAdapter.for(
96
- show_action.input.layout, @resource.model
96
+ show_action.input.layout, @resource.model
97
97
  ).input_clean(@resource.model, raw, @extra)
98
98
  end
99
99
 
@@ -46,13 +46,13 @@ module HaveAPI::Parameters
46
46
 
47
47
  def describe(context)
48
48
  {
49
- required: required?,
50
- label: @label,
51
- description: @desc,
52
- type: @type ? @type.to_s : String.to_s,
53
- validators: @validators ? @validators.describe : {},
54
- default: @default,
55
- protected: @protected || false,
49
+ required: required?,
50
+ label: @label,
51
+ description: @desc,
52
+ type: @type ? @type.to_s : String.to_s,
53
+ validators: @validators ? @validators.describe : {},
54
+ default: @default,
55
+ protected: @protected || false,
56
56
  }
57
57
  end
58
58
 
@@ -178,12 +178,14 @@ module HaveAPI
178
178
 
179
179
  if @direction == :input
180
180
  ret[:parameters] = context.authorization.filter_input(
181
- @params,
182
- ModelAdapters::Hash.output(context, ret[:parameters]))
181
+ @params,
182
+ ModelAdapters::Hash.output(context, ret[:parameters])
183
+ )
183
184
  else
184
185
  ret[:parameters] = context.authorization.filter_output(
185
- @params,
186
- ModelAdapters::Hash.output(context, ret[:parameters]))
186
+ @params,
187
+ ModelAdapters::Hash.output(context, ret[:parameters])
188
+ )
187
189
  end
188
190
 
189
191
  ret
@@ -205,11 +207,11 @@ module HaveAPI
205
207
  end
206
208
 
207
209
  case layout
208
- when :object, :hash
209
- params[namespace] ||= {}
210
+ when :object, :hash
211
+ params[namespace] ||= {}
210
212
 
211
- when :object_list, :hash_list
212
- params[namespace] ||= []
213
+ when :object_list, :hash_list
214
+ params[namespace] ||= []
213
215
  end
214
216
  end
215
217
 
@@ -298,14 +300,14 @@ module HaveAPI
298
300
 
299
301
  def valid_layout?(params)
300
302
  case layout
301
- when :object, :hash
302
- params[namespace].is_a?(Hash)
303
+ when :object, :hash
304
+ params[namespace].is_a?(Hash)
303
305
 
304
- when :object_list, :hash_list
305
- params[namespace].is_a?(Array)
306
+ when :object_list, :hash_list
307
+ params[namespace].is_a?(Array)
306
308
 
307
- else
308
- false
309
+ else
310
+ false
309
311
  end
310
312
  end
311
313
 
@@ -313,16 +315,16 @@ module HaveAPI
313
315
  ns = namespace
314
316
 
315
317
  case layout
316
- when :object, :hash
317
- yield(ns ? params[namespace] : params)
318
+ when :object, :hash
319
+ yield(ns ? params[namespace] : params)
318
320
 
319
- when :object_list, :hash_list
320
- (ns ? params[namespace] : params).each do |object|
321
- yield(object)
322
- end
321
+ when :object_list, :hash_list
322
+ (ns ? params[namespace] : params).each do |object|
323
+ yield(object)
324
+ end
323
325
 
324
- else
325
- false
326
+ else
327
+ false
326
328
  end
327
329
  end
328
330
 
@@ -59,20 +59,21 @@ module HaveAPI
59
59
  singular ? resource_name.singularize.underscore : resource_name.tableize
60
60
  end
61
61
 
62
- def self.routes(prefix='/')
62
+ def self.routes(prefix='/', resource_path: [])
63
63
  ret = []
64
64
  prefix = "#{prefix}#{@route || rest_name}/"
65
+ new_resource_path = resource_path + [resource_name.underscore]
65
66
 
66
67
  actions do |a|
67
68
  # Call used_by for selected model adapters. It is safe to do
68
69
  # only when all classes are loaded.
69
70
  a.initialize
70
71
 
71
- ret << Route.new(a.build_route(prefix).chomp('/'), a)
72
+ ret << Route.new(a.build_route(prefix).chomp('/'), a, new_resource_path)
72
73
  end
73
74
 
74
75
  resources do |r|
75
- ret << {r => r.routes(prefix)}
76
+ ret << {r => r.routes(prefix, resource_path: new_resource_path)}
76
77
  end
77
78
 
78
79
  ret
@@ -83,6 +84,9 @@ module HaveAPI
83
84
 
84
85
  context.resource = self
85
86
 
87
+ orig_resource_path = context.resource_path
88
+ context.resource_path = context.resource_path + [resource_name.underscore]
89
+
86
90
  hash[:actions].each do |action, path|
87
91
  context.action = action
88
92
  context.path = path
@@ -97,6 +101,8 @@ module HaveAPI
97
101
  ret[:resources][resource.resource_name.underscore] = resource.describe(children, context)
98
102
  end
99
103
 
104
+ context.resource_path = orig_resource_path
105
+
100
106
  ret
101
107
  end
102
108