Skip to content
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

Rename attributes when transforming to/from JSON #5

Open
d-Pixie opened this issue Jan 21, 2016 · 2 comments
Open

Rename attributes when transforming to/from JSON #5

d-Pixie opened this issue Jan 21, 2016 · 2 comments

Comments

@d-Pixie
Copy link

d-Pixie commented Jan 21, 2016

Hi.

Just wanted to pitch an idea and see what you think: I'm writing an integration gem for a public API. Like many JSON APIs it uses CamelCase for the key names that do not map cleanly to ruby attribute names. For example:

PriceInclVAT should map to price_incl_vat but the gem doesn't do any conversions, leaving you with two choices: roll your own or live with the weird attribute names.

I'd like to add the ability to convert key names when transforming to/from JSON. One way would be to just do a straight map, given at attribute declaration time:

require 'model_attribute'
class User
  extend ModelAttribute
  attribute :id,             :integer, key: 'UserID'
  attribute :paid,           :boolean, key: 'Paid' 
  attribute :name,           :string,  key: 'UserName'
  attribute :created_at,     :time,    key: 'RegistrationTime'
  attribute :vat_schedule,   :json,    key: 'VATSchedule'

  def initialize(attributes = {})
    set_attributes(attributes)
  end
end

or by assigning a conversion method

require 'model_attribute'
class User
  extend ModelAttribute
  attribute :id,             :integer
  attribute :paid,           :boolean
  attribute :name,           :string
  attribute :created_at,     :time
  attribute :vat_schedule,   :json

  def convert( key, export = false )
    map = {
      id: 'UserID',
      paid: 'Paid',
      name: 'UserName',
      created_at: 'RegistrationTime',
      vat_schedule: 'VATSchedule',
    }
    map.reverse! if export
    map[ key ]
  end

  def initialize(attributes = {})
    set_attributes(attributes)
  end
end

Idealy you could do both, use the key directly if it exists and call the method if it doesn't.

require 'model_attribute'
class User
  extend ModelAttribute
  attribute :id,             :integer, key: 'UserID'
  attribute :paid,           :boolean
  attribute :name,           :string,  key: 'UserName'
  attribute :created_at,     :time,    key: 'RegistrationTime'
  attribute :vat_schedule,   :json,    key: 'VATSchedule'

  def convert( key, export = false)
    return key.to_s.capitalize unless export
    'UserID'.gsub(/(.+[a-z])([A-Z])/, '\1_\2' ).downcase.to_sym
  end

  def initialize(attributes = {})
    set_attributes(attributes)
  end
end

Right now I use Virtus with a hand rolled version of the combined approach. I have a map and a generic method. If the key is in the map it's used else the method is run.

Would this be interesting for you? If so I can open a PR when I have something that we can discuss.

@dwaller
Copy link
Contributor

dwaller commented Feb 3, 2016

Firstly, sorry for taking so long to reply. I've adjusted my github notification settings so hopefully I'll do better next time!

You pose an interesting question. I wrote this gem with a direct mapping between attribute names and JSON keys because I could control the server on the other side to match that! Hence this gem also has opinions on what format to output values in the JSON (e.g. timestamps should be milliseconds since the epoch) for efficient serialisation and deserialisation on both servers.

So if too many of these conventions don't suit, then maybe this gem isn't a good place to start. I mean you could make it super-flexible, but then you're back to something as complex as Virtus, so maybe you should have stuck with that!

On the other hand, if it's just being able to map attribute names to JSON keys using something other than the identity function, then it shouldn't be too hard to bake that straight in, without adding too much complexity. My preference is for your first proposal - where the information about how the attribute behaves is all found in a single line.

As for implementation, currently ModelAttibute accepts a :default option; it would be easy to add :json_key but to save too many hash lookups in performance critical parts we probably need to extract an AttributeProperty class to contain the (type, default, json_key) information. That'll also avoid polluting the metaclass with instance variables.

Feel free to create a pull request along these lines - if it waits for me it could be a fair while in coming.

@d-Pixie
Copy link
Author

d-Pixie commented Feb 3, 2016

I had the exact same problem with one of my gems a while back, about notification settings for issues/PRs, so no worries there :)

Ok, I'll look at it and see if I can come up with something neat. If and when I do I'll open a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants