LoginSignup
2
3

More than 3 years have passed since last update.

solidusのproductがカートに入るまでの流れをまとめてみた

Posted at

solidusのカートに入るまでの流れ

solidusのfrontend gemを参考にしてその流れをコードを追って調べてみました。

①productページ

まずこちらがsolidus frontendのproducts/showのページ
スクリーンショット 2019-10-04 13.44.21.png
こちらのページの add to cart付近のコードをみてみるとformタグでvariant_id, quantityをorders/populateにpostメソッドで送信ていた。
スクリーンショット 2019-10-04 13.44.08.png

②orders controller

・(a)populateでは、まず@orderに現在のorderを代入している。
・次にvariant, quantityにフォームで入力した値を代入する。
・数量が1から2147483647までの間でなければ問題が出るため、極端に値が大き場合はエラーが発生するようにしている。
・(b)先ほど定義したvariantとquantityを使ってlineItemに数量追加または新規作成し、適用条件を満たしたpromotionやshipment情報があれば追加され、orderが更新される。
・上でエラーが発生すれば、エラー情報を含めてリダイレクトされる。エラーがなければ、cart_pathにレダイレクトされる。

以上がカートに商品を追加した時の流れです。多くのmodelが絡み合っていて情報を整理したと思い記述しました!
下にさらに詳しい情報を乗せています。

※(x)で以下に,コードをさらに追った時の補足情報をつけています。

orders_contoller.rb(frontend)
def populate
      # (a)
      @order = current_order(create_order_if_necessary: true)
      authorize! :update, @order, cookies.signed[:guest_token]

      variant  = Spree::Variant.find(params[:variant_id])
      quantity = params[:quantity].present? ? params[:quantity].to_i : 1

      # 2,147,483,647 is crazy. See issue https://github.com/spree/spree/issues/2695.
      if !quantity.between?(1, 2_147_483_647)
        @order.errors.add(:base, t('spree.please_enter_reasonable_quantity'))
      end

      # (b)
      begin
        @line_item = @order.contents.add(variant, quantity)
      rescue ActiveRecord::RecordInvalid => e
        @order.errors.add(:base, e.record.errors.full_messages.join(", "))
      end

      respond_with(@order) do |format|
        format.html do
          if @order.errors.any?
            flash[:error] = @order.errors.full_messages.join(", ")
            redirect_back_or_default(spree.root_path)
            return
          else
            redirect_to cart_path
          end
        end
      end
    end

(a)current_orderがわからなかったので、コードを追ってみると、以下の手順でorderを作成または取得していた。

①未完了のorderを探す。
find_order_by_token_or_user()で、orderのstatusがコンプリートしていないorderを探す。geust_tokenから探した後に、ユーザーログインしていたらそのユーザーの最後のコンプリートしていないorderを探す。

②新規のorderを作成(①でorderが見つからなければの場合)
options[:create_order_if_necessary]がtrueかつ@current_orderがnilのため、orderを新規作成し、current_userを作成したorderに代入

@current_orderにipアドレスを代入し、@current_orderを返す。
@current_order.record_ip_address(ip_address)で現在のipアドレスを代入していた。仮にすでにipアドレスが存在している場合は、すでに保存されたipアドレスと現在のipアドレスが異なる場合に更新するようになっている。

spree/core/controller_helpers_order.rb(core)
def current_order(options = {})
          options[:create_order_if_necessary] ||= false

          return @current_order if @current_order

          @current_order = find_order_by_token_or_user(options)

          if options[:create_order_if_necessary] && (@current_order.nil? || @current_order.completed?)
            @current_order = Spree::Order.new(new_order_params)
            @current_order.user ||= try_spree_current_user
            # See issue https://github.com/spree/spree/issues/3346 for reasons why this line is here
            @current_order.created_by ||= try_spree_current_user
            @current_order.save!
          end

          if @current_order
            @current_order.record_ip_address(ip_address)
            return @current_order
          end
        end

(b)@order.contents.add()がについてコードを追ってみた

このメソッドにより、LineItemの作成または既存していれば数量の追加やshipmentの追加や適用されたがあればpromotionの追加、最後に追加した情報をorderに更新していました。詳しい内容は以下の通りです。

①orderクラスのcontentsメソッドで、Spree::OrderContents.new(self)が作成され、@cotentsに代入させる。

②その@contents(Spree::OrderContentsクラスのインスタンス)に対してaddメソッドを実行している。

Order.rb(core)
    def contents
      @contents ||= Spree::OrderContents.new(self)
    end

③addメソッドをみてみると、add_to_line_itemが実行されている。add_to_line_itemでは、
まず、grab_line_item_by_variantによって、今回登録したいvariantがすでにLinItemとしてorderに登録されている場合は、そのLineItemを取り出してくる。初めて登録するvariantならnilが返る。

④続いて、上記でnilになれば、新しいLineItemを作成し、variantを追加する。
line_item.quantity += quantity.to_iにより、数量を追加。ここで既存のLineItemを使用する場合は、既存の数量に対して加算するようにしている。

⑤最後にオプションのshippmentがある場合はlineItemに追加し、保存して、LineItemを返している。

⑥返されたLineItemは、addメソッドのlien_itemに代入され、after_add_or_removeが実行される。

⑦reload_totalsメソッドでは、orderに対して現在、選択されている商品の支払い金額などを更新させ、shipmentオプションがあれば追加し、なければnilとなる。続いて、promotionが存在して、適用の条件を満たしていれば、ここで適用される。
再度reload_totalsメソッドが実行され、promotionやshipment適用後のorder更新が行われる。
最後にlineItemが返される

OrderContents.rb(core)
    def add(variant, quantity = 1, options = {})
      line_item = add_to_line_item(variant, quantity, options)
      after_add_or_remove(line_item, options)
    end

    # (省略)

    def add_to_line_item(variant, quantity, options = {})
      line_item = grab_line_item_by_variant(variant, false, options)

      line_item ||= order.line_items.new(
        quantity: 0,
        variant: variant,
      )

      line_item.quantity += quantity.to_i
      line_item.options = ActionController::Parameters.new(options).permit(PermittedAttributes.line_item_attributes).to_h

      line_item.target_shipment = options[:shipment]
      line_item.save!
      line_item
    end

      # (省略)
    def after_add_or_remove(line_item, options = {})
      reload_totals
      shipment = options[:shipment]
      shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments
      PromotionHandler::Cart.new(order, line_item).activate
      reload_totals
      line_item
    end

参考

公式ドキュメントやソースコードなど

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3