Rails fat model: how to return a hash (errors) or an object (when success)

I have an Order model and I am trying to move business logic to Order instead of OrderController. Here is the problem I am facing:

class Api::V1::OrdersController < ApplicationController
  before_action :authenticate_with_token!, only: [:create, :show, :index]
  respond_to :json
  def create
    order = current_retailer.orders.build
    order.checkout_cash(current_retailer, params[:order][:product_ids_with_quantities], order_params[:member_external_id])

    puts order
    if order.key?(:errors)
      render json: order, status: 422
    else
      render json: order, status: 201
    end
    puts "HEY!!!"
    puts order.inspect

end

      

so the method order.checkout_cash

is the business logic that I am implementing in the order model.

I need to know if it is valid or if it returns an error.

Here's my code in the order model:

  def checkout_cash(current_retailer, product_ids_with_quantities, member_external_id)
    puts "CASH!!!"
    order = current_retailer.orders.build
    order.payment_method = "cash"
    order.build_placements(product_ids_with_quantities)
    order.set_total_charge!
    if member_external_id.blank?
      return order
    else
      member = Member.find_by(member_external_id: external_id)
      if member
        order.add_points(member)
        return order
      else
        return {errors: "Not a member or wrong membership id. Please register first"}
      end
    end

      

In OrderController the line

if order.key?(:

      

leads to:

 NoMethodError:
   undefined method `key?' for #<Order:0x007fb7a29ec260>

      

I'm pretty sure because I am calling a hash function on the Order object. How do I do this in Rails?


Now:

def create
  order = current_retailer.orders.build
  checkout_result = order.checkout_cash(current_retailer, params[:order][:product_ids_with_quantities], order_params[:member_external_id])

  puts order

  if checkout_result.key?(:errors)
    render json: order, status: 422
  else
    order.save!
    order.reload
    render json: order, status: 201
  end

      

gets:

1) Api::V1::OrdersController POST #create create with default total_charge return 0 as total_charge
  Failure/Error: get :create, retailer_id: @retailer, order: {product_ids_with_quantities: []}
  NoMethodError:
    undefined method `key?' for #<Order:0x007ff69244fb70>
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activemodel-4.2.0/lib/active_model/attribute_methods.rb:433:in `method_missing'
  # ./app/controllers/api/v1/orders_controller.rb:11:in `create'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/action_controller/metal/implicit_render.rb:4:in `send_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/abstract_controller/base.rb:198:in `process_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/action_controller/metal/rendering.rb:10:in `process_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/abstract_controller/callbacks.rb:20:in `block in process_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:117:in `call'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:117:in `call'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:151:in `block in halting_and_conditional'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:234:in `call'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:234:in `block in halting'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:169:in `call'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:169:in `block in halting'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:92:in `call'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:92:in `_run_callbacks'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:734:in `_run_process_action_callbacks'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/callbacks.rb:81:in `run_callbacks'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/abstract_controller/callbacks.rb:19:in `process_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/action_controller/metal/rescue.rb:29:in `process_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/action_controller/metal/instrumentation.rb:31:in `block in process_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/notifications.rb:164:in `block in instrument'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activesupport-4.2.0/lib/active_support/notifications.rb:164:in `instrument'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/action_controller/metal/instrumentation.rb:30:in `process_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/activerecord-4.2.0/lib/active_record/railties/controller_runtime.rb:18:in `process_action'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/abstract_controller/base.rb:137:in `process'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionview-4.2.0/lib/action_view/rendering.rb:30:in `process'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/action_controller/test_case.rb:629:in `process'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/action_controller/test_case.rb:65:in `process'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/devise-3.5.1/lib/devise/test_helpers.rb:19:in `block in process'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/devise-3.5.1/lib/devise/test_helpers.rb:72:in `catch'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/devise-3.5.1/lib/devise/test_helpers.rb:72:in `_catch_warden'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/devise-3.5.1/lib/devise/test_helpers.rb:19:in `process'
  # /opt/twitter/rvm/gems/ruby-1.9.3-p551/gems/actionpack-4.2.0/lib/action_controller/test_case.rb:505:in `get'
  # ./spec/controllers/api/v1/orders_controller_spec.rb:37:in `block (4 levels) in <top (required)>'

      

+3


source to share


1 answer


You are not using the return value checkout_cash

. The correct code should be:

checkout_result = order.checkout_cash(current_retailer, params[:order][:product_ids_with_quantities], order_params[:member_external_id])

if checkout_result.key?(:errors)
  render json: order, status: 422
else
  render json: order, status: 201
end

      

UPDATE:



I am reading the method wrong checkout_cash

. I would advise you not to let the method return different classes depending on the input, this makes the caller know the internal behavior of the method. Since you are not using the object order

that is inside checkout_cash

, you can simply return an empty hash on success.

However, I think this method has too many responsibilities that do not belong to the class order

. You can write a form object to handle this spot check and keep your model simple. Check out this great article: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ .

+3


source







All Articles