Skip to content

Instantly share code, notes, and snippets.

@Yoshyn
Last active November 14, 2019 13:13
Show Gist options
  • Save Yoshyn/f9424e3697a93cb01330572e12718cf2 to your computer and use it in GitHub Desktop.
Save Yoshyn/f9424e3697a93cb01330572e12718cf2 to your computer and use it in GitHub Desktop.
Ruby & Rails : Set of snippet
# Contains :
# * replace_task.rb
# * convert_date.rb
# * detect_leaks.rb
# * trace_point_logger.rb : Display all function path, parameter and return to a file (a logger) POC
# * profile_gem_on_boot.rb : Loading time per gem
# * crow_flies_sqlite.rb : An implementation of crow flies for sqlite & rails
# * openssl.rb : Asyn & sync encryption with openssl
# Convert from ipaddr to netaddr:cidr type
# https://stackoverflow.com/questions/19305586/convert-from-ipaddr-to-netaddrcidr-type/24259011#24259011
# Profile memory & object use over a part of an application
# https://github.com/SamSaffron/memory_profiler
# Manage complex SQL query (mat. views)
# https://blog.castle.co/tech/10-06-2016/complex-sql-in-rails
# Modify the search hash parameter by transforming all date string in date format if the parameter key finish by '_date'.
# The purpose is to repare this kind of parameter to an sql request.
# Params :
# search (mandatory) : hash of params. They probably have been parsed in an index action ( something like : search = params[:search] || {})
# format (optional) : The format of the date that we expect in the search hash.
# zone (optional) : The zone of the date
def convert_date(search, format: '%d/%m/%Y', zone: Time.zone)
search.each do |key, value|
next unless key.respond_to?(:end_with?) && key.end_with?('_date')
search[key] = DateTime.strptime(value, format).in_time_zone(zone) rescue nil
end
end
#frozen_string_literal: true
# https://stackoverflow.com/questions/13939574/how-do-i-put-a-ruby-function-into-a-sqlite3-query
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails"
gem "sqlite3"
end
require "active_record"
require "minitest/autorun"
require "logger"
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
module GeoForSQLite
RAD_PER_DEG = Math::PI / 180
RM = 6371 # Earth radius in kmeters
def self.crow_flies_km(lat1, lon1, lat2, lon2)
lat1_rad, lat2_rad = lat1 * RAD_PER_DEG, lat2 * RAD_PER_DEG
lon1_rad, lon2_rad = lon1 * RAD_PER_DEG, lon2 * RAD_PER_DEG
a = Math.sin((lat2_rad - lat1_rad) / 2) ** 2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin((lon2_rad - lon1_rad) / 2) ** 2
c = 2 * Math::atan2(Math::sqrt(a), Math::sqrt(1 - a))
RM * c # Delta in meters
end
def self.load!(sqlite_database)
%w(cos acos sin).each do |math_method|
sqlite_database.create_function math_method, 1 do |func, *args|
func.result = Math.__send__(math_method, *args)
end
end
sqlite_database.create_function 'pi', 0 do |func, *args|
func.result = Math::PI
end
sqlite_database.create_function 'crow_flies_km', 4 do |func, *args|
func.result = crow_flies_km(*args)
end
end
end
GeoForSQLite.load!(ActiveRecord::Base.connection.raw_connection)
class CrowFliesSQLTest < Minitest::Test
def test_crow_flies_km_sql_result
paris_lat, paris_lng = 48.8566, 2.3522
rennes_lat, rennes_lng = 48.1135, -1.6757
result = ActiveRecord::Base.connection.execute("SELECT CROW_FLIES_KM(#{paris_lat}, #{paris_lng}, #{rennes_lat}, #{rennes_lng}) AS CROW_FLIES_KM;")
assert_equal(308, result[0]["CROW_FLIES_KM"].to_i)
end
def test_crow_flies_km_full_sql_result
paris_lat, paris_lng = 48.8566, 2.3522
rennes_lat, rennes_lng = 48.1135, -1.6757
lat1, lon1, lat2, lon2 = paris_lat, paris_lng, rennes_lat, rennes_lng
query = "ACOS( SIN(#{lat1}*PI()/180)*SIN(#{lat2}*PI()/180) + COS(#{lat1}*PI()/180)*COS(#{lat2}*PI()/180)*COS(#{lon2}*PI()/180-#{lon1}*PI()/180) ) * 6371"
result = ActiveRecord::Base.connection.execute("SELECT #{query} AS CROW_FLIES_KM;")
assert_equal(308, result[0]["CROW_FLIES_KM"].to_i)
end
end
=begin
# Run this into irb
require 'objspace'
ObjectSpace.trace_object_allocations_start
def generate_report(name: nil)
GC.start()
output_file = File.open("#{Rails.root}/tmp/rubyheap_#{name}_#{Process.getpgrp}_#{Time.now.to_i}.json", 'w')
dump = ObjectSpace.dump_all(output: output_file)
dump.close
File.open("#{Rails.root}/tmp/rubyheap_#{name}_#{Process.getpgrp}_count_object", 'a') do |f|
f.write("\n==============#{Time.now.to_i}==============\n")
f.write(ObjectSpace.count_objects.to_s)
end
end
end
def launch_test(name)
my_process_here()
generate_report(name: name)
my_process_here()
generate_report(name: name)
my_process_here()
generate_report(name: name)
end
name = 'unknown'
launch_test(name)
`ruby #{File.join(Rails.root, 'script', 'detect_leaks.rb')} #{Dir[File.join(Rails.root, 'tmp', "rubyheap_#{name}_*.json")].sort.join(' ')} > #{File.join(Rails.root, 'tmp', "leaks.#{name}")}`
=end
#!/usr/bin/env ruby
require 'set'
require 'json'
if ARGV.length != 3
puts "Usage: detect_leaks [FIRST.json] [SECOND.json] [THIRD.json]"
exit 1
end
first_addrs = Set.new
third_addrs = Set.new
line_errors = 0
# Get a list of memory addresses from the first dump
File.open(ARGV[0], "r").each_line do |line|
parsed = JSON.parse(line) rescue (line_errors+=1; nil)
first_addrs << parsed["address"] if parsed && parsed["address"]
end
# Get a list of memory addresses from the last dump
File.open(ARGV[2], "r").each_line do |line|
parsed = JSON.parse(line) rescue (line_errors+=1; nil)
third_addrs << parsed["address"] if parsed && parsed["address"]
end
diff = []
# Get a list of all items present in both the second and
# third dumps but not in the first.
File.open(ARGV[1], "r").each_line do |line|
parsed = JSON.parse(line) rescue (line_errors+=1; nil)
if parsed && parsed["address"]
if !first_addrs.include?(parsed["address"]) && third_addrs.include?(parsed["address"])
diff << parsed
end
end
end
# Group items
diff.group_by do |x|
[x["type"], x["file"], x["line"]]
end.map do |x,y|
# Collect memory size
[x, y.count, y.inject(0){|sum,i| sum + (i['bytesize'] || 0) }, y.inject(0){|sum,i| sum + (i['memsize'] || 0) }]
end.sort do |a,b|
b[1] <=> a[1]
end.each do |x,y,bytesize,memsize|
# Output information about each potential leak
puts "Leaked #{y} #{x[0]} objects of size #{bytesize}/#{memsize} at: #{x[1]}:#{x[2]}"
end
# Also output total memory usage, because why not?
memsize = diff.inject(0){|sum,i| sum + (i['memsize'] || 0) }
bytesize = diff.inject(0){|sum,i| sum + (i['bytesize'] || 0) }
puts "\n\nTotal Size: #{bytesize}/#{memsize}"
puts "\n\nWARNING : #{line_errors} can't be parsed. Maybe the heap are truncated"
def field_form_column(record, options)
new_field = Proc.new { | value = nil|
input_field = text_field_tag "record[field][]", value, :class => options[:class]
js_remove_onclick = "$(this).closest('.field_form_column').remove()"
remove_button = button_tag "Remove this required claim", onclick: js_remove_onclick, type: :button
content_tag(:div, input_field + remove_button, :class => "field_form_column")
}
existing_values = Array(record.public_send(:field)).reject(&:blank?)
inputs = existing_values.each_with_index.map do |value, index|
new_field.call(value, index)
end
js_add_onclick = "var div = document.createElement('div'); div.innerHTML='#{new_field.call}'; $('#add-required-claim')[0].before(div)"
add_button = button_tag "Add one required claim", onclick: js_add_onclick, type: :button, id: "add-required-claim"
(inputs.join + add_button).html_safe
end
text = "This is data"
# Encrypt data with Public key :
formated_cert = OneLogin::RubySaml::Utils.format_cert(Gaston.saml_security.certificate)
cert = OpenSSL::X509::Certificate.new(formated_cert)
public_key = cert.public_key # .to_pem
text_enc = Base64.encode64(public_key.public_encrypt(text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING))
# Decrypt data with Private key :
formatted_private_key = OneLogin::RubySaml::Utils.format_private_key(Gaston.saml_security.private_key)
private_key = OpenSSL::PKey::RSA.new(formatted_private_key)
private_key.private_decrypt(Base64.decode64(text_enc), OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
# On Customer side (public) : verify signature
digest = OpenSSL::Digest::SHA256.new
signature = private_key.sign(digest, text)
public_key.verify digest, signature, text
#private_key.verify digest, signature, text
# Symetric encryptions :
----------------
cipher = OpenSSL::Cipher::AES256.new :CBC
cipher.encrypt
cipher.key = "ThisPasswordIsReallyHardToGuess!"
cipher_text = cipher.update('This is a secret message') + cipher.final
----------------
decipher = OpenSSL::Cipher::AES256.new :CBC
decipher.decrypt
decipher.key = "ThisPasswordIsReallyHardToGuess!"
decipher.update(cipher_text) + decipher.final
=> "This is a secret message"
# https://tomkadwill.com/message-signing-with-ruby-openssl-rsa
# https://gist.github.com/ericchen/3081968
# Profile rails on boot.
# http://mildlyinternet.com/code/profiling-rails-boot-time.html
# https://github.com/schneems/derailed_benchmarks
# Into config/boot.rb
require "benchmark"
def require(file_name)
result = nil
begin
time = Benchmark.realtime do
result = super
end
if time > 0.1
puts "#{time} #{file_name}"
end
rescue StandardError => ex
puts "require(#{file_name} fails : #{ex} #{ex.backtrace}"
raise ex
end
result
end
Rake::TaskManager.class_eval do
def replace_task(task_name, task_scope)
scope_backup = @scope
@scope = Rake::Scope.new(task_scope)
task_name_full = @scope.path_with_task_name(task_name)
@tasks[task_name_full].clear
@tasks[task_name_full] = yield
@scope = scope_backup
end
end
Rake.application.replace_task('prepare', 'db:test') do
task :prepare => %w(environment load_config) do
# Code here
end
end
begin
require "bundler/inline"
rescue LoadError => e
$stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
raise e
end
gemfile(true) do
source "https://rubygems.org"
end
require 'logger'
class TracePoint::Logger < Logger
def initialize(*args); @indent = 0; super(*args); end
def format_message(severity, timestamp, programe, msg)
"[#{severity}][#{timestamp}][#{programe}]" + (" " * @indent * 2) + "-> #{msg}\n"
end
def increment_indent!; @indent+=1; end
def decrement_indent!; @indent-=1; end
end
# logfile = File.open(RAILS_ROOT + '/log/trace_point_logger.log', 'a') #create log file
# logfile.sync = true #automatically flushes data to file
TracePointLogger = TracePoint::Logger.new(STDOUT)
class A
def foo(a,b = 0)
lv = 9
"Hello"
end
end
class B
def self.foo(a)
lv = 6
A.new.foo(a)
end
end
trace_c = TracePoint.new(:call) do |tp|
TracePointLogger.increment_indent!
parameters = tp.defined_class.instance_method(tp.method_id).parameters.inject({}) do |acc, (kind, var)|
acc[var] = { kind: kind , value: tp.binding.local_variable_get(var) }
acc
end
TracePointLogger.debug({ func: "#{tp.defined_class}#{tp.method_id}", parameters: parameters }.to_s)
end
trace_r = TracePoint.new(:return) do |tp|
TracePointLogger.debug({ func: "#{tp.defined_class}#{tp.method_id}", return: tp.return_value.to_s }.to_s)
TracePointLogger.decrement_indent!
end
trace_c.enable
trace_r.enable
A.new.foo(3)
B.foo(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment