diff --git a/Gemfile b/Gemfile index 915125be..44125f49 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ end gem 'lotus-utils', '~> 0.3', '>= 0.3.1.dev', require: false, github: 'lotus/utils', branch: '0.3.x' gem 'lotus-router', '>= 0.2.0.dev', require: false, github: 'lotus/router', branch: '0.2.x' -gem 'lotus-validations', '>= 0.2.0.dev', require: false, github: 'lotus/validations', branch: '0.2.x' +gem 'lotus-validations', '>= 0.2.0.dev', require: false, github: 'lotus/validations', branch: 'master' gem 'simplecov', require: false gem 'coveralls', require: false diff --git a/lib/lotus/action/params.rb b/lib/lotus/action/params.rb index 336965b1..d22754b2 100644 --- a/lib/lotus/action/params.rb +++ b/lib/lotus/action/params.rb @@ -80,8 +80,8 @@ class Params # # params = SignupParams.new({}) # params.valid? # => raise ArgumentError - def self.param(name, options = {}) - attribute name, options + def self.param(name, options = {}, &block) + attribute name, options, &block nil end @@ -91,6 +91,17 @@ def self.whitelisting? defined_attributes.any? end + # Overrides the method in Lotus::Validation to build a class that + # inherits from Params rather than only Lotus::Validations. + # + # @since x.x.x + # @api private + def self.build_validation_class(&block) + kls = Class.new(Params) + kls.class_eval(&block) + kls + end + # @attr_reader env [Hash] the Rack env # # @since 0.2.0 diff --git a/test/action/params_test.rb b/test/action/params_test.rb index 89d2a3c4..0547e93b 100644 --- a/test/action/params_test.rb +++ b/test/action/params_test.rb @@ -131,6 +131,12 @@ param :name, presence: true param :tos, acceptance: true param :age, type: Integer + param :address do + param :line_one, presence: true + param :deep do + param :deep_attr, type: String + end + end end end @@ -143,22 +149,52 @@ params.valid?.must_equal false - params.errors.for(:email).must_include Lotus::Validations::Error.new(:email, :presence, true, nil) - params.errors.for(:name).must_include Lotus::Validations::Error.new(:name, :presence, true, nil) - params.errors.for(:tos).must_include Lotus::Validations::Error.new(:tos, :acceptance, true, nil) + params.errors.for(:email). + must_include Lotus::Validations::Error.new(:email, :presence, true, nil) + params.errors.for(:name). + must_include Lotus::Validations::Error.new(:name, :presence, true, nil) + params.errors.for(:tos). + must_include Lotus::Validations::Error.new(:tos, :acceptance, true, nil) + params.errors.for('address.line_one'). + must_include Lotus::Validations::Error.new('address.line_one', :presence, true, nil) end it "is it valid when all the validation criteria are met" do - params = TestParams.new({email: 'test@lotusrb.org', name: 'Luca', tos: '1'}) + params = TestParams.new({email: 'test@lotusrb.org', name: 'Luca', tos: '1', address: { line_one: '10 High Street' }}) params.valid?.must_equal true params.errors.must_be_empty end - it "has input available as methods" do - params = TestParams.new(name: 'John', age: '1') + it "has input available through the hash accessor" do + params = TestParams.new(name: 'John', age: '1', address: { line_one: '10 High Street' }) params[:name].must_equal('John') params[:age].must_equal(1) + params[:address][:line_one].must_equal('10 High Street') + end + + it "has input available as methods" do + params = TestParams.new(name: 'John', age: '1', address: { line_one: '10 High Street' }) + params.name.must_equal('John') + params.age.must_equal(1) + params.address.line_one.must_equal('10 High Street') + end + + it "has a nested object even when no input for that object was defined" do + params = TestParams.new({}) + params.address.wont_be_nil + end + + it "has the correct nested param superclass type" do + params = TestParams.new({address: { line_one: '123'}}) + params[:address].class.superclass.must_equal(Lotus::Action::Params) + 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[:name].must_equal 'John' + params[:address][:line_one].must_equal '10 High Street' + params[:address][:deep][:deep_attr].must_equal '1' end end diff --git a/test/fixtures.rb b/test/fixtures.rb index 2c42802f..39547b44 100644 --- a/test/fixtures.rb +++ b/test/fixtures.rb @@ -857,6 +857,34 @@ def call(params) redirect_to '/books' end end + + class Update + include FullStack::Action + + params do + param :id, type: Integer + param :book do + param :title, type: String, presence: true + param :author do + param :name, type: String, presence: true + param :favourite_colour + end + end + end + + def call(params) + valid = params.valid? + + self.status = 201 + self.body = Marshal.dump({ + method_access: params.book.author.name, + symbol_access: params[:book][:author][:name], + string_access: params['book']['author']['name'], + valid: valid, + errors: params.errors.to_h + }) + end + end end module Poll @@ -911,7 +939,7 @@ def initialize resolver = Lotus::Routing::EndpointResolver.new(namespace: FullStack::Controllers) routes = Lotus::Router.new(resolver: resolver) do get '/', to: 'home#index' - resources :books, only: [:index, :create] + resources :books, only: [:index, :create, :update] get '/poll', to: 'poll#start' diff --git a/test/integration/full_stack_test.rb b/test/integration/full_stack_test.rb index e8ed538b..e175d510 100644 --- a/test/integration/full_stack_test.rb +++ b/test/integration/full_stack_test.rb @@ -40,4 +40,38 @@ def app # last_response.body.must_include 'FullStack::Controllers::Poll::Step2' # last_response.body.must_include %(Step 1 completed) end + + it 'can access params with string symbols or methods' do + patch '/books/1', { + id: '1', + book: { + title: 'Lotus in Action', + author: { + name: 'Luca' + } + } + } + result = Marshal.load(last_response.body) + result.must_equal({ + method_access: 'Luca', + symbol_access: 'Luca', + string_access: 'Luca', + valid: true, + errors: {} + }) + end + + it 'validates nested params' do + patch '/books/1', { + id: '1', + book: { + title: 'Lotus in Action', + } + } + result = Marshal.load(last_response.body) + result[:valid].must_equal false + result[:errors].must_equal({ + 'book.author.name' => [Lotus::Validations::Error.new('book.author.name', :presence, true, nil)] + }) + end end