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.