Upgrade to Pro — share decks privately, control downloads, hide ads and more …

About Rails at Cookpad Summer Intern 2016 day1

About Rails at Cookpad Summer Intern 2016 day1

MOROHASHI Kyosuke

August 12, 2016
Tweet

More Decks by MOROHASHI Kyosuke

Other Decks in Programming

Transcript

  1. 

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

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

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

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


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

  13. --- /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. $ 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. $ 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. $ 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. --- 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. --- /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. $ 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. 

  21. --- 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. $ 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. ‣ POST /groups/42/add_member?person_id=4 ‣ άϧʔϓͷ add_member ॲཧΛݺͼग़͢ ‣ POST /groups/42/memberships?person_id=4

    ‣ "άϧʔϓʹࢀՃ͍ͯ͠Δ͜ͱ"(membership)Λ
 ࡞੒͢Δ ༨ஊ Ϧιʔε͸Ϟϊ͚ͩͰͳ͍ 
  24. +++ 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. ‣ ϑϨʔϜϫʔΫͷಈ͖Λ͢΂ͯઃఆ͢Δ(configuration)͢ΔͷͰͳ͘ɺ
 Rails͕૝ఆ͢Δن໿(convention)ʹԊͬͯಈ͘ɻ ‣ ARΫϥεItem͸ items ςʔϒϧͷϨίʔυΛѻ͏ ‣ GET /items͸

    ItemsController#index ΛݺͼɺΞΫγϣϯॲཧޙ͸ app/views/ items/index.html.* ͷςϯϓϨʔτΛϨϯμϦϯά͢Δ ‣ ม਺໊ͰܕΛදݱ͢ΔจԽ΋͋Δɻ ‣ Railsʹ׳ΕΔͱitems͸ෳ਺ͷItemΛ͋ΒΘ͢ม਺໊ͱಡΈऔΔɻ $POWFOUJPOPWFS$POpHVSBUJPO 
  26. ‣ 'New Item'ϦϯΫΛͨͲͬͯɺ͋ͨΒ͍͠ Item Λ
 ௥Ճ͠ͳ͍͞ ‣ 2ͭҎ্ͷItemΛ௥Ճ͠ͳ͍͞ ‣ image_url͸ۭͰΑ͍

    ‣ indexͷςϯϓϨʔτΛฤूͨ͠͏͑Ͱϒϥ΢βΛϦϩʔυ ͠ɺද͕ࣔมΘΔ͜ͱΛ֬ೝ͠ͳ͍͞ ࣮श෼ 
  27. %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. ‣ 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. $ heroku create $ git push heroku master $ heroku

    run rails db:migrate $ heroku open (のあとに、/items に移動) IFSPLVʹσϓϩΠ͢Δ ‣ σϓϩΠ͸ ೉͍͠  ‣ Ͱ͖Δ͚ͩૣ͍ஈ֊Ͱಈ͔࢝͠ΊΔ͜ͱ͕؊৺ 
  30. --- 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. # 削除後 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. --- 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. --- 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. --- 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. ‣ RailsΛ࢖͍͸͡Ίͨ ‣ ͍ΖΜͳgem͕͋Δ ‣ ATDD ‣ Convention over Configuration

    ‣ ʮδΣωϨʔλʯΛ࢖ͬͯ࠷ॳͷҰาΛૉૣ͘า͖ग़ͨ͠ ‣ herokuΛར༻ͨ͠θϩσϓϩΠ ͜ͷεςοϓͰֶΜͩ͜ͱ 
  36. --- 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. $ 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. $ 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. $ 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. ‣ ͓͢͢Ί঎඼ͷҰཡ ‣ recommended ϑϥά͕trueͷItemͷҰཡ ‣ (recommendedϑϥά͕trueͷ)ItemͷҰཡ ‣ (৚݅ͰϑΟϧλ͞Εͨ)ϦιʔεͷίϨΫγϣϯ ‣

    (৚݅ͰϑΟϧλ͞Εͨ͏͑Ͱɺ໊લΛ͚ͭΔՁ஋ͷ͋Δ)
 ϦιʔεͷίϨΫγϣϯ ϦιʔεΛઃܭ͢Δ͓͢͢Ί঎඼Ұཡͱ͸ 
  41. $ 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. $ 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. --- 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. $ 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. ‣ 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. --- 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. --- /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. $ 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. --- /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. 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. ‣ config/routes.rb Ͱ root 'portal#show' ͱࢦఆ͢ΔͱΞϓϦέʔγϣϯϧʔτ('/') ΞΫγϣϯΛࢦఆͰ͖Δɻ ‣ ͜ͷ৔߹portal#show ‣

    τοϓϖʔδ͸ɺখ͍͞ΞϓϦέʔγϣϯͰ͸ओཁΤϯςΟςΟ(ΫοΫύου Ͱ͍͏Ϩγϐ)ͷindexͰදݱ͞ΕΔ͜ͱ͕ଟ͍ɻ ‣ ͦͦ͜͜ͷن໛ʹͳΔͱɺʮಋઢͷ͋ͭ·ΓͰ͋Δτοϓϖʔδʯͱ͍͏Ϧιʔ εʹͳ͍ͬͯ͘ɻ ‣ ͦͷ৔߹ʮϙʔλϧϦιʔεʯͱͯ͠੾Γग़͢͜ͱ͕ଟ͍ [ཁग़య] ઃܭΛߟ͑ΔϙʔλϧϦιʔεͷಋೖ 
  52. --- 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. --- /dev/null +++ b/app/controllers/portal_controller.rb @@ -0,0 +1,5 @@ +class PortalController

    < ApplicationController + def show + @categories = Category.all + end +end ղ౴ྫ 
  54. --- /dev/null +++ b/app/views/portal/show.html.haml @@ -0,0 +1,3 @@ +%ul.categories +

    - @categories.each do |category| + %li= category.name ղ౴ྫ 
  55. --- 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. --- 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. $ 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. ‣ ʮ঎඼͸1ͷΧςΰϦʹଐ͢Δʯ ‣ Item belongs_to :category, optional: true ‣ item͕΋͍ͬͯΔcategory_idʹΑͬͯɺଐ͢Δ1ͭͷΧς

    ΰϦΛࢀর͢Δ ‣ ΧςΰϦΛࢀর͢ΔͨΊͷ#categoryͱ͍͏ϝιουͳͲ ͕࢖͑ΔΑ͏ʹͳΔ ‣ ΧςΰϦʹଐ͞ͳ͍͜ͱ΋Ͱ͖ΔͷͰɺoptional: trueΦϓ γϣϯΛ෇͚Δ ଟରPSରͷʮଐ͢Δʯؔ࿈ੑ 
  59. ‣ ʮΧςΰϦ͸ɺෳ਺ͷ঎඼Λॴ༗͍ͯ͠Δʯ ‣ Category has_many :items ‣ item͕΋͍ͬͯΔcategory_idʹΑͬͯɺॴ༗͢Δn ݸͷ঎඼Λࢀর͢Δ ‣

    ෳ਺঎඼Λࢀর͢ΔͨΊͷ#itemsͱ͍͏ϝιουͳ Ͳ͕࢖͑ΔΑ͏ʹͳΔ ରଟͷʮॴ༗͢Δʯؔ࿈ੑ 
  60. # 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. --- /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. --- 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. --- 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. --- 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. --- 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. $ git clone [email protected]: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. $ 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. ߟ͑Δʮ஫จʯͷσʔλߏ଄  ඼໨ ਺ྔ ୯Ձ খܭ แஸ   

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

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

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

      ು      ߹ܭ   ͝஫จ ഑ૹઌܙൺणΨʔσϯϓϨΠε'NPSP༷
  72. ߟ͑ΔJEҎ֎Ͱಋग़Ͱ͢΂͖΋ͷΛ෼͚Δ /'  ஫จJE ঎඼JE ਺ྔ খܭ   

           JE ސ٬JE ஫จ೔    JE ඼໨ ୯Ձ  แஸ    ು   JE ஫จऀ ॅॴ  NPSP ܙൺणΨʔσϯϓϨΠε
  73. ςʔϒϧ໊ɺΧϥϜ໊ΛܾΊΔ  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
  74. $ 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ʹͳΔ 
  75. # 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 ஫จͷγφϦΦΛߟ͑Δ 
  76. %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 ʹߪೖϑΥʔϜΛ଍͢ 
  77. # 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) 
  78. --- 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 ղ౴ྫ 
  79. --- /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 ղ౴ྫ 
  80. --- /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 ղ౴ྫ 
  81. --- /dev/null +++ b/app/models/line_item.rb +class LineItem < ApplicationRecord + belongs_to

    :order + belongs_to :item + + def subtotal + item.price * quantity + end +end ղ౴ྫ 
  82. --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,5 @@ +class OrdersController

    < ApplicationController + def show + @order = Order.find(params[:id]) + end +end ղ౴ྫ 
  83. --- /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 ղ౴ྫ 
  84. ‣ 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ʹͳΓɺ஫จ ৄࡉʹભҠ͢Δ ࣍ͷ໨ඪΛߟ͑Δ 
  85. ‣ 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. 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) 
  87. --- 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 ղ౴ྫ 
  88. --- 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 ղ౴ྫ 
  89. --- 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 
  90. ‣ [done] ঎඼ͷҰཡΛදࣔͰ͖Δ ‣ [done] Φεεϝ঎඼ͷҰཡΛදࣔͰ͖Δ ‣ [done] ΧςΰϦ෼͚͞Εͨ঎඼ͷҰཡΛදࣔͰ͖Δ ‣

    [done] ঎඼Λ஫จͰ͖Δ ‣ ֤ػೳΛ+40/"1*Ͱఏڙ͢Δ ΍Γ͍ͨ͜ͱϦετ: one more thing 
  91. $ 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Ͱ͍͍ͩͨͰ͖ͯͨ 
  92. --- 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Λमਖ਼ 
  93. $ 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΋௥ՃͰ͖ͯͦ͏ 
  94. ‣ categories#index ʹ͓͍ͯ΋ɺΧςΰϦҰཡͷJSONϨεϙϯεΛฦͤΔΑ͏ʹ ίϯτϩʔϥͱϏϡʔΛ௥Ճ͠ͳ͍͞ɻ ‣ ֤ΧςΰϦ͸ɺid, name, image_url, category_items_urlΛ࣋ͭ͜ͱ ‣

    λΠϜελϯϓ͸ෆཁ: ΧςΰϦΛͭͬͨ͘λΠϜελϯϓ͸͋·Γҙຯͳ ͍? ‣ url΋ෆཁ: ΧςΰϦৄࡉͱ͍͏Ϧιʔε΋ΞϓϦ͔Β࢖͍Έͪͳͦ͞͏ ࣮श෼ 
  95. --- /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 ղ౴ྫ 
  96. { "line_items": [ { "item_id": 8, "quantity": 1 }, {

    "item_id": 15, "quantity": 2 } ] } ஫จϦΫΤετͷྫ 
  97. # 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 
  98. { "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" } } ] } Ϩεϙϯεͷαϯϓϧ 
  99. --- 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͢Δ৔߹ 
  100. --- 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Ͱ΍Δͱ͖͸ϦΫΤετݩݕূΛͪΌΜͱ΍Ζ͏