About Rails at Cookpad Summer Intern 2016 day1

About Rails at Cookpad Summer Intern 2016 day1

70e13d9877054026fda46d5a5b53a236?s=128

MOROHASHI Kyosuke

August 12, 2016
Tweet

Transcript

  1. 4.

    

  2. 11.

    $ telnet cookpad.com 80 Trying 54.178.224.205... Connected to cookpad.com. Escape

    character is '^]'. (下記から) GET / HTTP/1.1 Host: cookpad.com (空行) (すごいことになる) )551Λ஻ͬͯΈΑ͏ 
  3. 13.

    ‣ HTTP/1.1 ‣ ௨৴͢ΔHTTPͷόʔδϣϯΛද͢ɻ޿͘࢖ΘΕ͍ͯΔͷ͸1.1 ‣ ݱ୅తͳ໰୊Λղܾ͢ΔͨΊHTTP/2΋ग़͖ͯͨ ‣ GET / HTTP/1.1

    ͷߦΛʮϦΫΤετϥΠϯʯͱݺͿ ‣ Host: cookpad.com ‣ 1αʔόͰෳ਺αʔϏεΛӡ༻Ͱ͖ΔͨΊɺ࿦ཧతʹͲͷαʔϏε΁ͷϦΫΤετͰ͋Δ͔Λࣔ͢ඞཁ͕ ͋Δ ‣ ۭߦ ‣ ϦΫΤετ͕ऴΘͬͨ΋ͷͱͯ͠αʔόͷॲཧ͕࢝·Δ ͦͷଞͷՕॴ 
  4. 20.

    Controller routing View Model request response DBMS   ‣

    Model: Ϟσϧ ‣ ΞϓϦέʔγϣϯͷॲཧͷຊମ ‣ σʔλΛಡΈॻ͖͠ɺॲཧΛ࣮ߦ͢Δ ‣ View: Ϗϡʔ ‣ Ϟσϧͷॲཧ݁ՌΛɺϢʔβΤʔδΣϯτ޲͚ʹඳը͢Δ ‣ Controller: ίϯτϩʔϥ ‣ ϦΫΤετΛड͚෇͚ͯΞΫγϣϯΛݺͼग़͢(ϧʔςΟϯά) ‣ ϞσϧͷॲཧΛݺͼग़͢ ‣ ద੾ͳϏϡʔΛϨϯμϦϯά͢Δ
  5. 21.

    Controller routing View Model request response DBMS   ‣

    ϑϨʔϜϫʔΫ: ͦΕҎ֎ͷશ෦ ‣ HTTPϦΫΤετΛղऍ͠ɺRubyΦϒδΣΫτʹͨ͠͏͑ͰϧʔςΟϯάॲཧΛݺͼग़͢ ‣ ϧʔςΟϯά͕ղܾͨ͠ΞΫγϣϯΛ࣋ͭίϯτϩʔϥΠϯελϯεΛॳظԽ͢Δ ‣ DB΁ͷ઀ଓΛཱ֬͠ɺదٓϓʔϧ͢Δ ‣ DB಺ͷϨίʔυΛRubyΦϒδΣΫτʹϚοϐϯά͠ɺಡΈॻ͖͠΍͍͢ΠϯλʔϑΣʔεΛఏڙ͢Δ ‣ ֎෦DSLͰॻ͔ΕͨϏϡʔΛςϯϓϨʔτΤϯδϯͰॲཧͰ͖ΔΑ͏ʹڮ౉͢ ‣ ϨϯμϦϯά͞Εͨॲཧ݁ՌΛHTTPϨεϙϯεͱͯ͠ϢʔβΤʔδΣϯτʹฦ͢ ‣ جຊతͳηΩϡϦςΟػߏΛఏڙ͢Δ (CSRFɺXSSɺSQLi) ‣ ....
  6. 25.

    ‣ ඇಉظॲཧɺόονॲཧ ‣ JavaScript͘Θ͘͠ ‣ CSS͘Θ͘͠ ‣ RDBMSҎ֎ͷσʔλετΞ ‣ WebΞϓϦΛಈ͔͢Πϯϑϥ


    (Ϋϥ΢υɺεέʔϧΞ΢τɺίϯςφԽ) ‣ heroku ࢖͏͚Ͳਂ͘͸ผ్ ͜ͷηογϣϯͰ΍Βͳ͍͜ͱ ཁࣗशPS͋ͱͰ࣭໰ͯ͠ 
  7. 28.

    $ mkdir ~/work $ cd ~/work $ gem install rails

    $ rails new --skip-spring --skip-turbolinks --skip-test --database postgresql <your_app> $ cd <your_app> $ ls -F Gemfile Gemfile.lock README.md Rakefile app/ bin/ config/ config.ru db/ lib/ log/ public/ tmp/ vendor/ $ git init $ git add . $ git commit -m 'rails new ...' 3BJMTΞϓϦҰ൪͸͡Ί 
  8. 29.

    $ vim Gemfile ``` --- a/Gemfile +++ b/Gemfile @@ -41,3

    +41,7 @@ end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +group :test, :development do + gem 'rspec-rails', '>= 3.5.1' + gem 'capybara' + gem 'factory_girl_rails' +end ``` $ bundle install "5%% "DDFQUBODF5FTU%SJWFO%FWFMPQNFOU ͷ४උ 
  9. 30.

    --- a/Gemfile +++ b/Gemfile @@ -45,5 +45,7 @@ end gem

    'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] group :test do + gem 'capybara' + gem 'factory_girl_rails' gem 'rspec-rails', '>= 3.5.0' end "5%% "DDFQUBODF5FTU%SJWFO%FWFMPQNFOU ͷ४උ ‣ 3BJMTฤͰ΋5%%Λ͢Δɻͨͩ͠ɺτοϓμ΢ϯͷ5%% "5%%  ‣ GFBUVSFTQFDΛॻͨ͘Ίʹ$BQZCBSB ‣ ςετσʔληοτΞοϓΛॻ͖΍͘͢͢ΔͨΊʹGBDUPSZ@HJSMΛೖΕΔ 
  10. 32.

    $ vim Gemfile ``` --- a/Gemfile +++ b/Gemfile @@ -28,6

    +28,8 @@ gem 'jbuilder', '~> 2.5' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +gem 'haml-rails' + group :development, :test do ``` $ bundle install *OTUBMM)BNM 
  11. 33.

    $ rails generate haml:application_layout convert $ git rm app/views/layouts/application.html.erb $

    git add app/views/layouts/application.html.haml $ git diff --cached ``` ... 略... diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml new file mode 100644 index 0000000..da94b80 --- /dev/null +++ b/app/views/layouts/application.html.haml @@ -0,0 +1,10 @@ +!!! +%html + %head + %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ + %title Utteiku + = csrf_meta_tags + = stylesheet_link_tag 'application', media: 'all' + = javascript_include_tag 'application' + %body + = yield ``` &3#ͱ)BNMͷҧ͍ 
  12. 34.

    

  13. 35.

    --- /dev/null +++ b/spec/features/show_items_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper'

    + +RSpec.feature "See shop items", type: :feature do + scenario 'A user can see vairous items' do + visit '/items' + + expect(page).to have_text('包丁') + expect(page).to have_text('フライパン') + end +end ঎඼ΛҰཡ͢Δ࠷௿ݶͷεςοϓ 
  14. 37.

    $ rails db:create $ bundle exec rspec ./spec/features/show_items_spec.rb F Failures:

    1) See shop items A user can see vairous items Failure/Error: visit '/items' ActionController::RoutingError: No route matches [GET] "/items" # ./spec/features/show_items_spec.rb:5:in `block (2 levels) in < ࣦഊ͢Δ 
  15. 38.

    $ rails g scaffold item \ name:string description:string price:integer image_url:string

    invoke active_record create db/migrate/20160721092822_create_items.rb create app/models/item.rb invoke scaffold_controller ... δΣωϨʔλΛ׆༻͢Δ ‣ TDB⒎PMEδΣωϨʔλͰɺΞϓϦέʔγϣϯͷʮ଍৔ʯΛ࡞Δ ‣ OBNF EFTDSJQUJPO QSJDF JNBHF@VSMΛ࣋ͬͨJUFNͷ$36% 
  16. 39.

    $ bundle exec rails db:migrate $ bundle exec rspec spec/features

    F Failures: 1) See shop items A user can see vairous items Failure/Error: expect(page).to have_text('包丁') expected to find text "包丁" in "Listing items Name Price Image url New I # ./spec/features/manage_items_spec.rb:7:in `block (2 levels) in <top (requ Finished in 0.72653 seconds (files took 5.27 seconds to load) 1 example, 1 failure TDBGGPMEޙʹɺECNJHSBUFͯ͠ςετΛྲྀͯ͠ΈΔ 
  17. 40.

    --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -16,6 +16,8 @@ # users

    commonly want. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +require 'factory_girl' + RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest @@ -96,4 +98,5 @@ RSpec.configure do |config| # as the one that triggered the failure. Kernel.srand config.seed =end + config.include FactoryGirl::Syntax::Methods end GBDUPSZ@HJSMͷಋೖ 
  18. 41.

    --- /dev/null +++ b/spec/factories/item.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do

    + factory :item do + description { "素敵な#{name}です。" } + price 1_000 + image_url { "http://example.com/images/item/#{id}.png" } + end +end + --- a/spec/features/show_items_spec.rb +++ b/spec/features/show_items_spec.rb @@ -2,6 +2,9 @@ require 'rails_helper' RSpec.feature "See shop items", type: :feature do scenario 'A user can see vairous items' do + create(:item, name: '包丁') + create(:item, name: 'フライパン') + visit '/items' expect(page).to have_text('包丁') GHͰςετσʔλΛ࡞Δ 
  19. 42.

    $ bundle exec rspec spec/features . Finished in 0.21943 seconds

    (files took 2.72 seconds to load) 1 example, 0 failures $ bundle exec rails s # 別ターミナルで $ open http://localhost:3000/items ࠷ॳͷςετ͕௨ͬͨ ‣ Ͳ͏ͳ͍ͬͯΔ͔ɺϒϥ΢βͰݟͯΈ·͠ΐ͏ 
  20. 43.

    

  21. 45.

    --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,4 @@ Rails.application.routes.draw do

    + resources :items # For details on the DSL available within this file, see http://guides.rubyon routing.html end 3BJMT͸3&45ͱ͍͏ߟ͑ํʹ΋ͱ͍͍ͮͯΔ ‣ 3&QSFTFOUBUJPOBM4UBUF5SBOTGFS ‣ )551ϝιου Ͳ͏͍ͨ͠ ͱύε ΫΤϦ ͳʹΛ Λͱ͍͏૊Έ߹ΘͤΛɺ
 Ϧιʔε΁ͷૢ࡞ɺͱͯ͠ந৅Խ͠ɺΞʔΩςΫνϟͷத৺ʹஔ͘ߟ͑ํ ‣ ͦͷϦιʔε΁ͷૢ࡞Λ΋ͬͯɺΞϓϦέʔγϣϯͷॲཧΛ࣮ݱ͢Δɻ ‣ ࠓճ͸ɺѻ͏঎඼σʔλJUFNΛɺͦͷ··Ϧιʔεͱͨ͠ɻ 
  22. 46.

    $ bundle exec rails routes
 Prefix Verb URI Pattern Controller#Action

    items GET /items(.:format) items#index POST /items(.:format) items#create new_item GET /items/new(.:format) items#new edit_item GET /items/:id/edit(.:format) items#edit item GET /items/:id(.:format) items#show PATCH /items/:id(.:format) items#update PUT /items/:id(.:format) items#update DELETE /items/:id(.:format) items#destroy SFTPVSDFTJUFNTͰఆٛ͞ΕΔૢ࡞ ‣ ෳ਺औಘɺ୯਺औಘɺ࡞੒ɺߋ৽ɺ࡟আ ‣ ࡞੒ɺߋ৽ͷͨΊͷϑΥʔϜΛؚΉը໘΋Ϧιʔεͱߟ͑ΒΕΔ ‣ JUFNT͕঎඼ JUFN ͷू߹Λද͠ɺͦͷू߹Λऔಘ (&5 ͢Δ͜ͱΛ΋ͬͯɺ ʮϒϥ΢β্ʹ঎඼Ұཡͷදࣔʯͱ͍͏ΞϓϦέʔγϣϯͷॲཧΛදݱͨ͠ɻ 
  23. 47.

    ‣ POST /groups/42/add_member?person_id=4 ‣ άϧʔϓͷ add_member ॲཧΛݺͼग़͢ ‣ POST /groups/42/memberships?person_id=4

    ‣ "άϧʔϓʹࢀՃ͍ͯ͠Δ͜ͱ"(membership)Λ
 ࡞੒͢Δ ༨ஊ Ϧιʔε͸Ϟϊ͚ͩͰͳ͍ 
  24. 48.

    +++ b/app/controllers/items_controller.rb @@ -0,0 +1,74 @@ +class ItemsController < ApplicationController

    + before_action :set_item, only: [:show, :edit, :update, :destroy] + + # GET /items + # GET /items.json + def index + @items = Item.all + end + *UFNT$POUSPMMFSJOEFY ‣ ϧʔςΟϯά͔Βݺ͹ΕΔͱ͖͸AJUFNTJOEFYAදه ‣ *UFN͸3BJMTඪ४ͷ03.ɺ"DUJWF3FDPSE#BTFΛܧঝͨ͠Ϋϥεɻ ‣ *UFNBMMͰɺJUFNTςʔϒϧͷશϨίʔυΛಡΈࠐΈɺ!JUFNTʹೖΕ Δ 
  25. 49.

    ‣ ϑϨʔϜϫʔΫͷಈ͖Λ͢΂ͯઃఆ͢Δ(configuration)͢ΔͷͰͳ͘ɺ
 Rails͕૝ఆ͢Δن໿(convention)ʹԊͬͯಈ͘ɻ ‣ ARΫϥεItem͸ items ςʔϒϧͷϨίʔυΛѻ͏ ‣ GET /items͸

    ItemsController#index ΛݺͼɺΞΫγϣϯॲཧޙ͸ app/views/ items/index.html.* ͷςϯϓϨʔτΛϨϯμϦϯά͢Δ ‣ ม਺໊ͰܕΛදݱ͢ΔจԽ΋͋Δɻ ‣ Railsʹ׳ΕΔͱitems͸ෳ਺ͷItemΛ͋ΒΘ͢ม਺໊ͱಡΈऔΔɻ $POWFOUJPOPWFS$POpHVSBUJPO 
  26. 50.

    ‣ 'New Item'ϦϯΫΛͨͲͬͯɺ͋ͨΒ͍͠ Item Λ
 ௥Ճ͠ͳ͍͞ ‣ 2ͭҎ্ͷItemΛ௥Ճ͠ͳ͍͞ ‣ image_url͸ۭͰΑ͍

    ‣ indexͷςϯϓϨʔτΛฤूͨ͠͏͑Ͱϒϥ΢βΛϦϩʔυ ͠ɺද͕ࣔมΘΔ͜ͱΛ֬ೝ͠ͳ͍͞ ࣮श෼ 
  27. 51.

    %h1 Listing items %ul.items - @items.each do |item| %li.item %h2=

    link_to item.name, item %p= item.description = link_to 'New Item', new_item_path JOEFYIUNMIBNMͷॻ͖׵͑ ྫ 
  28. 53.

    ‣ https://www.heroku.com/about ‣ Heroku is a cloud application platform –

    a new way of building and deploying web apps. ‣ RailsΞϓϦέʔγϣϯͷίʔυΛૹΔͱɺࣗಈతʹDBͳͲ Λ༻ҙ͠ɺαʔϏεΛىಈͯ͘͠ΕΔɻ ‣ ίϯςφԽٕज़ͷҰछ ‣ ΫοΫύουͰ΋Docker + Amazon ECSͰ੔උ͍ͯ͠Δ IFSPLVʹ͍ͭͯ 
  29. 54.

    $ heroku create $ git push heroku master $ heroku

    run rails db:migrate $ heroku open (のあとに、/items に移動) IFSPLVʹσϓϩΠ͢Δ ‣ σϓϩΠ͸ ೉͍͠  ‣ Ͱ͖Δ͚ͩૣ͍ஈ֊Ͱಈ͔࢝͠ΊΔ͜ͱ͕؊৺ 
  30. 56.

    --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,3 @@ Rails.application.routes.draw do

    - resources :items - # For details on the DSL available within this file, see http://guides.rubyo routing.html + resources :items, only: %i(index show) end ## 確認 $ bundle exec rails routes Prefix Verb URI Pattern Controller#Action items GET /items(.:format) items#index item GET /items/:id(.:format) items#show DPOpHSPVUFTSC 
  31. 58.

    # 削除後 class ItemsController < ApplicationController before_action :set_item, only: [:show]

    # GET /items # GET /items.json def index @items = Item.all end # GET /items/1 # GET /items/1.json def show end private # Use callbacks to share common setup or constraints between actions. def set_item @item = Item.find(params[:id]) end end ίϯτϩʔϥͰ΋TIPXͱJOEFYҎ֎Λফ͢ 
  32. 59.

    --- a/app/views/items/index.html.haml +++ b/app/views/items/index.html.haml
 
 %h1 Listing items %ul.items -

    @items.each do |item| %li.item %h2= link_to item.name, item %p= item.description - - = link_to 'New Item', new_item_path ࡞੒ΞΫγϣϯ΁ͷಋઢ΋ফ͢ 
  33. 60.

    --- a/app/views/items/show.html.haml +++ b/app/views/items/show.html.haml @@ -4,9 +4,7 @@ %b Name:

    = @item.name %p - %b Image url: - = @item.image_url - -= link_to 'Edit', edit_item_path(@item) -\| -= link_to 'Back', items_path + = image_tag(@item.image_url) TIPXIUNMIBNM΋੔ཧ͢Δ ྫ 
  34. 61.

    --- a/spec/features/show_items_spec.rb +++ b/spec/features/show_items_spec.rb @@ -5,7 +5,7 @@ RSpec.feature "See

    shop items", type: :feature do create(:item, name: '包丁') create(:item, name: 'フライパン') - visit '/items' + visit items_path expect(page).to have_text('包丁') expect(page).to have_text('フライパン') ςετίʔυ΋ϦϑΝΫλϦϯά͢Δ 
  35. 63.

    ‣ RailsΛ࢖͍͸͡Ίͨ ‣ ͍ΖΜͳgem͕͋Δ ‣ ATDD ‣ Convention over Configuration

    ‣ ʮδΣωϨʔλʯΛ࢖ͬͯ࠷ॳͷҰาΛૉૣ͘า͖ग़ͨ͠ ‣ herokuΛར༻ͨ͠θϩσϓϩΠ ͜ͷεςοϓͰֶΜͩ͜ͱ 
  36. 68.

    --- a/spec/features/show_items_spec.rb +++ b/spec/features/show_items_spec.rb @@ -10,5 +10,15 @@ RSpec.feature "See

    shop items", type: :feature do expect(page).to have_text('包丁') expect(page).to have_text('フライパン') end + + scenario 'A user can see recommended items' do + create(:item, name: '包丁', recommended: true) + create(:item, name: 'フライパン') + + visit recommended_items_path + + expect(page).to have_text('包丁') + expect(page).not_to have_text('フライパン') + end end ղ౴ྫ 
  37. 69.

    $ bundle exec rspec .F Failures: 1) See shop items

    A user can see recommended items Failure/Error: create(:item, name: '包丁', recommended: true) NoMethodError: undefined method `recommended=' for #<Item:0x007fc928b10118> # ./spec/features/manage_items_spec.rb:15:in `block (2 levels) SFDPNNFOEFEଐੑ͕ͳͯ͘ςετʹࣦഊ͢Δ 
  38. 70.

    $ bundle exec rails g migration add_recommended_to_items recommended:boolean $ vim

    db/migrate/20160704015612_add_recommended_to_items.rb ``` --- a/db/migrate/20160704015612_add_recommended_to_items.rb +++ b/db/migrate/20160704015612_add_recommended_to_items.rb @@ -1,5 +1,5 @@ class AddRecommendedToItems < ActiveRecord::Migration[5.0] def change - add_column :items, :recommended, :boolean + add_column :items, :recommended, :boolean, null: false, default: false end end ``` $ bundle exec rails db:migrate *UFNͷଐੑΛ௥Ճ͢Δ ‣ ͸͖ͬΓͨ͠ཧ༝͕ͳ͍ݶΓɺOVMM͸ڐ༰͠ͳ͍΄͏͕Α͍Ͱ͢ɻ 
  39. 71.

    $ bundle exec rspec .F Failures: 1) See shop items

    A user can see recommended items Failure/Error: visit recommended_items_path NameError: undefined local variable or method `recommended_items_path' for #<RSpec::ExampleGroups::SeeShopItems:0x007f89d42e9db0> # ./spec/features/manage_items_spec.rb:18:in `block (2 levels) in <top (req Finished in 0.23378 seconds (files took 5.4 seconds to load) 2 examples, 1 failure ϧʔςΟϯά͕ͳͯ͘ςετࣦഊ 
  40. 72.

    ‣ ͓͢͢Ί঎඼ͷҰཡ ‣ recommended ϑϥά͕trueͷItemͷҰཡ ‣ (recommendedϑϥά͕trueͷ)ItemͷҰཡ ‣ (৚݅ͰϑΟϧλ͞Εͨ)ϦιʔεͷίϨΫγϣϯ ‣

    (৚݅ͰϑΟϧλ͞Εͨ͏͑Ͱɺ໊લΛ͚ͭΔՁ஋ͷ͋Δ)
 ϦιʔεͷίϨΫγϣϯ ϦιʔεΛઃܭ͢Δ͓͢͢Ί঎඼Ұཡͱ͸ 
  41. 73.

    $ vim config/routes.rb ``` --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3

    +1,7 @@ Rails.application.routes.draw do - resources :items, only: %i(index show) + resources :items, only: %i(index show) do + collection do + get :recommended + end + end end ``` $ bundle exec rails routes | grep recommended recommended_items GET /items/recommended(.:format) items#recommended SFDPNNFOEFEJUFNT͸ίϨΫγϣϯϦιʔεͱͯ͠දݱ͢Δ 
  42. 74.

    $ vim config/routes.rb ``` --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3

    +1,7 @@ Rails.application.routes.draw do - resources :items, only: %i(index show) + resources :items, only: %i(index show) do + collection do + get :recommended + end + end end $ bundle exec rails routes | grep recommended recommended_items GET /items/recommended(.:format) items#recommended SFDPNNFOEFEJUFNT͸ίϨΫγϣϯϦιʔεͱͯ͠දݱ͢Δ ‣ 'JMUFSFETVCSFTPVSDFͰ͋Δͱղऍͯ͠DPOpHSPVUFTSCʹ௥Ճͨ͠ɻ ‣ SFDPNNFOEFE@JUFNTͱ͍͏໊લΛ͚ͭͨ ‣ (&5JUFNTSFDPNNFOEFEͱϦΫΤετ͞ΕΔ ‣ JUFNTSFDPNNFOEFEͰॲཧ͢Δ 
  43. 76.

    --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -12,6 +12,11 @@ class ItemsController

    < ApplicationController def show end + def recommended + @items = Item.where(recommended: true).all + render action: :index + end + private ղ౴ྫ 
  44. 77.

    $ rails console irb(main):001:0> item = Item.first Item Load (0.2ms)

    SELECT `items`.* FROM `items` ORDER BY `items`.`id` ASC LIMIT 1 => #<Item id: 1, name: "包丁", description: "よく切れる包丁です", price: 30000, image_url: "", ...> SBJMTDPOTPMF ‣ SBJMTDPOTPMFͰର࿩ίϯιʔϧʹೖΕΔ ‣ "3ϞσϧΦϒδΣΫτΛ௚઀ૢ࡞͠ɺ։ൃ΍σόοάʹ໾ཱͯΒΕΔ 
  45. 78.

    ‣ rails console্Ͱɺid͕࠷খͷitemͷrecommendedϑϥάΛtrueʹ͠ͳ͍͞ɻ ‣ id͕2൪໨ʹখ͍͞itemͷrecommedϑϥά͕falseͰ͋Δ͜ͱΛ֬ೝ͠ͳ͍͞ɻ ‣ app/views/items/index.html.hamlΛฤू͠ɺΦεεϝ͔Ͳ͏͔Λදࣔ͢ΔΑ͏ʹ ͠ͳ͍͞ɻ ‣ $

    open http://localhost:3000/items/recommended Ͱϒϥ΢βදࣔՄೳɻ ‣ [advanced] ͸΍͘ऴΘͬͨΒ: "pry"ͱ͍͏ϥΠϒϥϦΛௐࠪ͠ɺ
 ศརͦ͏ͳΒಋೖ͠ͳ͍͞ɻ ࣮श෼ 
  46. 79.

    --- a/app/views/items/index.html.haml +++ b/app/views/items/index.html.haml @@ -3,5 +3,8 @@ %ul.items -

    @items.each do |item| %li.item - %h2= link_to item.name, item + %h2 + - if item.recommended? + %span.recommended オススメ + = link_to item.name, item %p= item.description ղ౴ྫ 
  47. 84.

    --- /dev/null +++ b/spec/features/portal_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper'

    + +RSpec.feature "See shop categories", type: :feature do + scenario 'Listing categories' do + create(:category, name: '調理器具') + create(:category, name: '食器') + + visit root_path + + expect(page).to have_text('調理器具') + expect(page).to have_text('食器') + end +end τοϓϖʔδʹΧςΰϦͷҰཡΛදࣔͰ͖Δ 
  48. 85.

    $ bundle exec rails g model category name:string image_url:string invoke

    active_record create db/migrate/20160722075755_create_categories.rb create app/models/category.rb $ vim db/migrate/20160722075755_create_categories.rb # nameを非nullに $ bundle exec rails db:migrate == 20160722075755 CreateCategories: migrating ======================= -- create_table(:categories) -> 0.0332s == 20160722075755 CreateCategories: migrated (0.0333s) ============== "3ϞσϧΛ࡞੒͢Δ 
  49. 86.

    --- /dev/null +++ b/spec/factories/category.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do

    + factory :category do + name "雑貨" + image_url { "http://example.com/images/category/#{id}.png" } + end +end + ϑΝΫτϦΛ௥Ճ͢Δ 
  50. 87.

    F.. Failures: 1) Show categories on root Listing categories Failure/Error:

    visit root_path NameError: undefined local variable or method `root_path' for #<RSpec::ExampleGroups::ShowCategoriesOnRoot:0x007fe4891096c0> # ./spec/features/portal_spec.rb:8:in `block (2 levels) in <top Finished in 0.26761 seconds (files took 2.8 seconds to load) 3 examples, 1 failure Failed examples: ϧʔςΟϯάఆ͕ٛͳ͍ͨΊςετ͕ࣦഊ 
  51. 88.

    ‣ config/routes.rb Ͱ root 'portal#show' ͱࢦఆ͢ΔͱΞϓϦέʔγϣϯϧʔτ('/') ΞΫγϣϯΛࢦఆͰ͖Δɻ ‣ ͜ͷ৔߹portal#show ‣

    τοϓϖʔδ͸ɺখ͍͞ΞϓϦέʔγϣϯͰ͸ओཁΤϯςΟςΟ(ΫοΫύου Ͱ͍͏Ϩγϐ)ͷindexͰදݱ͞ΕΔ͜ͱ͕ଟ͍ɻ ‣ ͦͦ͜͜ͷن໛ʹͳΔͱɺʮಋઢͷ͋ͭ·ΓͰ͋Δτοϓϖʔδʯͱ͍͏Ϧιʔ εʹͳ͍ͬͯ͘ɻ ‣ ͦͷ৔߹ʮϙʔλϧϦιʔεʯͱͯ͠੾Γग़͢͜ͱ͕ଟ͍ [ཁग़య] ઃܭΛߟ͑ΔϙʔλϧϦιʔεͷಋೖ 
  52. 89.

    --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Rails.application.routes.draw do

    + root 'portal#show' + resources :items, only: %i(index show) do SPPU΁ͷϧʔςΟϯάΛఆٛ͢Δ 
  53. 92.

    --- /dev/null +++ b/app/controllers/portal_controller.rb @@ -0,0 +1,5 @@ +class PortalController

    < ApplicationController + def show + @categories = Category.all + end +end ղ౴ྫ 
  54. 93.

    --- /dev/null +++ b/app/views/portal/show.html.haml @@ -0,0 +1,3 @@ +%ul.categories +

    - @categories.each do |category| + %li= category.name ղ౴ྫ 
  55. 95.

    --- a/spec/features/manage_items_spec.rb +++ b/spec/features/manage_items_spec.rb @@ -20,5 +20,21 @@ RSpec.feature "See

    shop items", type: :feature do expect(page).to have_text('包丁') expect(page).not_to have_text('フライパン') end + + scenario 'A user can see categorized items from portal', type: :feature do + kitchenware = create(:category, name: '調理器具') + kitchenware.items.create!(name: '包丁', recommended: true) + kitchenware.items.create!(name: 'フライパン') + + tableware = create(:category, name: '食器') + tableware.items.create!(name: '大皿') + + visit root_path + click_on '調理器具' + expect(page).to have_text('包丁') + expect(page).to have_text('フライパン') + + expect(page).not_to have_text('大皿') + end end τοϓϖʔδ͔ΒΧςΰϦΛͨͲͬͯݟʹདྷ͍ͯΔ༷ࢠͷड͚ೖΕςετ 
  56. 96.

    --- a/spec/features/show_items_spec.rb +++ b/spec/features/show_items_spec.rb @@ -21,7 +21,7 @@ RSpec.feature "See

    shop items", type: :feature do expect(page).not_to have_text('フライパン') end - scenario 'A user can see categorized items from portal', type: :feature do + xscenario 'A user can see categorized items from portal', type: :feature do kitchenware = create(:category, name: '調理器具') kitchenware.items.create!(name: '包丁') 1SPUJQςετͷϖϯσΟϯά 
  57. 97.

    $ bundle exec rspec spec/features .*.. Pending: (Failures listed here

    are expected and do not affect your su 1) See shop categories A user can see categorized items
 from portal # Temporarily disabled with xscenario # ./spec/features/portal_spec.rb:14 Finished in 0.21527 seconds (files took 2.25 seconds to load) 4 examples, 0 failures, 1 pending QFOEJOH 
  58. 99.

    ‣ ʮ঎඼͸1ͷΧςΰϦʹଐ͢Δʯ ‣ Item belongs_to :category, optional: true ‣ item͕΋͍ͬͯΔcategory_idʹΑͬͯɺଐ͢Δ1ͭͷΧς

    ΰϦΛࢀর͢Δ ‣ ΧςΰϦΛࢀর͢ΔͨΊͷ#categoryͱ͍͏ϝιουͳͲ ͕࢖͑ΔΑ͏ʹͳΔ ‣ ΧςΰϦʹଐ͞ͳ͍͜ͱ΋Ͱ͖ΔͷͰɺoptional: trueΦϓ γϣϯΛ෇͚Δ ଟରPSରͷʮଐ͢Δʯؔ࿈ੑ 
  59. 100.

    ‣ ʮΧςΰϦ͸ɺෳ਺ͷ঎඼Λॴ༗͍ͯ͠Δʯ ‣ Category has_many :items ‣ item͕΋͍ͬͯΔcategory_idʹΑͬͯɺॴ༗͢Δn ݸͷ঎඼Λࢀর͢Δ ‣

    ෳ਺঎඼Λࢀর͢ΔͨΊͷ#itemsͱ͍͏ϝιουͳ Ͳ͕࢖͑ΔΑ͏ʹͳΔ ରଟͷʮॴ༗͢Δʯؔ࿈ੑ 
  60. 101.

    # spec/models/category_spec.rb require 'rails_helper' describe Category do describe 'has_many :item,

    learning test' do let(:category) { create(:category, name: '調理器具') } before do category.items.create!(name: '包丁', recommended: true) category.items.create!(name: 'フライパン') end specify { expect(category.items.map(&:name)).to match_array(['包丁', 'フライパン']) } specify { expect(category.items.first.category).to eq(category) } end end # ヒント: 下記のコマンドでマイグレーションファイルの雛形が生成できます $ bundle exec rails g migration add_category_id_to_items category_id:integer ࣮श෼ԼهͷςετΛ࡞੒͠ɺςετΛ௨͠ͳ͍͞ 3BJMT-FTTPO 
  61. 102.

    --- /dev/null +++ b/db/migrate/20160705051719_add_category_reference_to_items.rb @@ -0,0 +1,7 @@ +class AddCategoryReferenceToItems

    < ActiveRecord::Migration[5.0] + def change + add_column :items, :category_id, :integer, null: true + + add_index :items, :category_id + end +end ղ౴ྫϚΠάϨʔγϣϯ 
  62. 103.

    --- a/app/models/category.rb +++ b/app/models/category.rb @@ -1,2 +1,3 @@ class Category

    < ApplicationRecord + has_many :items end --- a/app/models/item.rb +++ b/app/models/item.rb @@ -1,2 +1,3 @@ class Item < ApplicationRecord + belongs_to :category, optional: true end ղ౴ྫ֤Ϟσϧͷؔ࿈ͷఆٛ 
  63. 105.

    --- a/config/routes.rb +++ b/config/routes.rb @@ -6,4 +6,8 @@ Rails.application.routes.draw do

    get :recommended end end + + resources :categories, only: %i(index) do + resources :items, only: %w(index) + end end ωετͨ͠ϧʔςΟϯάΛఆٛ͢Δ 
  64. 106.

    --- a/app/views/portal/show.html.haml +++ b/app/views/portal/show.html.haml @@ -1,3 +1,3 @@ %ul.categories -

    @categories.each do |category| - %li= category.name + %li= link_to category.name, category_items_path(category) ΧςΰϦʹଐ͢Δ঎඼΁ͷಋઢΛ࡞Δ 
  65. 108.

    --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -4,7 +4,7 @@ class ItemsController

    < ApplicationController # GET /items # GET /items.json def index - @items = Item.all + @items = collection_root end # GET /items/1 @@ -23,4 +23,12 @@ class ItemsController < ApplicationController @item = Item.find(params[:id]) end + def collection_root + if params[:category_id] + Category.find(params[:category_id]).items + else + Item.all + end + end + ղ౴ྫ 
  66. 110.

    $ git clone git@ghe.example.com:moro/ckpd_intern.git vendor/ckpd_intern $ rm -rf vendor/ckpd_intern/.git $

    vim Gemfile gem 'ckpd_intern', path: 'vendor/ckpd_intern' # これを追加 $ vim db/seed.rb --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,8 @@ # # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) +require 'ckpd_intern/seed' + +ActiveRecord::Base.transaction do + CkpdIntern::Seed.create_all! +end db/seed.rb Λฤूͯ͠γʔυσʔλΛ࡞Δ 
  67. 111.

    $ bundle exec rails db:seed (ブラウザで http://localhost:3000/items を見てみよう) $ git

    add vendor/ckpd_intern $ git commit -m '...' # heroku にデプロイ $ git push heroku master $ heroku run rails db:migrate $ heroku run rails db:seed db:seedΛͯ͠ΈΑ͏ 
  68. 115.

    ߟ͑Δʮ஫จʯͷσʔλߏ଄  ඼໨ ਺ྔ ୯Ձ খܭ แஸ   

      ು      ߹ܭ   ͝஫จ
  69. 117.
  70. 118.

    ߟ͑Δ෼͚ͨ΋ͷΛࢀরͰ͖ΔΑ͏ʹ /'  ஫จJE ඼໨ ਺ྔ ୯Ձ খܭ  แஸ

          ು      JE ஫จ೔  
  71. 119.

    ߟ͑Δಋग़Ͱ͖Δ΋ͷΛ෼͚ͯΈΔ /'  ஫จJE ঎඼JE ਺ྔ খܭ   

           JE ஫จ೔   JE ඼໨ ୯Ձ  แஸ    ು  
  72. 120.

    είʔϓ֎ʮ஫จʯʹ഑ૹઌ΋͋Δ৔߹  ඼໨ ਺ྔ ୯Ձ খܭ แஸ   

      ು      ߹ܭ   ͝஫จ ഑ૹઌܙൺणΨʔσϯϓϨΠε'NPSP༷
  73. 121.

    ߟ͑ΔJEҎ֎Ͱಋग़Ͱ͢΂͖΋ͷΛ෼͚Δ /'  ஫จJE ঎඼JE ਺ྔ খܭ   

           JE ސ٬JE ஫จ೔    JE ඼໨ ୯Ձ  แஸ    ು   JE ஫จऀ ॅॴ  NPSP ܙൺणΨʔσϯϓϨΠε
  74. 123.

    ςʔϒϧ໊ɺΧϥϜ໊ΛܾΊΔ  line_items id:pk order_id
 :fk item_id
 :fk quantity
 :integer

    5 100 201 2 6 100 202 1 orders id:pk ordered_at
 :timestamp status
 :enum 100 2016-08-11 ஫จ֬ఆ items id:pk name :string price
 :integer 201 แஸ 3,000 202 ು 2,500
  75. 124.

    $ bundle exec rails g model order status:integer ordered_at:datetime $

    bundle exec rails g model line_item \ order:references item:references quantity:integer ‣ ઌ΄Ͳͷ࿦ཧઃܭΛ΋ͱʹϞσϧͱϚΠάϨʔγϣϯϑΝΠϧΛੜ੒͢Δɻ ‣ FOVNͷ࣮૷͸JOUFHFSɺGL΋࣮૷͸ϢχʔΫΠϯσοΫε੍໿෇͖ͷ JOUFHFSʹͳΔ 
  76. 126.

    # spec/features/orders_spec.rb require 'rails_helper' RSpec.feature "Order items", type: :feature do

    scenario 'A user can order 2 hochyo' do kitchenware = create(:category, name: '調理器具') create(:item, category: kitchenware, name: '包丁', price: 3_000) visit root_path click_on '調理器具' click_on '包丁' fill_in '数量', with: '2' click_on 'カートに追加' expect(page).to have_content('ご注文ありがとうございました') expect(page).to have_content(%r!注文日時: \d{4}/\d{2}/\d{2} \d{2}:\d{2}!) expect(page).to have_css('.line_items td', text: '包丁') end end ஫จͷγφϦΦΛߟ͑Δ 
  77. 129.

    %p#notice= notice .item %h2= @item.name = image_tag @item.image_url, size: '400x300',

    alt: "#{@item.name}のイラストです %p.description= @item.description %p.price #{@item.price}円 .line_item = form_for LineItem.new(item: @item) do |f| = f.hidden_field :item_id = f.label :quantity, '数量' = f.number_field :quantity, size: 5, required: true = submit_tag 'カートに追加' = link_to 'Back', items_path ஫จϑΥʔϜͷྫ঎඼ৄࡉ JUFNTTIPX ʹߪೖϑΥʔϜΛ଍͢ 
  78. 130.

    # spec/features/orders_spec.rb require 'rails_helper' RSpec.feature "Order items", type: :feature do

    scenario 'A user can order 2 hochyo' do kitchenware = create(:category, name: '調理器具') create(:item, category: kitchenware, name: '包丁', price: 3_000) visit root_path click_on '調理器具' click_on '包丁' fill_in '数量', with: '2' click_on 'カートに追加' expect(page).to have_content('ご注文ありがとうございました') expect(page).to have_content(%r!注文日時: \d{4}/\d{2}/\d{2} \d{2}:\d{2}!) expect(page).to have_css('.line_items td', text: '包丁') end end ࣮श8: 20෼ ஫จॲཧΛ࣮૷͠ͳ͍͞ (Rails/Lesson8) 
  79. 131.

    --- a/config/routes.rb +++ b/config/routes.rb @@ -10,4 +10,7 @@ Rails.application.routes.draw do

    resources :categories, only: %i(index) do resources :items, only: %w(index) end + + resources :line_items + resources :orders ղ౴ྫ 
  80. 132.

    --- /dev/null +++ b/app/controllers/line_items_controller.rb @@ -0,0 +1,10 @@ +class LineItemsController

    < ApplicationController + def create + order = Order.create! + line_item = order.line_items.create! (params.require(:line_item).permit(:item_id, :quantity)) + order.checkout!(Time.now) + + redirect_to order + end +end ղ౴ྫ 
  81. 133.

    --- /dev/null +++ b/app/models/order.rb +class Order < ApplicationRecord + has_many

    :line_items + enum status: { + cart: 0, + checked_out: 1, + } + + def checkout!(at) + update!(status: :checked_out, ordered_at: at) + end +end ղ౴ྫ 
  82. 134.

    --- /dev/null +++ b/app/models/line_item.rb +class LineItem < ApplicationRecord + belongs_to

    :order + belongs_to :item + + def subtotal + item.price * quantity + end +end ղ౴ྫ 
  83. 135.

    --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,5 @@ +class OrdersController

    < ApplicationController + def show + @order = Order.find(params[:id]) + end +end ղ౴ྫ 
  84. 136.

    --- /dev/null +++ b/app/views/orders/show.html.haml @@ -0,0 +1,24 @@ +.order +

    %h2 ご注文ありがとうございました + .ordered_at + 注文日時: + %span=l @order.ordered_at, format: '%Y/%m/%d %H:%M' + + - if @order.line_items.present? + %table.line_items + %thead + %tr + %th 商品 + %th 単価 + %th 数量 + %th 小計 + %tbody + - @order.line_items.order(:id).each do |li| + %tr + %td= li.item.name + %td= li.item.price + %td= li.quantity + %td= li.subtotal + - else + %p.empty 商品はありません。 + = link_to '買い物を続ける', root_path ղ౴ྫ 
  85. 138.

    ‣ items#show ʹɺline_items#create͢ΔͨΊͷϑΥʔϜ͕͋Δ ‣ ਺ྔΛࢦఆͰ͖ΔɻʮΧʔτʹೖΕΔʯΠϝʔδ ‣ line_items#createͰ஫จΛ௥Ճ͢Δ ‣ ࠷ॳͷ஫จͷ৔߹ɺOrderΛ௥Ճ͠ɺIDΛηογϣϯʹೖΕΔ ‣

    cartεςʔλεͱͯ͠௥Ճ͢Δ ‣ ηογϣϯ͔Βʮcartεςʔλεʯͷ஫จΛ෮ݩ͠ɺͦ͜ʹline_itemΛ௥Ճ͢Δ ‣ ஫จΛ௥Ճͨ͋͠ͱ͸ɺorders#showʹϦμΠϨΫτ͢Δ ‣ ͦ͜Ͱɺങ͍෺Λଓ͚Δ(root_path΁ͷϦϯΫ)͔ɺ஫จΛ֬ఆ͢Δ(orders#update΁ͷϑΥʔϜ)ͷͲͪΒ ͔ʹભҠ͢Δ ‣ ஫จΛ֬ఆ͢ΔͱɺOrder#ordered_atʹͦͷ࣌ࠁ͕ηοτ͞Εɺεςʔλε͕checked_outʹͳΓɺ஫จ ৄࡉʹભҠ͢Δ ࣍ͷ໨ඪΛߟ͑Δ 
  86. 139.

    ‣ items#show ʹɺline_items#create͢ΔͨΊͷϑΥʔϜ͕͋Δ ‣ ਺ྔΛࢦఆͰ͖ΔɻʮΧʔτʹೖΕΔʯΠϝʔδ ‣ line_items#createͰ஫จΛ௥Ճ͢Δ ‣ ࠷ॳͷ஫จͷ৔߹ɺOrderΛ௥Ճ͠ɺIDΛηογϣϯʹೖΕΔ ‣

    cartεςʔλεͱͯ͠௥Ճ͢Δ ‣ ηογϣϯ͔Βʮcartεςʔλεʯͷ஫จΛ෮ݩ͠ɺͦ͜ʹline_itemΛ௥Ճ͢Δ ‣ ஫จΛ௥Ճͨ͋͠ͱ͸ɺorders#showʹϦμΠϨΫτ͢Δ ‣ ͦ͜Ͱɺങ͍෺Λଓ͚Δ(root_path΁ͷϦϯΫ)͔ɺ஫จΛ֬ఆ͢Δ(orders#update΁ͷϑΥʔϜ)ͷͲͪΒ ͔ʹભҠ͢Δ ‣ ஫จΛ֬ఆ͢ΔͱɺOrder#ordered_atʹͦͷ࣌ࠁ͕ηοτ͞Εɺεςʔλε͕checked_outʹͳΓɺ஫จ ৄࡉʹભҠ͢Δ ࣍ͷ໨ඪΛߟ͑Δ 
  87. 141.
  88. 143.

    require 'rails_helper' scenario 'A user can see categorized items from

    portal' do kitchenware = create(:category, name: '調理器具') create(:item, category: kitchenware, name: '包丁', price: 3_000) create(:item, category: kitchenware, name: '鍋', price: 2_500) visit root_path click_on '調理器具' click_on '包丁' fill_in '数量', with: '2' click_on 'カートに追加' click_on '買い物を続ける' expect(page.current_path).to eq('/') click_on '調理器具' click_on '鍋' fill_in '数量', with: '1' click_on 'カートに追加' click_on '注文を確定する' expect(page).to have_content('ご注文ありがとうございました') expect(page).to have_content(%r!注文日時: \d{4}/\d{2}/\d{2} \d{2}:\d{2}!) expect(page).to have_css('.line_items td', text: '包丁') expect(page).to have_css('.line_items td', text: '鍋') end ࣮श9: 30෼ ԼهςετΛ௨͠ͳ͍͞ (Rails/Lesson9) 
  89. 144.

    --- a/app/controllers/line_items_controller.rb +++ b/app/controllers/line_items_controller.rb @@ -1,10 +1,21 @@ class LineItemsController

    < ApplicationController def create - order = Order.create! + order = current_cart_order line_item = order.line_items.create!(params.require(:line_item).permit(:item_id, :quantity)) - order.checkout!(Time.now) redirect_to order end + + private + + def current_cart_order + if session[:current_order_id] + Order.where(status: :cart).find(session[:current_order_id]) + else + Order.create!.tap do |order| + session[:current_order_id] = order.id + end + end + end end ղ౴ྫ 
  90. 145.

    --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -2,4 +2,16 @@ class OrdersController

    < ApplicationController def show @order = Order.find(params[:id]) end + + def update + @order = Order.find(params[:id]) + if params.dig(:order, :status) == 'checked_out' + @order.checkout!(Time.now) + session.delete(:current_order_id) + + redirect_to @order + else + head(:bad_request) + end + end end ղ౴ྫ 
  91. 146.

    --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -1,8 +1,11 @@ .order -

    %h2 ご注文ありがとうございました - .ordered_at - 注文日時: - %span=l @order.ordered_at, format: '%Y/%m/%d %H:%M' + - if @order.cart? + %h2 カートの中のアイテム + - elsif @order.checked_out? + %h2 ご注文ありがとうございました + .ordered_at + 注文日時: + %span=l @order.ordered_at, format: '%Y/%m/%d %H:%M' - if @order.line_items.present? %table.line_items @@ -19,6 +22,13 @@ %td= li.item.price %td= li.quantity %td= li.subtotal + + - unless @order.checked_out? + = link_to '買い物を続ける', root_path + = form_for @order do |f| + = f.hidden_field :status, value: :checked_out + = submit_tag '注文を確定する' + - else %p.empty 商品はありません。 = link_to '買い物を続ける', root_path 
  92. 149.

    ‣ [done] ঎඼ͷҰཡΛදࣔͰ͖Δ ‣ [done] Φεεϝ঎඼ͷҰཡΛදࣔͰ͖Δ ‣ [done] ΧςΰϦ෼͚͞Εͨ঎඼ͷҰཡΛදࣔͰ͖Δ ‣

    [done] ঎඼Λ஫จͰ͖Δ ‣ ֤ػೳΛ+40/"1*Ͱఏڙ͢Δ ΍Γ͍ͨ͜ͱϦετ: one more thing 
  93. 153.

    $ curl -s https://localhost:3000/items/1.json | jq . { "id": 1,

    "name": "料理包丁", "description": "材料を切るときに使う、料理包丁です。", "price": 1000, "image_url": "ckpd_intern/item/cooking_ryouri_houchou.png", "created_at": "2016-08-02T04:33:21.796Z", "updated_at": "2016-08-02T04:33:21.796Z", "url": "https://localhost:3000/items/1.json" } TDBGGPMEͰ͍͍ͩͨͰ͖ͯͨ 
  94. 154.

    --- a/app/views/items/_item.json.jbuilder +++ b/app/views/items/_item.json.jbuilder @@ -1,2 +1,3 @@ -json.extract! item,

    :id, :name, :description, :price, \ :image_url, :created_at, :updated_at -json.url item_url(item, format: :json) \ No newline at end of file +json.extract! item, :id, :name, :description, :price, \ :created_at, :updated_at +json.image_url image_url(item.image_url) +json.url item_url(item, format: :json) TIPXKTPOKCVJMEFS͔Βͷ@JUFNKTPOKCVJMEFSΛमਖ਼ 
  95. 155.

    $ curl -s http://localhost:3000/items/1.json | jq . { "id": 1,

    "name": "料理包丁", "description": "材料を切るときに使う、料理包丁です。", "price": 1000, "created_at": "2016-08-02T04:28:57.000Z", "updated_at": "2016-08-02T04:28:57.000Z", "image_url": "http://localhost:3000/assets/ckpd_intern/item/ cooking_ryouri_houchou-35bc5437f2f2c2dd6f32f9d6b4248ce3272ff9998349fd .png", "url": "http://localhost:3000/items/1.json" } JNBHF@VSM΋௥ՃͰ͖ͯͦ͏ 
  96. 156.

    ‣ categories#index ʹ͓͍ͯ΋ɺΧςΰϦҰཡͷJSONϨεϙϯεΛฦͤΔΑ͏ʹ ίϯτϩʔϥͱϏϡʔΛ௥Ճ͠ͳ͍͞ɻ ‣ ֤ΧςΰϦ͸ɺid, name, image_url, category_items_urlΛ࣋ͭ͜ͱ ‣

    λΠϜελϯϓ͸ෆཁ: ΧςΰϦΛͭͬͨ͘λΠϜελϯϓ͸͋·Γҙຯͳ ͍? ‣ url΋ෆཁ: ΧςΰϦৄࡉͱ͍͏Ϧιʔε΋ΞϓϦ͔Β࢖͍Έͪͳͦ͞͏ ࣮श෼ 
  97. 157.

    --- /dev/null +++ b/app/controllers/categories_controller.rb @@ -0,0 +1,5 @@ +class CategoriesController

    < ApplicationController + def index + @categories = Category.all + end +end --- /dev/null +++ b/app/views/categories/index.json.jbuilder @@ -0,0 +1,5 @@ +json.array!(@categories) do |category| + json.extract! category, :id, :name + json.image_url image_url(category.image_url) + json.category_items_url category_items_url(category, format: :json) +end ղ౴ྫ 
  98. 159.

    { "line_items": [ { "item_id": 8, "quantity": 1 }, {

    "item_id": 15, "quantity": 2 } ] } ஫จϦΫΤετͷྫ 
  99. 160.

    # spec/requests/order_api_spec.rb require 'rails_helper' RSpec.describe "Order items via API", type:

    :request do let(:kitchenware) { create(:category, name: '調理器具') } let(:hocho) { create(:item, category: kitchenware, name: '包丁', price: 3_000) } let(:pot) { create(:item, category: kitchenware, name: '鍋', price: 2_500) } specify 'A client can POST whole order in one request' do post( orders_path, params: { line_items: [ {item_id: hocho.id, quantity: 2}, {item_id: pot.id, quantity: 1}, ] }, as: :json ) expect(response.status).to eq(201) expect(JSON.parse(response.body)['status']).to eq('checked_out') end end "1*ͷ&&ςετ 3BJMT-FTTPO 
  100. 161.

    { "id": 5, "ordered_at": "2016-07-11T07:41:04.781Z", "total": 3000, "status": "checked_out", "created_at":

    "2016-07-11T07:41:04.782Z", "updated_at": "2016-07-11T07:41:04.802Z", "url": "https://ancient-bastion-22709.herokuapp.com/orders/5", "line_items": [ { "quantity": 1, "subtotal": 1000, "item": { "id": 8, "name": "寸胴", "price": 1000, "url": "https://ancient-bastion-22709.herokuapp.com/items/8" } }, { "quantity": 2, "subtotal": 2000, "item": { "id": 15, "name": "お皿", "price": 1000, "url": "https://ancient-bastion-22709.herokuapp.com/items/15" } } ] } Ϩεϙϯεͷαϯϓϧ 
  101. 162.

    --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,10 @@ class ApplicationController

    < ActionController::Base - protect_from_forgery with: :exception + protect_from_forgery with: :exception, unless: :valid_api_request? + + private + + def valid_api_request? + # FIXME need authentication for realworld application + request.format.json? + end end FIXME: ࠓճAPIͰPOST͢Δ৔߹ 
  102. 165.

    --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,10 @@ class ApplicationController

    < ActionController::Base - protect_from_forgery with: :exception + protect_from_forgery with: :exception, unless: :valid_api_request? + + private + + def valid_api_request? + # FIXME need authentication for realworld application + request.format.json? + end end (࠶ܝ) Real WordͰ΍Δͱ͖͸ϦΫΤετݩݕূΛͪΌΜͱ΍Ζ͏