Construct nested OpenStruct object - ruby

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

Related

Activerecord returning all results as a hash

I have the following connecting to a db called dbblah and table1(names changed)
require 'active_record'
ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "192.168.1.10",
:database => "automation",
:username => "root",
:password => "password"
)
ActiveRecord::Base.pluralize_table_names = false
class Table1 < ActiveRecord::Base
end
db = Table1.find_by(db: 'dbname')
puts db
But when I run it, I am getting the results as a hash it looks like:
[root#localhost server]# ruby blah.rb
#<Table1:0x000000019796a8>
This is just the output of to_s method called on a new object - it is definitively not a hash. By default when calling puts method with a non-string, to_s method is called on that object to display a string. For ActiveRecord models to_s method results in exactely what you got.
Try calling p db to display result of method inspect called on that object, which will give you more insight in its internal structure.

data_mapper, attr_accessor, & serialization only serializing properties not attr_accessor attributes

I'm using data_mapper/sinatra and trying to create some attributes with attr_accessor. The following example code:
require 'json'
class Person
include DataMapper::Resource
property :id, Serial
property :first_name, String
attr_accessor :last_name
end
ps = Person.new
ps.first_name = "Mike"
ps.last_name = "Smith"
p ps.to_json
produces this output:
"{\"id\":null,\"first_name\":\"Mike\"}"
Obviously I would like for it to give me both the first and last name attributes. Any ideas on how to get this to work in the way one would expect so that my json has all of the attributes?
Also, feel free to also explain why my expectation (that I'd get all of the attributes) is incorrect. I'm guessing some internal list of attributes isn't getting the attr_accessor instance variables added to it or something. But even so, why?
Datamapper has it’s own serialization library, dm-serializer, that provides a to_json method for any Datamapper resource. If you require Datamapper with require 'data_mapper' in your code, you are using the data_mapper meta-gem that requires dm-serializer as part of it’s set up.
The to_json method provided by dm-serializer only serializes the Datamapper properties of your object (i.e. those you’ve specified with property) and not the “normal” properties (that you’ve defined with attr_accessor). This is why you get id and first_name but not last_name.
In order to avoid using dm-serializer you need to explicitly require those libraries you need, rather than rely on data_mapper. You will need at least dm-core and maybe others.
The “normal” json library doesn’t include any attributes in the default to_json call on an object, it just uses the objects to_s method. So in this case, if you replace require 'data_mapper' with require 'dm-core', you will get something like "\"#<Person:0x000001013a0320>\"".
To create json representations of your own objects you need to create your own to_json method. A simple example would be to just hard code the attributes you want in the json:
def to_json
{:id => id, :first_name => first_name, :last_name => last_name}.to_json
end
You could create a method that looks at the attributes and properties of the object and create the appropriate json from that instead of hardcoding them this way.
Note that if you create your own to_json method you could still call require 'data_mapper', your to_json will replace the one provided by dm-serializer. In fact dm-serializer also adds an as_json method that you could use to create the combined to_json method, e.g.:
def to_json
as_json.merge({:last_name => last_name}).to_json
end
Thanks to Matt I did some digging and found the :method param for dm-serializer's to_json method. Their to_json method was pretty decent and was basically just a wrapper for an as_json helper method so I overwrote it by just adding a few lines:
if options[:include_attributes]
options[:methods] = [] if options[:methods].nil?
options[:methods].concat(model.attributes).uniq!
end
The completed method override looks like:
module DataMapper
module Serializer
def to_json(*args)
options = args.first
options = {} unless options.kind_of?(Hash)
if options[:include_attributes]
options[:methods] = [] if options[:methods].nil?
options[:methods].concat(model.attributes).uniq!
end
result = as_json(options)
# default to making JSON
if options.fetch(:to_json, true)
MultiJson.dump(result)
else
result
end
end
end
end
This works along with an attributes method I added to a base module I use with my models. The relevant section is below:
module Base
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def attr_accessor(*vars)
#attributes ||= []
#attributes.concat vars
super(*vars)
end
def attributes
#attributes || []
end
end
def attributes
self.class.attributes
end
end
now my original example:
require 'json'
class Person
include DataMapper::Resource
include Base
property :id, Serial
property :first_name, String
attr_accessor :last_name
end
ps = Person.new
ps.first_name = "Mike"
ps.last_name = "Smith"
p ps.to_json :include_attributes => true
Works as expected, with the new option parameter.
What I could have done to selectively get the attributes I wanted without having to do the extra work was to just pass the attribute names into the :methods param.
p ps.to_json :methods => [:last_name]
Or, since I already had my Base class:
p ps.to_json :methods => Person.attributes
Now I just need to figure out how I want to support collections.

Rendering a simple Ruby hash with RABL

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.

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