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)>'
source to share
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/ .
source to share