Can builder be used to map an xml file to an object? - ruby-on-rails

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

Related

How to store string as array in database column using Ruby on Rails

This question is asked many times on SO. The main problem is nothing got fits into my situation.
Case is, I am not able to store typed content as array in database column.
text_field whose code is:
= text_field_tag 'product[keywords][]', #product.keywords, class: 'tab-input
product_keywords'
In controller strong parameters are:
params.require(:product).permit(:id, :name, :keywords => [])
Jquery code that is not not removing value upon deletion when typed wrong value but it add commas after each element as I want to take commas seperated value in one column.
$(document).on 'keyup', '.product_keywords', ->
keyword = #value.replace(/(\w)[\s,]+(\w?)/g, '$1, $2')
if keyword != #value
#value = keyword
return
model code:
serialize :keywords, Array
migration code:
class AddKeywordsToProducts < ActiveRecord::Migration[5.1]
def change
add_column :products, :keywords, :text
end
end
So, if someone writes, abc and hit space a comma is added in the end. after three typed words it will look like:
abc, dbx, she
now I want to store it as array in column but its not storing properly.
it stores as:
["abc, dbx, she"]
Also please can anybody tell me the best cases to handle these cases?
Plus best practices to deal with such cases using ruby so I will learn it for future?
You probably want a custom serializer as shown here. So instead of:
serialize :keywords, Array
You might do somewhat like:
serialize :keywords, KeywordSerializer
And somewhere in helpers:
class KeywordSerializer
def self.dump(what)
what.join(", ")
end
def self.load(what)
what.split(/\s*,\s*/)
end
end
Passing array elements using single form tag is not possible to pass as a array and passing array as a string, you need to process it near white-listing your params,
permitted_params = params.require(:product).permit(:id, :name, :keywords => [])
permitted_params[:keywords] = permitted_params[:keywords][0].split(/\s*,\s*/)

graphql-ruby, Date or Datetime type

I'm not the only one who doesn't know how to use Datetime types with GraphQL-Ruby: https://github.com/rmosolgo/graphql-ruby-demo/issues/27, as you can see there are 18 people like me.
How can I use Datetime to some fields like this?
Types::PlayerType = GraphQL::ObjectType.define do
name 'Player'
field :id, !types.ID
field :birth_date, types.Datetime #or what?
field :death_date, !types.Datetime #or what?
field :note, types.String
end
Maybe I have to use this (https://github.com/howtographql/graphql-ruby/blob/master/app/graphql/types/date_time_type.rb):
date_time_type.rb:
Types::DateTimeType = GraphQL::ScalarType.define do
name 'DateTime'
coerce_input ->(value, _ctx) { Time.zone.parse(value) }
coerce_result ->(value, _ctx) { value.utc.iso8601 }
end
Can someone explain it better?
GraphQL::Types::ISO8601DateTime and GraphQL::Types::ISO8601Date recently got added to graphql ruby library (Pull Request).
Usage example:
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :starts_on, GraphQL::Types::ISO8601Date, null: false
The type declaration should properly work. Keep in mind that the DateTimeType you pasted is in the module "Types". Use it like this:
Types::PlayerType = GraphQL::ObjectType.define do
name 'Player'
field :id, !types.ID
field :birth_date, Types::DateTimeType
field :death_date, Types::DateTimeType
field :note, types.String
end
According to source code, there are only 5 types(Int, String, Float, Boolean, ID) defined as method which means you cannot use types.DateTime.
To customize DateTimeType, you could reference its document of the gem and implement as the above you've mentioned about (date_time_type.rb), then you can use it like this:
field :birth_date, Types::DateTimeType
Not sure I have a better explanation, but I did get it to work by pasting the code from above to define my own scalar DateTimeType:
DateTimeType = GraphQL::ScalarType.define do
name 'DateTime'
coerce_input ->(value, _ctx) { Time.zone.parse(value) }
coerce_result ->(value, _ctx) { value.utc.iso8601 }
end
MyType = ::GraphQL::ObjectType.define do
name 'MyType'
field :when, type: !DateTimeType
end

Custom wrap JSON to Virtus Model?

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

How to get a list of all of one type of Virtus attribute?

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.

Mongoid fields auto sum

Everyone! I have a model:
class Model
include Mongoid::Document
field :price1, :type =>Integer
field :price2, :type =>Integer
field :price3, :type =>Integer <== I want this field to be always result of price1 + price2
end
My question is: How can I make :price3 to be always autofilled by sum of price1 + price2.
Thank you very much for help!
You want to use the callbacks interface. There are various callbacks exposed such as:
require "mongoid"
require "pp"
Mongoid.configure.connect_to("test")
class Model
include Mongoid::Document
field :price1, type: Integer
field :price2, type: Integer
field :price3, type: Integer
store_in collection: "mymodel"
before_save do |document|
document.price3 = document.price1 + document.price2
end
end
model = Model.new
model.price1 = 2
model.price2 = 3
model.save
Which results in the "price3" field being set to the sum of the other two values.

Resources