diff --git a/Gemfile b/Gemfile index b96032a3..bdea3a2a 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,9 @@ source :rubygems gemspec + +gem 'capybara' +gem 'pry' gem 'rails', '~> 3.2.0' gem 'rspec-rails' -gem 'capybara' gem 'sqlite3' diff --git a/README.md b/README.md index be507967..2993b7ce 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ You can use the generator for doing this ( `% rails g decorator user` ) # first_name:string last_name:string website:string end + # Implicit decorator. Works without configuration. # app/decorators/user_decorator.rb module UserDecorator def full_name @@ -47,6 +48,18 @@ You can use the generator for doing this ( `% rails g decorator user` ) link_to full_name, website end end + + # Explicit decorator. Allows you to use a different + # decorator for other contexts. Overrides the + # implicit decorator. + # app/decorators/user_json_decorator.rb + module UserJsonDecorator + def as_json(opts={}) + { :id => id, + :first_name => first_name } + end + end + # app/controllers/users_controller.rb class UsersController < ApplicationController @@ -55,10 +68,21 @@ You can use the generator for doing this ( `% rails g decorator user` ) end end + # app/controllers/remote_users_controller.rb + class RemoteUsersController < ApplicationController + decorate :user, :with => UserJsonDecorator # or :user_json_decorator + def show + @user = User.find(params[:id]) + respond_with @user + end + end + # app/views/users/index.html.erb <% @users.each do |user| %> <%= user.link %>
<% end %> + + ## Contributing to ActiveDecorator ## diff --git a/gemfiles/Gemfile-rails.3.0.x b/gemfiles/Gemfile-rails.3.0.x index 4aad9bf3..aabe2213 100644 --- a/gemfiles/Gemfile-rails.3.0.x +++ b/gemfiles/Gemfile-rails.3.0.x @@ -1,7 +1,7 @@ source :rubygems gem 'active_decorator', :path => '..' - +gem 'pry-rails' gem 'rails', '~> 3.0.0' gem 'rspec-rails' gem 'capybara' diff --git a/gemfiles/Gemfile-rails.3.1.x b/gemfiles/Gemfile-rails.3.1.x index debe90a5..e22a28c3 100644 --- a/gemfiles/Gemfile-rails.3.1.x +++ b/gemfiles/Gemfile-rails.3.1.x @@ -1,7 +1,7 @@ source :rubygems gem 'active_decorator', :path => '..' - +gem 'pry-rails' gem 'rails', '~> 3.1.0' gem 'rspec-rails' gem 'capybara' diff --git a/lib/active_decorator/decorator.rb b/lib/active_decorator/decorator.rb index f076fcb6..657c9ab8 100644 --- a/lib/active_decorator/decorator.rb +++ b/lib/active_decorator/decorator.rb @@ -11,7 +11,6 @@ def initialize def decorate(obj) return if obj.nil? - if obj.is_a? Array obj.each do |r| decorate r @@ -27,8 +26,9 @@ def to_a_with_decorator end else d = decorator_for obj.class + return obj unless d - obj.extend d unless obj.is_a? d + obj.extend d unless obj.is_a? d # do the include of the decorator end end @@ -36,10 +36,18 @@ def to_a_with_decorator def decorator_for(model_class) return @@decorators[model_class] if @@decorators.has_key? model_class - decorator_name = "#{model_class.name}Decorator" - d = decorator_name.constantize - d.send :include, ActiveDecorator::Helpers - @@decorators[model_class] = d + decorator = whos_my_decorator(model_class) + decorator.send :include, ActiveDecorator::Helpers + @@decorators[model_class] = decorator + + end + + def whos_my_decorator(model_class) + if model_class.respond_to? :decorated_with + return model_class.decorated_with + else + return "#{model_class.name}Decorator".constantize + end rescue NameError @@decorators[model_class] = nil end diff --git a/lib/active_decorator/helpers.rb b/lib/active_decorator/helpers.rb index 02193750..727f8f88 100644 --- a/lib/active_decorator/helpers.rb +++ b/lib/active_decorator/helpers.rb @@ -3,12 +3,12 @@ module Helpers def method_missing(method, *args, &block) super #TODO need to make sure who raised the error? - rescue NoMethodError, NameError => original_error - begin - ActiveDecorator::ViewContext.current.send method, *args, &block - rescue NoMethodError, NameError - raise original_error - end - end + rescue NoMethodError, NameError => original_error + begin + ActiveDecorator::ViewContext.current.send method, *args, &block + rescue NoMethodError, NameError + raise original_error + end + end end end diff --git a/lib/active_decorator/monkey/abstract_controller/rendering.rb b/lib/active_decorator/monkey/abstract_controller/rendering.rb index 7b0d4c6e..40803886 100644 --- a/lib/active_decorator/monkey/abstract_controller/rendering.rb +++ b/lib/active_decorator/monkey/abstract_controller/rendering.rb @@ -7,7 +7,26 @@ def view_assigns_with_decorator end hash end - alias_method_chain :view_assigns, :decorator + + module ClassMethods + def decorate model_name, opts={} + raise ArgumentError unless opts.keys.include? :with + klass = model_name.to_s.camelize.constantize + class << klass + attr_accessor :decorated_with + end + klass.decorated_with = decorator_class_with_unknown_input(opts[:with]) + end + + private + def decorator_class_with_unknown_input(symbol_or_constant) + if symbol_or_constant.is_a? Module + symbol_or_constant + else + symbol_or_constant.to_s.camelize.constantize + end + end + end end end diff --git a/lib/active_decorator/rspec.rb b/lib/active_decorator/rspec.rb new file mode 100644 index 00000000..a39448b1 --- /dev/null +++ b/lib/active_decorator/rspec.rb @@ -0,0 +1,26 @@ +module RSpec::Rails + module DecoratorExampleGroup + extend ActiveSupport::Concern + include RSpec::Rails::RailsExampleGroup + include ActionView::TestCase::Behavior + + def decorate(obj) + ActiveDecorator::Decorator.instance.decorate(obj) + obj + end + + included do + metadata[:type] = :decorator + + before do + ActiveDecorator::ViewContext.current = controller.view_context + end + end + end +end + +RSpec::configure do |c| + c.include RSpec::Rails::DecoratorExampleGroup, :type => :decorator, :example_group => { + :file_path => c.escaped_path(%w[spec decorators]) + } +end diff --git a/spec/fake_app/authors/show.html.erb b/spec/fake_app/authors/show.html.erb index 761ec664..41c8d565 100644 --- a/spec/fake_app/authors/show.html.erb +++ b/spec/fake_app/authors/show.html.erb @@ -1,2 +1,3 @@ <%= @author.name %> <%= @author.capitalized_name %> +<%= @author.fancy_name %> diff --git a/spec/fake_app/fake_app.rb b/spec/fake_app/fake_app.rb index ae9b00cb..57c832c1 100644 --- a/spec/fake_app/fake_app.rb +++ b/spec/fake_app/fake_app.rb @@ -24,7 +24,15 @@ class Application < Rails::Application # models class Author < ActiveRecord::Base has_many :books + + #decorated_with ArbitraryDecorator + + def self.decorated_with + ArbitraryDecorator + end + end + class Book < ActiveRecord::Base belongs_to :author end @@ -60,11 +68,28 @@ def cover_image end end + module ArbitraryDecorator + def fancy_name + name.insert(0, 'Awesome he is, ') + end + + def reverse_name + name.reverse + end + + def capitalized_name + name.capitalize + end + end + # controllers class ApplicationController < ActionController::Base self.append_view_path File.dirname(__FILE__) end + class AuthorsController < ApplicationController + decorate :author, :with => ArbitraryDecorator + def index if params[:variable_type] == 'array' @authors = Author.all @@ -83,6 +108,7 @@ def show end end + # migrations class CreateAllTables < ActiveRecord::Migration def self.up diff --git a/spec/requests/controller_ivar_spec.rb b/spec/requests/controller_ivar_spec.rb index bb88a954..82c6c0fc 100644 --- a/spec/requests/controller_ivar_spec.rb +++ b/spec/requests/controller_ivar_spec.rb @@ -5,6 +5,7 @@ @matz = Author.create! :name => 'matz' Author.create! :name => 'takahashim' end + after do Author.delete_all end @@ -26,4 +27,18 @@ page.should have_content 'takahashim' page.should have_content 'takahashim'.reverse end + + scenario "decorating Model with arbitrary decorator" do + visit "/authors/#{@matz.id}" + page.should have_content @matz.name.insert(0, 'Awesome he is, ') + end + + scenario "throw ArgumentError when no :with is passed" do + lambda { BooksController.decorate :book }.should raise_error ArgumentError + end + + scenario "should accept symbol for decorator class" do + BooksController.decorate :book, :with => :arbitrary_decorator + Book.decorated_with.should == ArbitraryDecorator + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6a9c1bdc..1afb2c89 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,10 +6,18 @@ # needs to load the app before loading rspec/rails => capybara require 'fake_app/fake_app' require 'rspec/rails' + # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} +module ::RSpec::Core + class ExampleGroup + include Capybara::DSL + include Capybara::RSpecMatchers + end +end + RSpec.configure do |config| config.before :all do CreateAllTables.up unless ActiveRecord::Base.connection.table_exists? 'authors'