I have a JSON object that looks like the following:
{
"id":"10103",
"key":"PROD",
"name":"Product",
"projectCategory":{
"id":"10000",
"name":"design",
"description":""
}
}
and a Virtus model that looks like the following:
class Project
include Virtus.model
attribute :id, Integer
attribute :key, String
attribute :name, String
attribute :category, String #should be the value of json["projectCategory"]["name"]
end
Everything lines up fine other than trying to map Project.category to json["projectCategory"]["name"].
So in total the end Virtus object I'm look for should look like:
"id" => "10103",
"key" => "PROD",
"name" => "Product",
"category" => "design"
Right now I'm creating a model instance with Project.new(JSON.parse(response)) or basically a hash of the json response. How can I custom map Virtus some attributes to my json response?
So I ended up figuring out you can override the self.new method allowing you to get to nested values in the hash you pass your Virtus model.
I ended up doing the following which worked fine:
class Project
include Virtus.model
attribute :id, Integer
attribute :name, String
attribute :key, String
attribute :category, String
def self.new(attributes)
new_attributes = attributes.dup
# Map nested obj "projectCategory.name" to Project.category
if attributes.key?("projectCategory") and attributes["projectCategory"].key?("name")
new_attributes[:'category'] = attributes["projectCategory"]["name"]
end
super(new_attributes)
end
end
Related
How to get a list of all of one type?
Like for example, a list of all of the String attributes?
Is there an easy Virtus solution or do I have to roll my own?
def my_model
include Virtus.model
attribute :a, Integer
attribute :b, Integer
attribute :c, String
attribute :d, String
attribute :w, Float
attribute :j, Float
end
I would like to essentially do my_model.something = [:c, :d]
OR is there any other way to get all of the String attributes in list form?
My ultimate goal is to be able to splat the attributes into various validations based on type.
You may use the attribute_set method along with the primitive to get the attribute Class and then you need to write your own method:
def self.get_string_attributes
attributes = []
self.attribute_set.each do |attribute|
attributes << attribute.name if attribute.primitive == String
end
attributes
end
I get this results with your MyModel:
MyModel.get_string_attributes
=> [:c, :d]
O̶r̶ ̶y̶o̶u̶ ̶c̶a̶n̶ ̶g̶o̶ ̶a̶ ̶s̶t̶e̶p̶ ̶f̶u̶r̶t̶h̶e̶r̶ ̶a̶n̶d̶ ̶u̶s̶e̶ ̶d̶e̶f̶i̶n̶e̶ ̶m̶e̶t̶h̶o̶d̶_̶m̶i̶s̶s̶i̶n̶g̶ ̶l̶i̶k̶e̶ ̶t̶h̶i̶s̶:̶
I hope this is what you're looking for.
I need to render some json data. I use rabl for this purposes....
I have such code in index.rabl:
collection #banks, :root => "bank", :object_root => false
attributes :id, :central_office_address, :location_id, :name, :year_of_foundation
it's generates me json....
But now i need to put there also some calculated field for each entry of object.
For example new field (is not in model): :exch_count and do for it something like(pseudo): :exch_count #banks[i].exchangers.count * 3
But how can i do this in ruby on rails + rabls?
You can pass a block to node that uses the bank as a parameter:
node(:exch_count) {|bank| bank.exchangers.count * 3 }
I'm looking for a way to map an xml file to a ruby class.
The class: https://github.com/airbrake/airbrake/blob/master/lib/airbrake/notice.rb
The ruby class actually has a to_xml method that uses builder to generate an xml file.
I need to do the opposite and take the xml and initialize the ruby object.
What's the best way for me to do this?
Performance is a consideration.
Some guys at work have used the happymapper gem. From their examples:
dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
require File.join(dir, 'happymapper')
file_contents = File.read(dir + '/../spec/fixtures/statuses.xml')
class User
include HappyMapper
element :id, Integer
element :name, String
element :screen_name, String
element :location, String
element :description, String
element :profile_image_url, String
element :url, String
element :protected, Boolean
element :followers_count, Integer
end
class Status
include HappyMapper
element :id, Integer
element :text, String
element :created_at, Time
element :source, String
element :truncated, Boolean
element :in_reply_to_status_id, Integer
element :in_reply_to_user_id, Integer
element :favorited, Boolean
has_one :user, User
end
statuses = Status.parse(file_contents)
statuses.each do |status|
puts status.user.name, status.user.screen_name, status.text, status.source, ''
end
Trying to mirror my API responses with as little code duplication as possible and have this so far....
Really, this is a "There has to be a better 'Rails way' to accomplish this..." question.
class Quote < ActiveRecord::Base
belongs_to :author
has_many :votes
def as_json(options={})
hash = super(except)
hash[:author] = self.author.name
hash[:vote_count] = self.votes.count
hash
end
def to_xml(options={})
hash = super(except)
hash[:author] = self.author.name // <---- line 14
hash[:vote_count] = self.votes.count
hash
end
private
def except
{ :except => [ :id, :created_at, :updated_at, :author_id ] }
end
end
JSON response works like a champ, but the xml throws this error
can't convert Symbol into Integer
app/models/quote.rb:14:in `[]='
app/models/quote.rb:14:in `to_xml'
As a secondary question, is the the best way to customize the output like I am? I'd like to not duplicate this logic if I can avoid it.
hash[:author] = self.author.name
hash[:vote_count] = self.votes.count
hash
to_xml returns an XML string, not a hash. That's why it's surprised by a symbol in the brackets: it thinks you're trying to modify a particular character, e.g. name[0] = 'A'
If you're interested in changing bits of the XML output, maybe you should just build a new hash of the attributes you want and run to_xml on that.
Let's say I have two objects: User and Race.
class User
attr_accessor :first_name
attr_accessor :last_name
end
class Race
attr_accessor :course
attr_accessor :start_time
attr_accessor :end_time
end
Now let's say I create an array of hashes like this:
user_races = races.map{ |race| {:user => race.user, :race => race} }
How do I then convert user_races into an array of structs, keeping in mind that I want to be able to access the attributes of both user and race from the struct element? (The key thing is I want to create a new object via Struct so that I can access the combined attributes of User and Race. For example, UserRace.name, UserRace.start_time.)
Try this:
class User
attr_accessor :first_name
attr_accessor :last_name
end
class Race
attr_accessor :course
attr_accessor :start_time
attr_accessor :end_time
end
UserRace = Struct.new(:first_name, :last_name, :course, :start_time, :end_time)
def get_user_race_info
user_races = races.map do |r|
UserRace.new(r.user.first_name, r.user.last_name,
r.course, r.start_time, r.end_time)
end
end
Now let's test the result:
user_races = get_user_race_info
user_races[0].first_name
user_races[0].end_time
Create a definition for the UserRace object (as a Struct), then just make an array of said objects.
UserRace = Struct.new(:user, :race)
user_races = races.map { |race| UserRace.new(race.user, race) }
# ...
user_races.each do |user_race|
puts user_race.user
puts user_race.race
end
if your hash has so many attributes such that listing them all:
user_races = races.map{ |race| {:user => race.user, :race => race, :best_lap_time => 552.33, :total_race_time => 1586.11, :ambient_temperature => 26.3, :winning_position => 2, :number_of_competitors => 8, :price_of_tea_in_china => 0.38 } } # garbage to show a user_race hash with many attributes
becomes cumbersome (or if you may be adding more attributes later), you can use the * ("splat") operator.
the splat operator converts an array into an argument list.
so you can populate Struct.new's argument list with the list of keys in your hash by doing:
UserRace = Struct.new(*races.first.keys)
of course, this assumes all hashes in your array have the same keys (in the same order).
once you have your struct defined, you can use inject to build the final array of objects. (inject greatly simplifies converting many objects from one data type to another.)
user_races.inject([]) { |result, user_race| result << UserRace.new(*user_race.values) }
You have this :::
user_races = races.map{ |race| {:user => race.user, :race => race} }
Now create a Struct as shown below :
UserRace = Struct.new(:user, :race)
And then ::
user_races.each do |user_race|
new_array << UserRace.new(user_race[:user],user_race[:race])
end
Haven't tested the code... should be fine... what say?
EDIT: Here I am adding the objects of UserRace to a new_array.