Module: ApiHammer::Rails

Includes:
HaltMethods
Defined in:
lib/api_hammer/halt.rb,
lib/api_hammer/rails.rb,
lib/api_hammer/check_required_params.rb,
lib/api_hammer/unmunged_request_params.rb

Defined Under Namespace

Modules: HaltMethods

Class Method Summary collapse

Instance Method Summary collapse

Methods included from HaltMethods

#halt_accepted, #halt_already_reported, #halt_authentication_timeout, #halt_bad_gateway, #halt_bad_request, #halt_bandwidth_limit_exceeded, #halt_conflict, #halt_created, #halt_expectation_failed, #halt_failed_dependency, #halt_forbidden, #halt_found, #halt_gateway_timeout, #halt_gone, #halt_http_version_not_supported, #halt_im_a_teapot, #halt_im_used, #halt_insufficient_storage, #halt_internal_server_error, #halt_length_required, #halt_locked, #halt_loop_detected, #halt_method_not_allowed, #halt_moved_permanently, #halt_multi_status, #halt_multiple_choices, #halt_network_authentication_required, #halt_no_content, #halt_no_response, #halt_non_authoritative_information, #halt_not_acceptable, #halt_not_extended, #halt_not_found, #halt_not_implemented, #halt_not_modified, #halt_ok, #halt_partial_content, #halt_payment_required, #halt_permanent_redirect, #halt_precondition_failed, #halt_precondition_required, #halt_proxy_authentication_required, #halt_redirect, #halt_request_entity_too_large, #halt_request_header_fields_too_large, #halt_request_timeout, #halt_request_uri_too_long, #halt_requested_range_not_satisfiable, #halt_reset_content, #halt_see_other, #halt_service_unavailable, #halt_temporary_redirect, #halt_too_many_requests, #halt_unauthorized, #halt_unavailable_for_legal_reasons, #halt_unordered_collection, #halt_unprocessable_entity, #halt_unsupported_media_type, #halt_upgrade_required, #halt_use_proxy, #halt_variant_also_negotiates

Class Method Details

.included(klass) ⇒ Object



6
7
8
9
10
# File 'lib/api_hammer/rails.rb', line 6

def self.included(klass)
  (@on_included || []).each do |included_proc|
    included_proc.call(klass)
  end
end

Instance Method Details

#check_required_params(*checks) ⇒ Object

halts with a 422 Unprocessable Entity and an appropriate error body if required params are missing

simple:

check_required_params(:id, :name)
  • params[:id] must be present
  • params[:name] must be present

less simple:

check_required_params(:id, :person => [:name, :height], :lucky_numbers => Array)
  • params[:id] must be present
  • params[:person] must be present and be a hash
  • params[:person][:name] must be present
  • params[:person][:height] must be present
  • params[:lucky_numbers] must be present and be an array


20
21
22
23
24
# File 'lib/api_hammer/check_required_params.rb', line 20

def check_required_params(*checks)
  errors = Hash.new { |h,k| h[k] = [] }
  check_required_params_helper(checks, params, errors, [])
  halt_unprocessable_entity(errors) if errors.any?
end

#find_or_halt(model, find_attrs, options = {}) ⇒ Object

attempts to find and return the given model (an ActiveRecord::Base subclass) with the given attributes. halts with 404 (does not return) if that fails. options[:status] may specify a different status if that is required.

e.g.:

find_or_halt(User, :email => '[email protected]')


81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/api_hammer/halt.rb', line 81

def find_or_halt(model, find_attrs, options={})
  options = {:status => 404}.merge(options)
  record = model.where(find_attrs).first
  unless record
    attributes = find_attrs.map{|attr, val| "#{attr}: #{val}" }.join(", ")
    model_name = model.table_name
    model_name = model_name.singularize if model_name.respond_to?(:singularize)
    message = I18n.t(:"errors.unknown_record_with_attributes", :default => "Unknown %{model_name}! %{attributes}",
      :model_name => model_name,
      :attributes => attributes
    )
    halt_error(options[:status], {model_name => [message]})
  end
  record
end

#halt(status, body, render_options = {}) ⇒ Object

halt and render the given body

Raises:



42
43
44
# File 'lib/api_hammer/halt.rb', line 42

def halt(status, body, render_options = {})
  raise(ApiHammer::Halt.new(body.inspect, body, render_options.merge(:status => status)))
end

#halt_error(status, errors, options = {}) ⇒ Object

halt and render the given errors in the body on the 'errors' key



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/api_hammer/halt.rb', line 47

def halt_error(status, errors, options = {})
  errors_as_json = errors.respond_to?(:as_json) ? errors.as_json : errors
  unless errors_as_json.is_a?(Hash)
    raise ArgumentError, "errors be an object representable in JSON as a Hash; got errors = #{errors.inspect}"
  end
  unless errors_as_json.keys.all? { |k| k.is_a?(String) || k.is_a?(Symbol) }
    raise ArgumentError, "errors keys must all be string or symbol; got errors = #{errors.inspect}"
  end
  unless errors_as_json.values.all? { |v| v.is_a?(Array) && v.all? { |e| e.is_a?(String) } }
    raise ArgumentError, "errors values must all be arrays of strings; got errors = #{errors.inspect}"
  end
  render_options = options.dup.with_indifferent_access
  body = {'errors' => errors}
  error_message = render_options.delete('error_message') || begin
    error_values = errors.values.inject([], &:+)
    if error_values.size <= 1
      error_values.first
    else
      # sentencify with periods 
      error_values.map { |v| v =~ /\.\s*\z/ ? v : v + '.' }.join(' ')
    end
  end
  body['error_message'] = error_message if error_message
  halt(status, body, render_options)
end

#handle_halt(halt) ⇒ Object

handle a raised ApiHammer::Halt or subclass and render it



30
31
32
33
34
35
36
37
38
39
# File 'lib/api_hammer/halt.rb', line 30

def handle_halt(halt)
  render_options = halt.render_options ? halt.render_options.dup : {}
  # rocket pants does not have a render method, just render_json 
  if respond_to?(:render_json, true)
    render_json(halt.body || {}, render_options)
  else
    render_options[:json] = halt.body || {}
    render(render_options)
  end
end

#unmunged_request_paramsObject

request parameters (not query parameters) without the nil/empty array munging that rails does



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/api_hammer/unmunged_request_params.rb', line 3

def unmunged_request_params
  # Thread.exclusive is not optimal but we need to ensure that any other params parsing occurring in other 
  # threads is not affected by disabling munging
  #
  # TODO when we are on a rails which has ActionDispatch::Request::Utils.perform_deep_munge, use that instead
  # of clobbering methods
  @unmunged_params ||= Thread.exclusive do
    unless ActionDispatch::Request.method_defined?(:real_deep_munge)
      ActionDispatch::Request.send(:alias_method, :real_deep_munge, :deep_munge)
    end
    ActionDispatch::Request.send(:define_method, :deep_munge) { |hash| hash }
    begin
      unmunged_params = nil
      newenv = request.env.merge('action_dispatch.request.request_parameters' => nil)
      ActionDispatch::ParamsParser.new(proc do |env|
        unmunged_params = env['action_dispatch.request.request_parameters']
      end).call(newenv)
      unmunged_params || ActionDispatch::Request.new(newenv).request_parameters
    ensure
      ActionDispatch::Request.send(:alias_method, :deep_munge, :real_deep_munge)
    end
  end
end