Ruby w/o rails
Oleksandr Simonov
About me
• Name: Oleksandr Simonov
• Occupation: Founder and Owner of Amoniac
• Hobby: Open Source contribution
• Github: @simonoff
Why Ruby w/o Rails?
• Rails is a monolithic stone
• Rails is slow
• Rails force you to be a lazy
rails is:
• activerecord
• activemodel
• activesupport
• actionmailer
• actionpack
• actionview
• activejob
• railties
benchmark
http://www.madebymarket.com/blog/dev/ruby-web-
benchmark-report.html
Lazy Rails developer
Alternatives
• Rack
• Sinatra
• Cuba
• Grape (API)
• Reel
• Nahami
• Sequel
• ROM
WEB ORM
Advantages
• Faster then Rails
• Less time for app loading
• New knowledge
Disadvantages
• No Rails like console
• No Rails like code autoloading
• No Rails helpers
• No "Magic"
• More code
Practical section
• Download list of Finnish companies
• Insert/Update local DB
• On API call returns e-invoice address
- app
- models
- services
- workers
- config
- boot.rb
- initializers
- 01-dotenv.rb
- 02-airbrake.rb
- 03-sequel.rb
- 04-sidekiq.rb
- db
- migrations
- init.rb
- config.ru
Structure
require 'rubygems'
require 'bundler/setup'
require_relative 'init' # Application code loading
require 'sidekiq/web'
use Rack::ContentLength
use Rack::CommonLogger, ::NetvisorSpreadsheets.
logger # use own logger
use Rack::ShowExceptions
run Rack::URLMap.new('/' => ::NetvisorSpreadsheets:
:Server, '/sidekiq' =>
Sidekiq::Web)
config.ru
init.rb
# encoding: UTF-8
ENV['RACK_ENV'] ||= 'development' # default environment
require 'rubygems'
module NetvisorSpreadsheets # Singletone
extend self
attr_accessor :db
def root # helper for get application root path
@root_path ||= ::Pathname.new(::File.join(File.dirname(__FILE__))).expand_path
end
def env # helper for get environment
@env ||= ENV["RACK_ENV"] || "development"
end
def logger # helper for logger
@logger ||= ::Logger.new(self.root.join('log', "#{self.env}.log").to_s)
end
end
::NetvisorSpreadsheets.logger.level = if ::NetvisorSpreadsheets.env == 'production'
::Logger::INFO
else
::Logger::DEBUG
end
# require boot file
require self.root.join('config', 'boot.rb').to_s
$LOAD_PATH.unshift ::NetvisorSpreadsheets.root # add root path to load
path
# autoload some initializers, models, workers and services
def load_folder(*path)
::Dir.glob(::NetvisorSpreadsheets.root.join(*path)).each { |r| require
r }
end
load_folder('config', 'initializers', '**/*.rb')
load_folder('app', 'models', '**/*.rb')
load_folder('app', 'workers', '**/*.rb')
load_folder('app', 'services', '**/*.rb')
# require sinatra server code
require 'config/server'
boot.rb
source 'https://rubygems.org'
gem 'sqlite3'
gem 'dotenv'
gem 'sequel'
gem 'sinatra'
gem 'sinatra-contrib'
gem 'sidekiq'
gem 'airbrake'
gem 'puma'
gem 'sidekiq-cron', '~> 0.3', require: false
gem 'sidekiq-unique-jobs', '~> 4.0'
Gemfile
require 'rubygems'
require 'bundler/setup'
require 'sequel'
require 'dotenv'
require 'rake'
env = ENV['RACK_ENV'] || 'development'
namespace :db do
desc 'Run migrations'
task :migrate, [:version] do |_t, args|
::Dotenv.load(".env", ".env.#{env}")
::Sequel.extension :migration
db = ::Sequel.connect(::ENV.fetch('DATABASE_URL'))
if args[:version]
puts "Migrating to version #{args[:version]}"
::Sequel::Migrator.run(db, 'db/migrations', target: args[:version].to_i)
else
puts 'Migrating to latest'
::Sequel::Migrator.run(db, 'db/migrations')
end
end
end
Rakefile
::Sequel.migration do
transaction
up do
create_table :companies do
primary_key :id
String :company_id, null: false, index: true
String :name, null: false
String :einvoice_address, null: false
String :einvoice_operator, null: false
end
end
down do
drop_table :companies
end
end
db/migrations/001_create_companies.rb
class CompanyUpdaterWorker
include ::Sidekiq::Worker
sidekiq_options unique: :while_executing
URL = "http://verkkolasku.tieke.fi/ExporVLOsoiteToExcel.aspx?type=csv"
def perform
path = ::Tempfile.new('vlo').path
if http_download_uri(::URI.parse(URL), path)
::NetvisorSpreadsheets::CompaniesFillerService.new(path).import
end
end
def http_download_uri(uri, filename)
begin
::Net::HTTP.new(uri.host, uri.port).start do |http|
http.request(Net::HTTP::Get.new(uri.request_uri)) do |response|
::File.open(filename, 'wb') do |io|
response.read_body { |chunk| io.write(chunk) }
end
end
end
rescue Exception => e
return false
end
true
end
end
companies_updater_worker.rb
companies_filler_service.rb
class CompanyUpdaterWorker
include ::Sidekiq::Worker
sidekiq_options unique: :while_executing
URL = "http://verkkolasku.tieke.fi/ExporVLOsoiteToExcel.aspx?type=csv"
def perform
path = ::Tempfile.new('vlo').path
if http_download_uri(::URI.parse(URL), path)
::NetvisorSpreadsheets::CompaniesFillerService.new(path).import
end
end
def http_download_uri(uri, filename)
begin
::Net::HTTP.new(uri.host, uri.port).start do |http|
http.request(Net::HTTP::Get.new(uri.request_uri)) do |response|
::File.open(filename, 'wb') do |io|
response.read_body { |chunk| io.write(chunk) }
end
end
end
rescue Exception => e
return false
end
true
end
end
require 'sinatra/base'
require 'sinatra/json'
module NetvisorSpreadsheets
class Server < Sinatra::Base
configure :production, :development do
enable :logging
set :json_encoder, :to_json
end
get '/' do
if params['company_id'] && params['company_id'].length > 0
json ::NetvisorSpreadsheets::CompanyFinderService.find(params['company_id'])
else
400
end
end
end
end
server.rb
What we get?
• Only 40Mb RAM
• 1 second app load
• Fast deployment
Questions

Ruby w/o Rails (Олександр Сімонов)

  • 1.
  • 2.
    About me • Name:Oleksandr Simonov • Occupation: Founder and Owner of Amoniac • Hobby: Open Source contribution • Github: @simonoff
  • 3.
    Why Ruby w/oRails? • Rails is a monolithic stone • Rails is slow • Rails force you to be a lazy
  • 5.
    rails is: • activerecord •activemodel • activesupport • actionmailer • actionpack • actionview • activejob • railties
  • 6.
  • 7.
  • 8.
    Alternatives • Rack • Sinatra •Cuba • Grape (API) • Reel • Nahami • Sequel • ROM WEB ORM
  • 9.
    Advantages • Faster thenRails • Less time for app loading • New knowledge
  • 10.
    Disadvantages • No Railslike console • No Rails like code autoloading • No Rails helpers • No "Magic" • More code
  • 11.
    Practical section • Downloadlist of Finnish companies • Insert/Update local DB • On API call returns e-invoice address
  • 13.
    - app - models -services - workers - config - boot.rb - initializers - 01-dotenv.rb - 02-airbrake.rb - 03-sequel.rb - 04-sidekiq.rb - db - migrations - init.rb - config.ru Structure
  • 14.
    require 'rubygems' require 'bundler/setup' require_relative'init' # Application code loading require 'sidekiq/web' use Rack::ContentLength use Rack::CommonLogger, ::NetvisorSpreadsheets. logger # use own logger use Rack::ShowExceptions run Rack::URLMap.new('/' => ::NetvisorSpreadsheets: :Server, '/sidekiq' => Sidekiq::Web) config.ru
  • 15.
    init.rb # encoding: UTF-8 ENV['RACK_ENV']||= 'development' # default environment require 'rubygems' module NetvisorSpreadsheets # Singletone extend self attr_accessor :db def root # helper for get application root path @root_path ||= ::Pathname.new(::File.join(File.dirname(__FILE__))).expand_path end def env # helper for get environment @env ||= ENV["RACK_ENV"] || "development" end def logger # helper for logger @logger ||= ::Logger.new(self.root.join('log', "#{self.env}.log").to_s) end end ::NetvisorSpreadsheets.logger.level = if ::NetvisorSpreadsheets.env == 'production' ::Logger::INFO else ::Logger::DEBUG end # require boot file require self.root.join('config', 'boot.rb').to_s
  • 16.
    $LOAD_PATH.unshift ::NetvisorSpreadsheets.root #add root path to load path # autoload some initializers, models, workers and services def load_folder(*path) ::Dir.glob(::NetvisorSpreadsheets.root.join(*path)).each { |r| require r } end load_folder('config', 'initializers', '**/*.rb') load_folder('app', 'models', '**/*.rb') load_folder('app', 'workers', '**/*.rb') load_folder('app', 'services', '**/*.rb') # require sinatra server code require 'config/server' boot.rb
  • 17.
    source 'https://rubygems.org' gem 'sqlite3' gem'dotenv' gem 'sequel' gem 'sinatra' gem 'sinatra-contrib' gem 'sidekiq' gem 'airbrake' gem 'puma' gem 'sidekiq-cron', '~> 0.3', require: false gem 'sidekiq-unique-jobs', '~> 4.0' Gemfile
  • 18.
    require 'rubygems' require 'bundler/setup' require'sequel' require 'dotenv' require 'rake' env = ENV['RACK_ENV'] || 'development' namespace :db do desc 'Run migrations' task :migrate, [:version] do |_t, args| ::Dotenv.load(".env", ".env.#{env}") ::Sequel.extension :migration db = ::Sequel.connect(::ENV.fetch('DATABASE_URL')) if args[:version] puts "Migrating to version #{args[:version]}" ::Sequel::Migrator.run(db, 'db/migrations', target: args[:version].to_i) else puts 'Migrating to latest' ::Sequel::Migrator.run(db, 'db/migrations') end end end Rakefile
  • 19.
    ::Sequel.migration do transaction up do create_table:companies do primary_key :id String :company_id, null: false, index: true String :name, null: false String :einvoice_address, null: false String :einvoice_operator, null: false end end down do drop_table :companies end end db/migrations/001_create_companies.rb
  • 20.
    class CompanyUpdaterWorker include ::Sidekiq::Worker sidekiq_optionsunique: :while_executing URL = "http://verkkolasku.tieke.fi/ExporVLOsoiteToExcel.aspx?type=csv" def perform path = ::Tempfile.new('vlo').path if http_download_uri(::URI.parse(URL), path) ::NetvisorSpreadsheets::CompaniesFillerService.new(path).import end end def http_download_uri(uri, filename) begin ::Net::HTTP.new(uri.host, uri.port).start do |http| http.request(Net::HTTP::Get.new(uri.request_uri)) do |response| ::File.open(filename, 'wb') do |io| response.read_body { |chunk| io.write(chunk) } end end end rescue Exception => e return false end true end end companies_updater_worker.rb
  • 21.
    companies_filler_service.rb class CompanyUpdaterWorker include ::Sidekiq::Worker sidekiq_optionsunique: :while_executing URL = "http://verkkolasku.tieke.fi/ExporVLOsoiteToExcel.aspx?type=csv" def perform path = ::Tempfile.new('vlo').path if http_download_uri(::URI.parse(URL), path) ::NetvisorSpreadsheets::CompaniesFillerService.new(path).import end end def http_download_uri(uri, filename) begin ::Net::HTTP.new(uri.host, uri.port).start do |http| http.request(Net::HTTP::Get.new(uri.request_uri)) do |response| ::File.open(filename, 'wb') do |io| response.read_body { |chunk| io.write(chunk) } end end end rescue Exception => e return false end true end end
  • 22.
    require 'sinatra/base' require 'sinatra/json' moduleNetvisorSpreadsheets class Server < Sinatra::Base configure :production, :development do enable :logging set :json_encoder, :to_json end get '/' do if params['company_id'] && params['company_id'].length > 0 json ::NetvisorSpreadsheets::CompanyFinderService.find(params['company_id']) else 400 end end end end server.rb
  • 24.
    What we get? •Only 40Mb RAM • 1 second app load • Fast deployment
  • 25.