Rails revolutionized the way we build web apps - but that was 10 years ago. These days, it's not uncommon to hear dramatic statements like, “Rails is dead” or even, “Ruby is dead”. Even though such conclusions are far-fetched, it’s a sign that some people have become tired of struggling with various problems that Rails doesn’t address.
We’re starting to see people leave Ruby - should you do the same? Or is there a place in the Ruby world where we can still innovate and prove that Ruby is a viable technological choice?
In this talk, I’ll introduce you to the growing ecosystem of modern Ruby libraries and frameworks. Let’s go beyond Rails and see that Ruby is doing just fine.
36. ROM-RB INNOVATIONS
▸ Read/Write separation
▸ Relations as first-class citizens
▸ Data transformations instead of object mutations
▸ Explicit relation composition instead of ambiguous query
DSLs
▸ Command API for custom db-specific database operations
▸ Externalized data validations
37. cfg = ROM::Configuration.new(:sql, 'sqlite::memory')
cfg.gateways[:default].create_table(:users) do
primary_key :id
column :name, String, null: false
column :age, Integer, null: true
end
cfg.gateways[:default].create_table(:tasks) do
primary_key :id
foreign_key :user_id, :users, null: false, on_delete: :cascade
column :title, String, null: true
end
38. class Users < ROM::Relation[:sql]
schema(infer: true)
end
class Tasks < ROM::Relation[:sql]
schema(infer: true)
end
SETS UP RELATION
ATTRIBUTES BASED ON DATABASE
SCHEMA SCHEMA
INFERS TABLE NAME FROM CLASS
NAME
52. require 'dry-struct'
class Task < Dry::Struct
attribute :title, Types::Strict::String
end
class User < Dry::Struct
attribute :name, Types::Strict::String
attribute :age, Types::Strict::Int
attribute :tasks, Types::Strict::Array.member(Task)
end
56. DRY-RB INNOVATIONS
▸ Advanced data validation
▸ Flexible type system with coercions and constraints
▸ Composable transactions with error handling based on
monads
▸ Simple yet powerful Dependency Injection
▸ Application state management with component lifecycles
58. class User < Dry::Struct
attribute :name, Types::Strict::String
attribute :age, Types::Strict::Int
end
59. module Types
include Dry::Types.module
Age = Types::Strict::Int.constrained(gt: 18)
end
class User < Dry::Struct
attribute :name, Types::Strict::String
attribute :age, Types::Age
end
63. UserSchema = Dry::Validation.Schema do
required(:login).value(:str?, size?: 2..64)
required(:age).maybe(:int?, gt?: 18)
end
IS IT A STRING WITH A SIZE
BETWEEN 2 AND 64 CHARS?
CAN BE NIL BUT
IF IT’S NOT THEN IS IT AN
INTEGER AND IS IT GREATER
THAN 18?
64. UserSchema.(login: "j", age: 19).errors
# {:login=>["length must be within 2 - 64"]}
UserSchema.(login: "jane", age: nil).errors
# {}
SHORTCUT FOR `#CALL` :)
72. require 'dry/system/container'
class Application < Dry::System::Container
configure do |config|
config.root = Pathname.new('./my/app')
end
end
# now you can register a logger
require 'logger'
Application.register('utils.logger', Logger.new($stdout))
# and access it
Application['utils.logger']
75. # under /my/app/lib/logger.rb we put
class Logger
# some neat logger implementation
end
# we can finalize the container which triggers auto-registration
Application.finalize!
# the logger becomes available
Application[‘logger']
78. class Application < Dry::Component::Container
configure do |config|
config.root = '/my/app'
end
end
# requires needed files and boots your persistence component
Application.boot!(:persistence)
# and now `database` becomes available
Application['database']
79. # lib/worker.rb
require ‘application/import'
class Worker
include Import['database']
def do_stuff
users = database.read('select * from users')
# ..
end
end
THIS WILL INJECT “DATABASE”
OBJECT AND MAKE IT AVAILABLE