SlideShare a Scribd company logo
1 of 131
Download to read offline
1/131
Me
Nikita Shilnikov
• github.com/flash-gordon
• Whatever developer
• dry-rb and rom-rb core team member
2/131
dry-rb
3/131
Thank you!
4/131
Web-application
5/131
request -> (application) -> response
6/131
response = application(request)
7/131
response = application.call(request)
8/131
9/131
class User < ApplicationRecord
before_save :set_name
def set_name
self.name = [first_name, last_name].join(' ')
end
end
10/131
class User < ApplicationRecord
before_save :set_name, unless: -> u { u.source == 'github' }
def set_name
self.name = [first_name, last_name].join(' ')
end
end
11/131
class CreateUserFromGithub
# ...
end
class CreateUser
# ...
end
12/131
class CreateUser
def call(params)
User.create(params) do |user|
user.name = build_name(user)
end
end
def build_name(user)
[user.first_name, user.last_name].join(' ')
end
end
13/131
Single responsibility principle
14/131
def create
user = CreateUser.new.call(params)
end
15/131
def create
create_user = CreateUser.new
user = create_user.call(params)
end
16/131
object + SRP = function
17/131
def create
create_user = proc do |params|
name = params.values_at(:first_name, :last_name).join(' ')
User.create(params.merge(name: name))
end
user = create_user.call(params)
end
18/131
def create
user = create_user.call(params)
end
def create_user
delimiter = ' '
proc do |params|
name = params.values_at(:first_name, :last_name).join(delimiter)
User.create(params.merge(name: name))
end
end
19/131
dry-rb
20/131
~20 gems
21/131
Each gem serves one purpose
22/131
Not a left-pad
23/131
Principles
• Functional objects
• Immutability
24/131
Functional objects
class CreateUser
attr_reader :repo
def initialize(repo)
@repo = repo
end
def call(data)
repo.create(data)
end
end
25/131
create_user = CreateUser.new(repo)
26/131
create_user.call(name: 'John')
 
 
27/131
create_user.call(name: 'John')
^^^^^^^^^^^ ^^^^^^^^^^^^^
 
28/131
create_user.call(name: 'John')
^^^^^^^^^^^| |^^^^^^^^^^^^^
   object | | data     
29/131
create_user.(name: 'John')
^^^^^^^^^^^|^^^^^^^^^^^^^^
  function | data      
30/131
create_user.(name: 'Wilhelm')
create_user.(name: 'Hendrik')
create_user.(name: 'Pieter')
create_user.(name: 'Pierre')
create_user.(name: 'Maria')
create_user.(name: 'Philipp')
create_user.(name: 'Albert')
create_user.(name: 'Karl')
31/131
app = Application.new
app.call(request_1)
app.call(request_2)
app.call(request_3)
app.call(request_4)
app.call(request_5)
app.call(request_6)
app.call(request_7)
app.call(request_8)
app.call(request_9)
32/131
application -> ... -> create_user -> ...
33/131
The big picture
app = Application.new
status, headers, body = app.call(request_1)
34/131
Stack
1. Route
2. Validate
3. Issue updates (if any)
4. Fetch data
5. Render
35/131
Route with dry-web-roda
36/131
dry-web-roda = dry-web + Roda
37/131
dry-web-roda
class Shop::Application < Dry::Web::Roda::Application
end
38/131
dry-web-roda
class Shop::Application < Dry::Web::Roda::Application
route 'users' do |r|
r.is do
r.post do
# create user
end
end
end
end
39/131
dry-web-roda
r.post do
create_user = CreateUser.new
create_user.call(r.params['user'])
end
40/131
class CreateUser
def call(input)
end
end
41/131
class CreateUser
def call(input)
validate(input)
end
end
42/131
class CreateUser
def call(input)
something.call(input)
end
end
43/131
Something is a schema
44/131
Validate with
dry-validation
45/131
dry-validation
validate and coerce
46/131
Coercion
params[:user_id]
47/131
Coercion
params[:user_id].class # => ???
48/131
Coercion
params[:user_id].class
# => String | Fixnum | Hash | Array
49/131
Coercion
params[:user_id].to_i
50/131
Coercion
'abyr'.to_i # => 0
51/131
52/131
Coercion
Integer('40') # => 40
53/131
Coercion
Integer('abyr') # => ArgumentError
54/131
Coercion
Integer('050') # => 40
55/131
Coercion
Integer('050', 10) # => 50
56/131
Coercion
Integer(50, 10) # => ArgumentError
57/131
dry-validation
class CreateUser
Schema = Dry::Validation.Form do
end
end
58/131
dry-validation
class CreateUser
Schema = Dry::Validation.Form do
required(:name).filled(:str?)
end
end
59/131
dry-validation
class CreateUser
Schema = Dry::Validation.Form do
required(:name).filled(:str?)
required(:age).filled(:int?, gteq?: 18)
end
end
60/131
dry-validation
CreateUser::Schema.('name' => 'John', 'age' => '20').to_h
=> { name: "John", age: 20 }
61/131
dry-validation
CreateUser::Schema.('name' => 'John', 'age' => '20').to_h
=> { name: "John", age: 20 }
^^^^^^^
62/131
dry-validation
CreateUser::Schema.('name' => 'John', 'age' => '16').errors
=> { age: ["must be greater than or equal to 18"] }
63/131
dry-validation
CreateUser::Schema.('name' => 'John', 'age' => 'abyr').errors
=> { age: ["must be an integer"] }
64/131
dry-validation
Replaces Strong Parameters and ActiveModel::Validations
65/131
class CreateUser
def call(input)
validation = Schema.(input)
if validation.success?
create_from_data(validation.output)
end
end
end
66/131
def create_from_data(data)
user_repo = ???
user_repo.create(data)
end
67/131
def create_from_data(data)
user_repo = User
user_repo.create(data)
end
68/131
def create_from_data(data)
user_repo = UserRepository.new(rom)
user_repo.create(data)
end
69/131
dry-container
70/131
dry-container
Shop::Container = Dry::Container.new
Shop::Container.register(:user_repo) do
UserRepository.new(rom)
end
71/131
dry-container
def create_from_data(data)
user_repo = Shop::Container.resolve(:user_repo)
user_repo.create(data)
end
72/131
class CreateUser
attr_reader :user_repo
def initialize
@user_repo = Shop::Container.resolve(:user_repo)
end
# ...
def create_from_data(data)
user_repo.create(data)
end
end
73/131
dry-auto_inject
DI tool for Ruby
74/131
dry-auto_inject
Shop::Import = Dry::AutoInject(Shop::Container)
75/131
dry-auto_inject
class CreateUser
include Shop::Import[repo: 'repositories.user']
def call(input)
validation = Schema.(input)
if validation.success?
repo.create(validation.output)
end
end
end
76/131
dry-auto_inject
create_user = CreateUser.new
77/131
dry-auto_inject
create_user = CreateUser.new(repo: injection)
78/131
Shop::Container.namespace(:operations) do
register(:create_user) do
Operations::CreateUser.new
end
end
79/131
Shop::Container.namespace(:operations) do
register(:create_user) do
Shop::Operations::CreateUser.new
end
register(:create_post) do
Shop::Operations::CreatePost.new
end
register(:create_tag) do
Shop::Operations::CreateTag.new
end
register(:create_account) do
Shop::Operations::CreateAccount.new
end
register(:update_user) do
Shop::Operations::UpdateUser.new
end
register(:update_post) do
Shop::Operations::UpdatePost.new
end
register(:update_tag) do
Shop::Operations::UpdateTag.new
end
register(:update_account) do
Shop::Operations::UpdateAccount.new
end
end
80/131
dry-system
81/131
dry-system
module Shop
class Container < Dry::Web::Container
configure do
config.auto_register = 'lib'.freeze
config.system_dir = 'system'.freeze
config.root = Pathname(__FILE__).dirname.join('../..')
end
load_paths! 'lib' # Adds to $LOAD_PATH
end
end
82/131
dry-system
r.post do
r.resolve 'operations.create_user' do |create|
create.(r.params['user'])
end
end
83/131
dry-system
r.resolve
1. Uses require
2. Searches for operations/create_user.rb
3. Creates Operations::CreateUser
84/131
r.post do
r.resolve 'operations.create_user' do |create|
user = create.(r.params['user'])
r.redirect_to "/users/#{ user.id }"
end
end
85/131
class CreateUser
def call(input)
validation = Schema.(input)
if validation.success?
repo.create(validation.output)
else
# ???
end
end
end
86/131
class CreateUser
def call(input)
validation = Schema.(input)
if validation.success?
repo.create(validation.output)
else
validation
end
end
end
87/131
dry-monads
88/131
dry-monads
89/131
dry-monads
Either = Left | Right
Right(user)
Left(error)
90/131
dry-monads
def call(input)
validation = Schema.(input)
if validation.success?
user = repo.create(validation.output)
Right(user)
else
Left(validation)
end
end
91/131
dry-matcher
92/131
dry-matcher
class CreateUser
include Dry::Matcher.for(:call, with: Dry::Matcher::EitherMatcher)
def call(input)
validation = Schema.(input)
if validation.success?
user = repo.create(validation.output)
Right(user)
else
Left(validation)
end
end
end
93/131
dry-matcher
r.post do
r.resolve 'operations.create_user' do |create|
create.(r.params['user']) do |m|
m.success do |user|
r.redirect_to "/users/#{ user.id }"
end
m.failure do |validation|
# render errors
end
end
end
end
94/131
Render with dry-view
95/131
dry-view
r.post do
r.resolve 'operations.create_user' do |create|
create.(r.params['user']) do |m|
m.success do |user|
r.redirect_to "/users/#{ user.id }"
end
m.failure do |validation|
r.view 'users.new', validation: validation
end
end
end
end
96/131
dry-view
module Views
module Users
class New < Shop::ViewController
configure do |config|
config.template = "users/new"
end
expose :validation
end
end
end
97/131
Business logic with
dry-transaction
98/131
CreateUser = ValidateUser + PersistUser
99/131
dry-transaction
class CreateUser
Transaction = Dry::Transaction(container: Shop::Container) do
end
end
100/131
dry-transaction
class CreateUser
Transaction = Dry::Transaction(container: Shop::Container) do
step :validate, with: 'operations.validate'
step :persist, with: 'persistance.commands.create_user'
end
end
101/131
dry-transaction
class CreateUser
Transaction = Dry::Transaction(container: Shop::Container) do
step :validate, with: 'operations.validate'
step :persist, with: 'persistance.commands.create_user'
end
def call(input, &block)
Transaction.(input, validate: [Schema], &block)
end
end
102/131
dry-transaction
class CreateUser
Transaction = Dry::Transaction(container: Shop::Container) do
step :validate, with: 'operations.validate'
step :persist, with: 'persistance.commands.create_user'
end
def call(input, &block)
Transaction.(input, validate: [Schema], &block)
end ^^^^^^^^^^^^^^^^^^
end
103/131
dry-transaction
class Validate
def call(input, schema)
validation = schema.call(input)
if validation.success?
Right(validation.output)
else
Left(validation)
end
end
end
104/131
Data objects with dry-struct
105/131
dry-struct
class User < Dry::Struct
attribute :name, Types::Strict::String
attribute :email, Types::Strict::String
attribute :age, Types::Strict::Int
end
106/131
dry-struct
user = User.new(name: 'John', email: 'john@doe.com', age: 33)
=> #<User name="John" email="john@doe.com" age=33>
user.name # => "John"
user.email # => "john@doe.com"
107/131
dry-struct
User.new(name: nil, email: 'john@doe.com', age: 33)
# => Dry::Struct::Error: [User.new]
# nil (NilClass) has invalid type for :name
108/131
dry-struct
class User < Dry::Struct
Name = Types::Strict::String
attribute :name, Name
attribute :email, Types::Strict::String
attribute :age, Types::Strict::Int
end
109/131
dry-types
Name = Types::Strict::String.constrained(min_size: 3)
Name['Li']
# => Dry::Types::ConstraintError:
# "Li" violates constraints (min_size?(3, "Li") failed)
110/131
dry-types
Source = Types::Strict::String.enum('web', 'github')
Source['web'] # => "web"
Source['foo']
# => Dry::Types::ConstraintError: "foo" violates constraints
111/131
Recap
response = application.call(request)
action = router.call(env)
data = Schema.call(params)
user = create_user.call(data)
html = template.call(user)
112/131
Recap
response = application.call(request)
^^^^^^^^^^^
       action = router.call(env)
^^^^^^
         data = Schema.call(params)
^^^^^^
    user = create_user.call(data)
^^^^^^^^^^^
       html = template.call(user)
^^^^^^^^
113/131
Recap
• dry-web-roda
• dry-validation
• dry-container
• dry-auto_inject
• dry-system
• dry-monads
• dry-matcher
• dry-view
• dry-transaction
• dry-struct
• dry-types
114/131
A new wave
115/131
No mutable state
116/131
No monkey patching
117/131
Toolset
118/131
Stuff that works
119/131
120/131
Can I use it? (Yes!)
121/131
122/131
Easy start with dry-validation and
dry-container
123/131
Choose what works for you
124/131
125/131
Get involved!
• dry-rb.org
• discuss.dry-rb.org
• gitter.im/dry-rb/chat
126/131
Ruby doesn't suck (anymore)
127/131
Ruby = Smalltalk + ...
128/131
Ruby = Smalltalk + Perl + ...
129/131
Ruby = Smalltalk + Perl + Lisp
130/131
Thank you
• github.com/flash-gordon
• @NikitaShilnikov
• github.com/dry-rb
• icelab/dry-web-skeleton
• icelab/berg
131/131

More Related Content

Similar to Dry Ruby Web Application Architecture

Debugging on Rails. Mykhaylo Sorochan
Debugging on Rails. Mykhaylo SorochanDebugging on Rails. Mykhaylo Sorochan
Debugging on Rails. Mykhaylo SorochanSphere Consulting Inc
 
How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30fiyuer
 
Rupicon 2014 Action pack
Rupicon 2014 Action packRupicon 2014 Action pack
Rupicon 2014 Action packrupicon
 
Local SQLite Database with Node for beginners
Local SQLite Database with Node for beginnersLocal SQLite Database with Node for beginners
Local SQLite Database with Node for beginnersLaurence Svekis ✔
 
All I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web FrameworkAll I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web FrameworkBen Scofield
 
ITB_2023_CommandBox_Task_Runners_Brad_Wood.pdf
ITB_2023_CommandBox_Task_Runners_Brad_Wood.pdfITB_2023_CommandBox_Task_Runners_Brad_Wood.pdf
ITB_2023_CommandBox_Task_Runners_Brad_Wood.pdfOrtus Solutions, Corp
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)James Titcumb
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207patter
 
Desarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosDesarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosEdgar Suarez
 
What's new in Rails 4
What's new in Rails 4What's new in Rails 4
What's new in Rails 4Fabio Akita
 
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)James Titcumb
 
Symfony console: build awesome command line scripts with ease
Symfony console: build awesome command line scripts with easeSymfony console: build awesome command line scripts with ease
Symfony console: build awesome command line scripts with easeOscar Merida
 
Drupalcon 2023 - How Drupal builds your pages.pdf
Drupalcon 2023 - How Drupal builds your pages.pdfDrupalcon 2023 - How Drupal builds your pages.pdf
Drupalcon 2023 - How Drupal builds your pages.pdfLuca Lusso
 
2023 - Drupalcon - How Drupal builds your pages
2023 - Drupalcon - How Drupal builds your pages2023 - Drupalcon - How Drupal builds your pages
2023 - Drupalcon - How Drupal builds your pagessparkfabrik
 
Desymfony 2011 - Habemus Bundles
Desymfony 2011 - Habemus BundlesDesymfony 2011 - Habemus Bundles
Desymfony 2011 - Habemus BundlesAlbert Jessurum
 
CMake Tutorial
CMake TutorialCMake Tutorial
CMake TutorialFu Haiping
 

Similar to Dry Ruby Web Application Architecture (20)

Debugging on Rails. Mykhaylo Sorochan
Debugging on Rails. Mykhaylo SorochanDebugging on Rails. Mykhaylo Sorochan
Debugging on Rails. Mykhaylo Sorochan
 
Debugging on rails
Debugging on railsDebugging on rails
Debugging on rails
 
How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30
 
Devise and Rails
Devise and RailsDevise and Rails
Devise and Rails
 
Rupicon 2014 Action pack
Rupicon 2014 Action packRupicon 2014 Action pack
Rupicon 2014 Action pack
 
The Rails Way
The Rails WayThe Rails Way
The Rails Way
 
Anatomy of a reusable module
Anatomy of a reusable moduleAnatomy of a reusable module
Anatomy of a reusable module
 
Local SQLite Database with Node for beginners
Local SQLite Database with Node for beginnersLocal SQLite Database with Node for beginners
Local SQLite Database with Node for beginners
 
All I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web FrameworkAll I Need to Know I Learned by Writing My Own Web Framework
All I Need to Know I Learned by Writing My Own Web Framework
 
ITB_2023_CommandBox_Task_Runners_Brad_Wood.pdf
ITB_2023_CommandBox_Task_Runners_Brad_Wood.pdfITB_2023_CommandBox_Task_Runners_Brad_Wood.pdf
ITB_2023_CommandBox_Task_Runners_Brad_Wood.pdf
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207
 
Desarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosDesarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutos
 
What's new in Rails 4
What's new in Rails 4What's new in Rails 4
What's new in Rails 4
 
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
 
Symfony console: build awesome command line scripts with ease
Symfony console: build awesome command line scripts with easeSymfony console: build awesome command line scripts with ease
Symfony console: build awesome command line scripts with ease
 
Drupalcon 2023 - How Drupal builds your pages.pdf
Drupalcon 2023 - How Drupal builds your pages.pdfDrupalcon 2023 - How Drupal builds your pages.pdf
Drupalcon 2023 - How Drupal builds your pages.pdf
 
2023 - Drupalcon - How Drupal builds your pages
2023 - Drupalcon - How Drupal builds your pages2023 - Drupalcon - How Drupal builds your pages
2023 - Drupalcon - How Drupal builds your pages
 
Desymfony 2011 - Habemus Bundles
Desymfony 2011 - Habemus BundlesDesymfony 2011 - Habemus Bundles
Desymfony 2011 - Habemus Bundles
 
CMake Tutorial
CMake TutorialCMake Tutorial
CMake Tutorial
 

Recently uploaded

#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationSafe Software
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
APIForce Zurich 5 April Automation LPDG
APIForce Zurich 5 April  Automation LPDGAPIForce Zurich 5 April  Automation LPDG
APIForce Zurich 5 April Automation LPDGMarianaLemus7
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Neo4j
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksSoftradix Technologies
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 

Recently uploaded (20)

#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping Elbows
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
APIForce Zurich 5 April Automation LPDG
APIForce Zurich 5 April  Automation LPDGAPIForce Zurich 5 April  Automation LPDG
APIForce Zurich 5 April Automation LPDG
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024Build your next Gen AI Breakthrough - April 2024
Build your next Gen AI Breakthrough - April 2024
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other Frameworks
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 

Dry Ruby Web Application Architecture