virtus icon indicating copy to clipboard operation
virtus copied to clipboard

Elements with specific type

Open anoam opened this issue 9 years ago • 7 comments

Hi! It doesn't seems like bug or something. But it's look pretty weird for me. Here is my code:

class Foo
  include Virtus.model(nulify_blank: true)
  attribute :int_array, Array[Integer]
end

foo = Foo.new(int_array: ["", "1", 2, "1bar", "baz"])
foo.int_array #=> ["", 1, 2, "1bar", "baz"]

Is there ways to drop attributes that wasn't converted to integer?

anoam avatar Dec 11 '15 07:12 anoam

This is correct behaviour, how would you coerce "baz" to a Integer?

You can filter it yourself

foo.int_array.select { |k,v| v.is_a? Integer }

hl avatar Dec 29 '15 15:12 hl

@zenry no it's not and it never will be. If a user asks for an integer array and a non-integer is given it should throw at minimum or reject at the extreme... that is correct behavior.

envygeeks avatar Dec 29 '15 20:12 envygeeks

Well I have strict mode on so in my case it will fail loudly. That said I would not like Virtus or any library for that matter to just discard elements in my array. Coerce in non strict mode means to me that it will try to coerce it into the proper type and leave the rest as is.

What you guys want sounds more like a filter.

hl avatar Dec 29 '15 21:12 hl

I don't think it should coerce that's for sure, I think it should throw though.

envygeeks avatar Dec 29 '15 21:12 envygeeks

Agreed, but I also like that fact that you can turn that option on and off (strict mode)

hl avatar Dec 29 '15 21:12 hl

Hi! Sorry for my late reply. First of all, I want to say that coercing array elements ignores nulify_blank: true. In my example even "" was casted as "", not nil.

Second, default coersions are unexpectable. When result of coercing "baz" to Integer is "baz" also looks little bit weird. I expected to see 0 or `nil. Using strict can be useful. But does not raising-catching exceptions means bad performance? Ofcourse, I can resolve it with custom coercion.

But coercing arrays is problem for me. Is there any ways to customize it? I want something like this:

class MyInteger < Virtus::Attribute
  primitive Integer

  def coerce(input)
    res = super
    res.is_a?(Integer) ? res : nil
  end
end

class MyArray < Virtus::Attribute::Array
  def coerce
    super.uniq.compact
  end
end

class Foo
  include Virtus.model(nulify_blank: true)

  attribute :int_array, MyArray[MyInteger]
end

So, behaviour I look for, could be like this:

Foo.new(int_array: ["", "1", 2, "1bar", "baz"]).int_array #=> [1, 2]

In sources I found Collection class. It looks like what i need. But it's documentation is poor. I can't understand how to use it.

Also I could override getter like this.

class Foo::ArrayCoercer
  def initialize(array)
    @array = array
  end

  def to_a
    @array.uniq.compact
  end
end

class Foo
  include Virtus.model(nulify_blank: true)

  attribute :int_array, Array[Integer]

  def int_array
    ArrayCoercer.new(super)
  end
end

anoam avatar Feb 12 '16 09:02 anoam

I'm facing a similar issue. I want a custom Array attribute, that ignores nil entries:

class CompactedArray < Virtus::Attribute::Array
  def coerce(value)
    super&.compact
  end
end

class Foo
  include Virtus.mode

  attribute :bars, CompactedArray[Bar]
end

But coerce never gets called. It does work if I replace Virtus::Attribute::Array with Virtus::Attribute, but then it will complain about other methods that need to be implemented in my CompactedArray. Any ideas on how to implement it?

phstc avatar Oct 05 '16 00:10 phstc