Skip to content

Instantly share code, notes, and snippets.

@bencrouse
Created August 5, 2011 00:16
Show Gist options
  • Save bencrouse/1126643 to your computer and use it in GitHub Desktop.
Save bencrouse/1126643 to your computer and use it in GitHub Desktop.
DDDish Rails
#
# Controller - Application service layer until a separation has clear value
#
#
class CartController < ApplicationController
before_filter :find_variant
rescue_from Model::InvalidOperation, with: :domain_error
def add_item
current_cart.add_variant(@variant, params[:quantity].to_i)
resource = CartResource.save_model(current_cart)
if resource.saved?
redirect_to cart_path
else
@errors = resource.errors
render 'products/show'
end
end
def remove_item
current_cart.remove_variant(@variant)
resource = CartResource.save_model(current_cart)
if resource.saved?
redirect_to cart_path
else
@errors = resource.errors
render 'show'
end
end
protected
def domain_error(error)
flash[:error] = error.message
render :show
end
def find_variant
@variant = VariantResource.get_model(params[:variant_id])
end
def current_cart
@current_cart ||= CartResource.get_model(session[:cart_id])
end
end
#
# Resource - responsible for domain model persistence
#
#
class CartResource
include DataMapper::Resource
storage_names[:default] = 'carts'
property :id, Serial
property :subtotal_price, Money
property :discount_price, Money
property :total_price, Money
belongs_to :user, 'UserResource', required: false
has 1, :order, 'OrderResource', constraint: :destroy!
has n, :items, 'CartItemResource', constraint: :destroy!
# DSL for saving domain models (allow .get_model and .save_model to be abstracted)
aggregate_root Cart
aggregates items: :items
end
#
# Domain Model - responsible for business logic and rules
#
#
class Cart
include Model
include Pricing::Model
attribute :id, String
attr_reader :items
calculates :subtotal_price, SubtotalCalculator
calculates :discount_price, DiscountCalculator
calculates :total_price, TotalCalculator
def initialize(*)
super
@items ||= []
end
def quantity
items.inject(0) { |sum, i| sum + i.quantity }
end
def calculate_prices
items.each(&:calculate_prices)
super
end
def add_variant(variant, quantity)
validate variant, :purchasable?, 'sorry, this item is unavailable'
items << CartItem.new(variant: variant, quantity: quantity)
calculate_prices
self
end
def remove_variant(variant)
items.reject! { |i| i.variant == variant }
calculate_prices
self
end
end
#
# Infrastructure
#
#
module Model
class InvalidOperation < StandardError; end
def self.included(klass)
klass.class_eval { include Virtus }
end
def initialize(*)
super
yield self if block_given?
end
def validate(value, validation, message)
if value.respond_to?(validation)
raise InvalidOperation, message if !value.send(validation)
elsif validation =~ /^not_/ && value.respond_to?(validation[4..-1])
raise InvalidOperation, message if value.send(validation[4..-1])
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment