Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON to ActiveRecord (deserialize)

I'm building a web API with Ruby, Grape and ActiveRecord. Coming from ASP.NET Web API I'm used to having automatic model binding from JSON to a class object which then can be saved with Entity Framework. I have been searching a bit to find out if there is anything similiar to this when using ActiveRecord but haven't found anything which make me think that I'm missing something really obvious. What is the best way to deserialize JSON into ActiveRecord models?

UPDATE

Matts answer worked great for simple types but when I'm having associations I get the following error: ActiveRecord::AssociationTypeMismatch: Connector(#70297711668780) expected, got Hash(#70297701477140)

This is the code I'm running

class Card < ActiveRecord::Base
   has_many    :connectors, autosave: true
end

class Connector < ActiveRecord::Base
   has_one  :card
   has_one  :connected_card, :class_name => "Card", :foreign_key => "connected_card_id"
end

json = "{\"id\":\"1\", \"connectors\": [{\"id\": 1, \"card_id\":1}]}"
card = Card.new(ActiveSupport::JSON.decode(json))
like image 228
Abris Avatar asked Apr 30 '15 13:04

Abris


2 Answers

You can use standard JSON decoding from ActiveSupport, which will give you a hash.

You will need to then build a new ActiveRecord object from that hash.

my_object = Foo.new(ActiveSupport::JSON.decode(some_json))

Swap new for create if you want it to save in one line.

like image 108
Matt Avatar answered Oct 23 '22 08:10

Matt


In your model Card you have created an association named connectors. This is an ActiveRecord::Relation method, and it will be expecting a bunch of models from the class Connector. You, otherwise, are passing an array of hashes.

{"id"=>"1", "connectors"=>[{"id"=>1, "card_id"=>1}]}

In to your #new method this becomes something like:

Card.new(id: 1, connectors: [{id: 1, card_id: 1}])

Once you get there, the connectors array should be filled with Connector instances, not hashes. And that's why is failing.

You have two options. First is to rename the key in a before_action in the controller and use nested attributes:

class Card < ActiveRecord::Base
  has_many :connectors
  accepts_nested_attributes_for :connectors
end

class CardsController < ApplicationController
  before_action :set_nested

  private
  def set_nested
    params["connectors_attributes"] = params.delete("connectors")
  end
end

Another option is to create a callback in the model to create the objects from the hash.

class Card < ActiveRecord::Base
  def initialize(params={})
    params["connectors"] = params["connectors"].map {|connector| Connector.new(connector)}
    super(params)
  end
end

Any of the above will fit to your needs. IMO the best one is the first, as you won't be redefining ActiveRecord#Methods

like image 37
Jorge de los Santos Avatar answered Oct 23 '22 07:10

Jorge de los Santos