moonrope 1.3.3 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +47 -0
  4. data/MIT-LICENCE +20 -0
  5. data/README.md +24 -0
  6. data/bin/moonrope +28 -0
  7. data/docs/authentication.md +114 -0
  8. data/docs/controllers.md +106 -0
  9. data/docs/exceptions.md +27 -0
  10. data/docs/introduction.md +29 -0
  11. data/docs/structures.md +214 -0
  12. data/example/authentication.rb +50 -0
  13. data/example/controllers/meta_controller.rb +14 -0
  14. data/example/controllers/users_controller.rb +92 -0
  15. data/example/structures/pet_structure.rb +12 -0
  16. data/example/structures/user_structure.rb +35 -0
  17. data/lib/moonrope.rb +5 -4
  18. data/lib/moonrope/action.rb +170 -40
  19. data/lib/moonrope/authenticator.rb +42 -0
  20. data/lib/moonrope/base.rb +67 -6
  21. data/lib/moonrope/controller.rb +4 -2
  22. data/lib/moonrope/doc_context.rb +94 -0
  23. data/lib/moonrope/doc_server.rb +123 -0
  24. data/lib/moonrope/dsl/action_dsl.rb +159 -9
  25. data/lib/moonrope/dsl/authenticator_dsl.rb +35 -0
  26. data/lib/moonrope/dsl/base_dsl.rb +21 -18
  27. data/lib/moonrope/dsl/controller_dsl.rb +60 -9
  28. data/lib/moonrope/dsl/filterable_dsl.rb +27 -0
  29. data/lib/moonrope/dsl/structure_dsl.rb +28 -2
  30. data/lib/moonrope/errors.rb +13 -0
  31. data/lib/moonrope/eval_environment.rb +82 -3
  32. data/lib/moonrope/eval_helpers.rb +47 -8
  33. data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
  34. data/lib/moonrope/guard.rb +35 -0
  35. data/lib/moonrope/html_generator.rb +65 -0
  36. data/lib/moonrope/param_set.rb +11 -1
  37. data/lib/moonrope/rack_middleware.rb +66 -37
  38. data/lib/moonrope/railtie.rb +31 -14
  39. data/lib/moonrope/request.rb +43 -15
  40. data/lib/moonrope/structure.rb +100 -18
  41. data/lib/moonrope/structure_attribute.rb +39 -0
  42. data/lib/moonrope/version.rb +1 -1
  43. data/moonrope.gemspec +21 -0
  44. data/spec/spec_helper.rb +32 -0
  45. data/spec/specs/action_spec.rb +455 -0
  46. data/spec/specs/base_spec.rb +29 -0
  47. data/spec/specs/controller_spec.rb +31 -0
  48. data/spec/specs/param_set_spec.rb +31 -0
  49. data/templates/basic/_action_form.erb +77 -0
  50. data/templates/basic/_errors_table.erb +32 -0
  51. data/templates/basic/_structure_attributes_list.erb +55 -0
  52. data/templates/basic/action.erb +168 -0
  53. data/templates/basic/assets/lock.svg +3 -0
  54. data/templates/basic/assets/reset.css +101 -0
  55. data/templates/basic/assets/style.css +348 -0
  56. data/templates/basic/assets/tool.svg +4 -0
  57. data/templates/basic/assets/try.js +157 -0
  58. data/templates/basic/authenticator.erb +52 -0
  59. data/templates/basic/controller.erb +20 -0
  60. data/templates/basic/index.erb +114 -0
  61. data/templates/basic/layout.erb +46 -0
  62. data/templates/basic/structure.erb +23 -0
  63. data/test/test_helper.rb +81 -0
  64. data/test/tests/action_access_test.rb +63 -0
  65. data/test/tests/actions_test.rb +524 -0
  66. data/test/tests/authenticators_test.rb +87 -0
  67. data/test/tests/base_test.rb +35 -0
  68. data/test/tests/controllers_test.rb +49 -0
  69. data/test/tests/eval_environment_test.rb +136 -0
  70. data/test/tests/evel_helpers_test.rb +60 -0
  71. data/test/tests/examples_test.rb +11 -0
  72. data/test/tests/helpers_test.rb +97 -0
  73. data/test/tests/param_set_test.rb +44 -0
  74. data/test/tests/rack_middleware_test.rb +131 -0
  75. data/test/tests/request_test.rb +232 -0
  76. data/test/tests/structures_param_extensions_test.rb +159 -0
  77. data/test/tests/structures_test.rb +398 -0
  78. metadata +71 -56
@@ -0,0 +1,50 @@
1
+ authenticator :default do
2
+
3
+ description <<-DESCRIPTION
4
+ To authenticate to the API, you need to pass an appropriate API token with your
5
+ request. To find out how to obtain an API token, please refer to the Authentication
6
+ API documentaton which outlines the available methods available for this.
7
+ DESCRIPTION
8
+
9
+ header "X-Auth-Application", "The API application which is generated by us and provided to you.", :example => "abc123abc123abc123"
10
+ header "X-Auth-Token", "The API token for the user you wish to authenticate as.", :example => "abc123abc123abc123"
11
+
12
+ error "InvalidApplicationToken", "The application token provided in X-Auth-Application is not valid.", :attributes => {:token => "The token used to find the application"}
13
+ error "InvalidAuthToken", "The auth token provided in X-Auth-Token is not valid.", :attributes => {:token => "The token that was used"}
14
+ error "ExpiredAuthToken", "The auth token provided in X-Auth-Token has expired.", :attributes => {:expired_at => "The time the token expired"}
15
+
16
+ lookup do
17
+ if app_token = header['X-Auth-Application']
18
+ api_application = APIApplication.find_by_token(app_token)
19
+ if api_application.nil?
20
+ error "InvalidApplicationToken", :token => app_token
21
+ end
22
+
23
+ if auth_token = header['X-Auth-Token']
24
+ api_token = api_application.api_tokens.find_by_token(auth_token)
25
+ if api_token.nil? || api_token.inactive?
26
+ error "InvalidAuthToken", :token => auth_token
27
+ end
28
+
29
+ if api_token.expired?
30
+ error "ExpiredAuthToken", :expired_at => api_token.expired_at
31
+ end
32
+
33
+ api_token
34
+ end
35
+ end
36
+ end
37
+
38
+ rule :default, "NotAuthenticated", "Must be authenticated with a valid user API token." do
39
+ identity.is_a?(APIToken) && identity.user
40
+ end
41
+
42
+ rule :must_be_admin, "MustBeAdmin", "Must be authenticated as a valid admin user." do
43
+ identity.is_a?(APIToken) && identity.user && identity.user.admin?
44
+ end
45
+
46
+ rule :anonymous, "MustBeAnonymous", "Must not be authenticated (no auth headers provided)." do
47
+ identity == :anonymous
48
+ end
49
+
50
+ end
@@ -0,0 +1,14 @@
1
+ controller :meta do
2
+
3
+ friendly_name "Meta API"
4
+ description <<-DESC
5
+ The meta API provides you with access to information about the API itself.
6
+ DESC
7
+
8
+ action :version do
9
+ description "Return the current software version"
10
+ returns :string, :eg => "v1.2.3"
11
+ action { LlamaCom::VERSION }
12
+ end
13
+
14
+ end
@@ -0,0 +1,92 @@
1
+ controller :users do
2
+
3
+ friendly_name "Users API"
4
+ description <<-DESC
5
+ The Users API provides full access to manage the users
6
+ which exist on your account.
7
+ DESC
8
+
9
+ action :list do
10
+ title "List all users"
11
+ description "This action will return a list of all users which the authenticated user has access to."
12
+ param :page, "The page number", :default => 1, :type => Integer
13
+ param :per_page, "The number of items to return per page", :default => 30, :type => Integer
14
+ sortable :username, :id, :created_at, :updated_at
15
+ paginated
16
+ filterable do
17
+ attribute :username, :type => String
18
+ attribute :age, :type => Integer
19
+ attribute :admin, :type => :boolean
20
+ end
21
+ returns :array, :structure => :user
22
+ action do
23
+ paginate(User.all) do |user|
24
+ structure user, :return => true
25
+ end
26
+ end
27
+ end
28
+
29
+ action :show do
30
+ title "Get unit information"
31
+ param :username, "The user's username", :type => String, :required => true
32
+ returns :hash, :structure => :user, :structure_opts => {:paramable => true}
33
+ error "UserNotFound", "No user was found matching the given username", :attributes => {:username => "The username which was looked up"}
34
+ action do
35
+ if user = User.find_by_username(params.username)
36
+ structure user, :return => true
37
+ else
38
+ error 'UserNotFound', :username => params.username
39
+ end
40
+ end
41
+ end
42
+
43
+ shared_action :properties do
44
+ param :username, "The user's username", :type => String
45
+ param :first_name, "The user's first name", :type => String
46
+ param :last_name, "The user's last name", :type => String
47
+ param :email_address, "The user's e-mail address", :type => String
48
+ param :password, "The user's password", :type => String
49
+ param :admin, "Should this user be an admin?", :type => :boolean do |object, value|
50
+ if identity.admin?
51
+ object.admin = value
52
+ end
53
+ end
54
+ end
55
+
56
+ action :create do
57
+ title "Create a new user"
58
+ description <<-DESCRIPTION
59
+ This action will create a new user with the properties which have been provided.
60
+ DESCRIPTION
61
+ use :properties
62
+ returns :hash, :structure => :user, :structure_opts => {:full => true}
63
+ error "ValidationError", "The details provided were not sufficient to save the user", :attributes => {:errors => "An array of errors for each field"}
64
+ action do
65
+ user = User.new
66
+ if user.save
67
+ structure user, :return => true
68
+ else
69
+ error 'ValidationError', :errors => user.errors
70
+ end
71
+ end
72
+ end
73
+
74
+ action :update do
75
+ title "Update an existing user"
76
+ description "This action will update an existing user with the properties provided."
77
+ param :id, "The ID of the user to update", :type => Integer, :required => true
78
+ use :properties
79
+ returns :hash, :structure => :user, :structure_opts => {:full => true}
80
+ error 'UserNotFound', "The user specified could not be found", :attributes => {:id => "The ID that was looked up"}
81
+ error "ValidationError", "The details provided were not sufficient to save the user", :attributes => {:errors => "An array of errors for each field"}
82
+ action do
83
+ user = User.find_by_id(params.id) || error('UserNotFound', :id => params.id)
84
+ if user.save
85
+ structure user, :return => true
86
+ else
87
+ error 'ValidationError', :errors => user.errors
88
+ end
89
+ end
90
+ end
91
+
92
+ end
@@ -0,0 +1,12 @@
1
+ structure :pet do
2
+
3
+ # Uncomment the below line to stop this structure being documented.
4
+ # no_doc!
5
+
6
+ basic :id, "The ID of the pet", :type => Integer, :eg => 148
7
+ basic :name, "The name of the pet", :type => String, :eg => "Fido"
8
+ basic :color, "What color is the pet?", :type => String, :eg => "Green"
9
+
10
+ expansion :user, "The user who owns this pet", :type => Hash, :structure => :user
11
+
12
+ end
@@ -0,0 +1,35 @@
1
+ structure :user do
2
+
3
+ basic :id, "The user's internal system ID", :type => Integer, :eg => 123
4
+ basic :username, "The user's unique username", :type => String, :eg => "adamcooke"
5
+ group :name do
6
+ basic :first, "The user's first name", :type => String, :eg => "Adam", :source_attribute => :first_name
7
+ basic :last, "The user's last name", :type => String, :eg => "Cooke", :source_attribute => :last_name
8
+ end
9
+
10
+ full :admin, "Is this user an administrator?", :type => :boolean
11
+ full :age, "The user's age", :type => Integer, :doc => false
12
+ full :created_at, "The timestamp the user was created", :type => :timestamp
13
+ full :updated_at, "The timestamp the user was updated", :type => :timestamp
14
+
15
+ expansion :pets, "All pets that belong to this user", :structure => :pet, :type => Array
16
+
17
+ expansion :balance, "The user's balance", :type => Float, :eg => 12.50 do
18
+ o.user.balance
19
+ end
20
+
21
+ expansion :hidden, "This is a hidden expansion", :doc => false do
22
+ o.user.hidden
23
+ end
24
+
25
+ condition Proc.new { identity.admin? }, "Can only be accessed by API users with admin access" do
26
+ # This value will only be provided to users who are accesing the API with
27
+ # the permission to view
28
+ full :support_pin, "The PIN this user needs to use to access support", :type => String, :eg => "4953"
29
+ end
30
+
31
+ condition :default => :anonymous do
32
+ full :mask, "The unique mask that represents this user", :type => String, :eg => 'abc123abc123'
33
+ end
34
+
35
+ end
@@ -11,7 +11,8 @@ require 'moonrope/dsl/base_dsl'
11
11
  require 'moonrope/dsl/action_dsl'
12
12
  require 'moonrope/dsl/controller_dsl'
13
13
  require 'moonrope/dsl/structure_dsl'
14
-
14
+ require 'moonrope/dsl/authenticator_dsl'
15
+ require 'moonrope/authenticator'
15
16
  require 'moonrope/errors'
16
17
  require 'moonrope/eval_helpers'
17
18
  require 'moonrope/eval_environment'
@@ -26,13 +27,13 @@ require 'moonrope/version'
26
27
  require 'moonrope/railtie' if defined?(Rails)
27
28
 
28
29
  module Moonrope
29
-
30
+
30
31
  class << self
31
32
  attr_accessor :logger
32
-
33
+
33
34
  def logger
34
35
  @logger ||= Logger.new(STDOUT)
35
36
  end
36
37
  end
37
-
38
+
38
39
  end
@@ -1,3 +1,6 @@
1
+ require 'moonrope/dsl/action_dsl'
2
+ require 'moonrope/action_result'
3
+
1
4
  module Moonrope
2
5
  class Action
3
6
 
@@ -13,14 +16,35 @@ module Moonrope
13
16
  # @return [Hash] the params available for the action
14
17
  attr_reader :params
15
18
 
19
+ # @return [String] the title of the action
20
+ attr_accessor :title
21
+
16
22
  # @return [String] the description of the action
17
23
  attr_accessor :description
18
24
 
19
- # @return [Proc] the access check condition for the action
20
- attr_accessor :access
25
+ # @return [Array] the actual action blocks for the action
26
+ attr_accessor :actions
27
+
28
+ # @return [Symbol] the name of the authenticator for this action
29
+ attr_accessor :authenticator
30
+
31
+ # @return [Symbol] the name of the access rule for this action
32
+ attr_accessor :access_rule
33
+
34
+ # @return [Hash] the errors which can be retuend by this action
35
+ attr_accessor :errors
36
+
37
+ # @return [Hash] details of what will be returned on success
38
+ attr_accessor :returns
39
+
40
+ # @return [Bool] whether or not the action should be documented
41
+ attr_accessor :doc
42
+
43
+ # @return [Array] additional traits that have been applied to this action
44
+ attr_reader :traits
21
45
 
22
- # @return [Proc] the action for the action
23
- attr_accessor :action
46
+ # @return [Hash] a hash of filters that are applied
47
+ attr_reader :filters
24
48
 
25
49
  #
26
50
  # Initialize a new action
@@ -33,6 +57,10 @@ module Moonrope
33
57
  @controller = controller
34
58
  @name = name
35
59
  @params = {}
60
+ @errors = {}
61
+ @traits = []
62
+ @actions = []
63
+ @filters = {}
36
64
  @dsl = Moonrope::DSL::ActionDSL.new(self)
37
65
  @dsl.instance_eval(&block) if block_given?
38
66
  end
@@ -49,23 +77,51 @@ module Moonrope
49
77
  end
50
78
  end
51
79
 
80
+ #
81
+ # Return the authenticator that should be used when executing this action
82
+ #
83
+ # @return [Moonrope::Authenticator]
84
+ #
85
+ def authenticator_to_use
86
+ @authenticator_to_use ||= begin
87
+ if @authenticator
88
+ @controller.base.authenticators[@authenticator] || :not_found
89
+ elsif @controller.authenticator
90
+ @controller.base.authenticators[@controller.authenticator] || :not_found
91
+ else
92
+ @controller.base.authenticators[:default] || :none
93
+ end
94
+ end
95
+ end
96
+
97
+ #
98
+ # Return the access rule to use for this action#
99
+ #
100
+ # @return [Symbol]
101
+ #
102
+ def access_rule_to_use
103
+ @access_rule_to_use ||= access_rule || @controller.access_rule || :default
104
+ end
105
+
52
106
  #
53
107
  # Execute a block of code and catch approprite Moonrope errors and return
54
108
  # a result.
55
109
  #
56
- def convert_errors_to_action_result(&block)
110
+ def convert_errors_to_action_result(start_time = nil, &block)
57
111
  begin
58
112
  yield block
59
113
  rescue => exception
60
114
  case exception
61
115
  when Moonrope::Errors::RequestError
62
116
  result = ActionResult.new(self)
117
+ result.time = start_time ? (Time.now - start_time).round(2) : nil
63
118
  result.status = exception.status
64
119
  result.data = exception.data
65
120
  result
66
121
  else
67
122
  if error_block = @controller.base.external_errors[exception.class]
68
123
  result = ActionResult.new(self)
124
+ result.time = start_time ? (Time.now - start_time).round(2) : nil
69
125
  error_block.call(exception, result)
70
126
  result
71
127
  else
@@ -86,7 +142,7 @@ module Moonrope
86
142
  if request.is_a?(EvalEnvironment)
87
143
  eval_environment = request
88
144
  else
89
- eval_environment = EvalEnvironment.new(@controller.base, request)
145
+ eval_environment = EvalEnvironment.new(@controller.base, request, self)
90
146
  end
91
147
 
92
148
  #
@@ -95,27 +151,24 @@ module Moonrope
95
151
  #
96
152
  eval_environment.default_params = self.default_params
97
153
 
98
- #
99
- # Set the current action to the eval environment so it knows what action
100
- # invoked this.
101
- #
102
- eval_environment.action = self
154
+ start_time = Time.now
103
155
 
104
- convert_errors_to_action_result do
156
+ convert_errors_to_action_result(start_time) do
105
157
  #
106
158
  # Validate the parameters
107
159
  #
108
160
  self.validate_parameters(eval_environment.params)
109
161
 
110
- start_time = Time.now
111
-
112
162
  # Run before filters
113
163
  controller.before_actions_for(name).each do |action|
114
164
  eval_environment.instance_eval(&action.block)
115
165
  end
116
166
 
117
167
  # Run the actual action
118
- response = eval_environment.instance_eval(&action)
168
+ response = nil
169
+ actions.each do |action|
170
+ response = eval_environment.instance_exec(response, &action)
171
+ end
119
172
 
120
173
  # Calculate the length of time this request takes
121
174
  time_to_run = Time.now - start_time
@@ -144,34 +197,25 @@ module Moonrope
144
197
  if request.is_a?(EvalEnvironment)
145
198
  eval_environment = request
146
199
  else
147
- eval_environment = EvalEnvironment.new(@controller.base, request)
200
+ eval_environment = EvalEnvironment.new(@controller.base, request, self)
148
201
  end
149
202
 
150
- access_condition = self.access || @controller.access || @controller.base.default_access
151
-
152
- if eval_environment.auth
153
- # If there's no authentication object, access is permitted otherwise
154
- # we'll do the normal testing.
155
- if access_condition.is_a?(Proc)
156
- !!eval_environment.instance_exec(self, &access_condition)
157
- elsif access_condition.is_a?(Symbol)
158
- !!(eval_environment.auth.respond_to?(access_condition) && eval_environment.auth.send(access_condition))
159
- elsif access_condition.is_a?(Hash) && access_condition[:must_be] && access_condition[:with]
160
- !!(eval_environment.auth.is_a?(access_condition[:must_be]) &&
161
- eval_environment.auth.respond_to?(access_condition[:with]) &&
162
- eval_environment.auth.send(access_condition[:with])
163
- )
164
- elsif access_condition.is_a?(Hash) && access_condition[:must_be]
165
- !!(eval_environment.auth.is_a?(access_condition[:must_be]))
166
- elsif access_condition == true
167
- true
203
+ if authenticator_to_use.is_a?(Moonrope::Authenticator)
204
+ if rule = authenticator_to_use.rules[access_rule_to_use]
205
+ eval_environment.instance_exec(self, &rule[:block]) == true
168
206
  else
169
- false
207
+ if access_rule_to_use == :default
208
+ # The default rule on any authenticator will allow everything so we
209
+ # don't need to worry about this not being defined.
210
+ true
211
+ else
212
+ # If an access rule that doesn't exist has been requested, we will
213
+ # raise an internal error.
214
+ raise Moonrope::Errors::MissingAccessRule, "The rule '#{access_rule_to_use}' was not found on '#{authenticator_to_use.name}' authenticator"
215
+ end
170
216
  end
171
217
  else
172
- # No authentication object is available to test with. The result here
173
- # depends on whether or not an access condition has been defined or not.
174
- !access_condition
218
+ true
175
219
  end
176
220
  end
177
221
 
@@ -191,12 +235,98 @@ module Moonrope
191
235
  raise Moonrope::Errors::ParameterError, "`#{name}` parameter is invalid"
192
236
  end
193
237
 
194
- if value[:type] && param_set[name] && !param_set[name].is_a?(value[:type])
195
- raise Moonrope::Errors::ParameterError, "`#{name}` should be a `#{value[:type]}` but is a `#{param_set[name].class}`"
238
+ if value[:options].is_a?(Array) && param_set[name] && !value[:options].include?(param_set[name])
239
+ raise Moonrope::Errors::ParameterError, "`#{name}` must be one of #{value[:options].join(', ')}"
240
+ end
241
+
242
+ if value[:type] && param_set[name]
243
+ if value[:type] == :boolean
244
+ if BOOLEAN_VALUES.include?(param_set[name])
245
+ param_set._set_value(name, TRUE_LIKE_VALUES.include?(param_set[name]))
246
+ else
247
+ raise Moonrope::Errors::ParameterError, "`#{name}` should be a boolean value"
248
+ end
249
+ elsif value[:type].is_a?(Symbol) || value[:type].is_a?(String)
250
+ # Value is a symbol, nothing to do.
251
+ elsif !param_set[name].is_a?(value[:type])
252
+ raise Moonrope::Errors::ParameterError, "`#{name}` should be a `#{value[:type]}` but is a `#{param_set[name].class}`"
253
+ end
196
254
  end
197
255
  end
198
256
  true
199
257
  end
200
258
 
259
+ TRUE_LIKE_VALUES = ['true', '1', 1, true]
260
+ FALSE_LIKE_VALUES = ['false', '0', 0, false]
261
+ BOOLEAN_VALUES = TRUE_LIKE_VALUES + FALSE_LIKE_VALUES
262
+
263
+ #
264
+ # Does this action allow the user to include/exclude full attributes when
265
+ # calling this action?
266
+ #
267
+ def can_change_full?
268
+ if returns && opts = returns[:structure_opts]
269
+ opts[:paramable] == true ||
270
+ (opts[:paramable].is_a?(Hash) && opts[:paramable].has_key?(:full))
271
+ else
272
+ false
273
+ end
274
+ end
275
+
276
+ #
277
+ # Does this action include full attributes by default?
278
+ #
279
+ def includes_full_attributes?
280
+ if returns && opts = returns[:structure_opts]
281
+ (opts[:paramable].is_a?(Hash) && opts[:paramable][:full] == true) ||
282
+ opts[:full] == true
283
+ else
284
+ false
285
+ end
286
+ end
287
+
288
+ #
289
+ # Does this action allow the user to include/exclude expansions when calling
290
+ # this action?
291
+ #
292
+ def can_change_expansions?
293
+ if returns && opts = returns[:structure_opts]
294
+ opts[:paramable] == true ||
295
+ (opts[:paramable].is_a?(Hash) && opts[:paramable].has_key?(:expansions))
296
+ else
297
+ false
298
+ end
299
+ end
300
+
301
+ #
302
+ # Does this action include full attributes by default?
303
+ #
304
+ def includes_expansion?(expansion)
305
+ if returns && opts = returns[:structure_opts]
306
+ (opts[:paramable].is_a?(Hash) && opts[:paramable][:expansions] == true) ||
307
+ opts[:expansions] == true ||
308
+ (opts[:paramable].is_a?(Hash) && opts[:paramable][:expansions].is_a?(Array) && opts[:paramable][:expansions].include?(expansion)) ||
309
+ (opts[:expansions].is_a?(Array) && opts[:expansions].include?(expansion))
310
+ else
311
+ false
312
+ end
313
+ end
314
+
315
+ #
316
+ # Which expansions is the user permitted to include/exclude when calling this
317
+ # action.
318
+ #
319
+ def available_expansions
320
+ if returns && (structure = returns[:structure]) && can_change_expansions?
321
+ if returns[:structure_opts][:paramable].is_a?(Hash) && returns[:structure_opts][:paramable][:expansions].is_a?(Array)
322
+ returns[:structure_opts][:paramable][:expansions]
323
+ else
324
+ @controller.base.structure(structure).all_expansions
325
+ end
326
+ else
327
+ []
328
+ end
329
+ end
330
+
201
331
  end
202
332
  end