diff --git a/lib/hanami/action.rb b/lib/hanami/action.rb index 4708c2f7..a96d3102 100644 --- a/lib/hanami/action.rb +++ b/lib/hanami/action.rb @@ -39,7 +39,7 @@ def self.gem_loader loader.ignore( "#{root}/hanami-controller.rb", "#{root}/hanami/controller/version.rb", - "#{root}/hanami/action/{constants,errors,params,validatable}.rb" + "#{root}/hanami/action/{constants,errors,validatable}.rb" ) loader.inflector.inflect("csrf_protection" => "CSRFProtection") end @@ -72,6 +72,7 @@ def self.gem_loader setting :public_directory, default: Config::DEFAULT_PUBLIC_DIRECTORY setting :before_callbacks, default: Utils::Callbacks::Chain.new, mutable: true setting :after_callbacks, default: Utils::Callbacks::Chain.new, mutable: true + setting :contract_class # @!scope class @@ -105,24 +106,6 @@ def self.inherited(subclass) include Validatable if defined?(Validatable) end end - - if instance_variable_defined?(:@params_class) - subclass.instance_variable_set(:@params_class, @params_class) - end - end - - # Returns the class which defines the params - # - # Returns the class which has been provided to define the - # params. By default this will be Hanami::Action::Params. - # - # @return [Class] A params class (when whitelisted) or - # Hanami::Action::Params - # - # @api private - # @since 0.7.0 - def self.params_class - @params_class || BaseParams end # Placeholder for the `.params` method. Raises an error when the hanami-validations gem is not @@ -298,12 +281,21 @@ def self.handle_exception(...) config.handle_exception(...) end + # @since 2.0.0 + # @api private + private attr_reader :config + + # @since 2.2.0 + # @api private + private attr_reader :contract + # Returns a new action # # @since 2.0.0 # @api public - def initialize(config: self.class.config) + def initialize(config: self.class.config, contract: nil) @config = config + @contract = contract || config.contract_class&.new # TODO: tests showing this overridden by a dep freeze end @@ -316,7 +308,7 @@ def call(env) response = nil halted = catch :halt do - params = self.class.params_class.new(env) + params = Params.new(env: env, contract: contract) request = build_request( env: env, params: params, @@ -412,10 +404,6 @@ def _requires_no_body?(res) private - # @since 2.0.0 - # @api private - attr_reader :config - # @since 2.0.0 # @api private def enforce_accepted_mime_types(request) diff --git a/lib/hanami/action/base_params.rb b/lib/hanami/action/base_params.rb deleted file mode 100644 index 203d7173..00000000 --- a/lib/hanami/action/base_params.rb +++ /dev/null @@ -1,170 +0,0 @@ -# frozen_string_literal: true - -require "rack/request" -require "hanami/utils/hash" - -module Hanami - class Action - # Provides access to params included in a Rack request. - # - # Offers useful access to params via methods like {#[]}, {#get} and {#to_h}. - # - # These params are available via {Request#params}. - # - # This class is used by default when {Hanami::Action::Validatable} is not included, or when no - # {Validatable::ClassMethods#params params} validation schema is defined. - # - # @see Hanami::Action::Request#params - # - # @api private - # @since 0.7.0 - class BaseParams - # @attr_reader env [Hash] the Rack env - # - # @since 0.7.0 - # @api private - attr_reader :env - - # @attr_reader raw [Hash] the raw params from the request - # - # @since 0.7.0 - # @api private - attr_reader :raw - - # Returns a new frozen params object for the Rack env. - # - # @param env [Hash] a Rack env or an hash of params. - # - # @since 0.7.0 - # @api private - def initialize(env) - @env = env - @raw = _extract_params - @params = Utils::Hash.deep_symbolize(@raw) - freeze - end - - # Returns the value for the given params key. - # - # @param key [Symbol] the key - # - # @return [Object,nil] the associated value, if found - # - # @since 0.7.0 - # @api public - def [](key) - @params[key] - end - - # Returns an value associated with the given params key. - # - # You can access nested attributes by listing all the keys in the path. This uses the same key - # path semantics as `Hash#dig`. - # - # @param keys [Array] the key - # - # @return [Object,NilClass] return the associated value, if found - # - # @example - # require "hanami/controller" - # - # module Deliveries - # class Create < Hanami::Action - # def handle(req, *) - # req.params.get(:customer_name) # => "Luca" - # req.params.get(:uknown) # => nil - # - # req.params.get(:address, :city) # => "Rome" - # req.params.get(:address, :unknown) # => nil - # - # req.params.get(:tags, 0) # => "foo" - # req.params.get(:tags, 1) # => "bar" - # req.params.get(:tags, 999) # => nil - # - # req.params.get(nil) # => nil - # end - # end - # end - # - # @since 0.7.0 - # @api public - def get(*keys) - @params.dig(*keys) - end - - # This is for compatibility with Hanami::Helpers::FormHelper::Values - # - # @api private - # @since 0.8.0 - alias_method :dig, :get - - # Returns true at all times, providing a common interface with {Params}. - # - # @return [TrueClass] always returns true - # - # @see Hanami::Action::Params#valid? - # - # @api public - # @since 0.7.0 - def valid? - true - end - - # Returns a hash of the parsed request params. - # - # @return [Hash] - # - # @since 0.7.0 - # @api public - def to_h - @params - end - alias_method :to_hash, :to_h - - # Iterates over the params. - # - # Calls the given block with each param key-value pair; returns the full hash of params. - # - # @yieldparam key [Symbol] - # @yieldparam value [Object] - # - # @return [to_h] - # - # @since 0.7.1 - # @api public - def each(&blk) - to_h.each(&blk) - end - - private - - # @since 0.7.0 - # @api private - def _extract_params - result = {} - - if env.key?(Action::RACK_INPUT) - result.merge! ::Rack::Request.new(env).params - result.merge! _router_params - else - result.merge! _router_params(env) - env[Action::REQUEST_METHOD] ||= Action::DEFAULT_REQUEST_METHOD - end - - result - end - - # @since 0.7.0 - # @api private - def _router_params(fallback = {}) - env.fetch(ROUTER_PARAMS) do - if session = fallback.delete(Action::RACK_SESSION) - fallback[Action::RACK_SESSION] = Utils::Hash.deep_symbolize(session) - end - - fallback - end - end - end - end -end diff --git a/lib/hanami/action/csrf_protection.rb b/lib/hanami/action/csrf_protection.rb index c6d1d014..b803618e 100644 --- a/lib/hanami/action/csrf_protection.rb +++ b/lib/hanami/action/csrf_protection.rb @@ -131,14 +131,14 @@ def invalid_csrf_token?(req, res) return false unless verify_csrf_token?(req, res) missing_csrf_token?(req, res) || - !::Rack::Utils.secure_compare(req.session[CSRF_TOKEN], req.params[CSRF_TOKEN]) + !::Rack::Utils.secure_compare(req.session[CSRF_TOKEN], req.params.raw[CSRF_TOKEN.to_s]) end # Verify the CSRF token was passed in params. # # @api private def missing_csrf_token?(req, *) - Hanami::Utils::Blank.blank?(req.params[CSRF_TOKEN]) + Hanami::Utils::Blank.blank?(req.params.raw[CSRF_TOKEN.to_s]) end # Generates a random CSRF Token diff --git a/lib/hanami/action/params.rb b/lib/hanami/action/params.rb index 5e82a4d3..a91cc139 100644 --- a/lib/hanami/action/params.rb +++ b/lib/hanami/action/params.rb @@ -1,7 +1,21 @@ # frozen_string_literal: true +require "rack/request" +require "hanami/utils/hash" + module Hanami class Action + # Provides access to params included in a Rack request. + # + # Offers useful access to params via methods like {#[]}, {#get} and {#to_h}. + # + # These params are available via {Request#params}. + # + # This class is used by default when {Hanami::Action::Validatable} is not included, or when no + # {Validatable::ClassMethods#params params} validation schema is defined. + # + # @see Hanami::Action::Request#params + # A set of params requested by the client # # It's able to extract the relevant params from a Rack env of from an Hash. @@ -12,12 +26,21 @@ class Action # * Default: it returns the given hash as it is. It's useful for testing purposes. # # @since 0.1.0 - class Params < BaseParams + class Params + # Permits all params and returns them as symbolized keys. Stands in for a + # `Dry::Validation::Contract` when neither {Action.params} nor {Action.contract} are called. + # + # @see {Params#initialize} + # # @since 2.2.0 # @api private - class Validator < Dry::Validation::Contract - params do - optional(:_csrf_token).filled(:string) + class DefaultContract + def self.call(attrs) = Result.new(attrs) + + class Result + def initialize(attrs) = @attrs = Utils::Hash.deep_symbolize(attrs) + def to_h = @attrs + def errors = {} end end @@ -120,28 +143,48 @@ def _nested_attribute(keys, key) # @api public # @since 0.7.0 def self.params(&block) - @_validator = Class.new(Validator) { params(&block || -> {}) }.new - end + unless defined?(Dry::Validation::Contract) + message = %(To use `.params`, please add the "hanami-validations" gem to your Gemfile) + raise NoMethodError, message + end - # Defines validations for the params, using a dry-validation contract. - # - # @param block [Proc] the contract definition - # - # @see https://dry-rb.org/gems/dry-validation/ - # - # @api public - # @since 2.2.0 - def self.contract(&block) - @_validator = Class.new(Validator, &block).new + @_contract = Class.new(Dry::Validation::Contract) { params(&block || -> {}) }.new end class << self # @api private # @since 2.2.0 - attr_reader :_validator + attr_reader :_contract end - # rubocop:disable Lint/MissingSuper + # @attr_reader env [Hash] the Rack env + # + # @since 0.7.0 + # @api private + attr_reader :env + + # @attr_reader raw [Hash] the raw params from the request + # + # @since 0.7.0 + # @api private + attr_reader :raw + + # Returns structured error messages + # + # @return [Hash] + # + # @since 0.7.0 + # + # @example + # params.errors + # # => { + # :email=>["is missing", "is in invalid format"], + # :name=>["is missing"], + # :tos=>["is missing"], + # :age=>["is missing"], + # :address=>["is missing"] + # } + attr_reader :errors # Initialize the params and freeze them. # @@ -151,35 +194,75 @@ class << self # # @since 0.1.0 # @api private - def initialize(env) + def initialize(env:, contract: nil) @env = env @raw = _extract_params - validation = self.class._validator.call(raw) + # Fall back to the default contract here, rather than in the `._contract` method itself. + # This allows `._contract` to return nil when there is no user-defined contract, which is + # important for the backwards compatibility behavior in `Validatable::ClassMethods#params`. + contract ||= self.class._contract || DefaultContract + validation = contract.call(raw) + @params = validation.to_h @errors = Errors.new(validation.errors.to_h) freeze end - # rubocop:enable Lint/MissingSuper - - # Returns structured error messages + # Returns the value for the given params key. # - # @return [Hash] + # @param key [Symbol] the key + # + # @return [Object,nil] the associated value, if found # # @since 0.7.0 + # @api public + def [](key) + @params[key] + end + + # Returns an value associated with the given params key. + # + # You can access nested attributes by listing all the keys in the path. This uses the same key + # path semantics as `Hash#dig`. + # + # @param keys [Array] the key + # + # @return [Object,NilClass] return the associated value, if found # # @example - # params.errors - # # => { - # :email=>["is missing", "is in invalid format"], - # :name=>["is missing"], - # :tos=>["is missing"], - # :age=>["is missing"], - # :address=>["is missing"] - # } - attr_reader :errors + # require "hanami/controller" + # + # module Deliveries + # class Create < Hanami::Action + # def handle(req, *) + # req.params.get(:customer_name) # => "Luca" + # req.params.get(:uknown) # => nil + # + # req.params.get(:address, :city) # => "Rome" + # req.params.get(:address, :unknown) # => nil + # + # req.params.get(:tags, 0) # => "foo" + # req.params.get(:tags, 1) # => "bar" + # req.params.get(:tags, 999) # => nil + # + # req.params.get(nil) # => nil + # end + # end + # end + # + # @since 0.7.0 + # @api public + def get(*keys) + @params.dig(*keys) + end + + # This is for compatibility with Hanami::Helpers::FormHelper::Values + # + # @api private + # @since 0.8.0 + alias_method :dig, :get # Returns flat collection of full error messages # @@ -224,6 +307,21 @@ def valid? errors.empty? end + # Iterates over the params. + # + # Calls the given block with each param key-value pair; returns the full hash of params. + # + # @yieldparam key [Symbol] + # @yieldparam value [Object] + # + # @return [to_h] + # + # @since 0.7.1 + # @api public + def each(&blk) + to_h.each(&blk) + end + # Serialize validated params to Hash # # @return [::Hash] @@ -242,6 +340,36 @@ def to_h def deconstruct_keys(*) to_hash end + + private + + # @since 0.7.0 + # @api private + def _extract_params + result = {} + + if env.key?(Action::RACK_INPUT) + result.merge! ::Rack::Request.new(env).params + result.merge! _router_params + else + result.merge! _router_params(env) + env[Action::REQUEST_METHOD] ||= Action::DEFAULT_REQUEST_METHOD + end + + result + end + + # @since 0.7.0 + # @api private + def _router_params(fallback = {}) + env.fetch(ROUTER_PARAMS) do + if session = fallback.delete(Action::RACK_SESSION) + fallback[Action::RACK_SESSION] = Utils::Hash.deep_symbolize(session) + end + + fallback + end + end end end end diff --git a/lib/hanami/action/request.rb b/lib/hanami/action/request.rb index 4c123366..1db3000c 100644 --- a/lib/hanami/action/request.rb +++ b/lib/hanami/action/request.rb @@ -18,10 +18,7 @@ class Action class Request < ::Rack::Request # Returns the request's params. # - # For an action with {Validatable} included, this will be a {Params} instance, otherwise a - # {BaseParams}. - # - # @return [BaseParams,Params] + # @return [Params] # # @since 2.0.0 # @api public diff --git a/lib/hanami/action/validatable.rb b/lib/hanami/action/validatable.rb index ffd4cda7..b6e92b29 100644 --- a/lib/hanami/action/validatable.rb +++ b/lib/hanami/action/validatable.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "params" - module Hanami class Action # Support for validating params when calling actions. @@ -98,12 +96,17 @@ module ClassMethods # @api public # @since 0.3.0 def params(klass = nil, &block) - if klass.nil? - klass = const_set(PARAMS_CLASS_NAME, Class.new(Params)) - klass.params(&block) - end + contract_class = + if klass.nil? + Class.new(Dry::Validation::Contract) { params(&block) } + elsif klass < Params + # Handle subclasses of Hanami::Action::Params. + klass._contract.class + else + klass + end - @params_class = klass + config.contract_class = contract_class end # Defines a validation contract for the params passed to {Hanami::Action#call}. @@ -187,12 +190,9 @@ def params(klass = nil, &block) # @api public # @since 2.2.0 def contract(klass = nil, &block) - if klass.nil? - klass = const_set(PARAMS_CLASS_NAME, Class.new(Params)) - klass.contract(&block) - end + contract_class = klass || Class.new(Dry::Validation::Contract, &block) - @params_class = klass + config.contract_class = contract_class end end end diff --git a/spec/isolation/without_hanami_validations_spec.rb b/spec/isolation/without_hanami_validations_spec.rb index d88e3ec9..90e70faa 100644 --- a/spec/isolation/without_hanami_validations_spec.rb +++ b/spec/isolation/without_hanami_validations_spec.rb @@ -11,11 +11,7 @@ expect(defined?(Hanami::Action::Validatable)).to be(nil) end - it "doesn't load Hanami::Action::Params" do - expect(defined?(Hanami::Action::Params)).to be(nil) - end - - it "doesn't have params DSL" do + it "doesn't have Hanami::Action.params" do expect do Class.new(Hanami::Action) do params do @@ -28,7 +24,7 @@ ) end - it "doesn't have the contract DSL" do + it "doesn't have Hanami::Action.contract" do expect do Class.new(Hanami::Action) do contract do @@ -43,26 +39,28 @@ ) end - it "has params that don't respond to .valid?" do - action = Class.new(Hanami::Action) do - def handle(req, res) - res.body = [req.params.respond_to?(:valid?), req.params.valid?] + it "doesn't have Hanami::Action::Params.params" do + expect do + Class.new(Hanami::Action::Params) do + params do + required(:id).filled + end end - end - - response = action.new.call({}) - expect(response.body).to eq(["[true, true]"]) + end.to raise_error( + NoMethodError, + %(To use `.params`, please add the "hanami-validations" gem to your Gemfile) + ) end - it "has params that don't respond to .errors" do + it "has params that are always valid" do action = Class.new(Hanami::Action) do def handle(req, res) - res.body = req.params.respond_to?(:errors) + res.body = [req.params.respond_to?(:valid?), req.params.valid?] end end response = action.new.call({}) - expect(response.body).to eq(["false"]) + expect(response.body).to eq(["[true, true]"]) end end diff --git a/spec/support/fixtures.rb b/spec/support/fixtures.rb index 5a4f8a32..4007de68 100644 --- a/spec/support/fixtures.rb +++ b/spec/support/fixtures.rb @@ -1903,20 +1903,7 @@ def call(env) end end -class ContractAction < Hanami::Action - contract do - params do - required(:birth_date).filled(:date) - required(:book).schema do - required(:title).filled(:str?) - end - end - - rule(:birth_date) do - key.failure("you must be 18 years or older") if value < Date.today << (12 * 18) - end - end - +class ContractActionBase < Hanami::Action def handle(request, response) if request.params.valid? response.status = 201 @@ -1930,7 +1917,7 @@ def handle(request, response) end end -class ExternalContractParams < Hanami::Action::Params +class ContractAction < ContractActionBase contract do params do required(:birth_date).filled(:date) @@ -1945,8 +1932,24 @@ class ExternalContractParams < Hanami::Action::Params end end -class ExternalContractAction < ContractAction - contract ExternalContractParams +class ExternalContract < Dry::Validation::Contract + params do + required(:birth_date).filled(:date) + required(:book).schema do + required(:title).filled(:str?) + end + end + + rule(:birth_date) do + key.failure("you must be 18 years or older") if value < Date.today << (12 * 18) + end +end + +class ExternalContractAction < ContractActionBase + contract ExternalContract +end + +class DependencyContractAction < ContractActionBase end class WhitelistedUploadDslContractAction < Hanami::Action diff --git a/spec/unit/hanami/action/base_param_spec.rb b/spec/unit/hanami/action/base_param_spec.rb index a7a4a834..3974eb5c 100644 --- a/spec/unit/hanami/action/base_param_spec.rb +++ b/spec/unit/hanami/action/base_param_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -RSpec.describe Hanami::Action::BaseParams do +# TODO: merge these tests into params_spec +RSpec.describe Hanami::Action::Params do let(:action) { Test::Index.new } describe "#initialize" do @@ -22,7 +23,7 @@ describe "#each" do it "iterates through params" do expected = {song: "Break The Habit"} - params = described_class.new(expected.dup) + params = described_class.new(env: expected.dup) actual = {} params.each do |key, value| actual[key] = value @@ -33,7 +34,7 @@ end describe "#get" do - let(:params) { described_class.new(delivery: {address: {city: "Rome"}}) } + let(:params) { described_class.new(env: {delivery: {address: {city: "Rome"}}}) } it "returns value if present" do expect(params.get(:delivery, :address, :city)).to eq("Rome") diff --git a/spec/unit/hanami/action/contract_spec.rb b/spec/unit/hanami/action/contract_spec.rb index 692903e5..6790bd2f 100644 --- a/spec/unit/hanami/action/contract_spec.rb +++ b/spec/unit/hanami/action/contract_spec.rb @@ -25,7 +25,7 @@ end end - describe "provided by a standlone params class using a contract" do + describe "provided by a standlone contract class" do let(:action) { ExternalContractAction.new } context "when it has errors" do @@ -47,23 +47,25 @@ end end - describe "standalone class" do - it "validates the input" do - params_class = Class.new(Hanami::Action::Params) { - contract do - params do - required(:start_date).value(:date) - end - - rule(:start_date) do - key.failure("must be in the future") if value <= Date.today - end - end - } + describe "provided by an injected contract dependency" do + let(:action) { DependencyContractAction.new(contract: ExternalContract.new) } + + context "when it has errors" do + it "returns them" do + response = action.call("birth_date" => "2000-01-01") + + expect(response.status).to eq 302 + expect(response.body).to eq ["{:errors=>{:book=>[\"is missing\"], :birth_date=>[\"you must be 18 years or older\"]}}"] + end + end - params = params_class.new(start_date: "2000-01-01") + context "when it is valid" do + it "works" do + response = action.call("birth_date" => Date.today - (365 * 15), "book" => {"title" => "Hanami"}) - expect(params.errors.to_h).to eq(start_date: ["must be in the future"]) + expect(response.status).to eq 201 + expect(response.body).to eq ["{\"new_name\":\"HANAMI\"}"] + end end end diff --git a/spec/unit/hanami/action/csrf_protection_spec.rb b/spec/unit/hanami/action/csrf_protection_spec.rb index 80086429..ba85fe0d 100644 --- a/spec/unit/hanami/action/csrf_protection_spec.rb +++ b/spec/unit/hanami/action/csrf_protection_spec.rb @@ -36,7 +36,7 @@ let(:session_token) { "abc123" } context "matching CSRF token in request" do - let(:request) { super().merge(_csrf_token: session_token) } + let(:request) { super().merge("_csrf_token" => session_token) } it "accepts the request" do expect(response.status).to eq 200 diff --git a/spec/unit/hanami/action/params_spec.rb b/spec/unit/hanami/action/params_spec.rb index 424899c8..f6973ac5 100644 --- a/spec/unit/hanami/action/params_spec.rb +++ b/spec/unit/hanami/action/params_spec.rb @@ -36,7 +36,6 @@ expect(response[:params][:id]).to eq(1) expect(response[:params][:unknown]).to be(nil) expect(response[:params][:upload]).to eq(upload) - expect(response[:params][:_csrf_token]).to eq("3") expect(response[:params].raw.fetch("id")).to eq("1") expect(response[:params].raw.fetch("unknown")).to eq("2") @@ -96,9 +95,9 @@ expect(response.body).to eq([%({:id=>23, :article=>{:tags=>[:cool]}})]) end - it "doesn't filter _csrf_token" do + it "removes _csrf_token" do response = action.call(_csrf_token: "abc") - expect(response.body).to eq([%({:_csrf_token=>"abc"})]) + expect(response.body).to eq([%({})]) end end @@ -108,9 +107,9 @@ expect(response.body).to match(%({:id=>23})) end - it "doesn't filter _csrf_token" do + it "removes _csrf_token" do response = Rack::MockRequest.new(action).request("PATCH", "?id=1", params: {_csrf_token: "def", x: {foo: "bar"}}) - expect(response.body).to match(%(:_csrf_token=>"def")) + expect(response.body).not_to match("_csrf_token") expect(response.body).to match(%(:id=>1)) end end @@ -157,7 +156,7 @@ describe "validations" do it "isn't valid with empty params" do - params = TestParams.new({}) + params = TestParams.new(env: {}) expect(params.valid?).to be(false) @@ -170,7 +169,7 @@ end it "isn't valid with empty nested params" do - params = NestedParams.new(signup: {}) + params = NestedParams.new(env: {signup: {}}) expect(params.valid?).to be(false) @@ -186,18 +185,22 @@ end it "is it valid when all the validation criteria are met" do - params = TestParams.new(email: "test@hanamirb.org", - password: "123456", - password_confirmation: "123456", - name: "Luca", - tos: "1", - age: "34", - address: { - line_one: "10 High Street", - deep: { - deep_attr: "blue" - } - }) + params = TestParams.new( + env: { + email: "test@hanamirb.org", + password: "123456", + password_confirmation: "123456", + name: "Luca", + tos: "1", + age: "34", + address: { + line_one: "10 High Street", + deep: { + deep_attr: "blue" + } + } + } + ) expect(params.valid?).to be(true) expect(params.errors).to be_empty @@ -205,7 +208,7 @@ end it "has input available through the hash accessor" do - params = TestParams.new(name: "John", age: "1", address: {line_one: "10 High Street"}) + params = TestParams.new(env: {name: "John", age: "1", address: {line_one: "10 High Street"}}) expect(params[:name]).to eq("John") expect(params[:age]).to be(1) @@ -213,7 +216,7 @@ end it "allows nested hash access via symbols" do - params = TestParams.new(name: "John", address: {line_one: "10 High Street", deep: {deep_attr: 1}}) + params = TestParams.new(env: {name: "John", address: {line_one: "10 High Street", deep: {deep_attr: 1}}}) expect(params[:name]).to eq("John") expect(params[:address][:line_one]).to eq("10 High Street") expect(params[:address][:deep][:deep_attr]).to be(1) @@ -224,9 +227,11 @@ context "with data" do let(:params) do TestParams.new( - name: "John", - address: {line_one: "10 High Street", deep: {deep_attr: 1}}, - array: [{name: "Lennon"}, {name: "Wayne"}] + env: { + name: "John", + address: {line_one: "10 High Street", deep: {deep_attr: 1}}, + array: [{name: "Lennon"}, {name: "Wayne"}] + } ) end @@ -257,7 +262,7 @@ end context "without data" do - let(:params) { TestParams.new({}) } + let(:params) { TestParams.new(env: {}) } it "returns nil for nil argument" do expect(params.get(nil)).to be(nil) @@ -282,7 +287,7 @@ end describe "#to_h" do - let(:params) { TestParams.new(name: "Jane") } + let(:params) { TestParams.new(env: {name: "Jane"}) } it "returns a ::Hash" do expect(params.to_h).to be_kind_of(::Hash) @@ -317,7 +322,7 @@ } } - actual = TestParams.new(input).to_h + actual = TestParams.new(env: input).to_h expect(actual).to eq(expected) expect(actual).to be_kind_of(::Hash) @@ -350,7 +355,7 @@ } } - actual = TestParams.new(input).to_h + actual = TestParams.new(env: input).to_h expect(actual).to eq(expected) expect(actual).to be_kind_of(::Hash) @@ -361,7 +366,7 @@ end describe "#to_hash" do - let(:params) { TestParams.new(name: "Jane") } + let(:params) { TestParams.new(env: {name: "Jane"}) } it "returns a ::Hash" do expect(params.to_hash).to be_kind_of(::Hash) @@ -396,7 +401,7 @@ } } - actual = TestParams.new(input).to_hash + actual = TestParams.new(env: input).to_hash expect(actual).to eq(expected) expect(actual).to be_kind_of(::Hash) @@ -429,7 +434,7 @@ } } - actual = TestParams.new(input).to_hash + actual = TestParams.new(env: input).to_hash expect(actual).to eq(expected) expect(actual).to be_kind_of(::Hash) @@ -439,7 +444,7 @@ it "does not stringify values" do input = {"name" => 123} - params = TestParams.new(input) + params = TestParams.new(env: input) expect(params[:name]).to be(123) end @@ -449,9 +454,11 @@ describe "#deconstruct_keys" do let(:params) do TestParams.new( - name: "John", - address: {line_one: "10 High Street", deep: {deep_attr: 1}}, - array: [{name: "Lennon"}, {name: "Wayne"}] + env: { + name: "John", + address: {line_one: "10 High Street", deep: {deep_attr: 1}}, + array: [{name: "Lennon"}, {name: "Wayne"}] + } ) end @@ -472,7 +479,7 @@ end end - let(:params) { klass.new(book: {code: "abc"}) } + let(:params) { klass.new(env: {book: {code: "abc"}}) } it "returns Hanami::Action::Params::Errors" do expect(params.errors).to be_kind_of(Hanami::Action::Params::Errors) @@ -486,7 +493,7 @@ end it "appends message to already existing messages" do - params = klass.new(book: {}) + params = klass.new(env: {book: {}}) params.errors.add(:book, :code, "is invalid") expect(params.error_messages).to eq(["Code is missing", "Code is invalid"]) @@ -498,7 +505,7 @@ end it "raises error when try to add an error " do - params = klass.new({}) + params = klass.new(env: {}) expect { params.errors.add(:book, :code, "is invalid") }.to raise_error(ArgumentError, %(Can't add :book, :code, "is invalid" to {:book=>["is missing"]})) end