diff --git a/lib/representable/binding.rb b/lib/representable/binding.rb index 9864e399..8802d0a0 100644 --- a/lib/representable/binding.rb +++ b/lib/representable/binding.rb @@ -74,10 +74,10 @@ def skipable_empty_value?(value) value.nil? and !(self[:render_nil]) end + # DISCUSS: Do we use this anymore ? def default_for(value) - return self[:default] if skipable_empty_value?(value) - - value + return value unless skipable_empty_value?(value) + return self[:default].(exec_context: self) if self[:default] end attr_accessor :cached_representer diff --git a/lib/representable/definition.rb b/lib/representable/definition.rb index 32435130..7b9b73af 100644 --- a/lib/representable/definition.rb +++ b/lib/representable/definition.rb @@ -9,7 +9,8 @@ module Representable class Definition < ::Declarative::Definitions::Definition def initialize(sym, options={}, &block) - options[:extend] = options[:nested] if options[:nested] + options[:extend] = options[:nested] if options[:nested] + options[:default] = Representable::Option(options[:default]) if options[:default] super diff --git a/lib/representable/deserializer.rb b/lib/representable/deserializer.rb index 5c72dbfb..bb0839f7 100644 --- a/lib/representable/deserializer.rb +++ b/lib/representable/deserializer.rb @@ -24,7 +24,10 @@ module Representable end Default = ->(input, options) do - Binding::FragmentNotFound == input ? options[:binding][:default] : input + binding = options[:binding] + + return input if Binding::FragmentNotFound != input + binding[:default].call(exec_context: binding.send(:exec_context, options), keyword_arguments: options) end SkipParse = ->(input, options) do diff --git a/lib/representable/serializer.rb b/lib/representable/serializer.rb index 6c4f16bc..989341c4 100644 --- a/lib/representable/serializer.rb +++ b/lib/representable/serializer.rb @@ -14,7 +14,8 @@ module Representable RenderDefault = ->(input, options) do binding = options[:binding] - binding.skipable_empty_value?(input) ? binding[:default] : input + return input unless binding.skipable_empty_value?(input) + binding[:default].(exec_context: binding.send(:exec_context, options), keyword_arguments: options) end StopOnSkipable = ->(input, options) do diff --git a/test/default_test.rb b/test/default_test.rb index 0ab50bae..106720d2 100644 --- a/test/default_test.rb +++ b/test/default_test.rb @@ -1,34 +1,75 @@ require "test_helper" class DefaultTest < MiniTest::Spec - Song = Struct.new(:id, :title) + TIMESTAMP = Time.now + + Composer = Struct.new(:id, :name, :keywords) + Song = Struct.new(:id, :title, :album, :composers, :created_at, :created_by) representer! do property :id property :title, default: "Huber Breeze" #->(options) { options[:default] } + property :album, default: ->(represented:, **) { represented.album || "Spring" } + collection :composers, instance: ->(*) { Composer.new } do + property :id + property :name, default: "Unknown" + property :keywords, default: ->(represented:, **) { [represented.name.downcase] } + end + nested :metadata do + property :created_by, default: :default_creator, exec_context: :decorator + property :created_at, default: ->(*) { TIMESTAMP } + + def default_creator(*) + represented.composers&.first ? represented.composers.first.id : nil + end + + def created_by + represented.created_by + end + + def created_by=(value) + represented.created_by = value + end + end end describe "#from_hash" do - let(:song) { Song.new.extend(representer) } + let(:new_song) { Song.new.extend(representer) } + let(:old_song) { Song.new(1, nil, "Atumn",[Composer.new(1, "Jerry")]).extend(representer) } + + it { _(old_song.from_hash({})).must_equal Song.new(1, "Huber Breeze", "Atumn",[Composer.new(1, "Jerry")]) } + it { _(new_song.from_hash({})).must_equal Song.new(nil, "Huber Breeze", "Spring") } + + it { _(old_song.from_hash({"title"=>"Blindfold", "album"=>"Lil"})).must_equal Song.new(1, "Blindfold", "Lil",[Composer.new(1, "Jerry")]) } + it { _(new_song.from_hash({"title"=>"Blindfold", "album"=>"Lil"})).must_equal Song.new(nil, "Blindfold", "Lil") } - it { _(song.from_hash({})).must_equal Song.new(nil, "Huber Breeze") } # default doesn't apply when empty string. - it { _(song.from_hash({"title"=>""})).must_equal Song.new(nil, "") } - it { _(song.from_hash({"title"=>nil})).must_equal Song.new(nil, nil) } - it { _(song.from_hash({"title"=>"Blindfold"})).must_equal Song.new(nil, "Blindfold") } + it { _(old_song.from_hash({"title"=>"", "album"=>""})).must_equal Song.new(1, "", "",[Composer.new(1, "Jerry")]) } + it { _(new_song.from_hash({"title"=>"", "album"=>""})).must_equal Song.new(nil, "", "") } + it { _(old_song.from_hash({"title"=>nil, "album"=>nil})).must_equal Song.new(1, nil, nil,[Composer.new(1, "Jerry")]) } + it { _(new_song.from_hash({"title"=>nil, "album"=>nil})).must_equal Song.new(nil, nil, nil) } + + # defaults within empty collections and nested + it { _(old_song.from_hash({"composers"=>[],"metadata"=>{}})).must_equal Song.new(1, "Huber Breeze", "Atumn", [], TIMESTAMP, nil) } + it { _(new_song.from_hash({"composers"=>[],"metadata"=>{}})).must_equal Song.new(nil, "Huber Breeze", "Spring", [], TIMESTAMP, nil) } + it { _(old_song.from_hash({"composers"=>[{}],"metadata"=>{"created_at"=>"", "created_by"=>""}})).must_equal Song.new(1, "Huber Breeze", "Atumn", [Composer.new(nil, "Unknown", ["unknown"])], "", "") } + it { _(new_song.from_hash({"composers"=>[{}],"metadata"=>{"created_at"=>"", "created_by"=>""}})).must_equal Song.new(nil, "Huber Breeze", "Spring", [Composer.new(nil, "Unknown", ["unknown"])], "", "") } + + # defaults within filled collections and nested + it { _(new_song.from_hash({"composers"=>[{"id"=>1,"name"=>"Tom"}],"metadata"=>{"created_at"=>2022}})).must_equal Song.new(nil, "Huber Breeze", "Spring", [Composer.new(1, "Tom", ["tom"])], 2022, 1) } end describe "#to_json" do it "uses :default when not available from object" do - _(Song.new.extend(representer).to_hash).must_equal({"title"=>"Huber Breeze"}) + _(Song.new.extend(representer).to_hash).must_equal({"title"=>"Huber Breeze", "album"=>"Spring", "metadata"=>{"created_at"=>TIMESTAMP}}) end it "uses value from represented object when present" do - _(Song.new(nil, "After The War").extend(representer).to_hash).must_equal({"title"=>"After The War"}) + _(Song.new(nil, "After The War", "1964").extend(representer).to_hash).must_equal({"title"=>"After The War","album"=>"1964","metadata"=>{"created_at"=>TIMESTAMP}}) end it "uses value from represented object when emtpy string" do - _(Song.new(nil, "").extend(representer).to_hash).must_equal({"title"=>""}) + _(Song.new(nil, "", "").extend(representer).to_hash).must_equal({"title"=>"", "album"=>"","metadata"=>{"created_at"=>TIMESTAMP}}) end end end \ No newline at end of file diff --git a/test/definition_test.rb b/test/definition_test.rb index 7a505ed6..9009681a 100644 --- a/test/definition_test.rb +++ b/test/definition_test.rb @@ -220,7 +220,7 @@ class DefinitionTest < MiniTest::Spec it "accepts a default value" do @def = Representable::Definition.new(:song, :default => "Atheist Peace") - assert_equal "Atheist Peace", @def[:default] + assert_equal "Atheist Peace", @def[:default].(exec_context: Object) end end