Closed attack closed 10 years ago
Like that:
# custom data type
class Temperature; end
class TemperatureAttribute < Virtus::Attribute
primitive Temperature
default nil
def set(instance, value)
# do stuff, access to value, instance, default_value & primitive
end
end
class Forecast
include Virtus.model
attr_accessor :units
attribute :high, TemperatureAttribute
end
Hope this helps
Actually, that's not gonna work since #set
is being mixed in after an attribute instance was created. Re-opening.
For now I'd suggest overriding attribute writer method. This seems like a reasonable solution and the quickest one. Lemme know if that would be a sufficient solution for you.
We have an upcoming new feature where you will be able to customize attribute behavior with a custom extension, that would allow you to tweak #set
method.
I would suggest overriding writer method though, or maybe encapsulating coercion logic inside Temperature
constructor. It doesn't sound right that an attribute coercer would know so much details about temperature.
@attack, will any of @solnic's recommendations work for you? (or have they worked for you already?)
I've got a similar situation where we have a custom type, StrictDate
, which had a custom coercion method to make sure that we get nil
when the value can't be converted.
# Virtus 0.5.5
class StrictDate < Virtus::Attribute::Object
primitive ::Date
coercion_method :to_strict_date
end
module Virtus
class Coercion
class String < Virtus::Coercion::Object
# Default to nil instead of the uncoerced string
def self.to_strict_date(value)
coerced = to_date(value)
coerced = coerced.is_a?(::Date) ? coerced : nil
end
end
end
end
As of 1.0.0, I've tried changing that to
class StrictDate < Virtus::Attribute
primitive ::Date
def coerce(value)
coerced = to_date(value)
coerced = coerced.is_a?(::Date) ? coerced : nil
end
end
This errors b/c to_date
is not defined. Debugging shows that w/in the coerce
method, the primitive
is BasicObject
rather than the expected ::Date
object. Also, the suggested API (from the README) coercer[value.class]
errors because []
is not a method for the coercer
.
What am I missing? Perhaps I'm going about this all wrong?
@stevenharman I think the bulk of the coercion logic was moved out to https://github.com/solnic/coercible
@dkubb Yeah, I noticed that. Perhaps I'm not understanding how the Virtus
API for Virtus::Attribute
is intended to be used. What I'm trying above seems consistent with what is described in the README. This seems like a typical use case - custom attribute types based on a different primitive, yet I can't figure out how to use the API to accomplish it. Any pointers?
@stevenharman I think @elskwid is probably more familiar with the intended API. I've contributed a few things in the past, but I haven't used it in production yet, and my current knowledge of specifics is a bit stale.
@dkubb, @stevenharman: yes, I have more recent experience. I am taking a stroll through Virtus issues tonight. I'll see if I can't help out here.
@stevenharman, the Virtus::Attribute.primitive
interface can get finicky (unstable) when you use a primitive for a built-in primitive, something like Date
, String
, etc. The lookup inside Virtus gets confused and you end up with the BasicObject
that you noted.
Looking at your example it seems that you don't want a custom attribute as much as you want to handle coercions for these dates differently. Here's one way to do that:
The coercer just needs to respond to call(input)
. Which means you can use your imagination.
class NilDateCoercer
def self.call(input)
coerced = begin
Virtus.coercer[input.class].to_date(input)
rescue ::Coercible::UnsupportedCoercion
nil
end
coerced.is_a?(::Date) ? coerced : nil
end
end
class A
include Virtus.model
attribute :at, Date, coercer: NilDateCoercer
end
gives you:
[1] pry(main)> a = A.new
=> #<A:0x007fb1289b70e0 @at=nil>
[2] pry(main)> a.at = "10/12/2013"
=> "10/12/2013"
[3] pry(main)> a.at
=> #<Date: 2013-12-10 ((2456637j,0s,0n),+0s,2299161j)>
[4] pry(main)> a.at = "not-a-date"
=> "not-a-date"
[5] pry(main)> a.at
=> nil
Keep in mind that if you turn on strict
for the coercion then your coercer also needs to respond to .success?(primitive, value)
Thanks for the explanation and example, @elskwid.
You bet, @stevenharman! I'm going to close this issue. @attack if you need more info please feel free to reopen or create a new issue. Have a great day!
I am attempting to use Virtus for attributes with custom data types. In a prior version (ie 1.0.0.beta0), this was trivial.
I am aware of #211 which demonstrates an alternative for the writer_class. Unfortunately this does not allow access to the instance of which the attribute is a part of (eg, the context of the :coercer proc would not have access to Forecast#units).
I am also aware of the Readme for Custom Coercions, but the context of the Virtus::Attribute#coerce method does not have access to the User instance.
I understand this example is based of an early beta and many changes have been made since then. Do you have any suggestions for creating custom coercions with access to the instance during attribute write and/or read?
Thanks for this great library! Mark