花見入門



こんにちは、Habr! 少し前に、私は花見のフレームワークに興味を持ち、頭の中に落ち着くために、初心者向けの公式ガイドを翻訳し始めました。 これをコミュニティと共有したいと思います。 翻訳を原作に近づけようとしましたが、私は作家というよりは読者であり、翻訳についてのコメントがあれば、遠慮なく表現してください。







このガイドでは、最初のプロジェクトを花見で作成し、簡単なWebアプリケーションを作成します。 フレームワークのすべての主要コンポーネントに触れ、テストで作成されたすべてをカバーします。







花見とは?



Hanamiは、多くのマイクロライブラリで構成されるRuby MVCフレームワークです。

シンプルで安定したAPI、最小限のDSL、および多くの責任を持つ魔法のような洗練されたクラスよりもシンプルなオブジェクトを使用する設定があります。







純粋な責任を持つ単純なオブジェクトを使用するコストは、定型的なコード以上のものです。 Hanamiは、低レベルの実装コードを提供することにより、この余分な作業を削減する方法を提供します。







なぜ花見ですか?



花見を選択する理由は3つあります。







花見軽量



花見コードは比較的短いです。 彼は、すべてのWebアプリケーションが必要とするものを自分で書かないように、必要なものだけに責任があります。 Hanamiにはいくつかのオプションモジュールが付属しており、他のライブラリも簡単に接続できます。 建築における花見の意味。







「レールウェイ」に飽きたら、花見に注意してください



Hanamiはコントローラーアクションをクラスに基づいているため、単独で簡単にテストできます。

また、花見は、インタラクター(ユースケース)でビジネスロジックを記述することをお勧めします。

ビューはテンプレートとは別であるため、それらのロジックは単独でテストすることもできます。







花見はスレッドセーフです



マルチスレッドを使用すると、アプリケーションのパフォーマンスを向上させることができます。 スレッドセーフコードを記述することは難しくありません。Hanami(アプリケーション全体またはその一部)はスレッドセーフです。







ガイド



このガイドでは、高度なコンポーネントと、本格的なアプリケーションでそれらを準備する方法(構成、使用、テスト)について説明します。 議論される架空のプロジェクトは「Bookshelf」と呼ばれます。人々が本を共有し、本を購入するオンラインコミュニティです。







はじめに



作成者による開会挨拶



こんにちは このページを読んでいるなら、おそらく花見についてもっと知りたいと思うでしょう。 おめでとうございます! サポートされ、安全で、高速で、テスト可能なアプリケーションを構築する新しい方法を探しているなら、あなたは良い手にいます。 花見はあなたのような人のために作られています。







あなたが完全な初心者であるか経験豊富な開発者であるかに関係なく、あなたに警告することは価値があります、 学習プロセスは難しいでしょう 。 開発に関する特定の見通しを開発した10年後、あなたの習慣のいくつかを破ることは難しいかもしれません。 変化は常に自分への挑戦です。







一部の機能は特に健康に見えないように見えることもありますが、それは常にあなたの意見の問題ではありません。 これは、習慣、設計ミス、またはバグの問題である可能性があります。 善意のあるコミュニティと私は、毎日花見の改善に努めています。







このガイドでは、最初のプロジェクトを花見で作成し、簡単なWebベースのアプリケーション「bookshelf」を作成します。 フレームワークのすべての主要コンポーネントに触れ、テストで作成されたすべてをカバーします。







問題が発生した場合や混乱した場合は、 ’めずにチャットルームにアクセスして質問してください。 いつでも喜んで手伝ってくれる人がいます。







楽しんでください

ルカ・ギディ

花見クリエーター







まえがき



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







BundlerRakeなどのいくつかのものに精通している必要があります。ターミナルで作業し、 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 , .








All Articles