花芋入門


こんにちは、Habr 少し前に、私は花芋のフレヌムワヌクに興味を持ち、頭の䞭に萜ち着くために、初心者向けの公匏ガむドを翻蚳し始めたした。 これをコミュニティず共有したいず思いたす。 翻蚳を原䜜に近づけようずしたしたが、私は䜜家ずいうよりは読者であり、翻蚳に぀いおのコメントがあれば、遠慮なく衚珟しおください。


このガむドでは、最初のプロゞェクトを花芋で䜜成し、簡単なWebアプリケヌションを䜜成したす。 フレヌムワヌクのすべおの䞻芁コンポヌネントに觊れ、テストで䜜成されたすべおをカバヌしたす。


花芋ずは


Hanamiは、倚くのマむクロラむブラリで構成されるRuby MVCフレヌムワヌクです。
シンプルで安定したAPI、最小限のDSL、および倚くの責任を持぀魔法のような掗緎されたクラスよりもシンプルなオブゞェクトを䜿甚する蚭定がありたす。


玔粋な責任を持぀単玔なオブゞェクトを䜿甚するコストは、定型的なコヌド以䞊のものです。 Hanamiは、䜎レベルの実装コヌドを提䟛するこずにより、この䜙分な䜜業を削枛する方法を提䟛したす。


なぜ花芋ですか


花芋を遞択する理由は3぀ありたす。


花芋軜量


花芋コヌドは比范的短いです。 圌は、すべおのWebアプリケヌションが必芁ずするものを自分で曞かないように、必芁なものだけに責任がありたす。 Hanamiにはいく぀かのオプションモゞュヌルが付属しおおり、他のラむブラリも簡単に接続できたす。 建築における花芋の意味。


「レヌルりェむ」に飜きたら、花芋に泚意しおください


Hanamiはコントロヌラヌアクションをクラスに基づいおいるため、単独で簡単にテストできたす。
たた、花芋は、むンタラクタヌナヌスケヌスでビゞネスロゞックを蚘述するこずをお勧めしたす。
ビュヌはテンプレヌトずは別であるため、それらのロゞックは単独でテストするこずもできたす。


花芋はスレッドセヌフです


マルチスレッドを䜿甚するず、アプリケヌションのパフォヌマンスを向䞊させるこずができたす。 スレッドセヌフコヌドを蚘述するこずは難しくありたせん。Hanamiアプリケヌション党䜓たたはその䞀郚はスレッドセヌフです。


ガむド


このガむドでは、高床なコンポヌネントず、本栌的なアプリケヌションでそれらを準備する方法構成、䜿甚、テストに぀いお説明したす。 議論される架空のプロゞェクトは「Bookshelf」ず呌ばれたす。人々が本を共有し、本を賌入するオンラむンコミュニティです。


はじめに


䜜成者による開䌚挚拶


こんにちは このペヌゞを読んでいるなら、おそらく花芋に぀いおもっず知りたいず思うでしょう。 おめでずうございたす サポヌトされ、安党で、高速で、テスト可胜なアプリケヌションを構築する新しい方法を探しおいるなら、あなたは良い手にいたす。 花芋はあなたのような人のために䜜られおいたす。


あなたが完党な初心者であるか経隓豊富な開発者であるかに関係なく、あなたに譊告するこずは䟡倀がありたす、 孊習プロセスは難しいでしょう 。 開発に関する特定の芋通しを開発した10幎埌、あなたの習慣のいく぀かを砎るこずは難しいかもしれたせん。 倉化は垞に自分ぞの挑戊です。


䞀郚の機胜は特に健康に芋えないように芋えるこずもありたすが、それは垞にあなたの意芋の問題ではありたせん。 これは、習慣、蚭蚈ミス、たたはバグの問題である可胜性がありたす。 善意のあるコミュニティず私は、毎日花芋の改善に努めおいたす。


このガむドでは、最初のプロゞェクトを花芋で䜜成し、簡単なWebベヌスのアプリケヌション「bookshelf」を䜜成したす。 フレヌムワヌクのすべおの䞻芁コンポヌネントに觊れ、テストで䜜成されたすべおをカバヌしたす。


問題が発生した堎合や混乱した堎合は、 ’めずにチャットルヌムにアクセスしお質問しおください。 い぀でも喜んで手䌝っおくれる人がいたす。


楜しんでください
ルカ・ギディ
花芋クリ゚ヌタヌ


たえがき


始める前に、いく぀かのこずを明確にしたす。 始めるために、Webアプリケヌション開発の基本的な知識が必芁だずしたす。


Bundler 、 Rakeなどのいく぀かのものに粟通しおいる必芁がありたす。タヌミナルで䜜業し、 Model、View、Controllerパタヌンを䜿甚しおアプリケヌションを構築できたす。


チュヌトリアルの埌半では、 SQLite Postgresqlデヌタベヌスを䜿甚したす。
先に進むには、Ruby 2.3以䞊の䜜業バヌゞョンずSQLite 3+が必芁です。


新しい花芋プロゞェクトを䜜成したしょう


Hanamiでプロゞェクトを䜜成するには、たずRubygemsを介しおHanami gemをむンストヌルする必芁がありたす。
次に、 hanami new consoleコマンドを䜿甚しお、新しいプロゞェクトを生成できたす。


 % gem install hanami % hanami new bookshelf 

デフォルトでは、プロゞェクトはSQLiteを䜿甚するように構成されたす。 実際のプロゞェクトでは、たずえばPostgresなどの別のデヌタベヌス゚ンゞンを指定できたす。


% hanami new bookshelf --database=postgres


これにより、コマンドが実行されたフォルダに新しいbookshelfフォルダが䜜成されたす。 それが䜕を含むか芋おください


 % cd bookshelf % tree -L 1 . ├── Gemfile ├── Rakefile ├── apps ├── config ├── config.ru ├── db ├── lib ├── public └── spec 6 directories, 3 files 

少なくずもそれに぀いお知っおおくべきこずは以䞋のずおりです。



Bundlerを䜿甚しおGemfileで指定された䟝存関係に進み、むンストヌルしたしょう。 次に、サヌバヌを開発モヌドで起動したす。


 % bundle install % bundle exec hanami server 

そしお...で最初の花芋プロゞェクトに䌚いたしょう http// localhost2300  ブラりザに次のようなものが衚瀺されるはずです。



花芋建築


Hanamiアヌキテクチャにより、単䞀のRubyプロセスに耇数のHanamiおよびRackアプリケヌションを含めるこずができたす。


これらのアプリケヌションはapps/ディレクトリにありたす。 これらはそれぞれ、ナヌザヌむンタヌフェむス、コントロヌルパネル、ダッシュボヌド、たたはHTTP APIなどの補品のコンポヌネントになりたす。


これらはすべお、 lib/フォルダヌにあるビゞネスロゞック配信メカニズムの䞀郚です。 これは、ドメむンモデルが蚘述されおいる堎所、盞互の盞互䜜甚であり、補品によっお提䟛される機胜を構成しおいたす 。


花芋建築は、ボブおじさんの玔粋建築のアむデアに匷く圱響されたした。


最初のテストを曞く


ブラりザで芋たアプリケヌションの開始画面は、単䞀のルヌトを決定するたでデフォルトで衚瀺されるペヌゞです。


Hanamiは、Webアプリケヌションを開発する方法ずしお、 行動駆動型開発 BDDを掚奚しおいたす。 最初のペヌゞを䜜成する前に、最初にその操䜜を説明する高レベルのテストを䜜成したす。


 # 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 

デフォルトのHanamiはBehavior-Based DevelopmentBDDをサポヌトしおいるずいう事実にもかかわらず、 特別なテストフレヌムワヌクを䜿甚する必芁はありたせん。特別な統合やラむブラリも必芁ありたせん。


Minitest デフォルトから始めたすが、オプション--test=rspec.プロゞェクトを䜜成する堎合は、 RSpecを䜿甚するこずもできたす--test=rspec.
その埌、Hanamiはヘルパヌずファむルテンプレヌトを生成したす。


芁件を満たしたす


すでにテストがあり、どのように萜ちるかを芋るこずができたす。


 % rake test Run options: --seed 44759 # Running: F Finished in 0.018611s, 53.7305 runs/s, 53.7305 assertions/s. 1) Failure: Homepage#test_0001_is successful [/Users/hanami/bookshelf/spec/web/features/visit_home_spec.rb:6]: Expected "<!DOCTYPE html>\n<html>\n <head>\n <title>Not Found</title>\n </head>\n <body>\n <h1>Not Found</h1>\n </body>\n</html>\n" to include "Bookshelf". 1 runs, 1 assertions, 1 failures, 0 errors, 0 skips 

合栌する時が来たした。 テストに合栌するために必芁な数行のコヌドを段階的に䜜成したす。


最初に远加する必芁があるのはルヌトです


 # apps/web/config/routes.rb root to: 'home#index' 

アプリケヌションのルヌトURLをhomeコントロヌラヌのアクションindexリダむレクトしたす詳现に぀いおは、 ルヌティングガむドを参照しおください。 これで、アクションindex䜜成できたす。


 # apps/web/controllers/home/index.rb module Web::Controllers::Home class Index include Web::Action def call(params) end end end 

これは空のアクションゲヌムであり、ロゞックは含たれおいたせん。 各アクションには察応するビュヌがありたす。ビュヌは、アクションに到着したリク゚ストぞの応答ずしお返される必芁があるRubyオブゞェクトを衚したす。


 # apps/web/views/home/index.rb module Web::Views::Home class Index include Web::View end end 

...これもたた空であり、テンプレヌトをレンダリングするだけです。 デフォルトでは、これはerbファむルですが、 slimたたはhaml䜿甚を犁止する人はいたせん。 テストに合栌するには、修正する必芁がありたす。 必芁なのは、本棚のタむトルを远加するこずだけです。


 # apps/web/templates/home/index.html.erb <h1>Bookshelf</h1> 

倉曎を保存し、テストを再床実行するず、この時間は過ぎたす。 いいね


 Run options: --seed 19286 # Running: . Finished in 0.011854s, 84.3600 runs/s, 168.7200 assertions/s. 1 runs, 2 assertions, 0 failures, 0 errors, 0 skips 

新しいアクションを生成したす


花芋の䞻芁なコンポヌネントに぀いお孊んだこずを掻甚しお、新しいアクションを远加したしょう。 Bookshelfプロゞェクトは、曞籍の䌚蚈を目的ずしおいたす。


曞籍に関する情報をデヌタベヌスに保存し、ナヌザヌにそれを管理する機䌚を䞎えたす。 最初のステップは、システムに保存されおいるすべおの曞籍のリストを衚瀺するこずです。


機胜テストの助けを借りお、私たちが目指しおいる機胜を説明したす。


 # spec/web/features/list_books_spec.rb require 'features_helper' describe 'List books' do it 'displays each book on the page' do visit '/books' within '#books' do assert page.has_css?('.book', count: 2), 'Expected to find 2 books' end end end 

テストは非垞に単玔で、クラッシュしたす。これは、アドレス/booksがアプリケヌションによっおただ認識されおいないためです。 これを修正するアクションコントロヌラヌを䜜成したす。


花芋発電機


Hanamiには、新しい機胜を構成する定型コヌドを枛らすためのゞェネレヌタヌがいく぀か付属しおいたす。
タヌミナルに入力


 % bundle exec hanami generate action web books#index 

このコマンドは、 Webアプリケヌションのブックコントロヌラヌで新しいアクションむンデックスを生成する必芁がありたす。
これにより、空のアクション、ビュヌ、テンプレヌトが提䟛され、 apps/web/config/routes.rbぞのルヌトも远加されapps/web/config/routes.rb 。


 get '/books', to: 'books#index' 

ZSHでは、゚ラヌzsh: no matches found: books#indexを取埗できzsh: no matches found: books#index 。 この堎合、別の構文を詊しおください。


 % hanami generate action web books/index 

ここで、テストに合栌するには、ファむルapps/web/templates/books/index.html.erbを次のようにするだけです。


 <h1>Bookshelf</h1> <h2>All books</h2> <div id="books"> <div class="book"> <h3>Patterns of Enterprise Application Architecture</h3> <p>by <strong>Martin Fowler</strong></p> </div> <div class="book"> <h3>Test Driven Development</h3> <p>by <strong>Kent Beck</strong></p> </div> </div> 

倉曎を保存し、テストに合栌するこずを確認しおください


コントロヌラヌずアクションの甚語は混乱を招く可胜性があるため、明確にする䟡倀がありたす。アクションはHanamiアプリケヌションの基瀎を構成したす。 コントロヌラは、いく぀かのアクションを組み合わせた単なるモゞュヌルです。
䞀般に、アプリケヌションに「コントロヌラヌ」が抂念的に存圚しおいるにもかかわらず、実際にはアクションゲヌムのみを䜿甚したす。


ゞェネレヌタを䜿甚しお、アプリケヌションぞの新しい゚ントリポむントを䜜成したした。 ただし、新しいテンプレヌトにはhome/index.html.erbず同じ<h1>が含たれおいるずいう事実に泚意する䟡倀がありたす。 それを修正したしょう。


レむアりト


パタヌンごずに同じ行を繰り返すこずを避けるために、レむアりトを䜿甚できたす。 ファむルapps/web/templates/application.html.erbを開き、次のようにしたす。


 <!DOCTYPE HTML> <html> <head> <title>Bookshelf</title> </head> <body> <h1>Bookshelf</h1> <%= yield %> </body> </html> 

これで、他のテンプレヌトから重耇した行を削陀できたす。


レむアりトは他のテンプレヌトず䌌おいたすが、暙準テンプレヌトをラップするために䜿甚されたす。 yieldキヌワヌドは、通垞のテンプレヌトのコンテンツに眮き換えられたす。 これは、ヘッダヌ、フッタヌ、メニュヌなどのアむテムを繰り返すのに最適な堎所です。


デヌタベヌススキヌマを倉曎する移行


正盎に蚀うず、テンプレヌト内のハヌドワむダヌドブックは䞍正行為です。 ラむブデヌタをアプリケヌションに远加したす。


たず、本に関するデヌタを保存するためのデヌタベヌス内のテヌブルが必芁です。 移行を䜿甚しお䜜成できたす。 ゞェネレヌタヌを䜿甚しお空の移行を䜜成したす。


 % bundle exec hanami generate migration create_books 

これにより、 db/migrations/20161115110038_create_books.rbような名前のファむルがdb/migrations/20161115110038_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 column :created_at, DateTime, null: false column :updated_at, DateTime, null: false end end end 

Hanamiは、デヌタベヌススキヌマの倉曎を蚘述するための特別な蚀語を提䟛したす。 䜿甚方法に぀いおは、移行ガむドをご芧ください 。


この堎合、゚ンティティの各属性の列を持぀新しいテヌブルを定矩したす。
デヌタベヌスを準備したしょう


 % bundle exec hanami db prepare 

デヌタを゚ンティティずしお衚珟する


デヌタベヌスに曞籍を保存し、ペヌゞに衚瀺したす。 そのためには、デヌタベヌスを読み曞きする方法が必芁です。 ゚ンティティずリポゞトリの玹介



゚ンティティは完党にデヌタベヌスに䟝存したせん。 これにより、テストが簡単になりたす 。


このため、 Book䟝存するデヌタを保存するリポゞトリが必芁です。
゚ンティティずリポゞトリの詳现に぀いおは、モデルガむドをご芧ください。


Hanamiはモデルのゞェネレヌタヌを提䟛しおいるので、 Book゚ンティティず察応するリポゞトリを䜜成したしょう。


 % bundle exec 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 

ゞェネレヌタヌは、゚ンティティ、リポゞトリ、およびテスト甚の関連ファむルを提䟛したす。


゚ンティティず連携したす


゚ンティティは基本的に単玔なRubyオブゞェクトに近いものです。 私たちは圌らに求めおいる行動に焊点を合わせなければならず、それからそれらを保存する方法にのみ焊点を合わせたす。


珟圚、単玔な゚ンティティクラスが必芁です。


 # lib/bookshelf/entities/book.rb class Book < Hanami::Entity 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 

リポゞトリの䜿甚


これで、リポゞトリを詊す準備が敎いたした。 Hanami consoleコマンドを䜿甚しお、アプリケヌションのコンテキストでIRbを起動し、既存のオブゞェクトを䜿甚できるようにしたす。


 % bundle exec hanami console >> repository = BookRepository.new => => #<BookRepository:0x007f9ab61fbb40 ...> >> repository.all => [] >> book = repository.create(title: 'TDD', author: 'Kent Beck') => #<Book:0x007f9ab61c23b8 @attributes={:id=>1, :title=>"TDD", :author=>"Kent Beck", :created_at=>2016-11-15 11:11:38 UTC, :updated_at=>2016-11-15 11:11:38 UTC}> >> repository.find(book.id) => #<Book:0x007f9ab6181610 @attributes={:id=>1, :title=>"TDD", :author=>"Kent Beck", :created_at=>2016-11-15 11:11:38 UTC, :updated_at=>2016-11-15 11:11:38 UTC}> 

Hanamiリポゞトリには、デヌタベヌスから1぀たたは耇数の゚ンティティの䞡方をロヌドするためのメ゜ッドがありたす。 既存のものを䜜成および曎新するこずもできたす。 リポゞトリ内の独自のク゚リに新しいメ゜ッドを定矩するこずもできたす。


その結果、Hanamiが゚ンティティずリポゞトリを䜿甚しおデヌタをモデル化する方法を芋たした。 ゚ンティティは動䜜を反映し、リポゞトリぱンティティずデヌタストアの通信に䜿甚されたす。 移行を䜿甚しお、デヌタベヌススキヌマに倉曎を適甚できたす。


動的デヌタを衚瀺


新しいデヌタモデリング゚クスペリ゚ンスを䜿甚しお、ブックリストペヌゞに倉化するデヌタを衚瀺できたす。 これに぀いおは、以前に䜜成したテストを適甚したす。


 # spec/web/features/list_books_spec.rb require 'features_helper' describe 'List books' do let(:repository) { BookRepository.new } before do repository.clear repository.create(title: 'PoEAA', author: 'Martin Fowler') repository.create(title: 'TDD', author: 'Kent Beck') end it 'displays each book on the page' do visit '/books' within '#books' do assert page.has_css?('.book', count: 2), 'Expected to find 2 books' end end end 

テストで必芁な゚ントリを䜜成し、ペヌゞ䞊の曞籍クラスの数がそれらに察応しおいるず述べたした。 テストを再床実行するず、デヌタベヌスに関連する゚ラヌが衚瀺される可胜性が高くなりたす。 開発デヌタベヌスは既に移行枈みですが、ただテストデヌタベヌスは移行しおいないこずに泚意しおください。


 % HANAMI_ENV=test bundle exec hanami db prepare 

これで、テンプレヌトを倉曎しお静的HTMLを削陀できたす。 ビュヌは、䜿甚可胜なすべおのレコヌドを調べお衚瀺する必芁がありたす。 フォヌムにこのような倉曎を必芁ずするテストを䜜成したす。


 # spec/web/views/books/index_spec.rb require 'spec_helper' require_relative '../../../../apps/web/views/books/index' describe Web::Views::Books::Index do let(:exposures) { Hash[books: []] } let(:template) { Hanami::View::Template.new('apps/web/templates/books/index.html.erb') } let(:view) { Web::Views::Books::Index.new(template, exposures) } let(:rendered) { view.render } it 'exposes #books' do view.books.must_equal exposures.fetch(:books) end describe 'when there are no books' do it 'shows a placeholder message' do rendered.must_include('<p class="placeholder">There are no books yet.</p>') end end describe 'when there are books' do let(:book1) { Book.new(title: 'Refactoring', author: 'Martin Fowler') } let(:book2) { Book.new(title: 'Domain Driven Design', author: 'Eric Evans') } let(:exposures) { Hash[books: [book1, book2]] } it 'lists them all' do rendered.scan(/class="book"/).count.must_equal 2 rendered.must_include('Refactoring') rendered.must_include('Domain Driven Design') end it 'hides the placeholder message' do rendered.wont_include('<p class="placeholder">There are no books yet.</p>') end end end 

曞籍がなく、衚瀺するものがない堎合、むンデックスペヌゞにメッセヌゞが衚瀺されるこずを瀺したした。 本がある堎合、それらのリストが衚瀺されたす。 デヌタビュヌのレンダリングは比范的簡単です。 Hanamiは、最小限のむンタヌフェむスを持぀シンプルなオブゞェクトから蚭蚈されおいるため、個々に簡単にテストでき、連携しお機胜したす。


蚈画を実装するためにテンプレヌトを曞き盎したしょう


 # apps/web/templates/books/index.html.erb <h2>All books</h2> <% if books.any? %> <div id="books"> <% books.each do |book| %> <div class="book"> <h2><%= book.title %></h2> <p><%= book.author %></p> </div> <% end %> </div> <% else %> <p class="placeholder">There are no books yet.</p> <% end %> 

機胜テストを再床実行するず、コントロヌラヌのアクションずしお倱敗したす
私たちの芋解のためにただ本を公開しおいたせん。 この堎合のテストを曞くこずができたす


 # spec/web/controllers/books/index_spec.rb require 'spec_helper' require_relative '../../../../apps/web/controllers/books/index' describe Web::Controllers::Books::Index do let(:action) { Web::Controllers::Books::Index.new } let(:params) { Hash[] } let(:repository) { BookRepository.new } before do repository.clear @book = repository.create(title: 'TDD', author: 'Kent Beck') end it 'is successful' do response = action.call(params) response[0].must_equal 200 end it 'exposes all books' do action.call(params) action.exposures[:books].must_equal [@book] end end 

通垞、アクションのテストの䜜成には2぀の偎面がありたす。応答オブゞェクトに関するステヌトメントを䜜成したす。応答オブゞェクトは、ステヌタス、ヘッダヌ、およびコンテンツのRack互換配列です。 たたは、呌び出し埌のアクションからデヌタが芋えるずいう事実に぀いお。 これで、アクションが倉数variable :booksを衚瀺するこずを瀺したした。


 # apps/web/controllers/books/index.rb module Web::Controllers::Books class Index include Web::Action expose :books def call(params) @books = BookRepository.new.all end end end 

exposeアクションクラスメ゜ッドを䜿甚しお、 @booksむンスタンス@books内容を誇瀺しお、ビュヌで䜿甚できるようにしたす。 これでテストに再床合栌できたす


 % bundle exec rake Run options: --seed 59133 # Running: ......... Finished in 0.042065s, 213.9543 runs/s, 380.3633 assertions/s. 6 runs, 7 assertions, 0 failures, 0 errors, 0 skips 

レコヌドフォヌムの䜜成


システムに新しい本を远加する機胜を䜜成するだけです。 蚈画は簡単です。詳现を入力できるフォヌムを含むペヌゞを䜜成したす。


ナヌザヌがフォヌムを送信するず、新しい゚ンティティを䜜成しお保存し、ナヌザヌを曞籍のリストのあるペヌゞにリダむレクトしたす。 クむズでストヌリヌを衚珟しおください


 # spec/web/features/add_book_spec.rb require 'features_helper' describe 'Add a book' do after do BookRepository.new.clear end it 'can create a new book' do visit '/books/new' within 'form#book-form' do fill_in 'Title', with: 'New book' fill_in 'Author', with: 'Some author' click_button 'Create' end current_path.must_equal('/books') assert page.has_content?('New book') end end 

フォヌムの基瀎を敷く


この時点で、アクション、ビュヌ、テンプレヌトがどのように機胜するかを認識する必芁がありたす。


プロセスを少しスピヌドアップし、すぐに興味深い郚分に進みたす。 最初に、「 New Book 」ペヌゞの新しいアクションを䜜成したす。


 % bundle exec hanami generate action web books#new 

これにより、アプリケヌションに新しいルヌトが远加されたす。


 # apps/web/config/routes.rb get '/books/new', to: 'books#new' 

次の興味深い点はテンプレヌトに関連しおいたす。これは、 Book゚ンティティのHTMLフォヌムを生成するためにHanamiフォヌムビルダヌを䜿甚するためです。


フォヌムヘルパヌの䜿甚


フォヌムのヘルパヌを䜿甚しお、1぀のapps/web/templates/books/new.html.erbを䜜成しapps/web/templates/books/new.html.erb 。


 # 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 %> 

フォヌムの各フィヌルドに<label>タグを远加し、各フィヌルドをラップしたす
Hanami HTMLヘルパヌを䜿甚する<div>コンテナ。


フォヌムの提出


フォヌムを送信するには、別のアクションが必芁です。 アクションBooks::Createたしょう


 % bundle exec hanami generate action web books#create --method=post 

これにより、アプリケヌションに新しいルヌトが远加されたす。


 # apps/web/config/routes.rb post '/books', to: 'books#create' 

アクションを䜜成する


createアクションには2぀のこずが必芁です。 ナニットテストでそれらを衚珟したしょう


 # spec/web/controllers/books/create_spec.rb require 'spec_helper' require_relative '../../../../apps/web/controllers/books/create' describe Web::Controllers::Books::Create do let(:action) { Web::Controllers::Books::Create.new } let(:params) { Hash[book: { title: 'Confident Ruby', author: 'Avdi Grimm' }] } before do BookRepository.new.clear end it 'creates a new book' do action.call(params) action.book.id.wont_be_nil action.book.title.must_equal params[:book][:title] end it 'redirects the user to the books listing' do response = action.call(params) response[0].must_equal 302 response[1]['Location'].must_equal '/books' end end 

それらを十分に簡単にしたす。 ゚ンティティをデヌタベヌスに曞き蟌む方法はすでに確認しおおり、 redirect_toを䜿甚しおredirect_toを実装できたす。


 # apps/web/controllers/books/create.rb module Web::Controllers::Books class Create include Web::Action expose :book def call(params) @book = BookRepository.new.create(params[:book]) redirect_to '/books' end end end 

この最小限の実装は、テストを再び成功させるのに十分なはずです。


 % bundle exec rake Run options: --seed 63592 # Running: ............... Finished in 0.081961s, 183.0142 runs/s, 305.0236 assertions/s. 12 runs, 14 assertions, 0 failures, 0 errors, 2 skips 

䜕ずおめでずうございたす


怜蚌でフォヌムを保護する


銬を保持したす 愚か者に察する防埡を行うには少し忍耐が必芁です。 誰かがフィヌルドに入力せずにフォヌムを送信するずどうなりたすか


デヌタベヌスに誀ったデヌタを入力するか、デヌタ敎合性違反に関する゚ラヌを確認できたす。 無効なデヌタをシステムから遠ざける方法が絶察に必芁です


テストでチェックを衚珟するには、テストが倱敗した堎合はどうすればよいのかを考える必芁がありたす。 bookshelf#newフォヌムに再レンダリングするこずをおbookshelf#newたす。そのため、ナヌザヌにもう䞀床蚘入する機䌚を䞎えたす。 この動䜜を単䜓テストで説明したす。


 # spec/web/controllers/books/create_spec.rb require 'spec_helper' require_relative '../../../../apps/web/controllers/books/create' describe Web::Controllers::Books::Create do let(:action) { Web::Controllers::Books::Create.new } after do BookRepository.new.clear end describe 'with valid params' do let(:params) { Hash[book: { title: '1984', author: 'George Orwell' }] } it 'creates a new book' do action.call(params) action.book.id.wont_be_nil end it 'redirects the user to the books listing' do response = action.call(params) response[0].must_equal 302 response[1]['Location'].must_equal '/books' end end describe 'with invalid params' do let(:params) { Hash[book: {}] } it 're-renders the books#new view' do response = action.call(params) response[0].must_equal 422 end it 'sets errors attribute accordingly' do response = action.call(params) response[0].must_equal 422 action.params.errors[:book][:title].must_equal ['is missing'] action.params.errors[:book][:author].must_equal ['is missing'] end end end 

ここで、テストでは2぀の代替シナリオを説明したす。元の成功したパスず、テストが倱敗する新しいシナリオです。 テストを修正するために、怜蚌を行いたす。


もちろん、すべおの怜蚌ルヌルを本質的に眮くこずができたす。花芋では、ナヌザヌ入力の゜ヌスに少し近い、぀たりアクションで盎接怜蚌ルヌルを定矩するこずもできたす。 Hanamiアクションは、 paramsクラスメ゜ッドを䜿甚しお有効なパラメヌタヌ倀を決定できたす。


このアプロヌチにより、同時にパラメヌタヌのホワむトリストを蚭定し信頌されおいないナヌザヌ入力に察する脆匱性を防ぐために残りは無芖されたす 、受け入れられる倀を指定できたす。


, , ,


 # apps/web/controllers/books/create.rb module Web::Controllers::Books class Create include Web::Action expose :book params do required(:book).schema do required(:title).filled(:str?) required(:author).filled(:str?) end end def call(params) if params.valid? @book = BookRepository.new.create(params[:book]) redirect_to '/books' else self.status = 422 end end end end 

, URL. , ?


HTTP
422 ( ) .
, , . apps/web/templates/books/new.html.erb .


 # apps/web/views/books/create.rb module Web::Views::Books class Create include Web::View template 'books/new' end end 

, Hanami , params , . , , , .


.



, - , , . .


, , params :


 # spec/web/views/books/new_spec.rb require 'spec_helper' require_relative '../../../../apps/web/views/books/new' class NewBookParams < Hanami::Action::Params params do required(:book).schema do required(:title).filled(:str?) required(:author).filled(:str?) end end end describe Web::Views::Books::New do let(:params) { NewBookParams.new(book: {}) } let(:exposures) { Hash[params: params] } let(:template) { Hanami::View::Template.new('apps/web/templates/books/new.html.erb') } let(:view) { Web::Views::Books::New.new(template, exposures) } let(:rendered) { view.render } it 'displays list of errors when params contains errors' do params.valid? # trigger validations rendered.must_include('There was a problem with your submission') rendered.must_include('Title is missing') rendered.must_include('Author is missing') end end 

:


 # spec/web/features/add_book_spec.rb require 'features_helper' describe 'Add a book' do # Spec written earlier omitted for brevity it 'displays list of errors when params contains errors' do visit '/books/new' within 'form#book-form' do click_button 'Create' end current_path.must_equal('/books') assert page.has_content?('There was a problem with your submission') assert page.has_content?('Title must be filled') assert page.has_content?('Author must be filled') end end 

params.errors ( ) .
apps/web/templates/books/new.html.erb :


 <% unless params.valid? %> <div class="errors"> <h3>There was a problem with your submission</h3> <ul> <% params.error_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> 

, "is required", , . .


 % bundle exec rake Run options: --seed 59940 # Running: .................. Finished in 0.078112s, 230.4372 runs/s, 473.6765 assertions/s. 15 runs, 27 assertions, 0 failures, 0 errors, 1 skips 


, . "web":


 # apps/web/config/routes.rb post '/books', to: 'books#create' get '/books/new', to: 'books#new' get '/books', to: 'books#index' root to: 'home#index' 

Hanami REST- , :


 resources :books, only: [:index, :new, :create] root to: 'home#index' 

, , , ,
routes :


 % bundle exec hanami routes Name Method Path Action books GET, HEAD /books Web::Controllers::Books::Index new_book GET, HEAD /books/new Web::Controllers::Books::New books POST /books Web::Controllers::Books::Create root GET, HEAD / Web::Controllers::Home::Index 

hanami routes ( _path _url routes ), HTTP , .


, resources , . , form_for ?


 <%= form_for :book, '/books' do # ... end %> 

, , , . routes , , :


 <%= form_for :book, routes.books_path do # ... end %> 

apps/web/controllers/books/create.rb :


 redirect_to routes.books_path 

芁玄する


Hanami !


, : Hanami, , ; ; , .


, . , the Hanami API , .


PS Translation Gang , , GeorgeGorbanev , .



Source: https://habr.com/ru/post/J336814/


All Articles