Long protobuf to poro serialization - ruby-on-rails

I am trying to compare protobuf and json data exchange speed between two ruby microservices. As I thought protobuf is much faster in most of cases. As the final test I want to check if converting decoded Message proto class to some poro will be faster than doing the same using json.
Here is an example of conversion with json:
def self.map_all_posts_from_json(data)
RubyProf.start
data.map do |post|
Post.new.tap do |new_post|
new_post.id = post["id"]
new_post.description = post["description"]
new_post.content = post["content"]
new_post.rating = post["rating"]
new_post.user = User.new.tap do |new_user|
new_user.id = post["user"]["id"]
new_user.name = post["user"]["name"]
new_user.surname = post["user"]["surname"]
new_user.nickname = post["user"]["nickname"]
end
new_post.comments = post["comments"].map do |comment|
Comment.new.tap do |new_comment|
new_comment.id = comment["id"]
new_comment.content = comment["content"]
new_comment.rating = comment["rating"]
new_comment.user = User.new.tap do |new_user|
new_user.id = comment["user"]["id"]
new_user.name = comment["user"]["name"]
new_user.surname = comment["user"]["surname"]
new_user.nickname = comment["user"]["nickname"]
end
end
end
end
end
result = RubyProf.stop
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
end
and protobuf:
def self.unserialize_all_posts(data)
RubyProf.start
data.posts.map do |post|
Post.from_message(post)
end
result = RubyProf.stop
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
end
Post.from_message method:
def self.from_message(message)
Post.new.tap do |post|
post.id = message.id
post.desription = message.description
post.content = message.content
post.rating = message.rating
post.user = User.from_message(message.user)
post.comments = message.comment_messages.map do |comment|
Comment.from_message(comment)
end
end
end
surprisingly json way was a lot faster. Then I investigated data returned by RubyProf and found out that there were lots and lots of calls to proto_class#method_missing and other suspect methods. Did any of you had similar problem? I am using 'google-protobuf' gem.

Related

Ruby on Rails JSON to Model Conversion

def create
#match_data = JSON.parse(request.raw_post)
#match = #match_data["match"]
match = Match.new
match.match_length = #match["match_length"]
match.quest = #match["quest"]
match.humans_team_stats = #match["humans_team_stats"]
match.supernaturals_team_stats = #match["supernaturals_team_stats"]
#match_data["users_match_stats"].each do |user|
user_account = User.find user["id"]
match.users << user_account
match.save!
user_match_stats = UserMatchStat.new
user_match_stats.user = user_account
user_match_stats.match = match
user_match_stats.kills = user["kills"]
user_match_stats.deaths = user["deaths"]
user_match_stats.assists = user["assists"]
user_match_stats.damage_dealt = user["damage_dealt"]
user_match_stats.damage_taken = user["damage_taken"]
user_match_stats.first_blood = user["first_blood"]
user_match_stats.save!
match.user_match_stats << user_match_stats
end
render json: #match_data
end
is this literally the best way to map an incoming json object to a rails model and make a record for it? There's gotta be a better way than this...
Provided your model's columns are the same as the keys in the JSON object (which it looks like they are from your code snippet), you can use symbolize_keys to turn the json objects into a ruby hash that your rails model will accept as commit params. It could look something like the following:
match = Match.create(match_length: #match["match_length"], quest: #match["quest"], humans_team_stats: #match["humans_team_stats"], supernaturals_team_stats: #match["supernaturals_team_stats"])
#match_data["users_match_stats"].each do |user|
user_account = User.find user["id"]
match.users << user_account
UserMatchStat.create(user.symbolize_keys)
end

Is using local variable instead of instance variable improve the performance(save memory) in Rails?

I have created Rails application and I have used lots of instance variables and most of them are not required in the views. Do I need to replace the unused instance variables for improving the performance?
Sample code:
def show
custom_fields_data = fetch_custom_field_data
#selected_custom_fields_opt_from_view = []
if custom_fields_data.present?
#listings = #listing.category.listings.where("price_cents!=? AND open= ?",0,true).reject { |l| l.author.main_admin? }
#selected_custom_fields_opt_from_view = custom_fields_data.map do |custom_field_data|
CustomField.find(custom_field_data[0]).options.find(custom_field_data[1])
end
#listings.each do |listing|
# array to store the selected a custom field's option from Database
selected_custom_fields_opt_from_db = []
listing.custom_field_values.each do |custom_field_value|
selected_custom_fields_opt_from_db.push(custom_field_value.selected_options.first)
end
if selected_custom_fields_opt_from_db.uniq.sort == #selected_custom_fields_opt_from_view.uniq.sort || (#selected_custom_fields_opt_from_view - selected_custom_fields_opt_from_db).empty?
similar_listing.push(listing)
end
end
#listings = similar_listing
end
#listing_with_filters = similar_listing.present? ? #listings.first : #listing
#selected_tribe_navi_tab = "home"
unless current_user?(#listing.author)
#listing.increment!(:times_viewed)
end
#current_image = if params[:image]
#listing.image_by_id(params[:image])
else
#listing.listing_images.first
end
#prev_image_id, #next_image_id = if #current_image
#listing.prev_and_next_image_ids_by_id(#current_image.id)
else
[nil, nil]
end
payment_gateway = MarketplaceService::Community::Query.payment_type(#current_community.id)
process = get_transaction_process(community_id: #current_community.id, transaction_process_id: #listing.transaction_process_id)
form_path = new_transaction_path(listing_id: #listing.id)
delivery_opts = delivery_config(#listing.require_shipping_address, #listing.pickup_enabled, #listing.shipping_price, #listing.shipping_price_additional, #listing.currency)
#category = #listing.category
#template_listing = #category.template_listing
if #current_user
# For Pivot table
#selected_custom_field = params[:custom_field] if params[:custom_field]
#listing_for_pivot = Listing.new
#listing_images = #listing.listing_images
#shape = get_shape(#listing.listing_shape_id)
#unit_options = ListingViewUtils.unit_options(#shape[:units], unit_from_listing(#template_listing)).first if #shape
#custom_field_questions = #category.custom_fields
#numeric_field_ids = numeric_field_ids(#custom_field_questions)
#category_tree = CategoryViewUtils.category_tree(
categories: ListingService::API::Api.categories.get(community_id: #current_community.id)[:data],
shapes: get_shapes,
locale: I18n.locale,
all_locales: #current_community.locales
)
if #template_listing.present?
#listing_for_pivot.title = #template_listing.title
#listing_for_pivot.description = #template_listing.description
#listing_images = #template_listing.listing_images if #template_listing.listing_images.present?
#listing_for_pivot.listing_shape_id = #template_listing.listing_shape_id
end
if (#current_user.location != nil)
temp = #current_user.location
temp.location_type = "origin_loc"
#listing_for_pivot.build_origin_loc(temp.attributes)
else
#listing_for_pivot.build_origin_loc(:location_type => "origin_loc")
end
#custom_field_area = CategoryCustomField.where(category_id: #category.id, custom_field_id: #category.custom_fields.pluck(:id))
#row = #category.custom_field_row
#row = #custom_field_area.first.custom_field if #row.nil? && #custom_field_area.first
#column = #category.custom_field_column
#column = #custom_field_area.second.custom_field if #column.nil? && #custom_field_area.second
#filters = #category.custom_field_filters
#filters = #custom_field_area.all.from(1).map { |category_custom_field| category_custom_field.custom_field } if #filters.nil? && #custom_field_area.size > 2
#selected_value_for_filter = []
if #filters.present?
if #selected_custom_field
#filters.each do |filter|
if (#selected_custom_field["#{filter.id.to_s}_"])
#selected_value_for_filter.push(filter.options.find(#selected_custom_field["#{filter.id.to_s}_"]))
else
#selected_value_for_filter.push(filter.options.first)
end
end
else
#filters.each do |filter|
#selected_value_for_filter.push(filter.options.first)
end
end
end
# Pivot table section end
end
#applicant = #category.listings.pluck(:author_id).uniq
#suggested_business_accounts = #category.people.where("people.id NOT IN (?)", #applicant);
if #suggested_business_accounts.present?
#business_locations =
#suggested_business_accounts.map do |person|
person.location
end
#business_locations.compact!
end
render locals: {
form_path: form_path,
payment_gateway: payment_gateway,
# TODO I guess we should not need to know the process in order to show the listing
process: process,
delivery_opts: delivery_opts,
listing_unit_type: #listing.unit_type
}
end
It is not recommended to use instance variables if you don't want to send them to views. The scope of the variables should be narrowest, therefore in your case if you are not using instance variables in the views you should convert them to local.
Using instance variables instead of local variables is a bad idea at least memory-wise.
Instance variable exists while the object that holds it exists. On the contrary, local variable exists only inside method/block it is defined.
Garbage collector does not care whether you use instance variable elsewhere beyond the method or not.
Thus, if you have instance variables, which you only intend to use within the method - change them to local ones.

contextual binding in rails

Normally i do like this
for loops
# app/view/products/index.html.haml
- #products.each do |product|
= product.name
= product.foo
= product.bar
normal scoping
# app/view/products/show.html.haml
= #product.name
= #product.price
= #product.xyz
See in above situation, i am repeating myself. I am using same product word every time. I want it something like which could attach/bind the method as per my context.
I rather prefer to do something like this
For loops i like do something like
- #products.each(context_binding: true) do
= name
= foo
= bar
for scoping
- context_binding #product do
= name
= price
= xyz
I guess that is possible and can be done with method missing i dont know how to do it. Can you give some hints so i can archive such type of things.
Just to give you an idea:
class With
def initialize(obj)
#obj = obj
#str = ''
end
def method_missing(name, *args, &block)
#str += #obj.send(name, *args, &block).to_s
end
end
def with(obj, &block)
With.new(obj).instance_eval(&block).to_s
end
Product = Struct.new(:name, :price)
product = Product.new('apple', '2')
output = with(product) do
name
price
end
puts output

How can I DRY this code and clean up my model?

I have the following two methods for a Number model.
def track
number = sanitize(tracking)
case determine_type(number)
when 'UPS'
tracker = ups.track(:tracking_number => number)
self.carrier = Carrier.where(:name => 'UPS').first
self.service = tracker.service_type
self.destination_country = tracker.destination_country
self.destination_state = tracker.destination_state
self.destination_city = tracker.destination_city
self.origin_country = tracker.origin_country
self.origin_state = tracker.origin_state
self.origin_city = tracker.origin_city
self.signature = tracker.signature_name
self.scheduled_delivery = tracker.scheduled_delivery_date
self.weight = tracker.weight
tracker.events.each do |event|
new_event = Event.new
new_event.number = self
new_event.city = event.city
new_event.state = event.state
new_event.postalcode = event.postal_code if event.postal_code
new_event.country = event.country
new_event.status = event.name
new_event.status_code = event.type
new_event.occured_at = event.occurred_at
new_event.save
end
end
save
end
def update_number
case determine_type(number)
when 'UPS'
tracker = ups.track(:tracking_number => tracking)
self.carrier = Carrier.where(:name => 'UPS').first
self.service = tracker.service_type
self.destination_country = tracker.destination_country
self.destination_state = tracker.destination_state
self.destination_city = tracker.destination_city
self.origin_country = tracker.origin_country
self.origin_state = tracker.origin_state
self.origin_city = tracker.origin_city
self.signature = tracker.signature_name
self.scheduled_delivery = tracker.scheduled_delivery_date
self.weight = tracker.weight
last_event = self.events.ordered.first.occured_at
tracker.events.each do |event|
if last_event and (event.occurred_at > last_event)
new_event = Event.new
new_event.number = self
new_event.city = event.city
new_event.state = event.state
new_event.postalcode = event.postal_code if event.postal_code
new_event.country = event.country
new_event.status = event.name
new_event.status_code = event.type
new_event.occured_at = event.occurred_at
new_event.save
end
end
end
save
end
As you can see, a lot of code is duplicated. And the problem becomes when I start adding the dozen or so other carriers (FedEx, USPS, DHL, etc)...my Number model gets big and hairy fast.
The only real difference between track and update_number is that update_number as an if comparison around the events to check if the events from the carrier are more recent than the latest events I have stored in the database, using that if last_event and (event.occurred_at > last_event) line.
So how can I clean up this code so my model doesn't get so fat?
A couple of things I would suggest:
Look at the Strategy Pattern, even though Ruby doesn't have interfaces, but it'll give you some ideas on how to better design this functionality (google for Strategy Pattern for Ruby to find alternatives). Basically, you'd want to have separate classes to handle your switch case statements and call the appropriate one at runtime.
Try to separate the tracker handling code from the events handling code. E.g., I would consider moving the event creation/saving logic for each of the tracker events to a separate class (or even to the Tracker entity, if you have one).
Hope this helps.
This should be solved using the Strategy pattern. For each possible tracker (DHL, UPS, ...) that will handle the creating and updating appropriately.
So that would become something like:
class Number
def track
tracker = get_tracker(tracking)
tracker.create_tracking(self)
save
end
def update_number
tracker = get_tracker(tracking)
tracker.update_tracking(self)
save
end
def get_tracker(tracking)
tracking_number = sanitize(tracking)
case determine_type(tracking_number)
when 'UPS'
UPSTracker.new(tracking_number)
when 'DHL'
DHLTracker.new(tracking_number)
end
end
end
class UPSTracker
def initialize(tracking_number)
#tracking_number = tracking_number
end
def create_tracking(number)
tracker = ups.track(:tracking_number => number)
update_number_properties(number, tracking)
# store events
tracker.events.each do |event|
create_new_event(event)
end
end
def update_tracking(number)
tracker = ups.track(:tracking_number => number)
update_number_properties(number, tracking)
last_event = self.events.ordered.first.occured_at
tracker.events.each do |event|
if last_event and (event.occurred_at > last_event)
create_new_event(event)
end
end
end
protected
def update_number_properties
number.carrier = Carrier.where(:name => 'UPS').first
number.service = tracker.service_type
number.destination_country = tracker.destination_country
number.destination_state = tracker.destination_state
number.destination_city = tracker.destination_city
number.origin_country = tracker.origin_country
number.origin_state = tracker.origin_state
number.origin_city = tracker.origin_city
number.signature = tracker.signature_name
number.scheduled_delivery = tracker.scheduled_delivery_date
number.weight = tracker.weight
end
def create_new_event
new_event = Event.new
new_event.number = self
new_event.city = event.city
new_event.state = event.state
new_event.postalcode = event.postal_code if event.postal_code
new_event.country = event.country
new_event.status = event.name
new_event.status_code = event.type
new_event.occured_at = event.occurred_at
new_event.save
end
end
This code could be further improved, I imagine the creating of events and trackings will be shared over the different carriers. So a shared base-class maybe. Secondly, in two methods inside Number we call the get_tracker: probably this tracker can be decided upon creation time (of the Number-instance) and should be fetched once and stored inside an instance variable.
Furthermore I would like to add that names should be meaningful, so a class Number does not sound meaningful enough to me. A name should express intent, and preferably match the names and concepts from your problem domain.
Something about your code doesn't make sense to me, the organization is a little strange and I don't have enough context on what your app looks like. The OOP-ish way to accomplish something like this in rails is pretty easy; you could also roll your own Strategy, Adapter, or even a Builder pattern to do this.
But, there is some low hanging fruit, you can refactor your code so the common parts are less obtrusive -- this is a little better but, create_events is still very case'y:
def track
create_events
end
def update_number
create_events {|e| last_event and (event.occurred_at > last_event) }
end
def create_events(&block)
case determine_type(number)
when 'UPS'
tracker = ups.track(:tracking_number => tracking)
self.carrier = Carrier.where(:name => 'UPS').first
self.assign_tracker(tracker)
end
tracker.events.each do |e|
self.create_event(e) unless (block_given? && !block.call(e))
end
save
end
def assign_tracker(tracker)
self.service = tracker.service_type
self.destination_country = tracker.destination_country
self.destination_state = tracker.destination_state
self.destination_city = tracker.destination_city
self.origin_country = tracker.origin_country
self.origin_state = tracker.origin_state
self.origin_city = tracker.origin_city
self.signature = tracker.signature_name
self.scheduled_delivery = tracker.scheduled_delivery_date
self.weight = tracker.weight
end
def create_event(event)
new_event = Event.new
new_event.number = self
new_event.city = event.city
new_event.state = event.state
new_event.postalcode = event.postal_code if event.postal_code
new_event.country = event.country
new_event.status = event.name
new_event.status_code = event.type
new_event.occured_at = event.occurred_at
new_event.save
end

Make GeoKit play nicely with my Address class

My Address class has a geocode class method that returns an array of address objects derived from geocoding the method's parameter (if the geocoding results in an exact match, the array will have one element).
One annoying part about writing this method is translating the GeoKit address objects to my address objects (e.g., "street_address" -> "address1"). Is there a better way to do this?
class Address < ActiveRecord::Base
def self.geocode(string)
return nil if string.nil?
results = Geokit::Geocoders::GoogleGeocoder.geocode(string)
address_objects = Array.new
results.all.each do |r|
params = Hash.new
params['address1'] = r.street_address
params['city'] = r.city
params['zipcode'] = r.zip
params['state'] = State.find_by_abbr(r.state)
params['country'] = Country.find_by_iso(r.country_code)
new_address = Address.new(params)
new_address.single_line_address = r.full_address
address_objects << new_address
end
return address_objects
end
end
What about the following
class Address < ActiveRecord::Base
def self.geocode(string)
return if string.nil?
results = Geokit::Geocoders::GoogleGeocoder.geocode(string)
results.all.map do |r|
Address.new do |address|
address.address1 = r.street_address
address.city = r.city
address.zipcode = r.zip
address.state = State.find_by_abbr(r.state)
address.country = Country.find_by_iso(r.country_code)
address.single_line_address = r.full_address
end
end
end
end

Resources