rocket_pants 1.4.1 → 1.5.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.
data/lib/rocket_pants.rb CHANGED
@@ -7,7 +7,8 @@ require 'moneta'
7
7
  require 'moneta/memory'
8
8
 
9
9
  module RocketPants
10
- require 'rocket_pants/exceptions'
10
+ require 'rocket_pants/error'
11
+ require 'rocket_pants/errors'
11
12
 
12
13
  # Set up the routing in advance.
13
14
  require 'rocket_pants/routing'
@@ -39,6 +40,7 @@ module RocketPants
39
40
  autoload :UrlFor, 'rocket_pants/controller/url_for'
40
41
 
41
42
  mattr_accessor :caching_enabled, :header_metadata
43
+
42
44
  self.caching_enabled = false
43
45
  self.header_metadata = false
44
46
 
@@ -51,6 +53,39 @@ module RocketPants
51
53
  def cache
52
54
  @@cache ||= Moneta::Memory.new
53
55
  end
56
+
57
+ def env
58
+ @@env ||= default_env
59
+ end
60
+
61
+ def env=(value)
62
+ value = value.presence && ActiveSupport::StringInquirer.new(value)
63
+ @@env = value
64
+ end
65
+
66
+ def default_env
67
+ env = Rails.env.to_s if defined?(Rails.env)
68
+ env ||= ENV['RAILS_ENV'].presence || ENV['RACK_ENV'].presence || "development"
69
+ ActiveSupport::StringInquirer.new env
70
+ end
71
+
72
+ def default_pass_through_errors
73
+ env.development? || env.test?
74
+ end
75
+
76
+ def pass_through_errors
77
+ if defined?(@@pass_through_errors) && [true, false].include?(@@pass_through_errors)
78
+ @@pass_through_errors
79
+ else
80
+ @@pass_through_errors = default_pass_through_errors
81
+ end
82
+ end
83
+ alias pass_through_errors? pass_through_errors
84
+
85
+ def pass_through_errors=(value)
86
+ @@pass_through_errors = value
87
+ end
88
+
54
89
  end
55
90
 
56
91
  end
@@ -0,0 +1,19 @@
1
+ # Provides a bunch of default active record integration entry points.
2
+ module RocketPants
3
+ module ActiveRecordIntegration
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ map_error! ActiveRecord::RecordNotFound, RocketPants::NotFound
8
+ map_error!(ActiveRecord::RecordNotSaved) { RocketPants::InvalidResource.new nil }
9
+ map_error! ActiveRecord::RecordInvalid do |exception|
10
+ RocketPants::InvalidResource.new exception.record.errors
11
+ end
12
+ end
13
+
14
+ ActiveSupport.on_load :active_record do
15
+ RocketPants::Base.send :include, RocketPants::ActiveRecordIntegration
16
+ end
17
+
18
+ end
19
+ end
@@ -1,4 +1,4 @@
1
- require 'rocket_pants/exceptions'
1
+ require 'rocket_pants/errors'
2
2
 
3
3
  module RocketPants
4
4
 
@@ -2,7 +2,7 @@ require 'active_support/core_ext/class/attribute'
2
2
  require 'active_support/core_ext/object/blank'
3
3
  require 'active_support/core_ext/string/inflections'
4
4
 
5
- require 'rocket_pants/exceptions'
5
+ require 'rocket_pants/errors'
6
6
 
7
7
  require 'api_smith'
8
8
  require 'will_paginate/collection'
@@ -13,8 +13,15 @@ module RocketPants
13
13
 
14
14
  module ClassMethods
15
15
 
16
- def map_error!(from, to)
17
- error_mapping[from] = to
16
+ # Declares that a given error class (e.g. ActiveRecord::RecordNotFound)
17
+ # should map to a second value - either an Exception class Or, more usefully,
18
+ # a callable object.
19
+ # @param [Class] from the exception class to map from.
20
+ # @param [Class, #call] to the callable to map to, or a callback block.
21
+ def map_error!(from, to = nil, &blk)
22
+ to = (to || blk)
23
+ raise ArgumentError, "Either an option must be provided or a block given." unless to
24
+ error_mapping[from] = (to || blk)
18
25
  rescue_from from, :with => :render_error
19
26
  end
20
27
 
@@ -47,7 +54,8 @@ module RocketPants
47
54
  # TODO: Add in notification hooks for non-standard exceptions.
48
55
  name = lookup_error_name(exception)
49
56
  message = (exception.message == exception.class.name) ? 'An unknown error has occurred.' : exception.message
50
- I18n.t name, lookup_error_context(exception).reverse_merge(:scope => :"rocket_pants.errors", :default => message)
57
+ context = lookup_error_context(exception).reverse_merge(:scope => :"rocket_pants.errors", :default => message)
58
+ I18n.t name, context.except(:metadata)
51
59
  end
52
60
 
53
61
  # Lookup error name will automatically handle translating errors to a
@@ -77,12 +85,17 @@ module RocketPants
77
85
  exception.respond_to?(:context) ? exception.context : {}
78
86
  end
79
87
 
80
- # Returns extra error details for a given object, making it useable
81
- # for hooking in external exceptions.
82
88
  def lookup_error_extras(exception)
83
89
  {}
84
90
  end
85
91
 
92
+ # Returns extra error details for a given object, making it useable
93
+ # for hooking in external exceptions.
94
+ def lookup_error_metadata(exception)
95
+ context = lookup_error_context exception
96
+ context.fetch(:metadata, {}).merge lookup_error_extras(exception)
97
+ end
98
+
86
99
  # Renders an exception as JSON using a nicer version of the error name and
87
100
  # error message, following the typically error standard as laid out in the JSON
88
101
  # api design.
@@ -95,13 +108,18 @@ module RocketPants
95
108
  klass < StandardError and error_mapping.has_key?(klass)
96
109
  end
97
110
  if normalised_class
98
- exception = error_mapping[normalised_class].new(exception.message)
111
+ mapped = error_mapping[normalised_class]
112
+ if mapped.respond_to?(:call)
113
+ exception = mapped.call(exception)
114
+ else
115
+ exception = mapped.new exception.message
116
+ end
99
117
  end
100
118
  self.status = lookup_error_status(exception)
101
119
  render_json({
102
120
  :error => lookup_error_name(exception).to_s,
103
121
  :error_description => lookup_error_message(exception)
104
- }.merge(lookup_error_extras(exception)))
122
+ }.merge(lookup_error_metadata(exception)))
105
123
  end
106
124
 
107
125
  end
@@ -8,7 +8,7 @@ module RocketPants
8
8
  include ActiveSupport::Rescuable
9
9
 
10
10
  DEFAULT_NOTIFIER_CALLBACK = lambda do |controller, exception, req|
11
- # Do nothing by default...
11
+ # Does nothing
12
12
  end
13
13
 
14
14
  NAMED_NOTIFIER_CALLBACKS = {
@@ -52,6 +52,7 @@ module RocketPants
52
52
  def process_action(*args)
53
53
  super
54
54
  rescue Exception => exception
55
+ raise if RocketPants.pass_through_errors?
55
56
  # Otherwise, use the default built in handler.
56
57
  logger.error "Exception occured: #{exception.class.name} - #{exception.message}"
57
58
  logger.error "Exception backtrace:"
@@ -0,0 +1,59 @@
1
+ module RocketPants
2
+
3
+ # Represents the standard error type as defined by the API.
4
+ # RocketPants::Error instances will be caught and automatically rendered as JSON
5
+ # by the controller during processing.
6
+ class Error < StandardError
7
+
8
+ # @overload error_name
9
+ # Returns the error name for this error class, defaulting to
10
+ # the class name underscorized minus _error.
11
+ # @return [Symbol] the given errors name.
12
+ # @overload error_name(value)
13
+ # Sets the error name for the current class.
14
+ # @param [#to_sym] the name of this error.
15
+ def self.error_name(value = nil)
16
+ if value.nil?
17
+ @name ||= name.underscore.split("/").last.sub(/_error$/, '').to_sym
18
+ else
19
+ @name = (value.presence && value.to_sym)
20
+ end
21
+ end
22
+
23
+ # @overload http_status
24
+ # Returns the http status code of this error, defaulting to 400 (Bad Request).
25
+ # @overload http_status(value)
26
+ # Sets the http status code for this error to a given symbol / integer.
27
+ # @param value [String, Fixnum] value the new status code.
28
+ def self.http_status(value = nil)
29
+ if value.nil?
30
+ @http_status ||= 400
31
+ else
32
+ @http_status = (value.presence && value)
33
+ end
34
+ end
35
+
36
+ # Gets the name of this error from the class.
37
+ def error_name
38
+ self.class.error_name
39
+ end
40
+
41
+ # Gets the http status of this error from the class.
42
+ def http_status
43
+ self.class.http_status
44
+ end
45
+
46
+ # Setter for optional data about this error, used for translation.
47
+ attr_writer :context
48
+
49
+ # Gets the context for this error, defaulting to nil.
50
+ # @return [Hash] the context for this param.
51
+ def context
52
+ @context ||= {}
53
+ end
54
+
55
+ error_name :unknown
56
+
57
+ end
58
+
59
+ end
@@ -1,60 +1,6 @@
1
1
  module RocketPants
2
-
3
- # Represents the standard error type as defined by the API.
4
- # RocketPants::Error instances will be caught and automatically rendered as JSON
5
- # by the controller during processing.
6
- class Error < StandardError
7
-
8
- # @overload error_name
9
- # Returns the error name for this error class, defaulting to
10
- # the class name underscorized minus _error.
11
- # @return [Symbol] the given errors name.
12
- # @overload error_name(value)
13
- # Sets the error name for the current class.
14
- # @param [#to_sym] the name of this error.
15
- def self.error_name(value = nil)
16
- if value.nil?
17
- @name ||= name.underscore.split("/").last.sub(/_error$/, '').to_sym
18
- else
19
- @name = (value.presence && value.to_sym)
20
- end
21
- end
22
-
23
- # @overload http_status
24
- # Returns the http status code of this error, defaulting to 400 (Bad Request).
25
- # @overload http_status(value)
26
- # Sets the http status code for this error to a given symbol / integer.
27
- # @param value [String, Fixnum] value the new status code.
28
- def self.http_status(value = nil)
29
- if value.nil?
30
- @http_status ||= 400
31
- else
32
- @http_status = (value.presence && value)
33
- end
34
- end
35
-
36
- # Gets the name of this error from the class.
37
- def error_name
38
- self.class.error_name
39
- end
40
-
41
- # Gets the http status of this error from the class.
42
- def http_status
43
- self.class.http_status
44
- end
45
-
46
- # Setter for optional data about this error, used for translation.
47
- attr_writer :context
48
-
49
- # Gets the context for this error, defaulting to nil.
50
- # @return [Hash] the context for this param.
51
- def context
52
- @context ||= {}
53
- end
54
-
55
- error_name :unknown
56
2
 
57
- end
3
+ require 'rocket_pants/error'
58
4
 
59
5
  # A simple map of data about errors that the rocket pants system can handle.
60
6
  class Errors
@@ -111,5 +57,28 @@ module RocketPants
111
57
  register! :bad_request, :http_status => :bad_request
112
58
 
113
59
  end
60
+
61
+ class InvalidResource < RocketPants::Error
62
+ http_status :unprocessable_entity
63
+ error_name :invalid_resource
64
+
65
+ # Errors are ActiveModel Errors
66
+ attr_reader :errors
67
+
68
+ def initialize(errors, *args)
69
+ @errors = errors
70
+ super *args
71
+ end
72
+
73
+ def context
74
+ super.tap do |ctx|
75
+ extras = (ctx[:metadata] ||= {})
76
+ extras[:messages] = errors.to_hash if errors
77
+ end
78
+ end
79
+
80
+ Errors.add self
81
+
82
+ end
114
83
 
115
84
  end
@@ -5,4 +5,6 @@ en:
5
5
  unauthenticated: "This action requires authentication to continue."
6
6
  invalid_version: "This action is not available in the given version of the api."
7
7
  not_implemented: "The feature you requested has not yet been implemented and hence is currently unavailable."
8
- not_found: "The requested resource could not be found."
8
+ not_found: "The requested resource could not be found."
9
+ bad_request: "The data given to this server does not meet our criteria."
10
+ invalid_resource: "The current resource was deemed invalid."
@@ -1,9 +1,10 @@
1
1
  module RocketPants
2
2
  class Railtie < Rails::Railtie
3
3
 
4
- config.rocket_pants = ActiveSupport::OrderedOptions.new
5
- config.rocket_pants.use_caching = nil
6
- config.rocket_pants.header_metadata = nil
4
+ config.rocket_pants = ActiveSupport::OrderedOptions.new
5
+ config.rocket_pants.use_caching = nil
6
+ config.rocket_pants.header_metadata = nil
7
+ config.rocket_pants.pass_through_errors = nil
7
8
 
8
9
  config.i18n.railties_load_path << File.expand_path('../locale/en.yml', __FILE__)
9
10
 
@@ -12,10 +13,11 @@ module RocketPants
12
13
  end
13
14
 
14
15
  initializer "rocket_pants.configuration" do |app|
15
- rp_config = app.config.rocket_pants
16
- rp_config.use_caching = Rails.env.production? if rp_config.use_caching.nil?
17
- RocketPants.caching_enabled = rp_config.use_caching
18
- RocketPants.header_metadata = rp_config.header_metadata unless rp_config.header_metadata.nil?
16
+ rp_config = app.config.rocket_pants
17
+ rp_config.use_caching = Rails.env.production? if rp_config.use_caching.nil?
18
+ RocketPants.caching_enabled = rp_config.use_caching
19
+ RocketPants.header_metadata = rp_config.header_metadata unless rp_config.header_metadata.nil?
20
+ RocketPants.pass_through_errors = rp_config.pass_through_errors unless rp_config.pass_through_errors.nil?
19
21
  # Set the rocket pants cache if present.
20
22
  RocketPants.cache = rp_config.cache if rp_config.cache
21
23
  end
@@ -38,6 +40,12 @@ module RocketPants
38
40
  end
39
41
  end
40
42
 
43
+ initializer "rocket_pants.setup_activerecord" do
44
+ if defined?(ActiveRecord)
45
+ require 'rocket_pants/active_record'
46
+ end
47
+ end
48
+
41
49
  rake_tasks do
42
50
  load "rocket_pants/tasks/rocket_pants.rake"
43
51
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rocket_pants
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -243,6 +243,7 @@ executables: []
243
243
  extensions: []
244
244
  extra_rdoc_files: []
245
245
  files:
246
+ - lib/rocket_pants/active_record.rb
246
247
  - lib/rocket_pants/base.rb
247
248
  - lib/rocket_pants/cache_middleware.rb
248
249
  - lib/rocket_pants/cacheable.rb
@@ -258,7 +259,8 @@ files:
258
259
  - lib/rocket_pants/controller/respondable.rb
259
260
  - lib/rocket_pants/controller/url_for.rb
260
261
  - lib/rocket_pants/controller/versioning.rb
261
- - lib/rocket_pants/exceptions.rb
262
+ - lib/rocket_pants/error.rb
263
+ - lib/rocket_pants/errors.rb
262
264
  - lib/rocket_pants/locale/en.yml
263
265
  - lib/rocket_pants/railtie.rb
264
266
  - lib/rocket_pants/routing.rb