Rendering a simple Ruby hash with RABL - ruby

I have a ruby hash that I'd like to render using RABL. The hash looks something like this:
#my_hash = {
:c => {
:d => "e"
}
}
I'm trying to render this with some RABL code:
object #my_hash => :some_object
attributes :d
node(:c) { |n| n[:d] }
but I'm receiving {"c":null}
How can I render this with RABL?

This works for arbitrary hash values.
object false
#values.keys.each do |key|
node(key){ #values[key] }
end
Worked for me using Rails 3.2.13 and Ruby 2.0.0-p195

Currently RABL doesn't play too nicely with hashes. I was able to work around this by converting my hash to an OpenStruct format (which uses a more RABL-friendly dot-notation). Using your example:
your_controller.rb
require 'ostruct'
#my_hash = OpenStruct.new
#my_hash.d = 'e'
your_view.rabl
object false
child #my_hash => :c do
attributes :d
end
results
{
"c":{
"d":"e"
}
}

Sometimes its easy to do too much imho.
How about just
render json: my_hash
And just like magic we can delete some code !

RABL deals in objects but does not require a particular ORM. Just objects that support dot notation. If you want to use rabl and all you have is a hash:
#user = { :name => "Bob", :age => 27, :year => 1976 }
then you need to first turn the hash into an object that supports dot notation:
#user = OpenStruct.new({ :name => "Bob", :age => 27, :year => 1976 })
and then within a RABL template treat the OpenStruct as any other object:
object #user
attributes :name, :age, :year
Consider that if everything you are doing in your app is just dealing in hashes and there is no objects or databases involved, you may be better off with an alternative more custom JSON builder such as json_builder or jbuilder.
Pasted from the official wiki page on RABL's github: https://github.com/nesquena/rabl/wiki/Rendering-hash-objects-in-rabl

RABL actually can render ruby hashes and arrays easily, as attributes, just not as the root object. So, for instance, if you create an OpenStruct like this for the root object:
#my_object = OpenStruct.new
#my_object.data = {:c => {:d => 'e'}}
Then you could use this RABL template:
object #my_object
attributes :data
And that would render:
{"data": {"c":{"d":"e"}} }
Alternatively, if you want :c to be a property of your root object, you can use "node" to create that node, and render the hash inside that node:
# -- rails controller or whatever --
#my_hash = {:c => {:d => :e}}
# -- RABL file --
object #my_hash
# Create a node with a block which receives #my_hash as an argument:
node { |my_hash|
# The hash returned from this unnamed node will be merged into the parent, so we
# just return the hash we want to be represented in the root of the response.
# RABL will render anything inside this hash as JSON (nested hashes, arrays, etc)
# Note: we could also return a new hash of specific keys and values if we didn't
# want the whole hash
my_hash
end
# renders:
{"c": {"d": "e"}}
Incidentally, this is exactly the same as just using render :json => #my_hash in rails, so RABL is not particularly useful in this trivial case ;) But it demonstrates the mechanics anyway.

By specifying a node like that, you are given access to the #my_hash object which you can then access attributes of. So I would just slightly change your code to be:
object #my_hash
node(:c) do |c_node|
{:d => c_node.d}
end
where c_node is essentially the #my_hash object. This should give you what you're expecting (shown here in JSON):
{
"my_hash":{
"c":{
"d":"e"
}
}
}

My answer is partially based on the below listed site:
Adapted from this site:
http://www.rubyquiz.com/quiz81.html
require "ostruct"
class Object
def to_openstruct
self
end
end
class Array
def to_openstruct
map{ |el| el.to_openstruct }
end
end
class Hash
def to_openstruct
mapped = {}
each{ |key,value| mapped[key] = value.to_openstruct }
OpenStruct.new(mapped)
end
end
Define this perhaps in an initializer and then for any hash just put to_openstruct and send that over to the rabl template and basically do what jnunn shows in the view.

Related

My class object, based on Hash, returns an empty object

I'm learning Ruby.
I have this class
class JspfPlaylist < Hash
def initialize(title=nil,creator=nil,annotation=nil,info=nil,location=nil,identifier=nil,image=nil,date=nil,attribution=[],link=[],meta=[],extension=[],track=[])
#title = title
#creator = creator
#annotation = annotation
#info = info
#location = location
#identifier = identifier
#image = image
#date = date
#attribution = attribution
#link = link
#meta = meta
#extension = extension
#track = track
end
end
When doing
playlist = JspfPlaylist.new
puts playlist
I except to get
{
:title => nil,
:creator => nil,
:annotation => nil,
:info => nil,
:location => nil,
:identifier => nil,
:image => nil,
:date => nil,
:attribution => [],
:link => [],
:meta => [],
:extension => [],
:track => []
}
while I'm currently getting
{}
Could someone explain why and how I could get the result I want ?
Thanks !
A Hash doesn't store values in instance variables. Instead, you need to use methods available on a Hash to store values, for example Hash#[]=.
Simplified example:
class JspfPlaylist < Hash
def initialize(title=nil)
self[:title] = title
# ...
end
end
JspfPlaylist.new
#=> {:title=>nil}
JspfPlaylist.new('foo')
#=> {:title=>"foo"}
Sidenote: IMHO inheriting from Hash (or any other core, low-level class) is not a good idea and in general I would call it a code smell. Mostly because your new class will have tons of methods that it will likely not need. At the same time, you override the original initializer and that might break methods defined on Hash or have other weird side effects.
When inheriting from Hash that basically means the new class is a Hash too. In your context you should ask yourself is a Playlist a Hash? But this, of course, depends on your specific needs.
Instead, I would suggest having a normal class that only uses a hash as an internal data structure (which could be easily and idiomatically exposed with a to_h method).

Construct nested OpenStruct object

I have to mimic a Google API response and create a 2-level deep data structure that is traversable by . like this:
=> user.names.first_name
Bob
Is there any smarter/better way than this:
user = OpenStruct.new(names: OpenStruct.new(first_name: 'Bob'))
This method is rude method but works,
require 'ostruct'
require 'json'
# Data in hash
data = {"names" => {"first_name" => "Bob"}}
result = JSON.parse(data.to_json, object_class: OpenStruct)
And another method is adding method to Hash class itself,
class Hash
def to_openstruct
JSON.parse to_json, object_class: OpenStruct
end
end
Using above method you can convert your hash to openstruct
data = {"names" => {"first_name" => "Bob"}}
data.to_openstruct

Mongoid embedded collection response to :find

I'm sending serialized data to a class which need to access a Mongoid document which may or may not be embedded.
In case of embedded document, I'm accepting a variable number of arguments which I reduce to get the embedded document.
The code is pretty simple:
def perform(object, *arguments)
#opts = arguments.extract_options!
#object = arguments.reduce(object){|object, args| object.public_send(*args)}
# [...]
I used public_send because AFAIK I only need to call public methods.
However, when I try to access an embedded document I have some really strange result where #object is an enumerator.
After some debugging, this is what I found that for any root document object and an embedded collection items, I have:
object.items.public_send(:find)
# => #<Enumerator: ...>
object.items.send(:find) # or __send__
# => nil
The method called is not the same at all when I call public_send or send!
How is it even possible?
Is it normal? Is that a bug?
public_send seems to invoke the find method of Array (Enumerable) but send (or __send__) invokes the find method of Mongoid
Edit: simple reproductible case:
require 'mongoid'
class User
include Mongoid::Document
field :name, type: String
embeds_many :groups
end
class Group
include Mongoid::Document
field :name, type: String
embedded_in :user
end
Mongoid.load_configuration({
sessions: {
default: {
database: 'send_find',
hosts: [
'localhost:27017'
]
}
}
})
user = User.create(name: 'john')
user.groups.create(name: 'g1')
user.groups.create(name: 'g2')
puts "public_send :find"
puts user.groups.public_send(:find).inspect
# => #<Enumerator: [#<Group _id: 5530dea57735334b69010000, name: "g1">, #<Group _id: 5530dea57735334b69020000, name: "g2">]:find>
puts "send :find"
puts user.groups.send(:find).inspect
# => nil
puts "__send__ :find"
puts user.groups.__send__(:find).inspect
# => nil
Okay, after a few hours of debugging, I found that it is actually a bug in Mongoid.
The relation is not an array but a proxy around the array, which delegates most methods to the array.
As public_send was also delegated but not send and __send__, the behavior was not the same.
For more information, see my pull request and the associated commit.

Active Record to_json\as_json on Array of Models

First off, I am not using Rails. I am using Sinatra for this project with Active Record.
I want to be able to override either to_json or as_json on my Model class and have it define some 'default' options. For example I have the following:
class Vendor < ActiveRecord::Base
def to_json(options = {})
if options.empty?
super :only => [:id, :name]
else
super options
end
end
end
where Vendor has more attributes than just id and name. In my route I have something like the following:
#vendors = Vendor.where({})
#vendors.to_json
Here #vendors is an Array vendor objects (obviously). The returned json is, however, not invoking my to_json method and is returning all of the models attributes.
I don't really have the option of modifying the route because I am actually using a modified sinatra-rest gem (http://github.com/mikeycgto/sinatra-rest).
Any ideas on how to achieve this functionality? I could do something like the following in my sinatra-rest gem but this seems silly:
#PLURAL.collect! { |obj| obj.to_json }
Try overriding serializable_hash intead:
def serializable_hash(options = nil)
{ :id => id, :name => name }
end
More information here.
If you override as_json instead of to_json, each element in the array will format with as_json before the array is converted to JSON
I'm using the following to only expose only accessible attributes:
def as_json(options = {})
options[:only] ||= self.class.accessible_attributes.to_a
super(options)
end

Is there an easy way to define a common interface for a group of unrelated objects?

I've got a class that serializes data. I may want to serialize this data as JSON, or perhaps YAML. Can I cleanly swap YAML for JSON objects in this case? I was hoping I could do something like the following. Is it a pipe dream?
FORMATS = {
:json => JSON,
:yaml => YAML,
}
def serialize(data, format)
FORMATS[format].serialize(data)
end
The code you have written should work just fine, provided that classes JSON and YAML both have class method called serialize. But I think the method that actually exists is #dump.
So, you would have:
require 'json'
require 'yaml'
FORMATS = {
:json => JSON,
:yaml => YAML,
}
def serialize(data, format)
FORMATS[format].dump(data)
end
hash = {:a => 2}
puts serialize hash, :json
#=> {"a":2}
puts serialize hash, :yaml
#=> ---
#=> :a: 2
If JSON and YAML are classes or modules that already exist, you can write:
FORMATS = { :json => "JSON", :yaml => "YAML" }
def serialize(data, format)
Kernel.const_get(FORMATS[format]).serialize(data) # 'serialize' is a class method in this case
end

Resources