A Handy Look at Hanami
Jason Charnes
@jmcharnes | Lensrentals.com
Other Ruby Frameworks
- Sinatra
- Padrino
- Cuba
Volt- A few others
https://blog.engineyard.com/2015/life-beyond-rails-brief-look-alternate-web-frameworks-ruby
What's Missing?
Hanami
"Hanami is a Ruby MVC web framework comprised of many micro-libraries. It has a simple, stable API, a minimal DSL, and prioritizes the use of plain objects over magical, over-complicated classes with too much responsibility."
Luca Guidi
Full-featured
Lightweight
Long-term
Testable
"I warn you that whether you're a total beginner or an experienced developer this learning process can be hard. After 10 years you develop a way of working, and it can be painful for you to change. However, without change, there is no challenge."
$ gem install hanami
Successfully installed hanami-utils-0.8.0
Successfully installed dry-configurable-0.1.7
Successfully installed dry-container-0.5.0
Successfully installed dry-equalizer-0.2.0
Successfully installed dry-logic-0.3.0
Successfully installed inflecto-0.0.2
Successfully installed dry-monads-0.1.1
Successfully installed ice_nine-0.11.2
Successfully installed dry-types-0.8.1
Successfully installed dry-validation-0.9.5
Successfully installed hanami-validations-0.6.0
Successfully installed url_mount-0.2.1
Successfully installed http_router-0.11.2
Successfully installed hanami-router-0.7.0
Successfully installed hanami-controller-0.7.0
Successfully installed hanami-view-0.7.0
Successfully installed hanami-helpers-0.4.0
Successfully installed hanami-mailer-0.3.0
Successfully installed hanami-assets-0.3.0
Successfully installed hanami-0.8.0
20 gems installed
$ gem install hanami
Successfully installed hanami-utils-0.8.0
Successfully installed dry-configurable-0.1.7
Successfully installed dry-container-0.5.0
Successfully installed dry-equalizer-0.2.0
Successfully installed dry-logic-0.3.0
Successfully installed inflecto-0.0.2
Successfully installed dry-monads-0.1.1
Successfully installed ice_nine-0.11.2
Successfully installed dry-types-0.8.1
Successfully installed dry-validation-0.9.5
Successfully installed hanami-validations-0.6.0
Successfully installed url_mount-0.2.1
Successfully installed http_router-0.11.2
Successfully installed hanami-router-0.7.0
Successfully installed hanami-controller-0.7.0
Successfully installed hanami-view-0.7.0
Successfully installed hanami-helpers-0.4.0
Successfully installed hanami-mailer-0.3.0
Successfully installed hanami-assets-0.3.0
Successfully installed hanami-0.8.0
20 gems installed
$ hanami new bookshelf
create .hanamirc
create .env.development
create .env.test
create Gemfile
create config.ru
create config/environment.rb
create lib/bookshelf.rb
create public/.gitkeep
create config/initializers/.gitkeep
create lib/bookshelf/entities/.gitkeep
create lib/bookshelf/repositories/.gitkeep
create lib/bookshelf/mailers/.gitkeep
create lib/bookshelf/mailers/templates/.gitkeep
create spec/bookshelf/entities/.gitkeep
create spec/bookshelf/repositories/.gitkeep
create spec/bookshelf/mailers/.gitkeep
create spec/support/.gitkeep
create db/migrations/.gitkeep
create Rakefile
create spec/spec_helper.rb
create spec/features_helper.rb
create db/schema.sql
create .gitignore
run git init . from "."
create apps/web/application.rb
create apps/web/config/routes.rb
create apps/web/views/application_layout.rb
create apps/web/templates/application.html.erb
create apps/web/assets/favicon.ico
create apps/web/controllers/.gitkeep
create apps/web/assets/images/.gitkeep
create apps/web/assets/javascripts/.gitkeep
create apps/web/assets/stylesheets/.gitkeep
create spec/web/features/.gitkeep
create spec/web/controllers/.gitkeep
create spec/web/views/.gitkeep
insert config/environment.rb
insert config/environment.rb
append .env.development
append .env.test
├── Gemfile ├── Rakefile ├── apps/ ├── config/ ├── config.ru ├── db/ ├── lib/ └── spec
$ bundle install
$ bundle exec hanami server
# spec/web/features/visit_home_spec.rb
require 'features_helper'
describe 'Visit home' do
it 'is successful' do
visit '/'
page.body.must_include('Bookshelf')
end
end
$ bundle exec rake test
Run options: --seed 46642
# Running:
F
Finished in 0.014917s, 67.0398 runs/s, 134.0796 assertions/s.
# apps/web/config/routes.rb
root to: 'home#index'
# apps/web/controllers/home/index.rb
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
end
end
end
# apps/web/views/home/index.rb
module Web::Views::Home
class Index
include Web::View
end
end
# apps/web/templates/home/index.html.erb
<h1>Bookshelf</h1>
# spec/web/features/visit_home_spec.rb
require 'features_helper'
describe 'Visit home' do
it 'is successful' do
visit '/'
page.body.must_include('Bookshelf')
end
end
- Route
- Controller
- View
- Template
$ bundle exec rake test
Run options: --seed 28944
# Running:
.
Finished in 0.010793s, 92.6533 runs/s, 185.3067 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
$ hanami generate model book
create lib/bookshelf/entities/book.rb
create lib/bookshelf/repositories/book_repository.rb
create spec/bookshelf/entities/book_spec.rb
create spec/bookshelf/repositories/book_repository_spec.rb
Domain Logic
Persistance Layer
Entities & Repositories
Entities
"An entity is something really close to a plain Ruby object, it doesn't know anything about our database structure. We should focus on the behaviors that we want from it and only then, how to save it."
# lib/bookshelf/entities/book.rb
class Book
include Hanami::Entity
attributes :title, :author
end
# spec/bookshelf/entities/book_spec.rb
require 'spec_helper'
describe Book do
it 'can be initialised with attributes' do
book = Book.new(title: 'Refactoring')
book.title.must_equal 'Refactoring'
end
end
$ bundle exec rake test
Run options: --seed 3092
# Running:
..
Finished in 0.013320s, 150.1501 runs/s, 225.2251 assertions/s.
2 runs, 3 assertions, 0 failures, 0 errors, 0 skips
$ hanami db create
$ hanami generate migration create_books
# db/migrations/2016..._create_books.rb
Hanami::Model.migration do
change do
create_table :books do
primary_key :id
column :title, String, null: false
column :author, String, null: false
end
end
end
$ hanami db migrate
# lib/bookshelf.rb
# ...
mapping do
collection :books do
entity Book
repository BookRepository
attribute :id, Integer
attribute :title, String
attribute :author, String
end
end
$ hanami console
>> BookRepository.all
=> []
>> book = Book.new(title: 'TDD', author: 'Kent Beck')
=> #<Book:0x007f9af1d4b028 @title="TDD", @author="Kent Beck">
>> BookRepository.create(book)
=> #<Book:0x007f9af1d13ec0 @title="TDD", @author="Kent Beck" @id=1>
>> BookRepository.find(1)
=> #<Book:0x007f9af1d13ec0 @title="TDD", @author="Kent Beck" @id=1>
# apps/web/templates/index.html.erb
<% if books.any? %>
<% books.each do |book| %>
<div class="book">
<h2><%= book.title %></h2>
<p><%= book.author %></p>
</div>
<% end %>
<% else %>
<p class="placeholder">There are no books yet.</p>
<% end %>
# apps/web/controllers/home/index.rb
module Web::Controllers::Home
class Index
include Web::Action
expose :books
def call(params)
@books = BookRepository.all
end
end
end
$ hanami generate action web books#new
# apps/web/config/routes.rb
get '/books/new', to: 'books#new'
# apps/web/templates/books/new.html.erb
<h2>Add book</h2>
<%=
form_for :book, '/books' do
div class: 'input' do
label :title
text_field :title
end
div class: 'input' do
label :author
text_field :author
end
div class: 'controls' do
submit 'Create Book'
end
end
%>
$ hanami generate action web books#create --method=post
# apps/web/config/routes.rb
post '/books', to: 'books#create'
# apps/web/config/routes.rb
post '/books', to: 'books#create'
# apps/web/controllers/books/create.rb
module Web::Controllers::Books
class Create
include Web::Action
expose :book
def call(params)
@book = BookRepository.create(Book.new(params[:book]))
redirect_to '/'
end
end
end
# apps/web/controllers/home/...
# apps/web/views/home/...
# apps/web/templates/home/...
# lib/bookshelf/entities/...
# lib/bookshelf/repositories/...
Full Featured?
- check_box
- color_field
- date_field
- datetime_field
- datetimelocalfield
- email_field
- fields_for
- file_field
- form_for
- hidden_field
- label
- number_field
- password_field
- radio_button
- select
- submit
- text_area
- text_field
- Parameters
- Request & Response
- Exposures
- Rack Integration
- MIME Types
- Cookies
- Sessions
- Exception Handling
- Control Flow
- HTTP Caching
- Share Code
- Testing
- Layouts
- Custom Error Pages
- HTML 5 Helpers
- Routing Helpers
- Number Helpers
- Markup Escape Helpers
- Asset Helpers
- Custom Helpers
- Mailers
- Mailer Tests
- Asset Pre-processing
- Asset Compressing
- CDN Asset Delivery
- CLI
Explicit
over
Implicit
Preference
What Next?
- hanamirb.org
- github.com/hanami
- @hanamirb
- chat.hanamirb.org
Thanks!
A Handy Look at Hanami
By Jason Charnes
A Handy Look at Hanami
- 1,275