Skip to content

Commit

Permalink
Merge branch 'master' into 0.7.x
Browse files Browse the repository at this point in the history
  • Loading branch information
jodosha committed Oct 6, 2016
2 parents 9038aa4 + f12eac8 commit eed6b23
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 63 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# Hanami::Controller
Complete, fast and testable actions for Rack

## v0.7.1 - 2016-10-06
### Added
- [Kyle Chong] Introduced `parsed_request_body` for action
- [Luca Guidi] Introduced `Hanami::Action::BaseParams#each`

### Fixed
- [Ayleen McCann] Use default content type when `HTTP_ACCEPT` is `*/*`
- [Kyle Chong] Don't stringify uploaded files
- [Kyle Chong] Don't stringify params values when not necessary

### Changed
- [akhramov & Luca Guidi] Raise `Hanami::Controller::IllegalExposureError` when try to expose reserved words: `params`, and `flash`.

## v0.7.0 - 2016-07-22
### Added
- [Luca Guidi] Introduced `Hanami::Action::Params#error_messages` which returns a flat collection of full error messages
Expand Down
9 changes: 9 additions & 0 deletions lib/hanami/action/base_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ def to_h
end
alias_method :to_hash, :to_h

# Iterates through params
#
# @param blk [Proc]
#
# @since 0.7.1
def each(&blk)
to_h.each(&blk)
end

private

# @since 0.7.0
Expand Down
10 changes: 9 additions & 1 deletion lib/hanami/action/exposable.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'hanami/action/exposable/guard'

module Hanami
module Action
# Exposures API
Expand All @@ -18,7 +20,9 @@ module Exposable
def self.included(base)
base.class_eval do
extend ClassMethods
expose :params
include Guard

_expose :params
end
end

Expand Down Expand Up @@ -68,6 +72,10 @@ def expose(*names)
end
end

# Alias of #expose to be used in internal modules.
# #_expose is not watched by the Guard
alias _expose expose

# Set of exposures attribute names
#
# @return [Array] the exposures attribute names
Expand Down
102 changes: 102 additions & 0 deletions lib/hanami/action/exposable/guard.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
require 'hanami/controller/error'

module Hanami
module Controller
# Exposure of reserved words
#
# @since 0.7.1
class IllegalExposureError < Error
end
end

module Action
module Exposable
# Guard for Exposures API.
# Prevents exposure of reserved words
#
# @since 0.7.1
#
# @see Hanami::Action::Exposable::Guard::ClassMethods#expose
# @see Hanami::Action::Exposable::Guard::ClassMethods#reserved_word?
module Guard
# Override Ruby's hook for modules.
# It prepends a guard for the exposures logic
#
# @param base [Class] the target action
#
# @since 0.7.1
# @api private
#
# @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
def self.included(base)
class << base
prepend ClassMethods
end
end

# Exposures API Guard class methods
#
# @since 0.7.1
# @api private
module ClassMethods
# Prevents exposure if names contain a reserved word.
#
# @param names [Array<Symbol>] the name(s) of the attribute(s) to be
# exposed
#
# @return [void]
#
# @since 0.7.1
def expose(*names)
detect_reserved_words!(names)

super
end

private

# Raises error if given names contain a reserved word.
#
# @param names [Array<Symbol>] a list of names to be checked.
#
# @return [void]
#
# @raise [IllegalExposeError] if names contain one or more of reserved
# words
#
# @since 0.7.1
# @api private
def detect_reserved_words!(names)
names.each do |name|
if reserved_word?(name)
raise Hanami::Controller::IllegalExposureError.new("#{name} is a reserved word. It cannot be exposed")
end
end
end

# Checks if a string is a reserved word
#
# Reserved word is a name of the method defined in one of the modules
# of a given namespace.
#
# @param name [Symbol] the word to be checked
# @param namespace [String] the namespace containing internal modules
#
# @return [true, false]
#
# @since 0.7.1
# @api private
def reserved_word?(name, namespace = 'Hanami')
if method_defined?(name) || private_method_defined?(name)
method_owner = instance_method(name).owner

Utils::String.new(method_owner).namespace == namespace
else
false
end
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/hanami/action/glue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module Glue
# @api private
# @since 0.3.0
def self.included(base)
base.class_eval { expose(:format) if respond_to?(:expose) }
base.class_eval { _expose(:format) if respond_to?(:_expose) }
end

# Check if the current HTTP request is renderable.
Expand Down
8 changes: 6 additions & 2 deletions lib/hanami/action/mime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -487,9 +487,13 @@ def content_type_with_charset
# @see https://github.com/hanami/controller/issues/104
def best_q_match(q_value_header, available_mimes)
values = ::Rack::Utils.q_values(q_value_header)

values = values.map do |req_mime, quality|
match = available_mimes.find { |am| ::Rack::Mime.match?(am, req_mime) }
if req_mime == DEFAULT_ACCEPT
# See https://github.com/hanami/controller/issues/167
match = default_content_type
else
match = available_mimes.find { |am| ::Rack::Mime.match?(am, req_mime) }
end
next unless match
[match, quality]
end.compact
Expand Down
32 changes: 9 additions & 23 deletions lib/hanami/action/params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ def error_messages(error_set = errors)
end
end

# Returns true if no validation errors are found,
# false otherwise.
#
# @return [TrueClass, FalseClass]
#
# @since 0.7.0
#
# @example
# params.valid? # => true
def valid?
@result.success?
end
Expand All @@ -106,32 +115,9 @@ def to_h

private

# @since 0.7.0
# @api private
def _extract_params
# FIXME: this is required for dry-v whitelisting
stringify!(super)
end

def _params
@result.output.merge(_router_params)
end

def stringify!(result)
result.keys.each do |key|
value = result.delete(key)
result[key.to_s] = case value
when ::Hash
stringify!(value)
when ::Array
value.map(&:to_s)
else
value.to_s
end
end

result
end
end
end
end
7 changes: 7 additions & 0 deletions lib/hanami/action/rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ module Rack
# @api private
HEAD = 'HEAD'.freeze

# The key that returns router parsed body from the Rack env
ROUTER_PARSED_BODY = 'router.parsed_body'.freeze

# Override Ruby's hook for modules.
# It includes basic Hanami::Action modules to the given class.
#
Expand Down Expand Up @@ -202,6 +205,10 @@ def request
@request ||= ::Hanami::Action::Request.new(@_env)
end

def parsed_request_body
@_env.fetch(ROUTER_PARSED_BODY, nil)
end

private

# Sets the HTTP status code for the response
Expand Down
2 changes: 1 addition & 1 deletion lib/hanami/action/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module Session
# @api private
def self.included(action)
action.class_eval do
expose :session
_expose :session
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/hanami/controller/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ module Controller
# Defines the version
#
# @since 0.1.0
VERSION = '0.7.0'.freeze
VERSION = '0.7.1'.freeze
end
end
12 changes: 12 additions & 0 deletions test/action/base_params_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,16 @@
@action.params.must_be :valid?
end
end

describe '#each' do
it 'iterates through params' do
params = Hanami::Action::BaseParams.new(expected = { song: 'Break The Habit' })
actual = Hash[]
params.each do |key, value|
actual[key] = value
end

actual.must_equal(expected)
end
end
end
54 changes: 54 additions & 0 deletions test/action/exposable_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'test_helper'

describe Hanami::Action::Exposable do
describe '#expose' do
it 'creates a getter for the given ivar' do
action = ExposeAction.new

response = action.call({})
response[0].must_equal 200

action.exposures.fetch(:film).must_equal '400 ASA'
action.exposures.fetch(:time).must_equal nil
end

describe 'when reserved word is used' do
subject { ExposeReservedWordAction.expose_reserved_word }

it 'should raise an exception' do
->() { subject }.must_raise Hanami::Controller::IllegalExposureError
end
end

describe 'when reserved word is not used' do
let(:action_class) do
Class.new do
include Hanami::Action

include Module.new { def flash; end }

expose :flash
end
end

subject { action_class.new.exposures }

it 'adds a key to exposures list' do
subject.must_include :flash
end
end
end

describe '#_expose' do
describe 'when exposuring a reserved word' do
it 'does not fail' do
ExposeReservedWordAction.expose_reserved_word(using_internal_method: true)

action = ExposeReservedWordAction.new
action.call({})

action.exposures.must_include :flash
end
end
end
end
21 changes: 21 additions & 0 deletions test/action/format_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ def call(params)
self.format = params[:format]
end
end

class Configuration
include Hanami::Action

configuration.default_request_format :jpeg

def call(params)
self.body = format
end
end
end

describe '#format' do
Expand Down Expand Up @@ -59,6 +69,17 @@ def call(params)
status.must_equal 200
end

# Bug
# See https://github.com/hanami/controller/issues/167
it "accepts '*/*' and returns configured default format" do
action = FormatController::Configuration.new
status, headers, _ = action.call({ 'HTTP_ACCEPT' => '*/*' })

action.format.must_equal :jpeg
headers['Content-Type'].must_equal 'image/jpeg; charset=utf-8'
status.must_equal 200
end

mime_types = ['application/octet-stream', 'text/html']
Rack::Mime::MIME_TYPES.each do |format, mime_type|
next if mime_types.include?(mime_type)
Expand Down
Loading

0 comments on commit eed6b23

Please sign in to comment.