Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow defining :default as lambda #263

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/representable/binding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion lib/representable/definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 4 additions & 1 deletion lib/representable/deserializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion lib/representable/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 50 additions & 9 deletions test/default_test.rb
Original file line number Diff line number Diff line change
@@ -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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be very helpful if this collection also showed how to use a default:.
BTW, is there a benefit of using instance: ->(*) { Composer.new } instead of class: Composer? The tests seem to pass either way.

property :id
property :name, default: "Unknown"
property :keywords, default: ->(represented:, **) { [represented.name.downcase] }
end
nested :metadata do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful if this nested also showed how to use a default:.

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
2 changes: 1 addition & 1 deletion test/definition_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down