From 02325470e383b0025b5d6220800acd7d6b27e44f Mon Sep 17 00:00:00 2001 From: Rob Hanlon Date: Fri, 13 Jul 2012 10:39:23 -0700 Subject: [PATCH] Initial commit. Add all of DecoratesBeforeRendering. --- .gitignore | 18 +++++ Gemfile | 4 + LICENSE | 22 ++++++ README.md | 31 ++++++++ Rakefile | 2 + decorates_before_rendering.gemspec | 22 ++++++ lib/decorates_before_rendering.rb | 63 +++++++++++++++ lib/decorates_before_rendering/version.rb | 3 + spec/decorates_before_rendering_spec.rb | 96 +++++++++++++++++++++++ 9 files changed, 261 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 decorates_before_rendering.gemspec create mode 100644 lib/decorates_before_rendering.rb create mode 100644 lib/decorates_before_rendering/version.rb create mode 100644 spec/decorates_before_rendering_spec.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2fb8fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +.rspec diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..105eefa --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in decorates_before_rendering.gemspec +gemspec diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1dbca01 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 Rob Hanlon + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd5fdfb --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# DecoratesBeforeRendering + +A small add-on for [Draper](http://github.com/jcasimir/draper) that automatically decorates +specified controller instance variables before rendering. Currently only works on objects +that respond to ```model_name````, but this could easily be expanded upon. + +## Installation + +Add this line to your application's Gemfile: + + gem 'decorates_before_rendering' + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install decorates_before_rendering + +## Usage + +See ```lib/decorates_before_rendering.rb``` for usage instructions. + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..f57ae68 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +#!/usr/bin/env rake +require "bundler/gem_tasks" diff --git a/decorates_before_rendering.gemspec b/decorates_before_rendering.gemspec new file mode 100644 index 0000000..33956a0 --- /dev/null +++ b/decorates_before_rendering.gemspec @@ -0,0 +1,22 @@ +# -*- encoding: utf-8 -*- +require File.expand_path('../lib/decorates_before_rendering/version', __FILE__) + +Gem::Specification.new do |gem| + gem.authors = ["Rob Hanlon"] + gem.email = ["rob@mediapiston.com"] + gem.description = %q{Small add-on for Draper that decorates models before rendering.} + gem.summary = %q{Small add-on for Draper that decorates models before rendering.} + gem.homepage = "http://github.com/ohwillie/decorates_before_rendering" + + gem.files = `git ls-files`.split($\) + gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + gem.name = "decorates_before_rendering" + gem.require_paths = ["lib"] + gem.version = DecoratesBeforeRendering::VERSION + + gem.add_development_dependency 'rspec', '>= 2.10.0' + gem.add_development_dependency 'rbx-require-relative', '>= 0.0.9' + + gem.add_dependency 'activesupport', '>= 3.2.6' +end diff --git a/lib/decorates_before_rendering.rb b/lib/decorates_before_rendering.rb new file mode 100644 index 0000000..3fc7f00 --- /dev/null +++ b/lib/decorates_before_rendering.rb @@ -0,0 +1,63 @@ +# -*- encoding : utf-8 -*- +require "decorates_before_rendering/version" +require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/class/attribute' + +# Decorates the specified fields. For instance, if you have +# +# class StuffController < ApplicationController +# include DecoratesBeforeRendering +# +# decorates :thing_1, :thing_2 +# end +# +# @thing_1 and @thing_2 will be decorated right before a rendering occurs. +# +module DecoratesBeforeRendering + module ClassMethods + def decorates(*unsigiled_ivar_names) + self.__ivars_to_decorate__ = unsigiled_ivar_names.map { |i| "@#{i}" } + end + end + + def render(*args) + __decorate_ivars__ + super(*args) + end + +private + + def __decorate_ivars__ + ivars_to_decorate = self.class.__ivars_to_decorate__ + + return if ivars_to_decorate.nil? + + ivars_to_decorate.each do |ivar_name| + ivar = instance_variable_get(ivar_name) + instance_variable_set(ivar_name, __decorator_for__(ivar)) unless ivar.nil? + end + end + + def __decorator_for__(ivar) + __decorator_name_for__(ivar).constantize.decorate(ivar) + end + + def __decorator_name_for__(ivar) + "#{__model_name_for__(ivar)}Decorator" + end + + def __model_name_for__(ivar) + if ivar.respond_to?(:model_name) + ivar + elsif ivar.class.respond_to?(:model_name) + ivar.class + else + raise ArgumentError, "#{ivar} does not have an associated model" + end.model_name + end + + def self.included(base) + base.class_attribute :__ivars_to_decorate__, :instance_accessor => false + base.extend ClassMethods + end +end diff --git a/lib/decorates_before_rendering/version.rb b/lib/decorates_before_rendering/version.rb new file mode 100644 index 0000000..818b784 --- /dev/null +++ b/lib/decorates_before_rendering/version.rb @@ -0,0 +1,3 @@ +module DecoratesBeforeRendering + VERSION = "0.0.1" +end diff --git a/spec/decorates_before_rendering_spec.rb b/spec/decorates_before_rendering_spec.rb new file mode 100644 index 0000000..8b746b5 --- /dev/null +++ b/spec/decorates_before_rendering_spec.rb @@ -0,0 +1,96 @@ +require_relative '../lib/decorates_before_rendering' + +class MyCompletelyFakeModelDecorator; end + +describe DecoratesBeforeRendering do + # NOTE: these are married together, so they're tested together. + describe '::decorates + #render' do + let(:sentinel) { double(:sentinel) } + let(:ivar) { double('@ivar') } + + # NOTE: This superclass is here so we know that the correct render gets + # called. It can't be defined in the subclass, or else that one + # will be the one that's used, as modules sit above their includers + # in the class hierarchy. + let(:superclass) do + Class.new do + def initialize(sentinel) + @sentinel = sentinel + end + + def render(*args) + @sentinel.render(*args) + end + end + end + let(:klass) do + Class.new(superclass) do + include DecoratesBeforeRendering + + attr_reader :ivar + + def initialize(sentinel, ivar) + super(sentinel) + + @ivar = ivar + end + end + end + let(:instance) { klass.new(sentinel, ivar) } + let(:args) { double('*args') } + + context "no ivars" do + it 'should render' do + sentinel.should_receive(:render).with(args) + instance.render(args) + end + end + + context "ivar is not present" do + it 'should render' do + sentinel.should_receive(:render).with(args) + instance.render(args) + end + end + + context "cannot find model name for ivar" do + it 'should raise an ArgumentError' do + klass.decorates(:ivar) + expect { + instance.render(args) + }.to raise_error(ArgumentError) + end + end + + context "ivar responds to model name" do + it "should decorate and render" do + sentinel.should_receive(:render).with(args) + MyCompletelyFakeModelDecorator.should_receive(:decorate).with(ivar) + ivar.stub(:model_name => 'MyCompletelyFakeModel') + klass.decorates(:ivar) + instance.render(args) + end + end + + context "ivar's class responds to model name" do + it "should decorate and render" do + sentinel.should_receive(:render).with(args) + MyCompletelyFakeModelDecorator.should_receive(:decorate).with(ivar) + ivar.stub_chain(:class, :model_name => 'MyCompletelyFakeModel') + klass.decorates(:ivar) + instance.render(args) + end + end + + context "subclass inherits attributes" do + it "should function correctly" do + klass.decorates(:ivar) + subclass_instance = Class.new(klass).new(sentinel, ivar) + sentinel.should_receive(:render).with(args) + MyCompletelyFakeModelDecorator.should_receive(:decorate).with(ivar) + ivar.stub_chain(:class, :model_name => 'MyCompletelyFakeModel') + subclass_instance.render(args) + end + end + end +end