narzero narzero -3 years ago 135
Ruby Question

Nested JSON to Ruby class (with validation)

I have the following JSON:

{
"ordernumber":"216300001000",
"datecreated":"2016-11-08T14:23:06.631Z",
"shippingmethod":"Delivery",
...
"customer":{
"firstname":"Victoria",
"lastname":"Validator"
},
"products":[
{
"sku":"ABC1",
"price":"9.99"
},
...
]
}


With the corresponding Ruby classes including validators:

class Task
include ActiveModel::Model
include ActiveModel::Serializers::JSON

validates ..., presence: true
...
end

class Product
include ActiveModel::Model
include ActiveModel::Serializers::JSON

validates ..., presence: true
...
end

class Customer
include ActiveModel::Model
include ActiveModel::Serializers::JSON

validates ..., presence: true
...
end


What I want to do is serialise the JSON to a Ruby class. The problem is that the Task class get's initialised correctly. But the nested classes like Customer and Product remain hashes. (A Task has one Customer and multiple Products)

Example:

json = %Q{{ "ordernumber":"216300001000", "datecreated":"2016-11-08T14:23:06.631Z", "shippingmethod":"Delivery", "customer":{ "firstname":"Victoria", "lastname":"Validator" }, "products":[ { "sku":"ABC1", "price":"9.99" } ] }}

task = Task.new()
task.from_json(json)

task.class
# => Task

task.products[0].class
# => Hash


How do I do this using ActiveModel and also validate the nested JSON? (I'm not using Rails)

Answer Source

As far as I know, ActiveModel::Model brings validations and other handy stuff, but it does not bring tools to handle association problems like this one. You have to implement his behavior yourself.

First of all, I'd use the builtin initialization system that ActiveModel::Model provides. Then I'd define products= and customer= to take the attributes and initialize instances of the proper classes. And call the validations of the associated records.

class Task
  include ActiveModel::Model

  attr_reader :products, :customer

  # ...

  validate :associated_records_are_valid

  def products=(ary)
    @products = ary.map(&Product.method(:new))
  end

  def customer=(attrs)
    @customer = Customer.new(attrs)
  end

  private

  def associated_records_are_valid
    products.all?(&:valid?) && customer.valid?
  end
end

attributes = JSON.parse(json_str)
task = Task.new(attributes)
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download