From c1dd6cd0318b6bdbed9952519dae4492f4ae20ea Mon Sep 17 00:00:00 2001 From: "Josep M. Blanquer" Date: Tue, 26 Jan 2016 16:27:52 -0800 Subject: [PATCH 1/2] Call object.dump in rendered for Dumpable instances When fully dumping an object (i.e., when no subfields were selected), that is not a Blueprint, ensure we call `object.dump` if they have the Attributor::Dumpable module. Signed-off-by: Josep M. Blanquer --- CHANGELOG.md | 4 ++- lib/praxis-blueprints/renderer.rb | 4 +-- spec/praxis-blueprints/renderer_spec.rb | 33 ++++++++++++++++++++++--- spec/support/spec_blueprints.rb | 1 + 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b0e3f2..20e7f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## next -* Ensure we call `object.dump` in Renderer when dumping a Attributor::Hash or collection of Attributor::Hash if no subfields were selected. +* Ensure we call `object.dump` in Renderer when fully dumping an instance (or array of instances) that have the Attributor::Dumpable module (i.e., when no subfields were selected) + * In other words, attributor types (custom or not) will need to include the Attributor::Dumpable module and properly implement the dump instance method to produce the right output with native ruby objects. + ## 3.1 diff --git a/lib/praxis-blueprints/renderer.rb b/lib/praxis-blueprints/renderer.rb index 5936591..b2950ff 100644 --- a/lib/praxis-blueprints/renderer.rb +++ b/lib/praxis-blueprints/renderer.rb @@ -57,7 +57,7 @@ def render(object, fields, view=nil, context: Attributor::DEFAULT_ROOT_CONTEXT) def _render(object, fields, view=nil, context: Attributor::DEFAULT_ROOT_CONTEXT) if fields == true return case object - when Attributor::Hash + when Attributor::Dumpable object.dump else object @@ -82,7 +82,7 @@ def _render(object, fields, view=nil, context: Attributor::DEFAULT_ROOT_CONTEXT) if subfields == true hash[key] = case value - when Attributor::Hash + when Attributor::Dumpable value.dump else value diff --git a/spec/praxis-blueprints/renderer_spec.rb b/spec/praxis-blueprints/renderer_spec.rb index 049a3e2..80e675b 100644 --- a/spec/praxis-blueprints/renderer_spec.rb +++ b/spec/praxis-blueprints/renderer_spec.rb @@ -4,13 +4,21 @@ let(:address) { Address.example } let(:prior_addresses) { 2.times.collect { Address.example } } + let(:alias_one) { FullName.example } + let(:alias_two) { FullName.example } + let(:aliases) { [alias_one, alias_two] } + let(:metadata_hash) { { something: 'here' } } + let(:metadata) { Attributor::Hash.load( metadata_hash ) } + let(:person) do Person.example( address: address, email: nil, prior_addresses: prior_addresses, alive: false, - work_address: nil + work_address: nil, + aliases: aliases, + metadata: metadata ) end @@ -27,7 +35,9 @@ }, prior_addresses: [{name: true}], work_address: true, - alive: true + alive: true, + metadata: true, + aliases: [true] } end @@ -36,7 +46,7 @@ subject(:output) { renderer.render(person, fields) } it 'renders existing attributes' do - output.keys.should match_array([:name, :full_name, :alive, :address, :prior_addresses]) + output.keys.should match_array([:name, :full_name, :alive, :address, :prior_addresses, :metadata, :aliases]) output[:name].should eq person.name output[:full_name].should eq({first: person.full_name.first, last: person.full_name.last}) @@ -50,6 +60,23 @@ expected_prior_addresses = prior_addresses.collect { |addr| {name: addr.name} } output[:prior_addresses].should match_array(expected_prior_addresses) + + expected_aliases = aliases.collect { |the_alias| the_alias.dump } + output[:aliases].should match_array( expected_aliases ) + + output[:metadata].should eq( metadata.dump ) + end + + context 'calls dump for non-Blueprint, but still Dumpable instances' do + it 'when rendering them in full as array members' do + alias_one.should_receive(:dump).and_call_original + output[:aliases].first.should eq( first: alias_one.first, last: alias_one.last ) + end + it 'when rendering them in full as leaf object' do + metadata.should_receive(:dump).and_call_original + output[:metadata].should eq( metadata_hash ) + end + end it 'does not render attributes with nil values' do diff --git a/spec/support/spec_blueprints.rb b/spec/support/spec_blueprints.rb index 0c721e9..51444e5 100644 --- a/spec/support/spec_blueprints.rb +++ b/spec/support/spec_blueprints.rb @@ -22,6 +22,7 @@ class Person < Praxis::Blueprint attribute :alive, Attributor::Boolean, default: true attribute :myself, Person attribute :friends, Attributor::Collection.of(Person) + attribute :metadata, Attributor::Hash end view :default do From beec198758876788c9e09c6dd93e7e398f34edc3 Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Thu, 18 Feb 2016 14:16:33 -0800 Subject: [PATCH 2/2] fix Blueprint#validate to also check any attribute requirements Signed-off-by: Dane Jensen --- lib/praxis-blueprints/blueprint.rb | 13 +++++++++++-- praxis-blueprints.gemspec | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/praxis-blueprints/blueprint.rb b/lib/praxis-blueprints/blueprint.rb index 1e7628f..b609355 100644 --- a/lib/praxis-blueprints/blueprint.rb +++ b/lib/praxis-blueprints/blueprint.rb @@ -193,7 +193,6 @@ def self.example(context=nil, **values) def self.validate(value, context=Attributor::DEFAULT_ROOT_CONTEXT, _attribute=nil) - raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context == nil context = [context] if context.is_a? ::String @@ -341,19 +340,29 @@ def render(view_name=nil, context: Attributor::DEFAULT_ROOT_CONTEXT,renderer: Re def validate(context=Attributor::DEFAULT_ROOT_CONTEXT) raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context == nil context = [context] if context.is_a? ::String + keys_with_values = [] raise "validation conflict" if @validating @validating = true - self.class.attributes.each_with_object(Array.new) do |(sub_attribute_name, sub_attribute), errors| + errors = [] + self.class.attributes.each do |sub_attribute_name, sub_attribute| sub_context = self.class.generate_subcontext(context,sub_attribute_name) value = self.send(sub_attribute_name) + unless value.nil? + keys_with_values << sub_attribute_name + end if value.respond_to?(:validating) # really, it's a thing with sub-attributes next if value.validating end errors.push(*sub_attribute.validate(value, sub_context)) end + self.class.attribute.type.requirements.each do |req| + validation_errors = req.validate(keys_with_values, context) + errors.push(*validation_errors) unless validation_errors.empty? + end + errors ensure @validating = false end diff --git a/praxis-blueprints.gemspec b/praxis-blueprints.gemspec index a5ae2c4..356ff2f 100644 --- a/praxis-blueprints.gemspec +++ b/praxis-blueprints.gemspec @@ -20,7 +20,7 @@ it results in a structured hash instead of an encoded string. Blueprints can aut spec.require_paths = ["lib"] spec.add_runtime_dependency(%q, ["~> 0"]) - spec.add_runtime_dependency(%q, [">= 4.2"]) + spec.add_runtime_dependency(%q, [">= 5.0.2"]) spec.add_runtime_dependency(%q, [">= 3"]) spec.add_development_dependency "bundler", "~> 1.6"