Refactor large controller method into model

Martin Valentino

I have a rails app ported from php codebase. I have a long controller method that basically calculate a total price based on the items in my cart. This is a legacy method that is directly ported from the php code.

def total
    order = @cart.get_or_create_order
    order_contents = order_contents_for(order)

    discounts = {
      events: {},
      subjects: {},
      products: {}
    }

    adjusted_pricing = {}
    free = false
    shipping = 0
    total = 0

    # Logic for computing shipping price

    # Construct discount hash

    order_contents.each do |item|
       if (item.variant_price.present?)
         price = item.variant_price
       end
       else
         price = item.price
       end

       price_adjustments = {}
       popped_from = []

       # it's the issue due to legacy database structure,
       # product_id, subject_id and event_id is each column in
       # the database
       if (discounts[:products][item.product_id])
          price_adjustments = discounts[:products][item.product_id]
          discounts[:products].delete(item.product_id)
          popped_from = [:products, item.product_id]
       elsif (discounts[:subjects][item.subject_id])
          price_adjustments = discounts[:subjects][item.subject_id]
          discounts[:subjects].delete(item.subject_id)
          popped_from = [:subjects, item.subject_id]
       elsif (discounts[:events][item.event_id])
          price_adjustments = discounts[:events][item.event_id]
          discounts[:events].delete(item.event_id)
          popped_from = [:events, item.event_id]
       end

       if (adjustment = price_adjustments['$'])
          adjusted_price = price + adjustment
       elsif (adjustment = price_adjustments['%'])
          adjusted_price = price + price * (adjustment / 100.0)
          discounts[popped_from[0]][popped_from[1]] = price_adjustments
       else
          adjusted_price = price
       end

       adjusted_pricing[item.product_id] = {price: adjusted_price, discount: price - adjusted_price}

       total += adjusted_price
    end

    total += shipping
end

Above code is a huge pieces of code for a method, so I'm trying to refactor it and move it to a model price_calculator.

def calculate_total_for(order)
  order_contents = order.current_order_contents
  product_adjustments = order.product_adjustments

  shipping = calculate_shipping_price(order_contents, product_adjustments)

  discounts = construct_discount_hash(product_adjustments)

  adjusted_pricing = construct_adjusted_price_hash(discounts, order_contents)

  total_price = adjusted_pricing.inject(0) { |total, (k, v)| total + v[:price] }

  {
    total_price: total_price + shipping,
    shipping: shipping,
    adjusted_pricing: adjusted_pricing
  }
end

What I did basically, still in the process of move the previous huge method into its own class and split the logic into a separate private method in that class such as calculate_shipping_price, construct_discount_hash.

I know it's far from a good code. Break it into private method ooks good in term of readability, but I start to feel it's getting hard to make a test for it. Hopefully, someone here can give an advice or a guideline, what's the best way to refactor above code in ruby.

PS: I'm new to ruby/rails, I previously code mostly in C#/Javascript, so there are some idiom or ruby-way to doing things that I'm not familiar with.

Mugur 'Bud' Chirica

For the example you mentioned, I would use Extract Class from Method refactoring and use a Service Object instead of moving everything in a model.

Here is an overview of how to do it, of course I leave the implementation for you:

class Test
  def total
    order = @cart.get_or_create_order
    order_contents = order_contents_for(order)

    discounts = {
      events: {},
      subjects: {},
      products: {}
    }

    service = CalculateTotal.new(order, order_contents, discounts)

    if service.success?
      # Success logic
    else
      flash[:error] = service.error
      # Failure logic
    end
  end
end

class CalculateTotal
  attr_reader :success, :error

  def initialize(order, order_contents, discounts)
    @order = order
    @order_contents = order_contents
    @discounts = discounts
  end

  def call
    sum_orders + adjusted_prices + shipping
  end

  def success?
    !@error
  end

  private

  def sum_orders
    # Logic

    if something_fails
      @error = 'There was an error calculating the price'
    end

    # Logic
  end

  def adjusted_prices
    # Logic
  end

  def shipping
    # Logic
  end
end

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

refactor and move controller code into model issues

From Dev

How to refactor large if else block in java servlet front controller

From Dev

Model method in Rails controller

From Dev

refactor model method multiple mount_uploader for carrierwave

From Dev

Delete File Method in Controller or Model?

From Dev

Calling model custom method in controller

From Dev

Rails App: Method in model or controller

From Dev

Model parameter not recognized by controller method

From Dev

Call a Method Model inside Controller

From Dev

Where to write this method in Model or Controller?

From Dev

Rails - Calling a model method in controller

From Dev

MVC architecture large action method on controller

From Dev

Refactor a case structure in a controller

From Dev

Refactor attribute to model association

From Dev

laravel 5.2 - Accessing Model in Controller Method

From Dev

Update in Ruby model method not feeding through controller

From Dev

How to call parent method from model controller

From Dev

index method in controller not working for active model serializer

From Dev

Spec for testing a controller method that calls model methods

From Dev

Laravel - Redirect in Model Binding executes controller method

From Dev

class method defined in model not showing up in controller

From Dev

Passing arguments to Controller to a method in User Model

From Dev

How to refactor jqGrid code, from controller's JsonResult method, to loosely coupled models?

From Dev

Refactor this: Generic extension method

From Dev

How refactor the method?

From Dev

Sling scheduler method refactor

From Dev

Refactor method to use generics

From Dev

Refactor Method Complexity in RAILS

From Dev

How refactor the method?

Related Related

HotTag

Archive