Generate ruby classes from json document - ruby-on-rails

Consuming a ruby json API, I want to save me some work and generate ruby objects off the bat. Any way to do this?
so you could transform this:
{"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}}
to this:
class Menu
attr_accessor :id
attr_accessor :file
attr_accessor :popup
end

If you're looking to turn a JSON string into a Ruby Hash you can do
my_hash = JSON.parse('{"foo":"bar"}')
puts my_hash['foo']

There is a wonderful gem for doing this. https://github.com/apotonick/representable/
Here's what your representable would look like
module MenuRepresenter
include Representable::JSON
property :id
property :value
property :popup
end
Create your model
class Menu
attr_accessor :id, :value, :popup
end
menu = Menu.new.extend(MenuRepresenter).from_json(json)
# You can convert it back into json via .to_json
# expect(menu.to_json).to eq(json)
The example above shows only the basic implementation, you would want to create another class for the menu item, take a look at the documentation at the github repo for more detailed information.

If you want "methodized" hashes which use accessor methods (not a bad idea at all!)
require "ostruct"
object_like_hash = OpenStruct.new(some_hash_with_string_or_symbol_keys)
object_like_hash.foo #=> same as some_hash["foo"]
Nonexistent keys will return nil and not raise unfortunately.

I think you are a little bit confused. In the question, you ask how to turn a JSON document into classes. In the comments, you say you want a JSON version of the RXSD XML tool, which however, turns XML schemas into Ruby classes.
Turning JSON documents into classes doesn't really make sense. If you compare the world of document markup to programming, documents correspond to objects and schemas correspond to classes (well, types, actually, but since we're talking about Ruby, let's not open that can of worms and stick with classes).
So, it makes sense to generate Ruby objects from JSON documents and it makes sense to generate Ruby classes from JSON schemas, but it doesn't make sense to generate Ruby classes from JSON documents. The bad news is of course that in order to be able to automatically generate Ruby classes from JSON schema is that in order for that to work, the JSON schema has to be in an automatically processable (IOW machine-readable) format.
Unfortunately, there is no such thing as a JSON schema, and thus JSON schemas tend to generally not be machine-readable, but rather are just a blurb of human-oriented English text on the API documentation page of the web service provider. If you're lucky. More often than not, there is no documentation at all about the JSON schema.
So, since there is no standardized way of describing JSON schemas, there cannot be a standardized tool for processing JSON schemas. Unlike XML, where there is a limited number of standardized schemas (DTD, XSD, RelaxNG).
Note that what I wrote above is not strictly true: there are specifications for JSON schemas (e.g. JSON-Schema) and there are Ruby implementations of those (e.g. Ruby/JSONSchema, validation only, doesn't generate classes), but nobody is using them, so they might just as well not exist.

Related

How do I store a Ruby object in my Rails app without using a database?

Given
This Ruby class:
class Quiz
attr_accessor :topic, :course, :highest_grade, :failing_grade, :answers_by_questions
def initialize(topic, course, highest_grade, failing_grade)
#topic = topic
#course = course
#highest_grade = highest_grade
#failing_grade = failing_grade
#answers_by_questions = Hash.new()
end
def add_question(question, answer)
#answers_by_questions[question] = answer
end
end
This instance of the Quiz class:
ch1_history_quiz = Quiz.new("Chapter 1", "History", 100, 65)
ch1_history_quiz.add_question("Which ancient civilisation created the boomerang?", "Aboriginal Australians")
Objective
Save this quiz object to be accessed later by all users of the app.
Question
How to store and access this quiz object in Rails 6.0 without representing it in a database?
Possible Solutions
Is it possible to use Marshal to pickle the object and save it to be used later?
What I've checked out so far
SO - Store json object in ruby on rails app: Because this is JSON, going back and forth between the JSON format and the parsed format is more intuitive.
SO - Can I create a database of ruby classes?: I don't really get the answer to this question. Some of the links are broken. I think the question is pretty similar, though.
Is it possible to use Marshal to pickle the object and save it to be
used later?
Yes, using your same code as example, you could serialize and deserialize the object recovering it just as it was.
irb(main):021:0> marshalled = Marshal.dump(ch1_history_quiz)
=> "\x04\bo:\tQuiz\n:\v#topicI\"\x0EChapter 1\x06:\x06ET:\f#courseI\"\fHistory\x06;\aT:\x13#highest_gradeii:\x13#failing_gradeiF:\x1A#answers_by_questions{\x06I\"6Which ancient civilisation created the boomerang?\x06;\aTI\"\eAboriginal Australians\x06;\aT"
irb(main):022:0> deserialized = Marshal.load(marshalled)
=> #<Quiz:0x00007f81d3995348 #topic="Chapter 1", #course="History", #highest_grade=100, #failing_grade=65, #answers_by_questions={"Which ancient civilisation created the boomerang?"=>"Aboriginal Australians"}>
Another option, more readable is serializing your object in YAML format:
irb(main):030:0> yamled = YAML.dump(ch1_history_quiz)
=> "--- !ruby/object:Quiz\ntopic: Chapter 1\ncourse: History\nhighest_grade: 100\nfailing_grade: 65\nanswers_by_questions:\n Which ancient civilisation created the boomerang?: Aboriginal Australians\n"
irb(main):031:0> deserialized = YAML.load(yamled)
=> #<Quiz:0x00007f81d398cb80 #topic="Chapter 1", #course="History", #highest_grade=100, #failing_grade=65, #answers_by_questions={"Which ancient civilisation created the boomerang?"=>"Aboriginal Australians"}>
With any of both options, you could save it later in a DB text field (with enough space to keep big objects), a plain-text file or whatever you choose.
Also, something very important to keep in mind are the security issues about using marshal or yaml, so objects must always be loaded from trusted sources.

How to make a XML generator so it can be usable for all kind of objects?

What is the best way to create xml generator in ruby, but in a way so it can receive any model and generate xml by its fields and values?
For now i can make a Struct:
Person = Struct.new(:name, :age, :grades)
Person.new('Name', 21, [{:math => 5, :english => 5}])
and than pass that object to algorithm that converts it to a hash and recursively generate xml using Nokogiri Builder.
But in this way I have to make a hash structure by hand, which isn't good I guess. How to make this so that I can just instantiate objects and pass them to a function?

ActiveModelSerializer with Sinatra

Can I use active_model_serializers with Sinatra? If not, is there any better way for json output in Sinatra for building a web service?
Yes, you can. However, the design and architecture of AMS is strongly focuses on ActiveModel instances, therefore if you don't use an ActiveModel-based library (such as Mongoid, ActiveRecord, etc) the choice may be overkill.
Still, the approach reflects the common Presenter pattern applied to JSON serialization. You can easily create your own simple library to extract the attributes you define from an object you pass.
Sinatra also provides some JSON serialization extensions. What they do by default, is to call as_json. That's not the best approach, it is not extremely flexible, but you can combine those two elements to create your own solution, or start from scratch.
You can, includes a json.rb inside the lib folder with the following piece of code and do require this file on your application.rb .
# lib/json.rb
module Sinatra
module JSON
def json(object, options={})
serializer = ActiveModel::Serializer.serializer_for(object, options)
if serializer
serializer.new(object, options).to_json
else
object.to_json(options)
end
end
end
end
To use just do
get '/foo' do
json Foo.find(params[:id])
end
get '/bar' do
json Bar.find(params[:id]), { scope: self }
end
I used to_json to return JSON output from Sinatra API's. It turns out that there are dozens of JSON gems for Ruby, of varying efficiency.
One approach is to create list of attributes for each object that you want to render to JSON. For example, if your User has an image that you don't want to render, you could create a blacklist for the User class:
JSON_BLACKLIST = [ 'image' ]
Then, when you render the JSON, you can call:
user.attributes.reject{|a| JSON_BLACKLIST.include?( a )}.to_json

Serialize/De-serialize objects via HTTP, Ruby (no ROR)

There is already similar question to this but I am not satisfied with answers since I am trying to do something more complex.
I have web service which provides list/single objects. Objects are Users, Categories, etc. Here is example of object:
<UserObject name="foo" description="bar" category=<Category name="cat1" description="bar"> locations=[<Location id=1>, <Location id=2>] >
In other words objects are somewhat complex and can be arrays of those objects. I am looking for a way to:
Serialize these object to JSON or Hash string
Send them over HTTP
Deserialize them to OpenStruct objects
Service that is serializing objects is not ROR.
App that is receiving and deserializing objects is ROR.
There must be some generic way to do this, I tried using to_json and JSON.parse but it only de-serializes object to one level. So for example above I would get:
<OpenStruct name="foo" description="bar" category="{\"name\"... JSON STRING}" locations="JSON STRING">
Instead of JSON STRINGs I would like to get objects inside object as it was in original.
Ruby: 1.9.3
Thanks
Take a look at the oj gem. It allows you to serialize and deserialize ruby objects to and from json. It also has the benefit of being very fast.
After looking into oj gem and contacting it's creator Peter Ohler, who was very nice and helped, I was able to get desired effect.
require 'oj'
# user instance is nested instance
json_string = Oj.dump user
# send over http
# de-serialize without domain classes (classes created by Oj gem)
user = Oj.load(json_string, { :auto_define => true })
Thanks to #josh-voigts for letting me know about the gem.

Parse a string as if it were a querystring in Ruby on Rails

I have a string like this:
"foo=bar&bar=foo&hello=hi"
Does Ruby on Rails provide methods to parse this as if it is a querystring, so I get a hash like this:
{
:foo => "bar",
:bar => "foo",
:hello => "hi"
}
Or must I write it myself?
EDIT
Please note that the string above is not a real querystring from a URL, but rather a string stored in a cookie from Facebook Connect.
The answer depends on the version of Rails that you are using. If you are using 2.3 or later, use Rack's builtin parser for params
Rack::Utils.parse_nested_query("a=2") #=> {"a" => "2"}
If you are on older Rails, you can indeed use CGI::parse. Note that handling of hashes and arrays differs in subtle ways between modules so you need to verify whether the data you are getting is correct for the method you choose.
You can also include Rack::Utils into your class for shorthand access.
The
CGI::parse("foo=bar&bar=foo&hello=hi")
Gives you
{"foo"=>["bar"], "hello"=>["hi"], "bar"=>["foo"]}
Edit:
As specified by Ryan Long this version accounts for multiple values of the same key, which is useful if you want to parse arrays too.
Edit 2:
As Ben points out, this may not handle arrays well when they are formatted with ruby on rails style array notation.
The rails style array notation is: foo[]=bar&foo[]=nop. That style is indeed handled correctly with Julik's response.
This version will only parse arrays correctly, if you have the params like foo=bar&foo=nop.
Edit : as said in the comments, symolizing keys can bring your server down if someone want to hurt you. I still do it a lot when I work on low profile apps because it makes things easier to work with but I wouldn't do it anymore for high stake apps
Do not forget to symbolize the keys for obtaining the result you want
Rack::Utils.parse_nested_query("a=2&b=tralalala").deep_symbolize_keys
this operation is destructive for duplicates.
If you talking about the Urls that is being used to get data about the parameters them
> request.url
=> "http://localhost:3000/restaurants/lokesh-dhaba?data=some&more=thisIsMore"
Then to get the query parameters. use
> request.query_parameters
=> {"data"=>"some", "more"=>"thisIsMore"}
If you want a hash you can use
Hash[CGI::parse(x).map{|k,v| [k, v.first]}]

Resources