-
Notifications
You must be signed in to change notification settings - Fork 108
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
base: master
Are you sure you want to change the base?
Conversation
I just tried using this in a rails app and updated the Gemfile:
But I struggle to use it: Message = Struct.new(:timestamp, :items, keyword_init: true)
Item = Struct.new(:id, :name, keyword_init: true)
class MessageRepresenter < Representable::Decorator
include Representable::JSON
nested :metadata, default: {} do
property :timestamp,
# default: ->(represented:, **) { represented.timestamp || Time.now.iso8601(3) } # msg.timestamp.call # => ArgumentError (missing keyword: represented)
# default: ->(options) { Time.now.iso8601(3) } # msg.timestamp.call # => ArgumentError (wrong number of arguments (given 0, expected 1))
default: -> { Time.now.iso8601(3) } # msg.timestamp.call # works
end
end
msg = Message.new
MessageRepresenter.new(msg).from_json('{}')
msg.timestamp # => #<Proc:0x00007fd748559818@(irb):9 (lambda)> It returns a lambda, which is the old behavior on master. BTW, it would be really nice to also add some tests for |
Good point @esambo , added tests for collections and nested attributes! Although, I don't have to explicitly call the procs as per your example 🤔 Can you confirm from your |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was able to use this new lambda default in the cloned repo once I switched to the branch and executed:
irb -I lib -r representable
:
Message = Struct.new(:timestamp, :items, keyword_init: true)
Item = Struct.new(:id, :name, keyword_init: true)
class MessageRepresenter < Representable::Decorator
include Representable::JSON
nested :metadata, default: {} do
property :timestamp,
default: ->(represented:, **_options) { Time.now.iso8601(3) }
end
end
def parsed_timestamp(json)
msg = Message.new
MessageRepresenter.new(msg).from_json(json)
msg.timestamp
end
# works as expected, and returns the current timestamp
parsed_timestamp '{}'
parsed_timestamp '{"metadata":{}}'
# does not work, and will not return the current timestamp
parsed_timestamp '{"metadata":{"timestamp":null}}'
parsed_timestamp '{"metadata":{"timestamp":""}}'
parsed_timestamp '{"metadata":{"timestamp":"NOW"}}'
But my example using a static default in a collection (that works on master) is broken and raises exceptions (in this feature branch):
Message = Struct.new(:timestamp, :items, keyword_init: true)
Item = Struct.new(:id, :name, keyword_init: true)
class MessageRepresenter < Representable::Decorator
include Representable::JSON
nested :metadata, default: {} do
property :timestamp,
default: :static_default,
parse_filter: ->(fragment, _options) {
runtime_default = Time.now.iso8601(3)
fragment == :static_default ? runtime_default : fragment
}
end
collection :items,
class: Item,
default: [{}], # ONLY works if collection key is missing from input. # this default value causes a warning!
# [Declarative] Defaults#merge! and #call still accept arrays and automatically prepend those. This is now deprecated, you should replace `ary` with `Declarative::Variables::Append(ary)`.
parse_filter: ->(fragment, options) {
fragment.empty? ? [Item.new(name: "collection default")] : fragment
} do
property :id, default: 0
nested :details, default: {} do
property :name, default: "missing"
end
end
end
# Empty payload. Defaults work
json = '{}'
msg = Message.new
MessageRepresenter.new(msg).from_json(json)
Time.parse(msg.timestamp).year # => 2022 # GOOD, a dynamic runtime default value in a nested property
msg.items # => [#<struct Item id=0, name="missing">] # GOOD, both have defaults
# Payload with empty collection. Defaults are broken
json = '{"items":[]}'
msg = Message.new
MessageRepresenter.new(msg).from_json(json)
msg.items # => [#<struct Item id=nil, name="collection default">]
msg.items.first.id # => nil # BAD, no default
|
||
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 |
There was a problem hiding this comment.
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 :name, default: "Unknown" | ||
property :keywords, default: ->(represented:, **) { [represented.name.downcase] } | ||
end | ||
nested :metadata do |
There was a problem hiding this comment.
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:
.
Sorry for the silence in between @esambo, I had some other stuff to look into. I'll reply to above comment soon. |
No problem, life can sometimes be very busy. |
Allow
:default
option onproperty
to be accepted as static value or any callable.