- 投稿日:2021-06-15T22:16:32+09:00
【Rails】カートモデルの代わりにsessionを使ってカート機能を実装する
はじめに 簡単な自己紹介 はじめまして、shunと申します。 善良で健康な23歳です。 現在、「私立探求学園」というプログラミングスクールの「Rails共同開発プログラム」に2期生として参加しております。 プログラミング歴は執筆時点で3ヶ月ほどです。 Qiitaに記事を投稿するのは今回が初めてになります。 そのため、専門性に欠ける部分や読みづらい点が多々あるかとは思いますがご容赦ください。 本記事の内容 カート機能を実装する ECサイトでよく目にするカート機能を実装します。 今回実装するカート機能では以下の事が可能です。 本稿では主にコントローラに焦点を当てて解説します。 ① 「カートへ」ボタン押下で商品をカートに入れる。 ② カート内商品を表示する。 ③ カート内商品の個数変更1、商品削除ができる。 ④ 「購入する」ボタンを押下でカート内を空にして購入完了画面を表示する。 ⑤ 購入完了画面で注文番号を表示する。 本記事の特徴 本記事では、カート内で商品情報を出し入れする際にはカートモデルを使用しません。 その代わりに、sessionに商品情報を保存し、運用します。 sessionを使う理由 そもそもカート情報をDBに保存しなくてよいのでは?? 「カート機能の実装にはカートモデルを作ること」という前提が多くの記事で見られるかと思います。では、実際にカートモデルの情報をどう扱うのか考えてみると、 「あるユーザーのカート内に何の商品が入っているか・いたかを表示する」 くらいしか僕には思いつきませんでした。 「何の商品が入っているか」についてはsession機能で事足りますし、「いたか」についてはそもそもユーザーからの需要が感じられません。 Amazonや楽天市場を見ていただければ一目瞭然ですが、大手ECサイトでも過去にカートに何の商品が入っていたか確認できるページはおそらく存在しないはずです。注文履歴は必須ですが、カート履歴は需要ないのでは??と考え、カート情報を保存するモデルは不要だと判断しました。 ストレージの節約 sessionを使ってカート機能を実装するとカート内の商品情報をDBに保存しなくて済むため、ストレージ容量の浪費をその分抑えることができます。サーバーのレンタル料にも直結するので、できるだけ無駄は省きたいですよね。。 参考記事 Qiita マークダウン記法 一覧表・チートシート 【Laravel】誰でも出来る!sessionを使用したカート機能実装の完全解説!! - 前編 - Rails5でカート機能を作るためのロジックを作ってみた Ruby 配列関連のいろいろ instance method Time#to_i RubyのString入門 環境 ruby 2.7.1 rails 6.0.3.6 bootstrap 5.0.0.beta3 完成イメージ ①)「カートへ」ボタン押下で商品をカートに入れる。 ユーザーは「商品詳細ページ」から「カートへ」のボタン押下で任意の個数の商品をカートに入れることが可能です。 ②) カート内商品を表示する。 ③) カート内商品の数量変更、商品削除ができる。 ①の「カートへ」ボタン押下で「カート内商品一覧画面」に遷移します。 一覧ページでは、カート内商品の確認、数量変更、商品削除、注文確定などが可能です。2 ④) 「購入する」ボタンを押下でカート内を空にして購入完了画面を表示する。 ⑤) 購入完了画面で注文番号を表示する。 「注文を確定する」ボタンを押すと注文が確定されます。 その間はカート内の商品が空になり注文番号が表示され、その後「購入完了画面」に遷移します。 前準備 user(ユーザー)がproduct(商品)を購入すると、order(注文)が発生し、orderにはorder_details(注文詳細)が紐付きます。 (1)モデル作成 userモデル、 productモデル、 orderモデル、 order_detailモデル productモデルには商品名や価格、カテゴリーid(商品をカテゴリーに分類する際に使用)などが、 orderモデルには注文日時や注文番号などが、 order_detailsモデルには商品名、注文個数、発送日など、注文の詳細なデータが入ります。 (2)helperメソッドのcurrent_userの定義 以下のようにcurrent_userメソッドを定義しておくと非常に便利です。 app/helpers/sessions_helper.rb module SessionsHelper . . . def current_user @current_user ||= User.find_by(id: session[:user_id]) end . . . end 完成形 routes周り routes Rails.application.routes.draw do . . . get 'carts/show', to: 'carts#show', as: 'carts_show' post 'carts/add_cart', to: 'carts#add_cart' patch 'carts/change_quantity', to: 'carts#change_quantity', as: 'change_item_quantity' delete 'carts/destroy_carts_item', to: 'carts#destroy_carts_item', as: 'destroy_carts_item' get '/perchase_completed/:id', to: 'orders#perchase_completed', as: 'perchase_completed' resources :products resources :users resources :orders end view周り (尺の都合上、本題から外れる部分を除いています) app/views/carts/show.html.erb(商品詳細画面) ・ ・ ・ <% if @cart.blank? %> <h1 class="text-center">カートに商品はありません</h1> <% else %> ・ ・ ・ <tbody> <% @cart.each.with_index(1) do |cart, i| %> <tr class="text-center"> <th class="align-middle"> <%= i %> </th> <td class="align-middle"> <%= cart[:name] %> </td> <td class="align-middle"> <%= cart[:category_name] %> </td> <td class="align-middle"> <%= cart[:price] %> 円 </td> <% #数量変更 %> <%= form_with url: change_item_quantity_path, method: :patch, local: true do |f| %> <td class="align-middle"> <%= f.number_field :quantity, value: cart[:quantity], min: 1, class: "btn btn-outline-dark"%> 個 </td> <td class="align-middle"> <%= cart[:sub_total] %> 円 </td> <td> <%= hidden_field_tag :product_id, cart[:product_id] %> <%= f.submit "更新", class: "btn btn-primary" %> </td> <% end %> <% #商品削除 %> <%= form_with url: destroy_carts_item_path, method: :delete, local: true do |f| %> <td class="border-0 align-middle"> <%= hidden_field_tag :product_id, cart[:product_id] %> <%= f.submit "削除", class: "btn btn-danger" %> </td> <% end %> </tr> <% end %> <tr class="text-center"> <td class="border-bottom-0 align-right"> 合計 </td> <td class="border-bottom-0 align-right"> <%= @cart_total_price %>円 </td> </tr> ・ ・ ・ </tbody> ・ ・ ・ <% end %> ・ ・ ・ app/views/orders/perchase_completed.html.erb(購入完了画面) <main> <div class="blockquote mt-5 text-center"> <h1 style="font-weight: bolder">購入完了しました</h1> </div> <div class="container-sm mt-5 text-center"> <p class="h1">ご購入ありがとうございます!</p> <p class="h1"> <%= @display_number %> </p> </div> <div class="container text-center mt-5"> <%= link_to "Topに戻る", root_path, class: "btn btn-primary mt-5" %> </div> </main> コントローラ周り app/controllers/carts_controller.rb class CartsController < ApplicationController def show return if session[:cart].blank? @cart = [] session[:cart].each do |cart| product = Product.find_by(id: cart["product_id"]) sub_total = product.price * cart["quantity"].to_i next unless product @cart.push({ product_id: product.id, name: product.product_name, category_name: product.category.category_name, price: product.price, quantity: cart["quantity"].to_i, sub_total: sub_total }) end @cart_total_price = cart_total_price(@cart) end def add_cart # session内の商品の有無で条件分岐 if session[:cart].blank? # 商品が入っていない場合 session[:cart] = [{ product_id: params["product_id"], quantity: params["quantity"].to_i }] return redirect_to carts_show_path end # 商品が既に入っている場合、追加する商品が重複するかで条件分岐 match = session[:cart].select {|cart| cart["product_id"] == params["product_id"] } # 重複が発生する場合 if match.present? match[0]["quantity"] += params["quantity"].to_i # 重複が発生しない場合 else session[:cart].push({ product_id: params["product_id"], quantity: params["quantity"].to_i }) end redirect_to carts_show_path end # カート内商品の数量変更 def change_quantity array_index = session[:cart].each_index.select {|i| session[:cart][i]["product_id"] == params["product_id"] } session[:cart][array_index[0]]["quantity"] = params["quantity"] redirect_to carts_show_path end # カート内商品の削除 def destroy_carts_item array_index = session[:cart].each_index.select {|i| session[:cart][i]["product_id"] == params["product_id"] } session[:cart].delete_at(array_index[0]) redirect_to carts_show_path end # カート内商品の合計金額の計算 def cart_total_price(cart) cart.sum { _1[:sub_total] } end end app/controllers/orders_controller.rb class OrdersController < ApplicationController . . . def create if session[:cart].blank? return redirect_to carts_show_path end order = current_user.orders.create!( order_date: Time.current, order_number: ランダムな数字を16桁出す, ) session[:cart].each do |cart| order.order_details.create( product_id: cart["product_id"], shipment_status_id: 1, order_detail_number: "#{cart["product_id"]}#{complex_data}".rjust(16, "0"), order_quantity: cart["quantity"], ) end session[:cart].clear redirect_to perchase_completed_path(order.id) end def perchase_completed @display_number = Order.find_by(id: params[:id]).order_number end . . . end 注文番号について 3 作成手順 ① 「カートへ」ボタン押下で商品をカートに入れる。 について見ていきます。 以下のような流れです。 商品詳細ページ(products/:id)で任意の個数を入力して「カートへ」ボタンを押下 ↓ 分岐)session[:cart]内は空? true → session[:cart]にそのまま追加 「カート内商品一覧ページ」にリダイレクト false → 分岐)商品を追加することで重複する? true → 既にカート内にある商品に入力値を加算 false → カートに追加 「カート内商品一覧ページ」にリダイレクト ※sessionに商品情報を格納するイメージを簡単に説明します。 session[:cart]に {商品id: params["商品id"], 個数: params[個数]} のハッシュのセットを追加して配列を作成します。 以下のようなイメージです。 session内部のイメージ session[:cart] = [{"商品id"=>"1", "個数"=>4}, {"商品id"=>"8", "個数"=>7}, {"商品id"=>"4", "個数"=>1}, ... ] 流れを詳しく見ていきます。 まずは商品詳細ページについてですが、 個数入力欄と「カートへ」の箇所をformタグを適用させます。 「カートへ」ボタンはf.submitで設置しますが、その際に@poduct.idを:product_idとしてparamsに渡すためにhidden_field_tagを追加します。 ボタン押下でカートコントローラのadd_cartメソッドが走ります。 app/views/products/show.html.erb <%= form_with url: carts_add_cart_path, method: :post, local: true do |f| %> <%= f.label :quantity, "購入個数", class: "pur-number-box"%>> <%= f.number_field :quantity, min: 1, class: "pur-number" %>> <%= hidden_field_tag :product_id, @product.id %> <%= f.submit "カートへ", class: "cart-button" %>> <% end %> add_cartsが走るためのroutes Rails.application.routes.draw do . . . post 'carts/add_cart', to: 'carts#add_cart' . . . resources :products resources :users resources :orders end add_cartメソッドが走った段階でparamsには商品id(product_id)と個数(quantity)が渡されています。 (商品idが"1"である商品「りんご」を10個選択した状態で、add_cartメソッドに渡されたparamsをコンソール上で取得した画像) "product_id"=>"1","quantity"=>"10"の表記が確認できます。 次は、cartsコントローラのadd_cartメソッド内部についてです。 前提として、既にカートに追加されている商品と同じ商品をカートに追加しようとした場合(重複している場合)、加算処理が必要になります。 cartsコントローラ内のadd_cartメソッド def add_cart # session内の商品の有無で条件分岐 if session[:cart].blank? # 商品が入っていない場合 session[:cart] = [{ product_id: params["product_id"], quantity: params["quantity"].to_i }] return redirect_to carts_show_path end # 商品が既に入っている場合、追加する商品が重複するかで条件分岐 match = session[:cart].select {|cart| cart["product_id"] == params["product_id"] } # 重複が発生する場合 if match.present? match[0]["quantity"] += params["quantity"].to_i # 重複が発生しない場合 else session[:cart].push({ product_id: params["product_id"], quantity: params["quantity"].to_i }) end redirect_to carts_show_path end 条件分岐が2つあるので、前半後半に分けてみていきます。 まずは前半からです。 blank?メソッドを使って、session[:cart]が空かどうかを判断します。 空の場合は素直にsession[:cart] に [{ product_id: params["product_id"], quantity: params["quantity"].to_i }]を代入し、 カート内商品一覧画面にリダイレクトさせます。 cartsコントローラ内のadd_cartメソッド def add_cart # session内の商品の有無で条件分岐 if session[:cart].blank? # 商品が入っていない場合 session[:cart] = [{ product_id: params["product_id"], quantity: params["quantity"].to_i }] return redirect_to carts_show_path end . . . end 次は後半を見ていきます。 空でない場合、商品が重複するか否かで条件分岐させますが、その前に条件と一致しているか否かを示す変数を定義します。変数をmatchとし、 match = session[:cart].select {|cart| cart["product_id"] == params["product_id"] } とします。 ここでは、paramsに渡された商品idと同一の商品をsession[:cart]の中からselectメソッドで特定しようとしています。なので、特定でき、matchに値が返る場合重複していることを意味し、特定できずに値が返らない場合重複していないことを意味します。。 それを踏まえ、条件分岐を見ていきます。 present?メソッドを使ってmatchの中身があるかどうかを見ています。 trueの場合、重複しているので加算処理を行い、match[0]["quantity"]にparamsで取得した値を加算して返しています。 falseの場合、pushメソッドでsession[:cart]にparamsで取得した商品idと数量をそのまま格納します。 条件分岐後、カート内商品一覧ページにリダイレクトさせます。 cartsコントローラ内のadd_cartメソッド def add_cart . . . # 商品が既に入っている場合、追加する商品が重複するかで条件分岐 match = session[:cart].select {|cart| cart["product_id"] == params["product_id"] } # 重複が発生する場合 if match.present? match[0]["quantity"] += params["quantity"].to_i # 重複が発生しない場合 else session[:cart].push({ product_id: params["product_id"], quantity: params["quantity"].to_i }) end redirect_to carts_show_path end 次は ②カート内商品一覧ページを表示する。 についてです。 まず@cartに商品情報が格納されているかどうかで条件分岐します。 (@cartについての説明はコントローラの方でします。) if @cart.blank? で 格納されていない場合、 「カートに商品はありません」 とviewで表示するのみです。 格納されている場合、else内の処理が行われます。 carts/show.html.erb(カート内商品一覧画面) ・ ・ ・ <% if @cart.blank? %> <h1 class="text-center">カートに商品はありません</h1> <% else %> ・ ・ ・ <tbody> <% @cart.each.with_index(1) do |cart, i| %> <tr class="text-center"> <th class="align-middle"> <%= i %> </th> <td class="align-middle"> <%= cart[:name] %> </td> <td class="align-middle"> <%= cart[:category_name] %> </td> <td class="align-middle"> <%= cart[:price] %> 円 </td> <% #数量変更 %> <%= form_with url: change_item_quantity_path, method: :patch, local: true do |f| %> <td class="align-middle"> <%= f.number_field :quantity, value: cart[:quantity], min: 1, class: "btn btn-outline-dark"%> 個 </td> <td class="align-middle"> <%= cart[:sub_total] %> 円 </td> <td> <%= hidden_field_tag :product_id, cart[:product_id] %> <%= f.submit "更新", class: "btn btn-primary" %> </td> <% end %> <% #商品削除 %> <%= form_with url: destroy_carts_item_path, method: :delete, local: true do |f| %> <td class="border-0 align-middle"> <%= hidden_field_tag :product_id, cart[:product_id] %> <%= f.submit "削除", class: "btn btn-danger" %> </td> <% end %> </tr> <% end %> <tr class="text-center"> <td class="border-bottom-0 align-right"> 合計 </td> <td class="border-bottom-0 align-right"> <%= @cart_total_price %>円 </td> </tr> ・ ・ ・ </tbody> ・ ・ ・ <% end %> ・ ・ ・ 肝心の@cartについてです。 session[:cart]に保存する段階では、[商品idと個数]の組合せだけで良かったのですが、カート内商品一覧ページでは商品の詳細な情報を表示する必要があります。そのため、session[:cart]に保存されている各商品の[商品idと個数]の配列に対しeachメソッドを使って取り出し、空の配列@cartに詳細な商品情報が反映された配列を新しく作って表示させます。 cartsコントローラ内のadd_cartメソッド def show return if session[:cart].blank? @cart = [] session[:cart].each do |cart| product = Product.find_by(id: cart["product_id"]) sub_total = product.price * cart["quantity"].to_i next unless product @cart.push({ product_id: product.id, name: product.product_name, category_name: product.category.category_name, price: product.price, quantity: cart["quantity"].to_i, sub_total: sub_total }) end @cart_total_price = cart_total_price(@cart) end . . . end viewの説明に戻ります。viewでも同様、@cartに保存されている配列をeachで取り出します。この際、各商品の行番号がほしいので、with_indexのオプションを追加しています。 これで②番が完了です。 次は ③カート内商品の数量変更、商品削除ができる。 についてです。 数量変更と商品削除はそれぞれmethodが違うので、それぞれ別のルーティングを設定し、formタグで値を渡してあげます。 routesは上が数量変更で下が商品削除です。 数量変更と商品削除に関してのroutes patch 'carts/change_quantity', to: 'carts#change_quantity', as: 'change_item_quantity' delete 'carts/destroy_carts_item', to: 'carts#destroy_carts_item', as: 'destroy_carts_item' 数量変更と商品削除についてのview <% #数量変更 %> <%= form_with url: change_item_quantity_path, method: :patch, local: true do |f| %> <td class="align-middle"> <%= f.number_field :quantity, value: cart[:quantity], min: 1, class: "btn btn-outline-dark"%> 個 </td> <td class="align-middle"> <%= cart[:sub_total] %> 円 </td> <td> <%= hidden_field_tag :product_id, cart[:product_id] %> <%= f.submit "更新", class: "btn btn-primary" %> </td> <% end %> <% #商品削除 %> <%= form_with url: destroy_carts_item_path, method: :delete, local: true do |f| %> <td class="border-0 align-middle"> <%= hidden_field_tag :product_id, cart[:product_id] %> <%= f.submit "削除", class: "btn btn-danger" %> </td> <% end %> 数量変更と商品削除のコントローラ # カート内商品の数量変更 def change_quantity array_index = session[:cart].each_index.select {|i| session[:cart][i]["product_id"] == params["product_id"] } session[:cart][array_index[0]]["quantity"] = params["quantity"] redirect_to carts_show_path end # カート内商品の削除 def destroy_carts_item array_index = session[:cart].each_index.select {|i| session[:cart][i]["product_id"] == params["product_id"] } session[:cart].delete_at(array_index[0]) redirect_to carts_show_path end まずは数量変更について見ていきます。 「個数」の入力欄(f.number_field)に任意の数字を入力し、「更新」ボタンを押すと個数が変更されます。 「更新」ボタンはf.submitで設置しますが、その際にcart[:product_id]をparamsに:product_idとして渡すためにhidden_field_tagを追加します。 数量変更のパス patch 'carts/change_quantity', to: 'carts#change_quantity', as: 'change_item_quantity' メソッド内部について見ていきます。 array_index = session[:cart].each_index.select {|i| session[:cart][i]["product_id"] == params["product_id"] } では、each_indexメソッドとselectメソッドを使っています。 https://www.sukerou.com/2018/06/blog-post_80.html 数量を変更したい商品のidと同一の商品をsession[:cart]内のからselectで指定し、each_indexで該当商品があった配列のindexを取得してarray_indexに代入しています。 次の行では、 配列のindexが入ったarray_indexを配列番号に当たる箇所に持ってきて、paramsの["quantity"]をsession[:cart]の["quantity"]に代入します。 そして最後に同ページにリダイレクトします。 数量変更のメソッド # カート内商品の数量変更 def change_quantity array_index = session[:cart].each_index.select {|i| session[:cart][i]["product_id"] == params["product_id"] } session[:cart][array_index[0]]["quantity"] = params["quantity"] redirect_to carts_show_path end これで数量は変更できるはずです。 続いて商品削除について見ていきます。 流れは数量変更に近いため、重複している箇所は端折りながら説明していきます。 まず、数量変更同様にhidden_field_tagを追加し、paramsに:product_idを渡すように設定します。 商品削除のview <% #商品削除 %> <%= form_with url: destroy_carts_item_path, method: :delete, local: true do |f| %> <td class="border-0 align-middle"> <%= hidden_field_tag :product_id, cart[:product_id] %> <%= f.submit "削除", class: "btn btn-danger" %> </td> <% end %> 「削除ボタン」押下でcartsコントローラのdestroy_carts_itemメソッドに飛びます。 商品削除のパス delete 'carts/destroy_carts_item', to: 'carts#destroy_carts_item', as: 'destroy_carts_item' メソッド内部では、 数量変更の時同様、selectメソッド、each_indexメソッドを使い、変数array_indexに該当の配列番号を代入します。 そして次の行で、session[:cart]のarray_indexを持つ配列をdelete_atメソッドで削除したのちに同ページにリダイレクトします。 商品削除 # カート内商品の削除 def destroy_carts_item array_index = session[:cart].each_index.select {|i| session[:cart][i]["product_id"] == params["product_id"] } session[:cart].delete_at(array_index[0]) redirect_to carts_show_path end これで商品を削除できるはずです。 次は ④「購入する」ボタンを押下でカート内を空にして購入完了画面を表示する。 についてです。 「注文を確定する」ボタン押下 ↓ orderコントローラのcreateメソッドが走り、session[:cart]を反映したorderデータとorder_detailsデータを作成 ↓ session[:cart]を空にする ↓ 購入完了画面に遷移 詳しく見ていきます。 まず、link_toメソッドを使って、「注文確定ボタン」を押すと、ordersコントローラのcreateメソッドに飛ぶように設定します。 app/views/carts/show.html.erbの「注文を確定する」ボタン . . . <td class="border-0"> <%= link_to "注文を確定する", orders_path, method: :post, class: "btn btn-primary"%> </td> . . . ルーティングの設定はresourcesで事足りるように設定しています。 order#createに対応するroute Rails.application.routes.draw do . . . resources :orders end createメソッド内部を詳しく見ていきます。 まず、blank?メソッドでsession[:cart]が空かどうかで条件分岐させます。 空の場合、returnで値を返してそれ以降無駄な処理が走らないようにしています。 次にuser_idに紐づくorderをcreateで作成し、その後、orderに紐づくorder_detailsをカート内の各商品ごとにeachメソッドを使って取得し、それぞれにcreateを使って作成します。 orderコントローラ内のcreateメソッド class OrdersController < ApplicationController . . . def create if session[:cart].blank? return redirect_to carts_show_path end order = current_user.orders.create!( order_date: Time.current, order_number: ランダムな数字を16桁出す, ) session[:cart].each do |cart| order.order_details.create( product_id: cart["product_id"], shipment_status_id: 1, order_detail_number: ランダムな数字を16桁出す, order_quantity: cart["quantity"], ) end session[:cart].clear redirect_to perchase_completed_path(order.id) end . . . end ※例) りんご(商品idが1)の商品を10個、 食パン(商品idが2)の商品を20個 がカートに入っている状態で「注文を確定する」ボタンを押すと、画像のようなorderデータ、order_detailsデータがDBに作成されます。 その後、clearメソッドでsession[:cart]を空にします。 購入完了画面にリダイレクトします。(ここで⑤で使うorder.idを渡しています。) 最後に ⑤購入完了画面で注文番号を表示する。 についてです。 ④でsession[:cart].clearが動いた後、購入完了画面にリダイレクトし、注文番号が表示されます。 以下のような流れになります。 購入完了画面にリダイレクト ↓ 購入完了画面のviewが表示され、インスタンス変数の@display_numberの定義元を辿る。 ↓ 定義元のorderコントローラ内のperchase_completedメソッドが働き、該当する注文番号を表示する。 詳しく見ていきます。 session[:cart].clearでカートを空にした後、 redirect_to perchase_completed_path(order.id)で購入完了画面にリダイレクトする際に、order.idを渡します。 次に購入完了画面のviewが表示され、@displayの定義元であるorderコントローラが走ります。 app/views/orders/perchase_completed.html.erb(購入完了画面) <main> <div class="blockquote mt-5 text-center"> <h1 style="font-weight: bolder">購入完了しました</h1> </div> <div class="container-sm mt-5 text-center"> <p class="h1">ご購入ありがとうございます!</p> <p class="h1"> <%= @display_number %> </p> </div> <div class="container text-center mt-5"> <%= link_to "Topに戻る", root_path, class: "btn btn-primary mt-5" %> </div> </main> orderコントローラのperchase_completedメソッドへ。 渡されたorder.idに紐づくorder_numberを特定して@display_numberに代入します。 これでviewに適切なorder_numberが@display_numberとして表示されます。 orderコントローラ内のperchase_completedメソッド class OrdersController < ApplicationController . . . def perchase_completed @display_number = Order.find_by(id: params[:id]).order_number end . . . end これで⑤が完了し、晴れて①~⑤の機能を兼ね備えたカート機能の完成です! 最後まで読んでいただき、ありがとうございました! ※訂正箇所など教えていただけると泣いて喜びます、、 本稿では個数と数量は同じような意味として扱っています。 ↩ 「買い物を続ける」ボタンを押下で商品検索画面に遷移可能ですが、商品検索画面はカート機能の対象外であるため説明は割愛させていただきます。 ↩ 一意な注文番号を作成するロジックについては割愛させていただきます。 ↩
- 投稿日:2021-06-15T21:41:05+09:00
Railsによるリクエストからレスポンスまでの流れ
Railsによるリクエストからレスポンスまでの流れについて はじめまして、新卒でWeb開発エンジニアへの就職を目指して、昨年の10月から勉強を始めている情報系大学院生のAtusと申します。 Railsで簡単な掲示板アプリは作成出来るようになりましたので 、今まで勉強したことを復習するためにRailsによるリクエストからレスポンスまでの流れを記したいと思います。 GETリクエストからレスポンスまでの流れ ①コントローラが呼ばれる 例えば静的なサイトに対しては、ブラウザのアドレスバーにhttps://localhost:3000/usersと送るとホストサーバーのポート3000番に対してusersフォルダ配下のindex.htmlファイルをhttps通信で取得してきてという内容になる。 Railsのアプリでは、サーバーに対してGETリクエスト送ると、リクエストの処理を行うコントローラが呼ばれる。 どのコントローラが呼ばれるかはroute.rbファイルに書かれてあるルーティングによって来まる。 ②データベースが参照される場合はモデルを静的なページを返す場合にはビューが呼ばれる データベースとの通信が必要な場合はコントローラがモデルを介してデータベースから必要なデータを取得する。 取得したデータを元にビューファイルを作成しクライアントに返す。 データベースを介さない静的なページを取得したい場合、コントローラから直接ビューが呼ばれ、クライアントのブラウザにレンダリングされる。 概念図 最後に 拙い説明でしたが、ご覧頂きありがとうございました。 これからも、学んだことを日々アップしていきたいと思います。
- 投稿日:2021-06-15T21:24:16+09:00
【Rails】お気に入り機能の追加
完成GIF映像 User(会員)はPost(投稿)に対して、Like(お気に入り)をすることができる。 Userは1つのPostに対して、1つしかお気に入り出来ない状態にする。 非同期通信で行えるようにする。 1.routesの編集 お気に入りの詳細などは必要ないのでresourseでルーティング作成を行う。 config/routes.rb Rails.application.routes.draw do resources :posts do resource :likes, only: [:create, :destroy] end end resoursesは、:idを付与する。 resourseは、:idを付与しない。 2.モデルの編集 user.rb has_many :posts, dependent: :destroy has_many :likes, dependent: :destroy attachment :profile_image カラム名 データ型 説明 id integer PK last_name string 苗字 first_name string 名前 profile_image_id string 画像 post.rb belongs_to :user has_many: likes attachment :post_image カラム名 データ型 説明 id integer PK title string タイトル content text 内容 post_image_id string 画像 GIF動画ではジャンルがありましたが、今回は関係ないので端折ります。 like.rb belongs_to :user belongs_to :post validates_uniqueness_of :post_id, scope: :user_id 中間テーブルとなるLikeテーブル カラム名 データ型 説明 id integer PK user_id integer FK post_id integer FK validates_uniquness_ofは属性の値が一意であることをバリデーションする。 :scope で一意制約を決めるカラムを選択。 ターミナル $ rails g model user $ rails g model post $ rails g model like //migrateファイルを編集する $ rails db:migrate 2.PostモデルにLikeに関わるメソッドを追記 post.rb class Post < ApplicationRecord def liked_by?(user) likes.where(user_id: user.id).exists? end end Postに付けられているLikeにWhereで検索をかけて、User_idが存在するかどうかを確認。 existsは条件にマッチするレコードがあれば、true。無ければfalseを返すメソッドです。 3.コントローラーの編集 likes_controller.rb class Public::LikesController < ApplicationController before_action :authenticate_user! def create @post = Post.find(params[:post_id]) //どの投稿にお気に入りするかの情報(post_id) like = @post.likes.new(user_id: current_user.id) //誰がお気に入りにしたかの情報(user_id) like.save redirect_to request_referer //直前のページへ遷移 end def destroy @post = Post.find(params[:post_id]) //どの投稿のお気に入りを削除するか(post_id) like = @post.likes.find_by(user_id: current_user.id) //誰のお気に入り情報を削除するか一件のみ取得(user_id) like.destroy redirect_to request_referer end end 4.viewの作成 views/likes/_like_btn.html.erb <% if post.liked_by?(current_user) %> <%= link_to "#{ post.likes.count }", post_likes_path(post), method: :delete, class: "fas fa-star",style:"color: #DAA520;" %> <% else %> <%= link_to "#{ post.likes.count }", post_likes_path(post), method: :post, class: "far fa-star", style:"color:black;"%> <% end %> if文でcurrent_userがお気に入りをしているかどうかを識別。 liked_by?はPost.rbに作成したメソッドですね。 #{ post.likes.count }はお気に入りのカウントを表示させます。 投稿一覧の部分に表示させてみます。 views/posts/index.html.erb <div class="container"> <div class="row"> <div class="col-md-2"> <%= link_to "投稿する", new_post_path, class:"btn btn-info btn-lg"%> </div> </div> <div class="row mt-4"> <%= render "public/posts/index", posts: @posts%> </div> </div> views/posts/_index.html.erb <% posts.each do |post|%> <div class="col-12 col-md-6 col-lg-4"> <div class="card my-2" style="width: 18rem; "> <%= link_to post_path(post) do%> <%= attachment_image_tag(post, :post_image,size:"250x200",fallback: "no_image.png",class:"card-img-top")%> <% end %> <div class="card-body"> <div class="border-bottom pb-2"> <p class="card-title font-weight-bold"><%= post.title%></p> <span>ジャンル :<p class="btn btn-light btn-sm mt-2 ml-2"><%= post.genre.name %></p></span> <div class="d-flex"> <div><%= render "public/likes/like_btn", post: post%></div> </div> </div> <p class="d-inline">from:<%= full_name(post.user)%></p> </div> </div> </div> <% end %> Bootstrapを使うと比較的簡単にレイアウトを整えます。 5.Ajax(非同期通信化)する 非同期通信とは、ページ全体の再読み込み無しでページを更新する方法です。 同期通信は、redirect_toなどを用いてサーバーとのやり取りを行ってページを表示させる方法になります。 非同期通信にすることで、レスポンスを待つ必要がなくなるのでユーザーにとって使いやすい形になります。 今回は、お気に入りする時に使ったlink_toにremote: trueを追記することでAjaxさせます。 viewにremote_trueの追記 views/likes/_like_btn.html.erb <% if post.liked_by?(current_user) %> <%= link_to "#{ post.likes.count }", post_likes_path(post), method: :delete, remote: true, class: "fas fa-star",style:"color: #DAA520;" %> <% else %> <%= link_to "#{ post.likes.count }", post_likes_path(post), method: :post, remote: true, class: "far fa-star", style:"color:black;"%> <% end %> 「どこのお気に入りを非同期するか」を判断させるため id="post_<%= post.id %>" => 「どこの投稿?」 id="like_btn" => 「お気に入りボタン」 を追加。 post.idが識別されないと、全てに対してお気に入りすることになってしまうので忘れないように。 views/posts/_index.html.erb <% posts.each do |post|%> <div class="col-12 col-md-6 col-lg-4"> <div class="card my-2" style="width: 18rem; "> <%= link_to post_path(post) do%> <%= attachment_image_tag(post, :post_image,size:"250x200",fallback: "no_image.png",class:"card-img-top")%> <% end %> <div class="card-body"> <div class="border-bottom pb-2"> <p class="card-title font-weight-bold"><%= post.title%></p> <span>ジャンル :<p class="btn btn-light btn-sm mt-2 ml-2"><%= post.genre.name %></p></span> <div id="post_<%= post.id %>" class="d-flex"> <div id="like_btn"><%= render "public/likes/like_btn", post: post%></div> </div> </div> <p class="d-inline">from:<%= full_name(post.user)%></p> </div> </div> </div> <% end %> controllerの編集 redirect_toの記述を削除。 これで同期通信ではなく、非同期通信するための準備が出来ました。 likes_controller.rb def create @post = Post.find(params[:post_id]) like = @post.likes.new(user_id: current_user.id) like.save end def destroy @post = Post.find(params[:post_id]) like = @post.likes.find_by(user_id: current_user.id) like.destroy end js.erbファイルの作成 viewで非同期化させて、controllerにrenderやredirect_toがない場合にcontroller名/アクション名.js.erbを読みに行きます。 なので、今回はapp/views/likes/create.jsとapp/views/likes/destroy.jsを作成しましょう。 create&destroy.js.erb $("#post_<%= @post.id%> #like_btn").html("<%= j(render 'public/likes/like_btn', post: @post )%>"); .html()で対象の内容を変更。 j()はescape_javascript()の省略形で、js.erbファイルで部分テンプレートを呼び出す際に必要。
- 投稿日:2021-06-15T20:41:38+09:00
OpenStructを用いる オブジェクト指向設計実践ガイドより
はじめに 本題はここOpenStructからです 読者対象 読者対象はプログラミング初学者です。 といっても全くの初学者ではなく、Railsチュートリアルを終えたレベルと定義します。 オブジェクト指向設計実践ガイド オブエジェクト指向設計実践ガイドの記事は、他の方も書いてありますが、 一度に全部まとめたものが多かったです。 この記事は、オブエジェクト指向らしく、各章ごとに、単一責任に、シンプルさを意識して書きました。 余談ですが、とある弁護士が弁護士は六法全書を丸暗記してないが、各条文を見ただけで、関係ある条文、知識を思い出すそうです。 この記事は、オブジェクト指向設計実践ガイドってこんなこと書いてあったなーと思いだすための記事でもあります。 また、オブエジェクト指向設計実践ガイドを読んだことがなくても、こういうことが書いてあるとのかー、とさわりだけでも理解して頂けたら幸いです。 オブジェクト指向設計実践ガイドで得た3つのポイント リファクタリング前のコードを読み、コードの危うさを察知できる。 リファクタリングするために、より抽象的なコードの書き方を学べる。 gemなどの抽象的なコードを読解できる。 一問一答 一問一答風の構成にしています。 リファクタリング前のコードを読み、コードの危うさ、嗅覚を養っていきましょう。 その後、リファクタリング前のコードの危うさを説明し、自分なりにリファクタリングしたコードを書いてみましょう。 最後に、もう一度、リファクタリング前のコードと、リファクタリング後のコードを読み比べ、 抽象的な思考、抽象的な書き方を共に学んでいきましょう。 OpenStruct 今回はOpenStructについて説明します。 OpenStructを用いて、よりコードを簡潔にしていきましょう。 オブエジェクト指向実践ガイドの第8章 「コンポジションでオブジェクトを組み合わせる」、 この章を理解していると言えるでしょう。 リファクタリング前 class Bicycle attr_reader :size, :parts def initialize(args = {}) @size = args[:size] @parts = args[:parts] end def spares parts.spares end end require 'forwardable' class Parts extend Forwardable def_delegators :@parts, :size,:each include Enumerable def initialize(parts) @parts = parts end def spares select{|part| part.needs_spare} end end class Part attr_reader :name, :description, :needs_spare def initialize(args) @name = args[:name] @description = args[:description] @needs_spare = args.fetch(:needs_spare,true) end end module PartsFactory def self.build(config,part_class = Part,parts_class = Parts) parts_class.new( config.collect{ |part_config| part_class.new( name: part_config[0], description: part_config[1], needs_spare: part_config.fetch(2,true) ) } ) end end road_config = [ ["chain","10-speed"], ["tire_size","23"], ["tape_color","Fox"] ] mountain_config = [ ["chain","10-speed"], ["tire_size","Manitou",false ], ["rear_shock","Fox"] ] road_bike = Bicycle.new( size: 'L', parts: PartsFactory.build(road_config) ) road_bike.spares mountain_bike = Bicycle.new( size: "L", parts: PartsFactory.build(mountain_config) ) mountain_bike.spares mountain_bike.parts.size 考察 PartsFactoryモジュールのbuildメソッドが複雑ですっきりさせたいですね。 また、PartクラスとPartsFactoryモジュールが重複しています。 リファクタリング後 class Bicycle attr_reader :size, :parts def initialize(args = {}) @size = args[:size] @parts = args[:parts] end def spares parts.spares end end require 'forwardable' class Parts extend Forwardable def_delegators :@parts, :size,:each include Enumerable def initialize(parts) @parts = parts end def spares select {|part| part.needs_spare} end end require 'ostruct' module PartsFactory def self.build(config,parts_class = Parts) parts_class.new( config.collect{|part_config| create_part(part_config) } ) end def self.create_part(part_config) OpenStruct.new( name: part_config[0], description: part_config[1], needs_spare: part_config.fetch(2,true) ) end end road_config = [ ["chain","10-speed"], ["tire_size","23"], ["tape_color","Fox"] ] mountain_config = [ ["chain","10-speed"], ["tire_size","Manitou",false ], ["rear_shock","Fox"] ] road_bike = Bicycle.new( size: 'L', parts: PartsFactory.build(road_config) ) road_bike.spares mountain_bike = Bicycle.new( size: "L", parts: PartsFactory.build(mountain_config) ) mountain_bike.spares mountain_bike.parts.size OpenStruct 新たにcreate_partメソッドを作り、buildメソッドを簡潔にしました。 そして、OpenStructを用いることによって、Partクラスを全て削除できました! 後記 OpenStructのより詳しい説明を後日加筆しようか考えてます 参考文献 オブエジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 投稿日:2021-06-15T20:18:53+09:00
モジュールを適切に使う オブジェクト指向設計実践ガイド
はじめに 本題はここモジュールからです 読者対象 読者対象はプログラミング初学者です。 といっても全くの初学者ではなく、Railsチュートリアルを終えたレベルと定義します。 オブジェクト指向設計実践ガイド オブエジェクト指向設計実践ガイドの記事は、他の方も書いてありますが、 一度に全部まとめたものが多かったです。 この記事は、オブエジェクト指向らしく、各章ごとに、単一責任に、シンプルさを意識して書きました。 余談ですが、とある弁護士が弁護士は六法全書を丸暗記してないが、各条文を見ただけで、関係ある条文、知識を思い出すそうです。 この記事は、オブジェクト指向設計実践ガイドってこんなこと書いてあったなーと思いだすための記事でもあります。 また、オブエジェクト指向設計実践ガイドを読んだことがなくても、こういうことが書いてあるとのかー、とさわりだけでも理解して頂けたら幸いです。 オブジェクト指向設計実践ガイドで得た3つのポイント リファクタリング前のコードを読み、コードの危うさを察知できる。 リファクタリングするために、より抽象的なコードの書き方を学べる。 gemなどの抽象的なコードを読解できる。 一問一答 一問一答風の構成にしています。 リファクタリング前のコードを読み、コードの危うさ、嗅覚を養っていきましょう。 その後、リファクタリング前のコードの危うさを説明し、自分なりにリファクタリングしたコードを書いてみましょう。 最後に、もう一度、リファクタリング前のコードと、リファクタリング後のコードを読み比べ、 抽象的な思考、抽象的な書き方を共に学んでいきましょう。 モジュール 今回はモジュールについて説明します。 下記リファクタリング前のコードはコンポジションで作られ、十分に使えるコードです。 今回は、インスタンスの処理をモジュール使ってすっきりさせましょう。 オブエジェクト指向実践ガイドの第8章 「コンポジションでオブジェクトを組み合わせる」、 この章を一部理解していると言えるでしょう。(残りの知識は別記事に書きます) リファクタリング前 class Bicycle attr_reader :size, :parts def initialize(args = {}) @size = args[:size] @parts = args[:parts] end def spares parts.spares end end class Parts attr_reader :parts def initialize(parts) @parts = parts end def spares parts.select{|part| part.needs_spare} end end class Part attr_reader :name, :description, :needs_spare def initialize(args) @name = args[:name] @description = args[:description] @needs_spare = args.fetch(:needs_spare,true) end end chain = Part.new(name: "chain",description: "10-speed") road_tire = Part.new(name: "tire_size",description: "23") tape = Part.new(name: "tape",description: "red") mountain_tire = Part.new(name: "tire_size",description: "2.1") rear_shock = Part.new(name: "rear_shock",description: "Foc") front_shock = Part.new(name: "front_shock", description: "Manitou", needs_spare: false) road_bike_parts = Parts.new([chain,road_tire,tape]) road_bike = Bicycle.new( size: "L", parts: road_bike_parts ) road_bike.size road_bike.spares mountain_bike_parts = Parts.new([chain,mountain_tire,front_shock,rear_shock]) mountain_bike = Bicycle.new( size: "L", parts: mountain_bike_parts ) mountain_bike.size mountain_bike.spares モジュールを用いる chain = Part.new(name: "chain",description: "10-speed") road_tire = Part.new(name: "tire_size",description: "23") tape = Part.new(name: "tape",description: "red") 上記の処理をすっきりさせたいですね。 モジュールを使ってリファクタリングしてみましょう。 リファクタリング後 class Bicycle attr_reader :size, :parts def initialize(args = {}) @size = args[:size] @parts = args[:parts] end def spares parts.spares end end class Parts attr_reader :parts def initialize(parts) @parts = parts end def spares parts.select{|part| part.needs_spare} end end class Part attr_reader :name, :description, :needs_spare def initialize(args) @name = args[:name] @description = args[:description] @needs_spare = args.fetch(:needs_spare,true) end end module PartsFactory def self.build(config,part_class = Part,parts_class = Parts) parts_class.new( config.collect{ |part_config| part_class.new( name: part_config[0], description: part_config[1], needs_spare: part_config.fetch(2,true) ) } ) end end road_config = [ ["chain","10-speed"], ["tire_size","23"], ["tape_color","Fox"] ] mountain_config = [ ["chain","10-speed"], ["tire_size","Manitou",false ], ["rear_shock","Fox"] ] road_bike = Bicycle.new( size: 'L', parts: PartsFactory.build(road_config) ) road_bike.spares mountain_bike = Bicycle.new( size: "L", parts: PartsFactory.build(mountain_config) ) mountain_bike.spares モジュール モジュールを新たに作り、インスタンスの処理がすっきりしました。 注意点!! PartsFactoryモジュールはまだ改善の余地はあります。(後日、別記事で書きます) 参考文献 オブエジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 投稿日:2021-06-15T19:45:21+09:00
コンポジション オブジェクト指向設計実践ガイドより
はじめに 本題はここコンポジションからです 読者対象 読者対象はプログラミング初学者です。 といっても全くの初学者ではなく、Railsチュートリアルを終えたレベルと定義します。 オブジェクト指向設計実践ガイド オブエジェクト指向設計実践ガイドの記事は、他の方も書いてありますが、 一度に全部まとめたものが多かったです。 この記事は、オブエジェクト指向らしく、各章ごとに、単一責任に、シンプルさを意識して書きました。 余談ですが、とある弁護士が弁護士は六法全書を丸暗記してないが、各条文を見ただけで、関係ある条文、知識を思い出すそうです。 この記事は、オブジェクト指向設計実践ガイドってこんなこと書いてあったなーと思いだすための記事でもあります。 また、オブエジェクト指向設計実践ガイドを読んだことがなくても、こういうことが書いてあるとのかー、とさわりだけでも理解して頂けたら幸いです。 オブジェクト指向設計実践ガイドで得た3つのポイント リファクタリング前のコードを読み、コードの危うさを察知できる。 リファクタリングするために、より抽象的なコードの書き方を学べる。 gemなどの抽象的なコードを読解できる。 一問一答 一問一答風の構成にしています。 リファクタリング前のコードを読み、コードの危うさ、嗅覚を養っていきましょう。 その後、リファクタリング前のコードの危うさを説明し、自分なりにリファクタリングしたコードを書いてみましょう。 最後に、もう一度、リファクタリング前のコードと、リファクタリング後のコードを読み比べ、 抽象的な思考、抽象的な書き方を共に学んでいきましょう。 コンポジション 今回は第8章コンポジションについて説明します。 下記リファクタリング前のコードは継承、フックメッセージが適切に使われおり、十分に使えるコードです。 今回は、継承を使わない観点、コンポジションという観点からリファクタリングして見て下さい。 オブエジェクト指向実践ガイドの第8章 「コンポジションでオブジェクトを組み合わせる」、 この章を一部理解していると言えるでしょう。(残りの知識は別記事に書きます) リファクタリング前 class Bicycle attr_reader :size, :parts def initialize(args = {}) @size = args[:size] @parts = args[:parts] end def spares parts.spares end end class Parts attr_reader :chain, :tire_size def initialize(args={}) @chain = chain || default_chain @tire_size = tire_size || default_tire_size post_initialize(args) end def spares { tire_size: tire_size, chain: chain }.merge(local_spares) end def post_initialize(args) nil end def local_spares {} end def default_chain "10-speed" end def default_tire_size raise NotImplementedError end end class RoadBikeParts < Parts attr_reader :tape_color def post_initialize(args) @tape_color = args[:tape_color] end def local_spares {tape_color: tape_color} end def default_tire_size "23" end end class MountainBikeParts < Parts attr_reader :front_shock, :rear_shock def post_initialize(args) @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] end def local_spares {rear_shock: rear_shock} end def default_tire_size "2.1" end end road_bike = Bicycle.new(size: "L",parts: RoadBikeParts.new(tape_color: "red")) road_bike.size road_bike.spares 継承を使わない書き方 Partsクラスを各サブクラスが継承しています。 継承を使わない観点からリファクタリングしてみましょう。 リファクタリング後 class Bicycle attr_reader :size, :parts def initialize(args = {}) @size = args[:size] @parts = args[:parts] end def spares parts.spares end end class Parts attr_reader :parts def initialize(parts) @parts = parts end def spares parts.select{|part| part.needs_spare} end end class Part attr_reader :name, :description, :needs_spare def initialize(args) @name = args[:name] @description = args[:description] @needs_spare = args.fetch(:needs_spare,true) end end chain = Part.new(name: "chain",description: "10-speed") road_tire = Part.new(name: "tire_size",description: "23") tape = Part.new(name: "tape",description: "red") mountain_tire = Part.new(name: "tire_size",description: "2.1") rear_shock = Part.new(name: "rear_shock",description: "Foc") front_shock = Part.new(name: "front_shock", description: "Manitou", needs_spare: false) road_bike_parts = Parts.new([chain,road_tire,tape]) road_bike = Bicycle.new( size: "L", parts: road_bike_parts ) road_bike.size road_bike.spares コンポジション かなりすっきりしましたね! ただ、インスタンス作成の処理がごちゃごちゃしているので、 まだ改善の余地はあります。(後日、別記事で書きます) 継承使って書くか? コンポジションで書くか? 私の現在のレベルではうまく説明できません。 引用します。 一般的なルールとしては、直面した問題がコンポジションによって解決できるなら、 コンポジションで解決することを優先するべきです。(中略) 継承がより良い選択肢であるのは、継承が低いリスクで高い利益を生み出してくれるときです。 『オブジェクト指向設計実践ガイド』 p229 参考文献 オブエジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 投稿日:2021-06-15T18:57:21+09:00
フックメッセージを使う オブジェクト指向実践ガイドより
読者対象 読者対象はプログラミング初学者です。 オブエジェクト指向実践ガイドの記事は、他の方も書いてありますが、 一度に全部まとめたものが多かったです。 この記事は、オブエジェクト指向らしく、各章ごとに、単一責任に、シンプルさを意識して書きました。 リファクタリング前のコードと、リファクタリング後のコードを読み比べ、 抽象的な思考、抽象的な書き方を共に学んでいきましょう。 一問一答 一問一答風の構成にしています。 リファクタリング前のコードを読み、コードの危うさ、嗅覚を養っていきましょう。 その後、リファクタリング前のコードの危うさを説明し、自分なりにリファクタリングしたコードを書いてみましょう。 オブエジェクト思考実践ガイド 余談ですが、とある弁護士が弁護士は六法全書を丸暗記してないが、各条文を見ただけで、関係ある条文、知識を思い出すそうです。 この記事は、オブジェクト指向実践ガイドってこんなこと書いてあったなーと思いだすための記事でもあります。 また、オブエジェクトし思考実践ガイドを読んだことがなくても、こういうことが書いてあるとのかー、とさわりだけでも理解して頂けたら幸いです。 オブエジェクト思考実践ガイドで得た3つのポイント リファクタリング前のコードを読み、コードの危うさを察知できる。 リファクタリングするために、より抽象的なコードの書き方を学べる。 gemなどの抽象的なコードを読解できる。 フックメッセージ 今回は第6章フックメッセージについて説明します。 下記コードを読んで、このコードの危うさを説明し、自分なりにリファクタリングしたコードを書けるレベルに達しているなら、 オブエジェクト指向実践ガイドの第6章 「継承によって振る舞いを獲得する」、 この章を理解していると言えるでしょう。 注意点 下記コードは第6章の後半とあって、それなりにリファクタリングされています。 絶対にダメな書き方というわけではありませんので注意して下さい。 リファクタリング前 class Bicycle attr_reader :size,:chain,:tire_size def initialize(args) @size = args[:size] @chain = args[:chain] || default_chain @tire_size = args[:tire_size] || default_tire_size end def spares { tire_size: tire_size, chain: chain } end def default_chain "10-speed" end end class RoadBike < Bicycle attr_reader :tape_color def initialize(args) @tape_color = args[:tape_color] super(args) end def spares super.merge({tape_color: tape_color}) end def default_tire_size "23" end end class MountainBike < Bicycle attr_reader :front_shock,:rear_shock def initialize(args) @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] super(args) end def spares super.merge({rear_shock: rear_shock}) end def default_tire_size "2.1" end end bike = RoadBike.new(size: "L",tape_color: "red") bike.size bike.spares このコードの危うさ 新たにサブクラスが作られたとします。 このサブクラスでdefault_tire_sizeが書かれてなかったら、エラーが発生します。 また、superの書き忘れで思わぬエラーが発生します。 サブクラスどんどん増えていくにつれて、上記のエラー発生率が高くなります。 リファクタリング後 class Bicycle attr_reader :size,:chain,:tire_size def initialize(args) @size = args[:size] @chain = args[:chain] || default_chain @tire_size = args[:tire_size] || default_tire_size post_initialize(args) end def post_initialize(args) nil end def spares { tire_size: tire_size, chain: chain }.merge(local_spares) end def local_spares {} end def default_chain "10-speed" end # 適切なエラーを表示するため def default_tire_size raise NotImplementedError end end class RoadBike < Bicycle attr_reader :tape_color def post_initialize(args) @tape_color = args[:tape_color] end def local_spares {tape_color: tape_color} end def default_tire_size "23" end end class MountainBike < Bicycle attr_reader :front_shock,:rear_shock def post_initialize(args) @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] end def local_spares {rear_shock: rear_shock} end def default_tire_size "2.1" end end bike = RoadBike.new(size: "L",tape_color: "red") bike.size bike.spares フックメッセージ default_tire_sizeのエラー処理を親クラスに記載しました。 ここから本題です。 親クラスのBicycleが各サブクラスにフックメッセージを送るようにしました。(post_initialize(args)) フックメッセージを使う目的は、 サブクラスからアルゴリズムの知識を取り除き、代わりにスーパークラスに制御を戻すことです。 (オブジェクト指向実践ガイド p172) 今回実装によって、superの書き忘れによるエラーを防ぐことができます。 また各サブクラスのコードの可読性も増しました。 新しいサブクラスを作る際も、リファクタリング前より簡潔に書けるようになります。 参考文献 オブエジェクト指向実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 投稿日:2021-06-15T18:57:21+09:00
フックメッセージを使う オブジェクト指向設計実践ガイドより
はじめに 本題はここフックメッセージからです 読者対象 読者対象はプログラミング初学者です。 といっても全くの初学者ではなく、Railsチュートリアルを終えたレベルと定義します。 オブジェクト指向設計実践ガイド オブエジェクト指向設計実践ガイドの記事は、他の方も書いてありますが、 一度に全部まとめたものが多かったです。 この記事は、オブエジェクト指向らしく、各章ごとに、単一責任に、シンプルさを意識して書きました。 余談ですが、とある弁護士が弁護士は六法全書を丸暗記してないが、各条文を見ただけで、関係ある条文、知識を思い出すそうです。 この記事は、オブジェクト指向設計実践ガイドってこんなこと書いてあったなーと思いだすための記事でもあります。 また、オブエジェクト指向設計実践ガイドを読んだことがなくても、こういうことが書いてあるとのかー、とさわりだけでも理解して頂けたら幸いです。 オブジェクト指向設計実践ガイドで得た3つのポイント リファクタリング前のコードを読み、コードの危うさを察知できる。 リファクタリングするために、より抽象的なコードの書き方を学べる。 gemなどの抽象的なコードを読解できる。 一問一答 一問一答風の構成にしています。 リファクタリング前のコードを読み、コードの危うさ、嗅覚を養っていきましょう。 その後、リファクタリング前のコードの危うさを説明し、自分なりにリファクタリングしたコードを書いてみましょう。 最後に、もう一度、リファクタリング前のコードと、リファクタリング後のコードを読み比べ、 抽象的な思考、抽象的な書き方を共に学んでいきましょう。 フックメッセージ 今回は第6章フックメッセージについて説明します。 下記コードを読んで、このコードの危うさを説明し、自分なりにリファクタリングしたコードを書けるレベルに達しているなら、 オブエジェクト指向設計実践ガイドの第6章 「継承によって振る舞いを獲得する」、 この章を理解していると言えるでしょう。 注意点 下記コードは第6章の後半とあって、それなりにリファクタリングされています。 絶対にダメな書き方というわけではありませんので注意して下さい。 リファクタリング前 class Bicycle attr_reader :size,:chain,:tire_size def initialize(args) @size = args[:size] @chain = args[:chain] || default_chain @tire_size = args[:tire_size] || default_tire_size end def spares { tire_size: tire_size, chain: chain } end def default_chain "10-speed" end end class RoadBike < Bicycle attr_reader :tape_color def initialize(args) @tape_color = args[:tape_color] super(args) end def spares super.merge({tape_color: tape_color}) end def default_tire_size "23" end end class MountainBike < Bicycle attr_reader :front_shock,:rear_shock def initialize(args) @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] super(args) end def spares super.merge({rear_shock: rear_shock}) end def default_tire_size "2.1" end end bike = RoadBike.new(size: "L",tape_color: "red") bike.size bike.spares このコードの危うさ 新たにサブクラスが作られたとします。 このサブクラスでdefault_tire_sizeが書かれてなかったら、エラーが発生します。 また、superの書き忘れで思わぬエラーが発生します。 サブクラスどんどん増えていくにつれて、上記のエラー発生率が高くなります。 リファクタリング後 class Bicycle attr_reader :size,:chain,:tire_size def initialize(args) @size = args[:size] @chain = args[:chain] || default_chain @tire_size = args[:tire_size] || default_tire_size post_initialize(args) end def post_initialize(args) nil end def spares { tire_size: tire_size, chain: chain }.merge(local_spares) end def local_spares {} end def default_chain "10-speed" end # 適切なエラーを表示するため def default_tire_size raise NotImplementedError end end class RoadBike < Bicycle attr_reader :tape_color def post_initialize(args) @tape_color = args[:tape_color] end def local_spares {tape_color: tape_color} end def default_tire_size "23" end end class MountainBike < Bicycle attr_reader :front_shock,:rear_shock def post_initialize(args) @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] end def local_spares {rear_shock: rear_shock} end def default_tire_size "2.1" end end bike = RoadBike.new(size: "L",tape_color: "red") bike.size bike.spares フックメッセージ default_tire_sizeのエラー処理を親クラスに記載しました。 ここから本題です。 親クラスのBicycleが各サブクラスにフックメッセージを送るようにしました。(post_initialize(args)) フックメッセージを使う目的は、 サブクラスからアルゴリズムの知識を取り除き、代わりにスーパークラスに制御を戻すことです。 (オブジェクト指向実践ガイド p172) 今回実装によって、superの書き忘れによるエラーを防ぐことができます。 また各サブクラスのコードの可読性も増しました。 新しいサブクラスを作る際も、リファクタリング前より簡潔に書けるようになります。 参考文献 オブエジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 投稿日:2021-06-15T18:22:16+09:00
クラス継承 オブエジェクト指向実践ガイドより
読者対象 読者対象はプログラミング初学者です。 オブエジェクト指向実践ガイドの記事は、他の方も書いてありますが、 一度に全部まとめたものが多かったです。 この記事は、オブエジェクト指向らしく、各章ごとに、単一責任に、シンプルさを意識して書きました。 リファクタリング前のコードと、リファクタリング後のコードを読み比べ、 抽象的な思考、抽象的な書き方を共に学んでいきましょう。 一問一答 一問一答風の構成にしています。 リファクタリング前のコードを読み、コードの危うさ、嗅覚を養っていきましょう。 その後、リファクタリング前のコードの危うさを説明し、自分なりにリファクタリングしたコードを書いてみましょう。 オブエジェクト思考実践ガイド 余談ですが、とある弁護士が弁護士は六法全書を丸暗記してないが、各条文を見ただけで、関係ある条文、知識を思い出すそうです。 この記事は、オブジェクト指向実践ガイドってこんなこと書いてあったなーと思いだすための記事でもあります。 また、オブエジェクトし思考実践ガイドを読んだことがなくても、こういうことが書いてあるとのかー、とさわりだけでも理解して頂けたら幸いです。 オブエジェクト思考実践ガイドで得た3つのポイント リファクタリング前のコードを読み、コードの危うさを察知できる。 リファクタリングするために、より抽象的なコードの書き方を学べる。 gemなどの抽象的なコードを読解できる。 クラス継承 今回は第6章クラスによる継承について説明します。 下記コードを読んで、このコードの危うさを説明し、自分なりにリファクタリングしたコードを書けるレベルに達しているなら、 オブエジェクト指向実践ガイドの第6章 「継承によって振る舞いを獲得する」、 この章を半分理解していると言えるでしょう。(残りは後日書きます) リファクタリング前 class Bicycle attr_reader :style,:size,:tape_color,:front_shock,:rear_shock def initialize(args) @style = args[:style] @size = args[:size] @tape_color = args[:tape_color] @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] end def spares if style == :road { chain: "10-speed", tire_size: "23", tape_color: tape_color } else { chain: "10-speed", tire_size: "2.1", rear_shock: rear_shock } end end end bike = Bicycle.new( style: :mountain, size: "s", front_shock: "Manitou", rear_shock: "Fox" ) bike.spares このコードの危険性 Bicycleクラスが肥大している。 特に、機能が拡張するたびに、sparesメソッド内のif文を修正しないといけない。←バグのもと リファクタリング class Bicycle attr_reader :size def initialize(args = {}) @size = args[:size] end end class RoadBike < Bicycle attr_reader :tape_color def initialize(args) @tape_color = args[:tape_color] super(args) end def spares { chain: "10-speed", tire_size: "23", tape_color: tape_color } end end class MountainBike < Bicycle attr_reader :front_shock,:rear_shock def initialize(args) @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] super(args) end def spares super.merge(rear_shock: rear_shock) end end road_bike = RoadBike.new( size: "M", tape_color: "red" ) road_bike.spares mountain_bike = MountainBike.new( size: "s", front_shock: "Manitou", rear_shock: "Fox" ) mountain_bike.size クラスの継承 Bicycleクラスの下に、新たにサブクラスを設置することによって、 Bicycleクラスがすっきりしました! 注意点 このリファクタリング後のコードがベストではありません。 より改善できます。サブクラスを設置しない方法もあります。 あくまでリファクタリング前よりかはよくなったと捉えて下さい。 参考文献 オブエジェクト指向実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 投稿日:2021-06-15T18:22:16+09:00
クラス継承 オブエジェクト指向設計実践ガイドより
はじめに 本題はここクラス継承からです 読者対象 読者対象はプログラミング初学者です。 といっても全くの初学者ではなく、Railsチュートリアルを終えたレベルと定義します。 オブジェクト指向設計実践ガイド オブエジェクト指向設計実践ガイドの記事は、他の方も書いてありますが、 一度に全部まとめたものが多かったです。 この記事は、オブエジェクト指向らしく、各章ごとに、単一責任に、シンプルさを意識して書きました。 余談ですが、とある弁護士が弁護士は六法全書を丸暗記してないが、各条文を見ただけで、関係ある条文、知識を思い出すそうです。 この記事は、オブジェクト指向設計実践ガイドってこんなこと書いてあったなーと思いだすための記事でもあります。 また、オブエジェクト指向設計実践ガイドを読んだことがなくても、こういうことが書いてあるとのかー、とさわりだけでも理解して頂けたら幸いです。 オブジェクト指向設計実践ガイドで得た3つのポイント リファクタリング前のコードを読み、コードの危うさを察知できる。 リファクタリングするために、より抽象的なコードの書き方を学べる。 gemなどの抽象的なコードを読解できる。 一問一答 一問一答風の構成にしています。 リファクタリング前のコードを読み、コードの危うさ、嗅覚を養っていきましょう。 その後、リファクタリング前のコードの危うさを説明し、自分なりにリファクタリングしたコードを書いてみましょう。 最後に、もう一度、リファクタリング前のコードと、リファクタリング後のコードを読み比べ、 抽象的な思考、抽象的な書き方を共に学んでいきましょう。 クラス継承 今回は第6章クラスによる継承について説明します。 下記コードを読んで、このコードの危うさを考え説明して見て下さい。 そして、自分なりにリファクタリングしたコードを書いてみましょう。 この記事を理解したなら、オブエジェクト指向設計実践ガイドの第6章 「継承によって振る舞いを獲得する」、 この章を半分理解していると言えるでしょう。(残りは後日書きます) リファクタリング前 class Bicycle attr_reader :style,:size,:tape_color,:front_shock,:rear_shock def initialize(args) @style = args[:style] @size = args[:size] @tape_color = args[:tape_color] @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] end def spares if style == :road { chain: "10-speed", tire_size: "23", tape_color: tape_color } else { chain: "10-speed", tire_size: "2.1", rear_shock: rear_shock } end end end bike = Bicycle.new( style: :mountain, size: "s", front_shock: "Manitou", rear_shock: "Fox" ) bike.spares コードの危険性 Bicycleクラスが肥大している。 特に、機能が拡張するたびに、sparesメソッド内のif文を修正しないといけない。←バグのもと リファクタリング後 class Bicycle attr_reader :size def initialize(args = {}) @size = args[:size] end end class RoadBike < Bicycle attr_reader :tape_color def initialize(args) @tape_color = args[:tape_color] super(args) end def spares { chain: "10-speed", tire_size: "23", tape_color: tape_color } end end class MountainBike < Bicycle attr_reader :front_shock,:rear_shock def initialize(args) @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] super(args) end def spares super.merge(rear_shock: rear_shock) end end road_bike = RoadBike.new( size: "M", tape_color: "red" ) road_bike.spares mountain_bike = MountainBike.new( size: "s", front_shock: "Manitou", rear_shock: "Fox" ) mountain_bike.size クラスの継承 Bicycleクラスの下に、新たにサブクラスを設置することによって、 Bicycleクラスがすっきりしました! 注意点 このリファクタリング後のコードがベストではありません。 より改善できます。サブクラスを設置しない方法もあります。 あくまでリファクタリング前よりかはよくなったと捉えて下さい。 後記 コードの危険性の説明を加筆しようか検討中 参考文献 オブエジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 投稿日:2021-06-15T18:07:04+09:00
Ruby 文字列の順番を変える
Rubyで勉強したことをアウトプットします。 やりたいこと 任意の文字列の最初の2文字を取得する 取得した2文字を最後尾に移動させた状態で、その文字列を出力する 最初に作成したコード def change_str(str) first_str = str[0] second_str = str[1] len = str.length arr = [] (2..len).each do |num| arr << str[num] end puts "#{arr.join}#{first_str}#{second_str}" end change_str("Hello") # >> "lloHe" joinメソッド joinメソッドを使用することで、配列内の要素を文字列として連結することができます。 # 配列を宣言 arr = ['a', 'b', 'c'] # 配列の要素を連結して文字列として返却します p arr.join # >> 'abc' 引数を指定すると、要素ごとに引数の文字列を結合することができます。 # 配列を宣言 arr = ['a', 'b', 'c'] # 配列の要素を連結して文字列として返却します p arr.join(":") # >> 'a:b:c' 改善後のコード def change_str(str) puts str[2..-1] + str[0..1] end left2("Hello") # >> "lloHe" 今回の勉強で、配列の操作方法について学びました。
- 投稿日:2021-06-15T16:57:59+09:00
各言語のDockerコンテナでIPアドレスを解決したい場合の代替コマンド
みなさん、Dockerは使ってますか?Dockerをドカドカ使っていると、 「社内のHTTP APIを呼びたいのに、なぜかホスト名をIPアドレスに解決できない」 「本番環境のではなく開発環境のHTTP APIを呼んでしまっている(っぽい)」 なんて、ことが稀によく発生します。 一方で、定石に従ってDockerイメージを作っていると、 「Dockerイメージには最小限のバイナリしか入れていないので hostもnslookup も使えない。だからホスト名がどう解決されているかも分からない。」 なんてことも起きがち。 そこで、最小限のコンテナに docker exec した時にホスト名解決を(雑に)確認できるコマンドを紹介します。 getent glibc に含まれるコマンドです。 $ getent hosts qiita.com 2406:da14:add:902:68c3:7e8d:a9c:949e qiita.com 2406:da14:add:900:8bc:d6f3:591c:eb91 qiita.com 2406:da14:add:901:3a2b:2b27:a25f:7360 qiita.com Ruby $ ruby -rresolv -e 'puts Resolv.getaddress ARGV[0]' qiita.com 54.92.41.140 Python $ python3 -c 'import sys, socket; print(socket.gethostbyname(sys.argv[1]))' qiita.com 54.250.97.111h
- 投稿日:2021-06-15T16:57:59+09:00
Dockerコンテナ内でIPアドレスを解決したい場合の代替コマンド
みなさん、Dockerは使ってますか?Dockerをドカドカ使っていると、 「社内のHTTP APIを呼びたいのに、なぜかホスト名をIPアドレスに解決できない」 「本番環境のではなく開発環境のHTTP APIを呼んでしまっている(っぽい)」 なんて、ことが稀によく発生します。 一方で、定石に従ってDockerイメージを作っていると、 「Dockerイメージには最小限のバイナリしか入れていないので hostもnslookup も使えない。だからホスト名がどう解決されているかも分からない。」 なんてことも起きがち。 そこで、最小限のコンテナに docker exec した時にホスト名解決を(雑に)確認できるコマンドを紹介します。 getent glibc に含まれるコマンドです。 $ getent hosts qiita.com 2406:da14:add:902:68c3:7e8d:a9c:949e qiita.com 2406:da14:add:900:8bc:d6f3:591c:eb91 qiita.com 2406:da14:add:901:3a2b:2b27:a25f:7360 qiita.com Ruby $ ruby -rresolv -e 'puts Resolv.getaddress ARGV[0]' qiita.com 54.92.41.140 Python $ python3 -c 'import sys, socket; print(socket.gethostbyname(sys.argv[1]))' qiita.com 54.250.97.111h
- 投稿日:2021-06-15T15:21:26+09:00
フィボナッチ数列を求めるワンライナー各言語まとめ
お遊び記事です.こちらの記事の姉妹版です.いやその,某ラノベ読んでたらとりあえず21番目までのフィボナッチ数列を求める魔術式ワンライナーが書きたくなりましてね,はい. 制約 フィボナッチ数を求める方法はいろいろありすぎるので,次の制約を設けます. 一般項は使わない. ライブラリやモジュールの読み込みは行わない. こうすると,自ずと関数型プログラミングに近くなるかもしれません.ますます魔(略 気のせいでした. 各言語での記述例 0番目から21番目の値を求める最も短いと思われるREPL実行記述(+確認した実装・バージョン)のみを載せています.より短い表現&他言語版歓迎. CommonLisp(SBCL2.0.0),Scheme(Gauche0.9.10) (do((a 0 b)(b 1(+ a b))(l '()`(,@l ,a)))((=(length l)22)l)) Python2(CPython2.7.17) reduce(lambda x,_:x+[x[-1]+x[-2]],'_'*20,[0,1]) Python3(CPython3.7.3) g=lambda*a:len(a)<22and g(*a,a[-1]+a[-2])or a;g(0,1) Python3.8(CPython3.9.5) [a:=0,b:=1]+[(b:=a+(a:=b))for _ in'_'*20] Ruby(2.5.5) a=-b=1;(0..21).map{b=a+a=b} JavaScript(Node.js10.24.0) [a=-1,b=1,...Array(20)].map(c=>b=a+(a=b)) J言語(9.0.2) (,{:+_2&{)^:20 i.2 Haskell(GHC8.4.4) let f@(_:g)=0:1:zipWith(+)f g in take 22 f PostScript(Ghostscript9.52) 0 == 0 1 20 {0 1 2 index -1 1 {pop exch 1 index add} for == pop pop} for Smalltalk(GNUSmalltalk3.2.5) | a b | a := -1. b := 1. (0 to: 21) collect: [:n | b := a + (a := b)] Perl(5.28.1) do{@_=(0,1);push@_,$_[-2]+$_[-1]for 0..19;@_} Clojure(1.10) (take 22((fn f[](lazy-cat[0 1](map +(f)(rest(f))))))) Julia(1.0.3) 1|>(a,b=0)->0:21 .|>_->a=(b+=a)-a Forth(Gforth0.7.3) :noname 1 0 22 0 do dup . tuck + loop ; execute R(3.5.2) c(a<-0,b<-1,replicate(20,b<<-a+(a<<-b))) 備考 記事に関する補足 reduceつおい.次々と反復構文に置き換えられてしまった…. J言語の例はねっとからのぱくりです.独自には記述できなかった….→そしてそのぱくりから更に短く(コメントより).J言語にはどうあがいても短さでは勝てなさそう. Perlの記述例については,デバッガモード起動(perl -de0)のxコマンドで表示することを想定しています. 更新履歴 2021-06-16:JavaScript,Perl,Smalltalk,Python2,Python3の記述例を変更(コメントより) 2021-06-16:Forth,Rの記述例を追加(Twitterより) 2021-06-16:確認した実装・バージョンとREPL実行の旨記載 2021-06-15:Ruby,J言語の記述例を変更(Twitter,コメントより) 2021-06-15:SchemeとCommon Lispの記述例を変更・統合(Twitterより) 2021-06-15:Haskell,PostScript,Smalltalk,Perl,Clojure,Julia,Python3.8の記述例を追加(Twitter,コメントより) 2021-06-15:初版公開(Scheme,Common Lisp,Python2,Python3,Ruby,JavaScript,J言語)
- 投稿日:2021-06-15T14:34:39+09:00
最大値を配列を使わずに標準出力する方法、配列を使う方法
最大値を配列を使わずに標準出力する方法、配列を使って標準出力に出力する方法の両方を投稿します。 配列を使わない方法 N = gets.to_i max_num =0 for i in (1..N).each do num = gets.to_i if num >= max_num max_num = num end end puts max_num 配列を使う方法 n = gets.to_i arr = Array.new(n) n.times { |i| arr[i] = gets.to_i } puts arr.max 配列を使う方がシンプルにコーディングできますね。
- 投稿日:2021-06-15T13:55:44+09:00
Rails × Docker環境内でmysqlサーバーに接続するまでの流れ
SQLの学習目的で、Dockerコンテナ内でSQLサーバーに接続する際に生じたエラーの原因とその解決方法を記します。 1. コンテナ内に接続 → ERROR 2002 (HY000) ~ (2) → ERROR 2002 (HY000) ~ (111) docker-compose exec web bundle exec /bin/bash root@b671078fa536:/myapp# mysql ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2) root@b671078fa536:/myapp# mkdir /var/run/mysqld root@b671078fa536:/myapp# touch /var/run/mysqld/mysqld.sock root@b671078fa536:/myapp# mysql ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (111) ERROR 2002 (HY000) ~ (2) このエラーは、mysqlに接続する為のソケットが存在しない事に由来している(らしい)。 なので上記コードでソケットを作成している。 ERROR 2002 (HY000) ~ (111) このエラーは、mysqlが起動していないことが原因(らしい)。 $ docker-compose ps Name Command State Ports ------------------------------------------------------------------------------------------- myapp_chrome_1 /opt/bin/entry_point.sh Up 0.0.0.0:4444->4444/tcp myapp_db_1 docker-entrypoint.sh --def ... Up 0.0.0.0:3306->3306/tcp, 33060/tcp myapp_web_1 entrypoint.sh bash -c rm - ... Up 0.0.0.0:3000->3000/tcp myapp_db_1は起動しているけどな? ここで少し時間を有する。思考錯誤の結果、以下の記事を参考にしました。 コンテナ指定接続 → mysqlサーバーへログイン 結果として自分の場合は、コンテナを指定してログインする事でmysqlサーバーへ正常にログインできるようになりました。 $ docker exec -it myapp_db_1 bash # コンテナ指定でログイン root@d9ca806f810a:/# mysql -u root # mysqlサーバーにログイン Enter password: Welcome to the MySQL monitor. Commands end with ; or \g.# 正常にログイン!! Your MySQL connection id is 8 Server version: 8.0.25 MySQL Community Server - GPL Copyright (c) 2000, 2021, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | myapp_development | | myapp_test | | mysql | | performance_schema | | sys | +--------------------+ DB名を指定してログインする事で時間節約 $ docker exec -it myapp_db_1 bash root@d9ca806f810a:/# mysql -u root -p myapp_development Enter password: Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 10 Server version: 8.0.25 MySQL Community Server - GPL Copyright (c) 2000, 2021, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> select works.title, SUM(footprints.counts) as total_footprint_counts from works inner join footprints where works.id = 1; +--------------+------------------------+ | title | total_footprint_counts | +--------------+------------------------+ | Blade Runner | 1 | +--------------+------------------------+ 1 row in set (0.00 sec) やはりDocker周辺は知識不足により、難しく感じました。
- 投稿日:2021-06-15T13:19:01+09:00
[Ruby on Rails] RSpecテストの導入
1.Gem group :test do ~ endに編集。 Gemfile // ---------- // group :test do gem "capybara" gem "rspec-rails" gem "factory_bot_rails" gem "faker" end // ---------- // $ bundle $ rails g rspec:install //RSpecの準備 config/enviroments/test.rb config.active_support.deprecation = :silence // stderrからsilenceに変更する end 2.FactoryBotのデータ作成 specフォルダの直下にfactroiesフォルダを作成 user.rbを作成 spec/factories/user.rb FactoryBot.define do factory :user do last_name { Faker::Lorem.characters(number: 5) } first_name { Faker::Lorem.characters(number: 5) } email { Faker::Internet.email("kinpachi") } password { Faker::Internet.password(min_length: 6) } password_confirmation { password } end end Faker::~~に続くのは他にも色々ある。 FactoryBotが使用できるように設定。 spec/rails_helper.rb // ---------- // config.include FactoryBot::Syntax::Methods end 3.単体テストと結合テスト 単体テスト クラスやメソッド(関数)といった単位でのテスト。 空白チェック 文字列チェック 数値チェック 結合テスト 単体テストの組み合わせで期待とする動作が行えているかをテストする。
- 投稿日:2021-06-15T10:50:31+09:00
ダックタイピング オブエジェクト指向設計実践ガイドより
はじめに 本題はここダックタイピングからです 読者対象 読者対象はプログラミング初学者です。 といっても全くの初学者ではなく、Railsチュートリアルを終えたレベルと定義します。 オブジェクト指向設計実践ガイド オブエジェクト指向設計実践ガイドの記事は、他の方も書いてありますが、 一度に全部まとめたものが多かったです。 この記事は、オブエジェクト指向らしく、各章ごとに、単一責任に、シンプルさを意識して書きました。 余談ですが、とある弁護士が弁護士は六法全書を丸暗記してないが、各条文を見ただけで、関係ある条文、知識を思い出すそうです。 この記事は、オブジェクト指向設計実践ガイドってこんなこと書いてあったなーと思いだすための記事でもあります。 また、オブエジェクト指向設計実践ガイドを読んだことがなくても、こういうことが書いてあるとのかー、とさわりだけでも理解して頂けたら幸いです。 オブジェクト指向設計実践ガイドで得た3つのポイント リファクタリング前のコードを読み、コードの危うさを察知できる。 リファクタリングするために、より抽象的なコードの書き方を学べる。 gemなどの抽象的なコードを読解できる。 一問一答 一問一答風の構成にしています。 リファクタリング前のコードを読み、コードの危うさ、嗅覚を養っていきましょう。 その後、リファクタリング前のコードの危うさを説明し、自分なりにリファクタリングしたコードを書いてみましょう。 最後に、もう一度、リファクタリング前のコードと、リファクタリング後のコードを読み比べ、 抽象的な思考、抽象的な書き方を共に学んでいきましょう。 ダックタイピング 今回はダックタイピングというコードの書き方を説明します。 下記コードを読んで、このコードの危うさを説明し、自分なりにリファクタリングしたコードを書けるレベルに達しているなら、 オブエジェクト指向設計実践ガイドの第五章 「ダックタイピングでコストを削減する」、 この章を理解していると言えるでしょう。 リファクタリング前 class Trip attr_reader :bicycles,:customers,:vehicle def prepare(prepares) prepares.each do |preparer| case preparer when Mechanic preparer.prepare_bicycles(bicycles) when TripCordinator preparer.buy_food(customers) when Driver preparer.gas_up(vehicle) preparer.fill_water_tank(vehicle) end end end end class Mechanic def prepare_bicycles(bicycles) bicycles.each {|bicycle| prepare_bicycle(bicycle)} end def prepare_bicycle(bicycle) end end class TripCordinator def buy_food(customers) ; end end class Driver def gas_up(vehicle) ;end def fill_water_tank(vehicle) ; end end trip =Trip.new prepares = [Mechanic.new,TripCordinator.new] trip.prepare(prepares) このコードの危険性 Tripクラスのprepareメソッドが各クラスに依存しすぎている。 機能が拡張するたびに、prepareメソッドを修正しないといけない。←バグのもと リファクタリング後 class Trip attr_reader :bicycles,:customers,:vehicle def prepare(prepares) prepares.each do |preparer| preparer.prepare_trip(self) end end end class Mechanic def prepare_trip(trip) trip.bicycles.each do |bicycle| prepare_bicycle(bicycle) end end def prepare_bicycle(bicycle) ;end end class TripCordinator def prepare_trip(trip) buy_food(trip.customer) end def buy_food(customers) ; end end class Driver def prepare_trip(trip) vehicle = trip.vehicle gas_up(vehicle) fill_water_tank(vehicle) end def gas_up(vehicle) ;end def fill_water_tank(vehicle) ; end end trip =Trip.new prepares = [Mechanic.new,TripCordinator.new] trip.prepare(prepares) ダックタイピング 各クラスにprepare_trip(trip)メソッドを書くことによって、 Tripクラスのprepareメソッドがすっきりしました!! このような書き方をダックタイピングといいます。 詳しくはオブジェクト指向設計実践ガイドに書いてあります。 参考文献 オブエジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 投稿日:2021-06-15T10:50:31+09:00
ダックタイピング オブエジェクト指向実践ガイドより
読者対象 読者対象はプログラミング初学者です。 オブエジェクト指向実践ガイドの記事は、他の方も書いてありますが、 一度に全部まとめたものが多かったです。 この記事は、オブエジェクト指向らしく、各章ごとに、単一責任に、シンプルさを意識して書きました。 リファクタリング前のコードと、リファクタリング後のコードを読み比べ、 抽象的な思考、抽象的な書き方を共に学んでいきましょう。 一問一答 一問一答風の構成にしています。 リファクタリング前のコードを読み、コードの危うさ、嗅覚を養っていきましょう。 その後、リファクタリング前のコードの危うさを説明し、自分なりにリファクタリングしたコードを書いてみましょう。 オブエジェクト思考実践ガイド 余談ですが、とある弁護士が弁護士は六法全書を丸暗記してないが、各条文を見ただけで、関係ある条文、知識を思い出すそうです。 この記事は、オブジェクト指向実践ガイドってこんなこと書いてあったなーと思いだすための記事でもあります。 また、オブエジェクトし思考実践ガイドを読んだことがなくても、こういうことが書いてあるとのかー、とさわりだけでも理解して頂けたら幸いです。 オブエジェクト思考実践ガイドで得た3つのポイント リファクタリング前のコードを読み、コードの危うさを察知できる。 リファクタリングするために、より抽象的なコードの書き方を学べる。 gemなどの抽象的なコードを読解できる。 ダックタイピング 今回はダックタイピングというコードの書き方を説明します。 下記コードを読んで、このコードの危うさを説明し、自分なりにリファクタリングしたコードを書けるレベルに達しているなら、 オブエジェクト指向実践ガイドの第五章 「ダックタイピングでコストを削減する」、 この章を理解していると言えるでしょう。 リファクタリング前 class Trip attr_reader :bicycles,:customers,:vehicle def prepare(prepares) prepares.each do |preparer| case preparer when Mechanic preparer.prepare_bicycles(bicycles) when TripCordinator preparer.buy_food(customers) when Driver preparer.gas_up(vehicle) preparer.fill_water_tank(vehicle) end end end end class Mechanic def prepare_bicycles(bicycles) bicycles.each {|bicycle| prepare_bicycle(bicycle)} end def prepare_bicycle(bicycle) end end class TripCordinator def buy_food(customers) ; end end class Driver def gas_up(vehicle) ;end def fill_water_tank(vehicle) ; end end trip =Trip.new prepares = [Mechanic.new,TripCordinator.new] trip.prepare(prepares) このコードの危険性 Tripクラスのprepareメソッドが各クラスに依存しすぎている。 機能が拡張するたびに、prepareメソッドを修正しないといけない。←バグのもと リファクタリング class Trip attr_reader :bicycles,:customers,:vehicle def prepare(prepares) prepares.each do |preparer| preparer.prepare_trip(self) end end end class Mechanic def prepare_trip(trip) trip.bicycles.each do |bicycle| prepare_bicycle(bicycle) end end def prepare_bicycle(bicycle) ;end end class TripCordinator def prepare_trip(trip) buy_food(trip.customer) end def buy_food(customers) ; end end class Driver def prepare_trip(trip) vehicle = trip.vehicle gas_up(vehicle) fill_water_tank(vehicle) end def gas_up(vehicle) ;end def fill_water_tank(vehicle) ; end end trip =Trip.new prepares = [Mechanic.new,TripCordinator.new] trip.prepare(prepares) ダックタイピング 各クラスにprepare_trip(trip)メソッドを書くことによって、 Tripクラスのprepareメソッドがすっきりしました!! このような書き方をダックタイピングといいます。 詳しくはオブエジェクト思考実践ガイドに書いてあります。 参考文献 オブエジェクト指向実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 投稿日:2021-06-15T10:01:31+09:00
bioDBnet db2db on Savon
前言 遺伝子IDの変換にはDAVIDがよく使われるが、同じNational Cancer Institute Advanced Biomedical Computing Centerが運営しているサイトにbioDBnetというのがある。bioDBnetはAPIを提供している1。APIにはJSON版とSOAP版があるが、JSON GETはURLのQuery Stringに引数を指定せねばならず2、大規模処理には不安がある。SOAPはPOSTなのでこの心配はない。故にSOAP版が推奨される。 実装 さてSOAPであるが、RubyにはSavonというライブラリが存在する。これを使うと当該処理は以下のように実装できる3。 require 'savon' kwargs = { #:endpoint => 'https://biodbnet.abcc.ncifcrf.gov/webServices/biodbnetSoapServer.php', :wsdl => 'https://biodbnet.abcc.ncifcrf.gov/webServices/bioDBnet.wsdl', } #kwargs[:log] = true #kwargs[:proxy] = "http://localhost:8888"; kwargs[:ssl_verify_mode] = :none # for mitmproxy client=Savon.client(kwargs) puts client.call(:db2db,message:{db2db_params:{input:'Ensembl Gene ID',outputs:'Gene Symbol,RefSeq mRNA Accession,RefSeq ncRNA Accession',input_values:'ENSG00000110693'}}).body[:db2db_response][:return] 変数はdb2dbParamにくるむ必要がある(Hashのルートにinputとか書いても無視される) WSDLでもそうなっているからそうなんだろうけど、ドキュメントにはComplex type db2dbParamsとしか書かれていないのでかなり厳しい。WSDLというものがどういうものなのかも理解していないし(通常であれば理解はしていなくても使えるよなぁ)。 messages内の変数はsnakecaseに変換する必要がある https://github.com/savonrb/savon/blob/v2.12.1/lib/savon/builder.rb#L163 にsnakecase変換処理がある。これが意味するのは、WSDLに記載のパラメータ名はsnakecaseに変換しなければならないということである。 WSDLにはこのような記載があるので、 <xsd:complexType name="db2dbParams"> <xsd:all> <xsd:element name="input" type="xsd:string"/> <xsd:element name="taxonId" type="xsd:string"/> <xsd:element name="inputValues" type="xsd:string"/> <xsd:element name="outputs" type="xsd:string"/> </xsd:all> </xsd:complexType> Savonのmessage引数は{db2db_params:{input:"INPUT",taxon_id:"TAXONID",input_values:"INPUTVALUES",outputs:"OUTPUTS"}}となる。db2dbParamsのままだと 単に無視される。 このことについては Savonのドキュメントに記載がない ので確実にはまりうる。 Charles使ってなかったら死んでた。 終わりに Python版は公式クライアント(DAVID / bioDBnet)があるのですが、例しかないので使い方がわからなかった、この使い方を把握するのに1年ほどかかってしまった笑(GUI使えば良い話ではあるんだけど、まあ、GUIだと再現性がないってことです…) DAVIDもAPIを提供しているが、使い勝手はbioDBnetとあまり変わらない上に、登録が必要4なので、(ID変換とかでなく)遺伝子クラスタリング等解析処理のバッチ処理が必要でないなら特段使う理由はないかと… ↩ GET自体はContent-Lengthヘッダ(リクエストボディ)を受け付けるが、bioDBnet APIの実装はこれを読むようになっていないようだ、しかもPOSTもだめ(Charlesやらtelnet -z sslやらで確認) ↩ ENSG00000110693はHSCM1…5ではなくSOX6という転写因子です。 ↩ ちょろっと書いておきますが、DAVIDの認証は、cookies = client.call(:authenticate,message:{'args0'=>'EMAIL'}).http.cookiesとして以降のリクエストではcookies:cookies引数を渡せばよいです ↩ ネメシスの見過ぎだ ↩
- 投稿日:2021-06-15T09:36:03+09:00
yield fromっぽいことをRubyで実現する
https://qiita.com/cielavenir/items/0cc9189f2c40d6047d8b の続き。 &procが使えなくなった1のでいよいよyield from(っぽい機能)を実現するしかなくなってきたのだが。 yield 1,2が来たらyield 1;yield 2に書き換えるようなデコレータを挟むことで実現することができた。23 def yield_from(*funcsyms) funcsyms.each{|funcsym| func = method(funcsym) define_method(funcsym){|*args,&blk| return to_enum(funcsym,*args) if !blk func.(*args){|*a|a.each{|e|blk.(e)}} } private funcsym } end def f4(n) return to_enum(:f4, n) if !block_given? return if n<=0 yield n yield *f4(n-1) end yield_from :f4 yield_from関数はgemとして公開した。 副作用として、本当に複数の変数をyieldしたい場合はArrayにくるまないといけません( cf https://github.com/cielavenir/procon/commit/88a4ec3e1c8a627b30e784a7e7497cadadf308c4#diff-31d364303970d4a730e0c5d0f0edc0e0bbea8235dfbda809bd74233ce5420a8fR28 )。 procを返すC extとかも検討したが相当面倒そうだったし、C extは競技プログラミングではそもそも使えない ↩ 実はデコレータの書き方調べるほうが大変だった ↩ よけいなものを挟むので実行時間は遅くなります(恨)。 ↩
- 投稿日:2021-06-15T03:17:47+09:00
[Ruby]スコア判定のプログラム
はじめに 最近RailsやReactでWebアプリケーションを作ることが多く、「基礎勉強最近してなくね?」と思ったのでその勉強記録として、残しておきます。 問題 80点以上の時にA判定を、40点以上80未満の場合B判定を、40点未満の場合C判定を出すプログラムを書きなさい 解答 def evaluate_grades(score) if score >= 80 p "A判定です" elsif score >= 40 p "B判定です" else p "C判定です" end end evaluate_grades(45) => "B判定です" 久しぶりに基礎勉強したが意外と楽しいなと感じたのでこれからも取り組み続けてプログラミング力をつけていきたいなと感じた。
- 投稿日:2021-06-15T02:12:00+09:00
whereメソッドで複数条件指定してみた。(boolean真偽値とintegerの比較条件)
はじめに Whereメソッドで複数条件で事前にフィルターにかけて、 ransackに渡すことをしたく調べたけれどひとつはboolean型の真偽値、ひとつはinteger型の値を比較するand検索が載っている記事がなかったので、色々試してできたので書いておきます。 Whereメソッドの使い方 基本的なWhereメソッドの使い方はこう Whereメソッド基本形 モデル名.where(カラム名: 条件) # nameカラムから「yamada_kun」という文字が入っているレコードを全て取得 User.where(name: "yamada_kun") これでyamada_kunのレコードが取り出せます。 複数条件の基本形はこう(ANDでの検索) 複数条件(AND) モデル名.where(カラム名: 条件 , カラム名: 条件) #または モデル名.where(カラム名: 条件).where(カラム名: 条件) # nameカラムから「yamada_kun」と「ageが20」が入っているレコードを全て取得 User.where(name: "yamada_kun" , age: 20) User.where(name: "yamada_kun").where(age: 20) 比較するWhereメソッドの書き方はこう Whereメソッド(比較) モデル名.where("カラム名 > ?" , 数字 ) # idが10以上のレコードを全て取得 User.where("id > ?", 10) boolean型とinteger型でAND検索 それではこの基本形と比較して条件を定義してやる方法を組み合わせてやってみましょう。 あれ、エラーになりました・・・。 できない、できない、できないと色々と試してみてできたのがこちら。 booleanとinteger(比較)でAND検索 モデル名.where("カラム名 条件 and カラム名 > 数字") # nameがyamadakunでidが10以上のレコードを全て取得 User.where("name= "yamada_kun" and id > 10) ちなみに僕がransackを使ってやったコードがこちら @q = Item.where("onlist = true and stock >= 1").ransack(params[:q]) みなさんの探し物が見つかりますように! 参考サイト Pikawaka whereメソッドを使って欲しいデータの取得をしよう! SamuraiEngineerBlog スマートに検索!Railsのwhere使い方まとめ!