solnic / virtus

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

Attribute names with weird characters #157

Closed alhafoudh closed 11 years ago

alhafoudh commented 11 years ago

I am consuming API that uses '@' in the attribute names. Is there a way to somehow alias the attribute name, but read/store it as different name? Now I get

class Contact
  include Virtus

  attribute :'firma@ref', String
end

NameError: `@firma@ref' is not allowed as an instance variable name

API response example:

  {
    "funkce": "dělník s r",
    "tel": "777741852",
    "mobil": "777 777 777",
    "id": "10",
    "lastUpdate": "2008-08-27T17:38:13.385+02:00",
    "firma": "code:VSE",
    "firma@ref": "/c/acme_s_r_o_/adresar/12.json",
    "firma@showAs": "VSE: Vysoká škola ekonomická",
    "jmeno": "Eliáš",
    "prijmeni": "Svoboda",
    "email": "svoboda@vse.cz"
  }
mbj commented 11 years ago

Put something infront of virtus that maps to desired key.

You could also patch your constructor:

class Contact
  include Virtus

  def self.new(attributes)
    attributes['your_clean_field']=attributes.delete('your_disallowed_field')
    super(attributes)
  end
end

@solnic I did not verified this works, but I think the super(attributes) call will call the virtus generated constructor?

EDIT: s/value/key/, fix syntax error.

BTW I'd not mutate the argument. My style would prefer to use attributes = attributes.dup before the key mapping.

alhafoudh commented 11 years ago

@mbj yes, it works :)

Working example:

class Contact
  include Virtus

  attribute :firma_ref, String

  def self.new(attributes)
    new_attributes = attributes.dup
    new_attributes[:'firma_ref'] = new_attributes.delete(:'firma@ref')
    super(new_attributes)
  end
end

It would be very helpful to have some key override like this:

class Contact
  include Virtus

  attribute :firma_ref, key: :'firma@ref', String

end

@solnic what do you think?

dkubb commented 11 years ago

I think that a specialized form object would be a better place for this kind of thing. I don't believe Virtus should be the first line of defence against bad input data; that's not it's job, and you need far more things for it to provide comprehensive protection anyway.

IMHO ActiveRecord's insistence on collapsing all responsibilities into a single object are probably tainting the ruby communities' expectations of what something like Virtus should do. I would rather see domain objects that aren't responsible for directly processing web user input.

alhafoudh commented 11 years ago

@dkubb Well, for example roar gem uses virtus for consuming API responses.

mbj commented 11 years ago

@alhafoudh I know multiple ways to cause exceptions in virtus/coercible with invalid input. I think the roar gem should verify the structure of the API response before using virtus. Or it trusts the API. I'd not do this.

mbj commented 11 years ago

@alhafoudh BTW I'm building a "data structure validator" with my https://github.com/mbj/ducktrap project, but this is in a very early stage and underdocumented / underspeced.

alhafoudh commented 11 years ago

You may be right. I think that my use of virtus is wrong.

solnic commented 11 years ago

@alhafoudh your use case requires a mapper. Virtus is not meant to be used as a mapping layer. You'll soon be able to use dm-mapper for what you need. Until then I suggest building a thin mapping layer yourself, it's really not hard :)