I want to use a transformed name/value in the property of a class than we get from the database. The reason I need this is I have two versions of an app that points to the same database, but the newer version recently updated the column name. So, for all apps that use the older version it has to be made compatible.
Environment: Rails version: 6.0.3.3 & Ruby version: ruby 2.7.1p83
Suppose there is class called Sprinter.
Sprinter table looks like:
id
name
m
s
1
Mr. Bolt
100
9.58
A sprinter instance would look like:
sprinter = {
"id" => 1,
"name" => "Mr. Bolt",
"m" => 100,
"s" => 9.58
}
The older version expects a different format. Now I want to have something like:
sprinter = {
"id" => 1,
"name" => "Mr. Bolt",
"cm" => 10000, # transformed from `m`
"ms" => 9580 # transformed from `s`
}
The m column name has been transformed to cm and the value has been transformed too. Same case for s to ms. The value transformation is not a big deal, but the variable name seems to be.
I'd prefer an internal update when I fetch the object. However if that's not supported, as a base-level solution, I'd prefer to change the json representation that I sent to the client. For reference, I use it as part of nested include like:
render status: :ok, json: {races: races}.as_json(
{:include => [:sprinters] }
)
Thanks.
Instead of mucking about with the internals of .as_json or how your model does serialization you can use a serialization layer instead to handle different representations of your model in JSON.
For example you can use ActiveModel::Serializers or JBuilder or even roll your own if thats your deal.
module API
module V2
class SprinterAdapter
def intialize(sprinter)
#sprinter = sprinter
end
def to_json
#sprinter.to_json.except("m", "s").merge(
"cm" => #sprinter.m * 100,
"ms" => #sprinter.s * 1000
)
end
end
end
end
And in your controller you would use the serializer to render the resource:
module API
module V2
class SprintersController < ApplicationController
def show
#sprinter = Sprinter.find(params[:id])
render json: SprinterAdapter.new(#sprinter)
end
end
end
end
See:
Thoughtbot: Better serialization, less as_json
Ruby Toolbox
You could transform keys/values by select from Sprinter:
Sprinter.select(:id, :name, 'm * 100 AS cm', 's * 1000 AS ms')
But not sure how to combine it with as_json
Related
I'm using Toptal's Chewy gem to connect and query my Elasticsearch, just like an ODM.
I'm using Chewy along with Elasticsearch 6, Ruby on Rails 5.2 and Active Record.
I've defined my index just like this:
class OrdersIndex < Chewy::Index
define_type Order.includes(:customer) do
field :id, type: "keyword"
field :customer do
field :id, type: "keyword"
field :name, type: "text"
field :email, type: "keyword"
end
end
end
And my model:
class Order < ApplicationRecord
belongs_to :customer
end
The problem here is that when I perform any query using Chewy, the customer data gets deserialized as a hash instead of an Object, and I can't use the dot notation to access the nested data.
results = OrdersIndex.query(query_string: { query: "test" })
results.first.id
# => "594d8e8b2cc640bb78bd115ae644637a1cc84dd460be6f69"
results.first.customer.name
# => NoMethodError: undefined method `name' for #<Hash:0x000000000931d928>
results.first.customer["name"]
# => "Frederique Schaefer"
How can I access the nested association using the dot notation (result.customer.name)? Or to deserialize the nested data inside an Object such as a Struct, that allows me to use the dot notation?
try to use
results = OrdersIndex.query(query_string: { query: "test" }).objects
It converts query result into active record Objects. so dot notation should work. If you want to load any extra association with the above result you can use .load method on Index.
If you want to convert existing ES nested object to accessible with dot notation try to reference this answer. Open Struct is best way to get things done in ruby.
Unable to use dot syntax for ruby hash
also, this one can help too
see this link if you need openStruct to work for nested object
Converting the just-deserialized results to JSON string and deserializing it again with OpenStruct as an object_class can be a bad idea and has a great CPU cost.
I've solved it differently, using recursion and the Ruby's native Struct, preserving the laziness of the Chewy gem.
def convert_to_object(keys, values)
schema = Struct.new(*keys.map(&:to_sym))
object = schema.new(*values)
object.each_pair do |key, value|
if value.is_a?(Hash)
object.send("#{key}=", convert_to_object(value.keys, value.values))
end
end
object
end
OrdersIndex.query(query_string: { query: "test" }).lazy.map do |item|
convert_to_object(item.attributes.keys, item.attributes.values)
end
convert_to_object takes an array of keys and another one of values and creates a struct from it. Whenever the class of one of the array of values items is a Hash, then it converts to a struct, recursively, passing the hash keys and values.
To presence the laziness, that is the coolest part of Chewy, I've used Enumerator::Lazy and Enumerator#map. Mapping every value returned by the ES query into the convert_to_object function, makes every entry a complete struct.
The code is very generic and works to every index I've got.
I am using barby gem to generate gs1-128 barcode. I am able to save the barcode in a .png file. Below is the code i am using,
def pti_label_preview
gtin_no = params[:gtin_no]
barcode = Barby::GS1128.new(gtin_no,'C','12')
full_path = "#{Rails.root}/public/uploads/barcode.svg"
File.open(full_path, 'w') { |f| f.write barcode.to_svg(:margin => 3, :xdim => 2, :height => 55) }
render :text => path
end
I created it by referring this. Barby::GS1128.new(gtin_no,'C','12') is accepting 3 argument, i want to know what are the 3 values i have to send to create barcode.
I have following values gs1_prefix, item no, check sum value, gtin no, lot no etc. What are the 3 values should i pass to GS1128 method
You can pull the repo of that gem down and find this.
class GS1128 < Code128
def initialize(data, type, ai)
self.application_identifier = ai
super(data, type)
end
...
end
So the answer to your question is the 3 arguments you pass are data, type and ai
In Grails, if I define a locale, and put a date on specific format on i18n file, like (dd/mm/AAAA), if call one request like:
http://myapp/myaction?object.date=10/12/2013
When I get print: params.date, it comes to me a date object.
How can I do the same on rails?
Normally the Rails handles this for you. For instance, the form helper datetime_select works in conjunction with some activerecord magic
to ensure ensure time/date types survive the round-trip. There are various alternatives to the standard date-pickers.
If this doesn't work for you e.g. rails isn't generating the forms, there are (at least) a couple of options.
One option, slightly evi, is to monkey-patch HashWithIndifferentAccess (used by request params) to do type conversions based on the key name. It could look something like:
module AddTypedKeys
def [](key)
key?(key) ? super : find_candidate(key.to_s)
end
private
# look for key with a type extension
def find_candidate(key)
keys.each do |k|
name, type = k.split('.', 2)
return typify_param(self[k], type) if name == key
end
nil
end
def typify_param(value, type)
case type
when 'date'
value.to_date rescue nil
else
value
end
end
end
HashWithIndifferentAccess.send(:include, AddTypedKeys)
This will extend params[] in the way you describe. To use it within rais, you can drop it into an initialiser, eg confg/initializers/typed_params.rb
To see it working, you can test with
params = HashWithIndifferentAccess.new({'a' => 'hello', 'b.date' => '10/1/2013', 'c.date' => 'bob'})
puts params['b.date'] # returns string
puts params['b'] # returns timestamp
puts params['a'] # returns string
puts params['c'] # nil (invalid date parsed)
However... I'm not sure it's worth the effort, and it will likely not work with Rails 4 / StrongParameters.
A better solution would be using virtual attributes in your models. See this SO post for a really good example using chronic.
I have a controller returning a json structure like so:
def show
# .......
o_json = deep_object_1_to_json(o)
render :json => o_json
end
private
def deep_object_1_to_json(o)
o.to_json(
:include => {....})
end
Now I need to extend it to return 2 objects. However the obvious solution is giving me problems:
def show
# .......
o1_json = deep_object_1_to_json(o)
o2_json = deep_object_2_to_json(o)
render :json =>
{
:object_1 => o1_json,
:object_2 => o2_json
}
end
This returns a json object with 2 strings of escaped json data!
The deep_object_2_to_json functions already have several layers of nested includes so I would rather not have to refactor these into a single function. Is there a way to make this easily extendable to add more objects in the future without the double escaping problem above?
Thanks for any pointers.
Sounds like you should be constructing something upon which to_json can easily be called.
The obvious candidate for active record objects is as_json. This does everything that to_json does (include the :include option and so on) except actually turning the object into json. Instead you get back a ruby hash which you can manipulate as you want and then call to_json. For example you could do
render :json => {
:o1 => object1.as_json(:include => :blah),
:o2 => object2.as_json(:include => :blah)
}
Your controller shouldn't be serializing the object as JSON until right before it hands it off to be rendered.
In other words deep_object_1_to_json should just be deep_object_1. Then you can package both return values into an array or hash and render the as JSON.
def show
# .......
o1 = deep_object_1(o)
o2 = deep_object_2(o)
render :json =>
{
:object_1 => o1,
:object_2 => o2
}
end
It might be a pain to change it now, but for the future of your system, you really ought to be doing it this way. JSON is just a format for sending objects over the wire or to disk; none of your code should have any references whatsoever to JSON unless it is passing it off to be rendered.
If you can refactor your class to return it's JSON via the to_json method, you can simply stick two or more objects into an array and call to_json on the array:
1.9.3-p125 :001 > require 'json'
=> true
1.9.3-p125 :002 > [{foo: "bar"}, {bar: "foo"}].to_json
=> "[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]"
Example:
def to_json
super(include: [:some_association])
end
I am trying to accomplish the following in Ruby:
person_struct = StructWithType.new "Person",
:name => String,
:age => Fixnum,
:money_into_bank_account => Float
And I would like it to accept both:
person_struct.new "Some Name",10,100000.0
and
person_struct.new "Some Name","10","100000.0"
That is, I'd like it to do data conversion stuff automatically.
I know Ruby is dinamically and I should not care about data types but this kind of conversion would be handy.
What I am asking is something similar to ActiveRecord already does: convert String to thedatatype defined in the table column.
After searching into ActiveModel I could not figure out how to to some TableLess that do this conversion.
After all I think my problem may require much less that would be offered by ActiveModel modules.
Of course I could implement a class by myself that presents this conversion feature, but I would rather know this has not yet been done in order to not reinvent the wheel.
Tks in advance.
I think that the implementation inside a class is so easy, and there is no overhead at all, so I don't see the reason to use StructWithType at all. Ruby is not only dynamic, but very efficient in storing its instances. As long as you don't use an attribute, there is none.
The implementation in a class should be:
def initialize(name, age, money_into_bank_account)
self.name = name
self.age = age.to_i
self.money_into_bank_account = money_into_bank_account.to_f
end
The implementation in StructWithType would then be one layer higher:
Implement for each type a converter.
Bind an instance of that converter in the class.
Use in the new implementation of StructWithType instances (not class) the converters of the class to do the conversion.
A very first sketch of it could go like that:
class StructWithType
def create(args*)
<Some code to create new_inst>
args.each_with_index do |arg,index|
new_value = self.converter[index].convert(arg)
new_inst[argname[index]]= new_value
end
end
end
The ideas here are:
You have an instance method named create that creates from the factory a new struct instance.
The factory iterates through all args (with the index) and searches for each arg the converter to use.
It converts the arg with the converter.
It stores in the new instance at the argname (method argname[] has to be written) the new value.
So you have to implement the creation of the struct, the lookup for converter, the lookup for the argument name and the setter for the attributes of the new instance. Sorry, no more time today ...
I have used create because new has a different meaning in Ruby, I did not want to mess this up.
I have found a project in github that fulfill some of my requirements: ActiveHash.
Even though I still have to create a class for each type but the type conversion is free.
I am giving it a try.
Usage example:
class Country < ActiveHash::Base
self.data = [
{:id => 1, :name => "US"},
{:id => 2, :name => "Canada"}
]
end
country = Country.new(:name => "Mexico")
country.name # => "Mexico"
country.name? # => true