solnic / virtus

[DISCONTINUED ] Attributes on Steroids for Plain Old Ruby Objects
MIT License
3.77k stars 228 forks source link

Elements with specific type #351

Open anoam opened 8 years ago

anoam commented 8 years ago

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?

hl commented 8 years ago

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 }
envygeeks commented 8 years ago

@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.

hl commented 8 years ago

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.

envygeeks commented 8 years ago

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

hl commented 8 years ago

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

anoam commented 8 years ago

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
phstc commented 7 years ago

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?