moonrope 1.4.1 → 2.0.0

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 (95) hide show
  1. checksums.yaml +4 -4
  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/html/assets/lock.svg +3 -0
  18. data/html/assets/reset.css +101 -0
  19. data/html/assets/style.css +348 -0
  20. data/html/assets/tool.svg +4 -0
  21. data/html/assets/try.js +151 -0
  22. data/html/authenticators/default.html +191 -0
  23. data/html/controllers/meta/version.html +144 -0
  24. data/html/controllers/meta.html +73 -0
  25. data/html/controllers/users/create.html +341 -0
  26. data/html/controllers/users/list.html +348 -0
  27. data/html/controllers/users/show.html +261 -0
  28. data/html/controllers/users/update.html +387 -0
  29. data/html/controllers/users.html +93 -0
  30. data/html/index.html +166 -0
  31. data/html/moonrope.txt +0 -0
  32. data/html/structures/pet.html +176 -0
  33. data/html/structures/user.html +338 -0
  34. data/lib/moonrope/action.rb +165 -37
  35. data/lib/moonrope/authenticator.rb +39 -0
  36. data/lib/moonrope/base.rb +24 -6
  37. data/lib/moonrope/controller.rb +4 -2
  38. data/lib/moonrope/doc_context.rb +94 -0
  39. data/lib/moonrope/doc_server.rb +123 -0
  40. data/lib/moonrope/dsl/action_dsl.rb +159 -9
  41. data/lib/moonrope/dsl/authenticator_dsl.rb +31 -0
  42. data/lib/moonrope/dsl/base_dsl.rb +21 -18
  43. data/lib/moonrope/dsl/controller_dsl.rb +60 -9
  44. data/lib/moonrope/dsl/filterable_dsl.rb +27 -0
  45. data/lib/moonrope/dsl/structure_dsl.rb +27 -2
  46. data/lib/moonrope/errors.rb +3 -0
  47. data/lib/moonrope/eval_environment.rb +82 -3
  48. data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
  49. data/lib/moonrope/eval_helpers.rb +28 -5
  50. data/lib/moonrope/guard.rb +35 -0
  51. data/lib/moonrope/html_generator.rb +65 -0
  52. data/lib/moonrope/param_set.rb +11 -1
  53. data/lib/moonrope/rack_middleware.rb +1 -1
  54. data/lib/moonrope/railtie.rb +31 -14
  55. data/lib/moonrope/request.rb +25 -14
  56. data/lib/moonrope/structure.rb +74 -11
  57. data/lib/moonrope/structure_attribute.rb +15 -0
  58. data/lib/moonrope/version.rb +1 -1
  59. data/lib/moonrope.rb +5 -4
  60. data/moonrope.gemspec +21 -0
  61. data/spec/spec_helper.rb +32 -0
  62. data/spec/specs/action_spec.rb +455 -0
  63. data/spec/specs/base_spec.rb +29 -0
  64. data/spec/specs/controller_spec.rb +31 -0
  65. data/spec/specs/param_set_spec.rb +31 -0
  66. data/templates/basic/_action_form.erb +77 -0
  67. data/templates/basic/_errors_table.erb +32 -0
  68. data/templates/basic/_structure_attributes_list.erb +55 -0
  69. data/templates/basic/action.erb +168 -0
  70. data/templates/basic/assets/lock.svg +3 -0
  71. data/templates/basic/assets/reset.css +101 -0
  72. data/templates/basic/assets/style.css +348 -0
  73. data/templates/basic/assets/tool.svg +4 -0
  74. data/templates/basic/assets/try.js +151 -0
  75. data/templates/basic/authenticator.erb +51 -0
  76. data/templates/basic/controller.erb +20 -0
  77. data/templates/basic/index.erb +114 -0
  78. data/templates/basic/layout.erb +46 -0
  79. data/templates/basic/structure.erb +23 -0
  80. data/test/test_helper.rb +81 -0
  81. data/test/tests/action_access_test.rb +63 -0
  82. data/test/tests/actions_test.rb +524 -0
  83. data/test/tests/authenticators_test.rb +87 -0
  84. data/test/tests/base_test.rb +35 -0
  85. data/test/tests/controllers_test.rb +49 -0
  86. data/test/tests/eval_environment_test.rb +136 -0
  87. data/test/tests/evel_helpers_test.rb +60 -0
  88. data/test/tests/examples_test.rb +11 -0
  89. data/test/tests/helpers_test.rb +97 -0
  90. data/test/tests/param_set_test.rb +44 -0
  91. data/test/tests/rack_middleware_test.rb +109 -0
  92. data/test/tests/request_test.rb +232 -0
  93. data/test/tests/structures_param_extensions_test.rb +159 -0
  94. data/test/tests/structures_test.rb +335 -0
  95. metadata +82 -48
@@ -1,3 +1,5 @@
1
+ require 'moonrope/structure_attribute'
2
+
1
3
  module Moonrope
2
4
  module DSL
3
5
  class StructureDSL
@@ -26,6 +28,10 @@ module Moonrope
26
28
  @conditions = []
27
29
  end
28
30
 
31
+ def no_doc!
32
+ @structure.doc = false
33
+ end
34
+
29
35
  def scope(options = {}, &block)
30
36
  scope_dsl = self.class.new(@structure)
31
37
  scope_dsl.options = options
@@ -35,12 +41,21 @@ module Moonrope
35
41
  def group(name, &block)
36
42
  scope_dsl = self.class.new(@structure)
37
43
  scope_dsl.groups = [@groups, name].flatten
44
+ scope_dsl.conditions = @conditions
38
45
  scope_dsl.instance_eval(&block)
39
46
  end
40
47
 
41
- def condition(condition, &block)
48
+ def condition(condition, description = nil, &block)
42
49
  scope_dsl = self.class.new(@structure)
50
+ if condition.is_a?(Hash) && condition.size == 1
51
+ condition = {:authenticator => condition.first[0], :access_rule => condition.first[1]}
52
+ elsif condition.is_a?(Symbol)
53
+ condition = {:authenticator => :default, :access_rule => condition}
54
+ else
55
+ condition = {:block => condition, :description => description}
56
+ end
43
57
  scope_dsl.conditions = [@conditions, condition].flatten
58
+ scope_dsl.groups = @groups
44
59
  scope_dsl.instance_eval(&block)
45
60
  end
46
61
 
@@ -61,6 +76,7 @@ module Moonrope
61
76
  attribute.source_attribute = options[:source_attribute]
62
77
  attribute.value = options[:value]
63
78
  attribute.example = options[:eg] || options[:example]
79
+ attribute.doc = options[:doc]
64
80
  attribute.groups = @groups
65
81
  attribute.conditions = @conditions
66
82
  @structure.attributes[type] << attribute
@@ -84,7 +100,16 @@ module Moonrope
84
100
 
85
101
  def expansion(name, *args, &block)
86
102
  if block_given?
87
- @structure.expansions[name] = {:block => block, :conditions => @conditions}
103
+ if args[0].is_a?(String)
104
+ attrs = args[1] || {}
105
+ attrs[:description] = args[0]
106
+ elsif args[0].is_a?(Hash)
107
+ attrs = atrs[0]
108
+ else
109
+ attrs = {}
110
+ end
111
+
112
+ @structure.expansions[name] = attrs.merge({:block => block, :conditions => @conditions})
88
113
  else
89
114
  attribute(:expansion, name, *args)
90
115
  end
@@ -5,6 +5,9 @@ module Moonrope
5
5
  end
6
6
 
7
7
  class HelperAlreadyDefined < Error; end
8
+ class MissingAuthenticator < Error; end
9
+ class MissingAccessRule < Error; end
10
+ class InvalidSharedAction < Error; end
8
11
 
9
12
  class RequestError < Error
10
13
  attr_reader :options
@@ -1,7 +1,11 @@
1
+ require 'moonrope/eval_helpers'
2
+ require 'moonrope/eval_helpers/filter_helper'
3
+
1
4
  module Moonrope
2
5
  class EvalEnvironment
3
6
 
4
7
  include Moonrope::EvalHelpers
8
+ include Moonrope::EvalHelpers::FilterHelper
5
9
 
6
10
  # @return [Moonrope::Base] the base object
7
11
  attr_reader :base
@@ -28,9 +32,10 @@ module Moonrope
28
32
  # @param request [Moonrope::Request]
29
33
  # @param accessors [Hash] additional variables which can be made available
30
34
  #
31
- def initialize(base, request, accessors = {})
35
+ def initialize(base, request, action = nil, accessors = {})
32
36
  @base = base
33
37
  @request = request
38
+ @action = action
34
39
  @accessors = accessors
35
40
  @default_params = {}
36
41
  reset
@@ -46,8 +51,8 @@ module Moonrope
46
51
  #
47
52
  # @return [Object] the authenticated object
48
53
  #
49
- def auth
50
- request ? request.authenticated_user : nil
54
+ def identity
55
+ request ? request.identity : nil
51
56
  end
52
57
 
53
58
  #
@@ -143,6 +148,54 @@ module Moonrope
143
148
  raise Moonrope::Errors::Error, "No structure found named '#{structure_name}'"
144
149
  end
145
150
 
151
+ if options.delete(:return)
152
+ if options.empty? && action && action.returns && action.returns[:structure_opts].is_a?(Hash)
153
+ options = action.returns[:structure_opts]
154
+ end
155
+ end
156
+
157
+ if request
158
+ if options[:paramable]
159
+ if options[:paramable].is_a?(Hash)
160
+ options[:expansions] = options[:paramable][:expansions]
161
+ options[:full] = options[:paramable][:full]
162
+ end
163
+
164
+ if options[:paramable] == true || options[:paramable].is_a?(Hash) && options[:paramable].has_key?(:expansions)
165
+ if request.params["_expansions"].is_a?(Array)
166
+ options[:expansions] = request.params["_expansions"].map(&:to_sym)
167
+ if options[:paramable].is_a?(Hash) && options[:paramable][:expansions].is_a?(Array)
168
+ whitelist = options[:paramable][:expansions]
169
+ options[:expansions].reject! { |e| !whitelist.include?(e) }
170
+ end
171
+ end
172
+
173
+ if request.params["_expansions"] == true
174
+ if options[:paramable].is_a?(Hash)
175
+ if options[:paramable][:expansions].is_a?(Array)
176
+ options[:expansions] = options[:paramable][:expansions]
177
+ elsif options[:paramable].has_key?(:expansions)
178
+ options[:expansions] = true
179
+ end
180
+ else
181
+ options[:expansions] = true
182
+ end
183
+ end
184
+
185
+ if request.params["_expansions"] == false
186
+ options[:expansions] = nil
187
+ end
188
+
189
+ end
190
+
191
+ if request.params.has?("_full")
192
+ if options[:paramable] == true || (options[:paramable].is_a?(Hash) && options[:paramable].has_key?(:full))
193
+ options[:full] = !!request.params["_full"]
194
+ end
195
+ end
196
+ end
197
+ end
198
+
146
199
  structure.hash(object, options.merge(:request => @request))
147
200
  end
148
201
 
@@ -169,5 +222,31 @@ module Moonrope
169
222
  self.structure_for(structure_name).is_a?(Moonrope::Structure)
170
223
  end
171
224
 
225
+ #
226
+ # Copy the list of parameters onto the given objectr
227
+ #
228
+ def copy_params_to(object, *params_to_copy)
229
+ if params_to_copy.first.is_a?(Hash)
230
+ options = params_to_copy.shift
231
+ if options[:from]
232
+ all_params = action.params.select { |_,p| p[:from_shared_action].include?(options[:from]) }
233
+ params_to_copy = params_to_copy + all_params.keys
234
+ end
235
+ end
236
+ params_to_copy.each do |param_name|
237
+ if param_definition = action.params[param_name]
238
+ if params.has?(param_name)
239
+ if param_definition[:apply]
240
+ instance_exec(object, params[param_name], &param_definition[:apply])
241
+ elsif object.respond_to?("#{param_name}=")
242
+ object.send("#{param_name}=", params[param_name])
243
+ end
244
+ end
245
+ else
246
+ raise Moonrope::Errors::Error, "Attempted to copy parameter #{parameter} to object but no definition exists"
247
+ end
248
+ end
249
+ end
250
+
172
251
  end
173
252
  end
@@ -0,0 +1,82 @@
1
+ module Moonrope
2
+ module EvalHelpers
3
+ module FilterHelper
4
+
5
+ #
6
+ # Return information which has been passed through the filterable filters
7
+ #
8
+ def filter(collection, &block)
9
+ filters_flags = {}
10
+ if params.filters.is_a?(Hash)
11
+ params.filters.each do |key, value|
12
+ options = {}
13
+ if filter = action.filters[key.to_sym]
14
+ if value.is_a?(Hash)
15
+ options[:operator] = value['operator'] ? value['operator'].to_sym : filter[:operators].first
16
+ options[:value] = value['value']
17
+ else
18
+ # If no hash is provided, we'll always attempt to use the first operator
19
+ # for the filter.
20
+ options[:operator] = filter[:operators].first
21
+ options[:value] = value
22
+ end
23
+
24
+ # Check that the operator is supported
25
+ unless filter[:operators].include?(options[:operator])
26
+ error 'FilterError', :issue_code => 'InvalidOperator', :issue_message => "The operator '#{options[:operator]}' is not supported for the '#{key}' attribute."
27
+ end
28
+
29
+ # Add this item to the flags which will be set at the end of the method
30
+ filters_flags[key] = options
31
+
32
+ # Do the filtering...
33
+ if filter[:block]
34
+ # If a block is provided, we'll refer to that to do the lookups
35
+ # and ensure that return the collection
36
+ collection = instance_exec(options[:operator], options[:value], collection, &filter[:block])
37
+ else
38
+ # If no block is provided, we'll fall back to Active Record like where
39
+ # lookups on the original collection.
40
+ case options[:operator]
41
+ when :eq
42
+ collection = collection.where("#{key} = ?", options[:value].to_s)
43
+ when :not_eq
44
+ collection = collection.where("#{key} != ?", options[:value].to_s)
45
+ when :starts_with
46
+ collection = collection.where("#{key} LIKE ?", "#{options[:value].to_s}%")
47
+ when :ends_with
48
+ collection = collection.where("#{key} LIKE ?", "%#{options[:value].to_s}")
49
+ when :gt
50
+ collection = collection.where("#{key} > ?", options[:value].to_s)
51
+ when :gte
52
+ collection = collection.where("#{key} >= ?", options[:value].to_s)
53
+ when :lt
54
+ collection = collection.where("#{key} < ?", options[:value].to_s)
55
+ when :lte
56
+ collection = collection.where("#{key} <= ?", options[:value].to_s)
57
+ when :in, :not_in
58
+ # For checking with arrays, we must make sure the user has sent us
59
+ # an array otherwise we'll raise an error.
60
+ unless options[:value].is_a?(Array)
61
+ error 'FilterError', :issue_code => "ArrayNeeded", :issue_message => "An array value is needed for '#{key}' for in/not_in operator"
62
+ end
63
+ values = options[:value].map(&:to_s)
64
+ collection = options[:operator] == :in ? collection.where(key => values) : collection.where.not(key => values)
65
+ else
66
+ error 'FilterError', :issue_code => "UnsupportedOperator", :issue_message => "The operator '#{options[:operator]}' is not supported."
67
+ end
68
+ end
69
+ else
70
+ # Raise an error if the attribute has been provided by the consumer
71
+ # that isn't supported by the action.
72
+ error 'FilterError', :issue_code => "UnsupportedAttribute", :issue_message => "The '#{key}' attribute is not supported for filtering on this action."
73
+ end
74
+ end
75
+ end
76
+ set_flag :filters, filters_flags
77
+ instance_exec(collection, &block)
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -7,7 +7,7 @@ module Moonrope
7
7
  # @param type [Symbol] the type of error to raise
8
8
  # @param message [String, Hash or Array] options to pass with the error (usually a message)
9
9
  #
10
- def error(type, code_or_message, message = nil)
10
+ def error(type, code_or_message = nil, message = nil)
11
11
  case type
12
12
  when :not_found then raise(Moonrope::Errors::NotFound, code_or_message)
13
13
  when :access_denied then raise(Moonrope::Errors::AccessDenied, code_or_message)
@@ -16,7 +16,11 @@ module Moonrope
16
16
  when :structured_error then structured_error(code_or_message, message)
17
17
  else
18
18
  if type.is_a?(String)
19
- structured_error(type, code_or_message, message.is_a?(Hash) ? message : {})
19
+ if code_or_message.is_a?(Hash)
20
+ structured_error(type, nil, code_or_message)
21
+ else
22
+ structured_error(type, code_or_message, message.is_a?(Hash) ? message : {})
23
+ end
20
24
  else
21
25
  raise Moonrope::Errors::RequestError, code_or_message
22
26
  end
@@ -31,7 +35,17 @@ module Moonrope
31
35
  # @param additional [Hash] additional data to return with the error
32
36
  #
33
37
  def structured_error(code, message, additional = {})
34
- raise Moonrope::Errors::StructuredError, additional.merge(:code => code, :message => message)
38
+ if action
39
+ if action.authenticator_to_use.is_a?(Moonrope::Authenticator)
40
+ errors = action.authenticator_to_use.errors.merge(action.errors)
41
+ else
42
+ errors = action.errors
43
+ end
44
+ if error = errors[code]
45
+ message = error[:description].gsub(/\{(\w+)\}/) { additional[$1.to_sym] }
46
+ end
47
+ end
48
+ raise Moonrope::Errors::StructuredError, {:code => code, :message => message}.merge(additional)
35
49
  end
36
50
 
37
51
  #
@@ -40,12 +54,21 @@ module Moonrope
40
54
  def paginate(collection, max_per_page = 60, &block)
41
55
  per_page = params.per_page || 30
42
56
  per_page = max_per_page if per_page < 1 || per_page > max_per_page
43
- paginated_results = collection.page(params.page).per(per_page)
44
- set_flag :paginated, {:page => params.page, :per_page => per_page, :total_pages => paginated_results.total_pages, :total_records => paginated_results.total_count}
57
+ paginated_results = collection.page(params.page || 1).per(per_page)
58
+ set_flag :paginated, {:page => params.page || 1, :per_page => per_page, :total_pages => paginated_results.total_pages, :total_records => paginated_results.total_count}
45
59
  paginated_results.to_a.map do |result|
46
60
  block.call(result)
47
61
  end
48
62
  end
49
63
 
64
+ #
65
+ # Return information sorted by appropriate values from parameters
66
+ #
67
+ def sort(collection, &block)
68
+ collection = collection.order(params.sort_by => params.order)
69
+ set_flag :sorted, {:by => params.sort_by, :order => params.order}
70
+ block.call(collection)
71
+ end
72
+
50
73
  end
51
74
  end
@@ -0,0 +1,35 @@
1
+ module ::Guard
2
+ class Moonrope < Plugin
3
+ def initialize(options)
4
+ super
5
+ @options = options
6
+ @options[:source] ||= "api"
7
+ @options[:destination] ||= ".apidoc"
8
+ end
9
+
10
+ def start
11
+ UI.info "Starting Moonrope Watching"
12
+ end
13
+
14
+ def reload
15
+ stop ; start
16
+ end
17
+
18
+ def run_all
19
+ generate_moonrope_docs
20
+ end
21
+
22
+ def run_on_modifications(paths)
23
+ generate_moonrope_docs
24
+ end
25
+
26
+ private
27
+
28
+ def generate_moonrope_docs
29
+ if File.exist?(File.join(@options[:destination], 'moonrope.txt'))
30
+ system("rm -Rf #{@options[:destination]}/*")
31
+ end
32
+ system("bundle exec moonrope #{@options[:source]} #{@options[:destination]}")
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,65 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+ require 'moonrope/doc_context'
4
+
5
+ module Moonrope
6
+ class HtmlGenerator
7
+
8
+ def initialize(base, template_root_path)
9
+ @base = base
10
+ @template_root_path = template_root_path
11
+ end
12
+
13
+ attr_reader :base
14
+ attr_reader :template_root_path
15
+
16
+ def host
17
+ ENV['MR_HOST']
18
+ end
19
+
20
+ def prefix
21
+ ENV['MR_PREFIX'] || 'api'
22
+ end
23
+
24
+ def version
25
+ ENV['MR_VERSION'] || 'v1'
26
+ end
27
+
28
+ def generate(output_path)
29
+ FileUtils.mkdir_p(output_path)
30
+ FileUtils.cp_r(File.join(@template_root_path, 'assets'), File.join(output_path, 'assets'))
31
+ FileUtils.touch(File.join(output_path, 'moonrope.txt'))
32
+ # Index
33
+ generate_file(output_path, "index.html", "index")
34
+ # Controllers
35
+ @base.controllers.select { |c| c.doc != false }.each do |controller|
36
+ generate_file(output_path, File.join("controllers", "#{controller.name}.html"), "controller", {:controller => controller})
37
+ controller.actions.select { |_,a| a.doc != false }.each do |_, action|
38
+ generate_file(output_path, File.join("controllers", controller.name.to_s, "#{action.name}.html"), "action", {:controller => controller, :action => action})
39
+ end
40
+ end
41
+ # Structures
42
+ @base.structures.select { |s| s.doc != false }.each do |structure|
43
+ generate_file(output_path, File.join("structures", "#{structure.name}.html"), "structure", {:structure => structure})
44
+ end
45
+ # Authenticators
46
+ @base.authenticators.values.select { |s| s.doc != false }.each do |authenticator|
47
+ generate_file(output_path, File.join("authenticators", "#{authenticator.name}.html"), "authenticator", {:authenticator => authenticator})
48
+ end
49
+
50
+ end
51
+
52
+ private
53
+
54
+ def generate_file(root_dir, output_file, template_file, variables = {})
55
+ file = DocContext.new(self, :html_extensions => true, :welcome_file => 'index', :output_file => output_file, :vars => variables)
56
+ file_string = file.render(File.join(@template_root_path, "#{template_file}.erb"))
57
+ layout = DocContext.new(self, :html_extensions => true, :welcome_file => 'index', :output_file => output_file, :vars => {:page_title => file.vars[:page_title], :active_nav =>file.vars[:active_nav], :body => file_string}).render(File.join(@template_root_path, "layout.erb"))
58
+ path = File.join(root_dir, output_file)
59
+ FileUtils.mkdir_p(File.dirname(path))
60
+ File.open(path, 'w') { |f| f.write(layout) }
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -29,7 +29,7 @@ module Moonrope
29
29
  #
30
30
  def _value_for(key)
31
31
  # Get the value from the params and defaults
32
- value = (@params[key.to_s] || @defaults[key.to_s])
32
+ value = @params.has_key?(key.to_s) ? @params[key.to_s] : @defaults[key.to_s]
33
33
  # Ensure that empty strings are actually nil.
34
34
  value = nil if value.is_a?(String) && value.length == 0
35
35
  # Return the value
@@ -39,6 +39,16 @@ module Moonrope
39
39
  alias_method :[], :_value_for
40
40
  alias_method :method_missing, :_value_for
41
41
 
42
+ #
43
+ # Set the value for a given param
44
+ #
45
+ # @param key [String]
46
+ # @param value [AnyObject]
47
+ #
48
+ def _set_value(name, value)
49
+ @params[name.to_s] = value
50
+ end
51
+
42
52
  #
43
53
  # Set the defaults for the param set
44
54
  #
@@ -79,7 +79,7 @@ module Moonrope
79
79
  result = request.execute
80
80
  json = result.to_json
81
81
  global_headers['Content-Length'] = json.bytesize.to_s
82
- [200, global_headers.merge(result.headers), [result.to_json]]
82
+ [200, global_headers.merge(result.headers), [json]]
83
83
  rescue JSON::ParserError => e
84
84
  [400, global_headers, [{:status => 'invalid-json', :details => e.message}.to_json]]
85
85
  rescue => e
@@ -1,19 +1,21 @@
1
+ require 'moonrope/doc_server'
2
+
1
3
  module Moonrope
2
4
  class Railtie < Rails::Railtie
3
5
 
4
6
  initializer 'moonrope.initialize' do |app|
5
7
 
6
8
  # Initialize a new moonrope base.
7
- app.config.moonrope = Moonrope::Base.load(Rails.root.join('api'))
9
+ Moonrope::Base.instance = Moonrope::Base.load(Rails.root.join('api'))
8
10
 
9
11
  # Set the logger
10
12
  Moonrope.logger = Rails.logger
11
13
 
12
14
  # Set the environment to match the Rails environment
13
- app.config.moonrope.environment = Rails.env.to_s
15
+ Moonrope::Base.instance.environment = Rails.env.to_s
14
16
 
15
17
  # Ensure all request use UTC
16
- app.config.moonrope.on_request = Proc.new do |base, env|
18
+ Moonrope::Base.instance.on_request = Proc.new do |base, env|
17
19
  Time.zone = 'UTC'
18
20
  end
19
21
 
@@ -22,29 +24,44 @@ module Moonrope
22
24
  Moonrope::Request.path_regex = app.config.moonrope_request_path_regex
23
25
  end
24
26
 
25
- # Catch ActiveRecord::RecordNotFound exception as a standard not-found error
26
- if defined?(ActiveRecord)
27
- app.config.moonrope.register_external_error ActiveRecord::RecordNotFound do |exception, result|
27
+ ActiveSupport.on_load(:active_record) do
28
+
29
+ # Catch ActiveRecord::RecordNotFound exception as a standard not-found error
30
+ Moonrope::Base.instance.register_external_error ActiveRecord::RecordNotFound do |exception, result|
28
31
  result.status = 'not-found'
29
32
  result.data = {:message => exception.message}
30
33
  end
31
34
 
32
- # Add a helper for auto setting parameters
33
- app.config.moonrope.dsl.instance_eval do
34
- helper :auto_set_params_for, :unloadable => false do |object|
35
- current_action = object.new_record? ? :create_only : :update_only
36
- request.action.params.select { |k,v| v[:set] == true || v[:set] == current_action }.keys.each do |param|
37
- object.send("#{param}=", params[param]) if params.has?(param)
38
- end
35
+ # Catch ActiveRecord::DeleteRestrictionError and raise an a DeleteRestrictionError
36
+ Moonrope::Base.instance.register_external_error ActiveRecord::DeleteRestrictionError do |exception, result|
37
+ result.status = 'error'
38
+ result.data = {:code => "DeleteRestrictionError", :message => "Object could not be deleted due to dependency"}
39
+ if exception.message =~ /([\w\-]+)\z/
40
+ result.data[:dependency] = $1
39
41
  end
40
42
  end
43
+
44
+ # Catch ActiveRecord::RecordInvalid and raise an a ValidationError
45
+ Moonrope::Base.instance.register_external_error ActiveRecord::RecordInvalid do |exception, result|
46
+ result.status = 'error'
47
+ errors = exception.record.errors.respond_to?(:to_api_hash) ? exception.record.errors.to_api_hash : exception.record.errors
48
+ result.data = {:code => "ValidationError", :message => "Object could not be saved due to a validation error", :errors => errors}
49
+ end
50
+
41
51
  end
42
52
 
53
+ # Insert the documentation middleware
54
+ app.middleware.use(
55
+ Moonrope::DocServer,
56
+ Moonrope::Base.instance,
57
+ :reload_on_each_request => !app.config.cache_classes
58
+ )
59
+
43
60
  # Insert the Moonrope middleware into the application's middleware
44
61
  # stack (at the bottom).
45
62
  app.middleware.use(
46
63
  Moonrope::RackMiddleware,
47
- app.config.moonrope,
64
+ Moonrope::Base.instance,
48
65
  :reload_on_each_request => !app.config.cache_classes
49
66
  )
50
67
 
@@ -3,10 +3,16 @@ module Moonrope
3
3
 
4
4
  class << self
5
5
  attr_accessor :path_regex
6
+ attr_accessor :path_prefix
6
7
 
7
8
  # @return [Regex] the regex which should be matched for all API requests
8
9
  def path_regex
9
- @path_regex ||= /\A\/api\/([\w\/\-\.]+)?/
10
+ @path_regex ||= /\A\/#{path_prefix}([\w\/\-\.]+)?/
11
+ end
12
+
13
+ # @return [Regex] the initial path of the prefix to be matched
14
+ def path_prefix
15
+ @path_prefix ||= /api\//
10
16
  end
11
17
  end
12
18
 
@@ -17,7 +23,7 @@ module Moonrope
17
23
  # @return [String] the name of the action which was requested
18
24
  attr_reader :action_name
19
25
  # @return [Object] the authenticated user
20
- attr_reader :authenticated_user
26
+ attr_reader :identity
21
27
 
22
28
  #
23
29
  # Initialize a new Moonrope::Request
@@ -80,25 +86,30 @@ module Moonrope
80
86
  # @return [Moonrope::ActionResult]
81
87
  #
82
88
  def execute
83
- eval_env = EvalEnvironment.new(@base, self)
84
- if @base.authenticator
89
+ eval_env = EvalEnvironment.new(@base, self, action)
90
+
91
+ if action.authenticator_to_use.is_a?(Moonrope::Authenticator)
85
92
  result = action.convert_errors_to_action_result do
86
- @authenticated_user = eval_env.instance_eval(&@base.authenticator)
87
- # If we are authenticated, check whether the action permits access to
88
- # this user, if not raise an error.
89
- if authenticated?
90
- unless action.check_access(eval_env) == true
91
- raise Moonrope::Errors::AccessDenied, "Access to #{controller.name}/#{action.name} is not permitted."
93
+ if block = action.authenticator_to_use.lookup
94
+ @identity = eval_env.instance_eval(&block)
95
+ end
96
+
97
+ unless action.check_access(eval_env) == true
98
+ if rule = action.authenticator_to_use.rules[action.access_rule_to_use]
99
+ eval_env.structured_error(rule[:error_code], rule[:description], :action => action.name, :controller => controller.name)
100
+ else
101
+ eval_env.structured_error("NotPermitted", "You are not permitted to access #{controller.name}/#{action.name}", :action => action.name, :controller => controller.name)
92
102
  end
93
103
  end
94
104
  end
95
105
 
96
106
  if result.is_a?(Moonrope::ActionResult)
97
- # If we already have a result, we should return it and no longer execute
98
- # this request.
99
107
  return result
100
108
  end
109
+ elsif action.authenticator_to_use == :not_found
110
+ raise Moonrope::Errors::MissingAuthenticator, "Wanted to use authenticator that was not defined."
101
111
  end
112
+
102
113
  action.execute(eval_env)
103
114
  end
104
115
 
@@ -132,7 +143,7 @@ module Moonrope
132
143
  # @return [Boolean]
133
144
  #
134
145
  def anonymous?
135
- authenticated_user.nil?
146
+ identity.nil?
136
147
  end
137
148
 
138
149
  #
@@ -141,7 +152,7 @@ module Moonrope
141
152
  # @return [Boolean]
142
153
  #
143
154
  def authenticated?
144
- !(authenticated_user.nil? || authenticated_user == false)
155
+ !(identity.nil? || identity == false)
145
156
  end
146
157
 
147
158
  #