Skip to content

Commit

Permalink
Split up decorate and decorate_collection
Browse files Browse the repository at this point in the history
  • Loading branch information
haines committed Nov 8, 2012
1 parent b5fc398 commit 7748ce5
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 98 deletions.
2 changes: 1 addition & 1 deletion lib/draper/decoratable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def decorated?

module ClassMethods
def decorate(options = {})
collection_decorator = decorator_class.decorate(self.scoped, options)
collection_decorator = decorator_class.decorate_collection(self.scoped, options)
block_given? ? yield(collection_decorator) : collection_decorator
end

Expand Down
42 changes: 23 additions & 19 deletions lib/draper/decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ def initialize(source, options = {})
handle_multiple_decoration if source.is_a?(Draper::Decorator)
end

class << self
alias_method :decorate, :new
end

# Adds ActiveRecord finder methods to the decorator class. The
# methods return decorated models, so that you can use
# `ProductDecorator.find(id)` instead of
Expand Down Expand Up @@ -60,17 +64,25 @@ def self.decorates_association(association_symbol, options = {})

return options[:with].decorate(orig_association) if options[:with]

collection = orig_association.respond_to?(:first)

klass = if options[:polymorphic]
orig_association.class
elsif association_reflection = find_association_reflection(association_symbol)
association_reflection.klass
elsif orig_association.respond_to?(:first)
elsif collection
orig_association.first.class
else
orig_association.class
end

decorated_associations[association_symbol] = "#{klass}Decorator".constantize.decorate(orig_association, options)
decorator_class = "#{klass}Decorator".constantize

if collection
decorated_associations[association_symbol] = decorator_class.decorate_collection(orig_association, options)
else
decorated_associations[association_symbol] = decorator_class.decorate(orig_association, options)
end
end
end

Expand Down Expand Up @@ -114,23 +126,15 @@ def self.allows(*methods)
security.allows(*methods)
end

# Initialize a new decorator instance by passing in
# an instance of the source class. Pass in an optional
# context into the options hash is stored for later use.
#
# When passing in a single object, using `.decorate` is
# identical to calling `.new`. However, `.decorate` can
# also accept a collection and return a collection of
# individually decorated objects.
#
# @param [Object] input instance(s) to wrap
# @param [Hash] options options to be passed to the decorator
def self.decorate(input, options = {})
if input.respond_to?(:each) && !input.is_a?(Struct) && (!defined?(Sequel) || !input.is_a?(Sequel::Model))
Draper::CollectionDecorator.new(input, options.reverse_merge(with: self))
else
new(input, options)
end
# Creates a new CollectionDecorator for the given collection.
#
# @param [Object] source collection to decorate
# @param [Hash] options passed to each item's decorator (except
# for the keys listed below)
# @option options [Class,Symbol] :with (self) the class used to decorate
# items, or `:infer` to call each item's `decorate` method instead
def self.decorate_collection(source, options = {})
Draper::CollectionDecorator.new(source, options.reverse_merge(with: self))
end

# Get the chain of decorators applied to the object.
Expand Down
14 changes: 10 additions & 4 deletions lib/draper/finders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def find(id, options = {})
end

def all(options = {})
decorate(finder_class.all, options)
decorate_collection(finder_class.all, options)
end

def first(options = {})
Expand All @@ -23,10 +23,16 @@ def last(options = {})
end

def method_missing(method, *args, &block)
if method.to_s.match(/^find_((all_|last_)?by_|or_(initialize|create)_by_).*/)
decorate(finder_class.send(method, *args, &block), args.dup.extract_options!)
result = finder_class.send(method, *args, &block)
options = args.extract_options!

case method.to_s
when /^find_((last_)?by_|or_(initialize|create)_by_)/
decorate(result, options)
when /^find_all_by_/
decorate_collection(result, options)
else
finder_class.send(method, *args, &block)
result
end
end

Expand Down
85 changes: 16 additions & 69 deletions spec/draper/decorator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,86 +55,33 @@
end
end

describe ".decorate" do
subject { ProductDecorator.decorate(source) }
describe ".decorate_collection" do
subject { ProductDecorator.decorate_collection(source) }
let(:source) { [Product.new, Widget.new] }

context "when given a single source object" do
let(:source) { Widget.new }

it "returns a decorator" do
subject.should be_a ProductDecorator
subject.source.should be source
end
end

context "when given a collection of source objects" do
let(:source) { [Product.new, Widget.new] }

it "returns a collection decorator" do
subject.should be_a Draper::CollectionDecorator
subject.source.should be source
end

it "uses itself as the item decorator by default" do
subject.each {|item| item.should be_a ProductDecorator}
end

context "when given :with => :infer" do
subject { ProductDecorator.decorate(source, with: :infer) }

it "infers the item decorators" do
subject.first.should be_a ProductDecorator
subject.last.should be_a WidgetDecorator
end
end
end

context "when given a struct" do
# Struct objects implement #each
let(:source) { Struct.new(:title).new("Godzilla") }

it "returns a decorator" do
subject.should be_a ProductDecorator
end
it "returns a collection decorator" do
subject.should be_a Draper::CollectionDecorator
subject.source.should be source
end

context "when given a Sequel model" do
# Sequel models implement #each
let(:source) { SequelProduct.new }

it "returns a decorator" do
subject.should be_a ProductDecorator
end
it "uses itself as the item decorator by default" do
subject.each {|item| item.should be_a ProductDecorator}
end

context "when given a collection of Sequel models" do
# Sequel models implement #each
let(:source) { [SequelProduct.new, SequelProduct.new] }
context "when given :with => :infer" do
subject { ProductDecorator.decorate_collection(source, with: :infer) }

it "returns a collection decorator" do
subject.should be_a Draper::CollectionDecorator
it "infers the item decorators" do
subject.first.should be_a ProductDecorator
subject.last.should be_a WidgetDecorator
end
end

context "with options" do
let(:options) { {some: "options"} }

subject { Draper::Decorator.decorate(source, options ) }
subject { ProductDecorator.decorate_collection(source, with: :infer, some: "options") }

context "when given a single source object" do
let(:source) { Product.new }

it "passes the options to the decorator" do
subject.options.should == options
end
end

context "when given a collection of source objects" do
let(:source) { [Product.new, Product.new] }

it "passes the options to the collection decorator" do
subject.options.should == options
end
it "passes the options to the collection decorator" do
subject.options.should == {some: "options"}
end
end
end
Expand Down
20 changes: 15 additions & 5 deletions spec/draper/finders_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@
ProductDecorator.find_by_name_and_size("apples", "large")
end

it "proxies find_all_by_(x) finders" do
Product.should_receive(:find_all_by_name_and_size).with("apples", "large")
ProductDecorator.find_all_by_name_and_size("apples", "large")
end

it "proxies find_last_by_(x) finders" do
Product.should_receive(:find_last_by_name_and_size).with("apples", "large")
ProductDecorator.find_last_by_name_and_size("apples", "large")
Expand All @@ -67,6 +62,21 @@
end
end

describe ".find_all_by_" do
it "proxies to the model class" do
Product.should_receive(:find_all_by_name_and_size).with("apples", "large")
ProductDecorator.find_all_by_name_and_size("apples", "large")
end

it "decorates the result" do
found = [Product.new, Product.new]
Product.stub(:find_all_by_name).and_return(found)
decorator = ProductDecorator.find_all_by_name("apples")
decorator.should be_a Draper::CollectionDecorator
decorator.source.should be found
end
end

describe ".all" do
it "returns a decorated collection" do
collection = ProductDecorator.all
Expand Down

0 comments on commit 7748ce5

Please sign in to comment.