From f1bef3df76283be525be40a767d0574a756daa3c Mon Sep 17 00:00:00 2001 From: sinsoku Date: Fri, 17 Aug 2012 01:18:30 +0900 Subject: [PATCH 1/8] support rspec --- lib/active_decorator/rspec.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 lib/active_decorator/rspec.rb 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 From bdba51478bd79fb559321a82a47b2b414eef4c32 Mon Sep 17 00:00:00 2001 From: Cameron Adamez Date: Wed, 21 Nov 2012 13:02:57 -0800 Subject: [PATCH 2/8] Added support for capybara/rspec from this ticket: https://github.com/rspec/rspec-rails/issues/503 --- spec/spec_helper.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6a9c1bdc..d5eff230 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' +require 'capybara/rspec' # 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' From db5fd486f3d210ff0edc50d2108b35b9def727c5 Mon Sep 17 00:00:00 2001 From: Cameron Adamez Date: Wed, 21 Nov 2012 17:43:19 -0800 Subject: [PATCH 3/8] Trying to add an optional decorator attr_accessor to ActiveRecord files. Not sure how to go about this... --- lib/active_decorator/decorator.rb | 10 +++++++--- lib/active_decorator/monkey/active_record/base.rb | 7 +++++++ spec/fake_app/authors/fancy_show.html.erb | 2 ++ spec/fake_app/fake_app.rb | 11 +++++++++++ spec/requests/controller_ivar_spec.rb | 6 ++++++ 5 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 lib/active_decorator/monkey/active_record/base.rb create mode 100644 spec/fake_app/authors/fancy_show.html.erb diff --git a/lib/active_decorator/decorator.rb b/lib/active_decorator/decorator.rb index f076fcb6..4a20ba1b 100644 --- a/lib/active_decorator/decorator.rb +++ b/lib/active_decorator/decorator.rb @@ -35,13 +35,17 @@ def to_a_with_decorator private 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 = decorator_for_model_class(model_class) d.send :include, ActiveDecorator::Helpers @@decorators[model_class] = d rescue NameError @@decorators[model_class] = nil end + + def decorator_for_model_class(model_class) + decorator_name = model_class.decorator || "#{model_class.name}Decorator" + decorator_name.constantize + end end end diff --git a/lib/active_decorator/monkey/active_record/base.rb b/lib/active_decorator/monkey/active_record/base.rb new file mode 100644 index 00000000..810913ec --- /dev/null +++ b/lib/active_decorator/monkey/active_record/base.rb @@ -0,0 +1,7 @@ +class ActiveRecord + class Base + class << self + attr_accessor :decorator + end + end +end diff --git a/spec/fake_app/authors/fancy_show.html.erb b/spec/fake_app/authors/fancy_show.html.erb new file mode 100644 index 00000000..db717c90 --- /dev/null +++ b/spec/fake_app/authors/fancy_show.html.erb @@ -0,0 +1,2 @@ +<%= @author.name %> +<%= @author.number_name %> diff --git a/spec/fake_app/fake_app.rb b/spec/fake_app/fake_app.rb index ae9b00cb..6ff8c3a2 100644 --- a/spec/fake_app/fake_app.rb +++ b/spec/fake_app/fake_app.rb @@ -17,6 +17,7 @@ class Application < Rails::Application # routes ActiveDecoratorTestApp::Application.routes.draw do resources :authors, :only => [:index, :show] do + member { get :fancy_show } resources :books, :only => :show end end @@ -42,6 +43,11 @@ def capitalized_name name.capitalize end end +module FancyAuthorDecorator + def number_name + name.sum + end +end module BookDecorator def reverse_title title.reverse @@ -76,6 +82,11 @@ def index def show @author = Author.find params[:id] end + + def fancy_show + Author.decorator = FancyAuthorDecorator + @author = Author.find params[:id] + end end class BooksController < ApplicationController def show diff --git a/spec/requests/controller_ivar_spec.rb b/spec/requests/controller_ivar_spec.rb index bb88a954..0caa1ec4 100644 --- a/spec/requests/controller_ivar_spec.rb +++ b/spec/requests/controller_ivar_spec.rb @@ -26,4 +26,10 @@ page.should have_content 'takahashim' page.should have_content 'takahashim'.reverse end + + scenario 'decorating a model object with custom decorator' do + visit "/authors/#{@matz.id}/fancy_show" + page.should have_content 'matz' + page.should have_content '444' + end end From fa7f4588c6c7ff8ad7f648d7eded381b24dc56b8 Mon Sep 17 00:00:00 2001 From: Matt Clark Date: Mon, 26 Nov 2012 10:57:50 -0800 Subject: [PATCH 4/8] decorate_with for arbitrarily named decorators --- Gemfile | 4 ++- gemfiles/Gemfile-rails.3.0.x | 2 +- gemfiles/Gemfile-rails.3.1.x | 2 +- lib/active_decorator/decorator.rb | 20 +++++++++----- lib/active_decorator/helpers.rb | 14 +++++----- .../monkey/abstract_controller/rendering.rb | 13 +++++++++- spec/fake_app/authors/show.html.erb | 1 + spec/fake_app/fake_app.rb | 26 +++++++++++++++++++ spec/requests/controller_ivar_spec.rb | 6 +++++ spec/spec_helper.rb | 9 +++++++ 10 files changed, 80 insertions(+), 17 deletions(-) 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/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..24228862 100644 --- a/lib/active_decorator/monkey/abstract_controller/rendering.rb +++ b/lib/active_decorator/monkey/abstract_controller/rendering.rb @@ -7,7 +7,18 @@ def view_assigns_with_decorator end hash end - alias_method_chain :view_assigns, :decorator + + module ClassMethods + def decorate decorate_hash + decorate_hash.each do |k,v| + klass = k.to_s.camelize.constantize + class << klass + attr_accessor :decorated_with + end + klass.decorated_with = v + end + end + end end 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..84556b24 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 => 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..d8e707ab 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,9 @@ 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 end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6a9c1bdc..9ad33bea 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -6,10 +6,19 @@ # 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} +#https://github.com/rspec/rspec-rails/issues/503 +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' From be3e21ed4c80e6bb1e0bc4ad21e7e971cb265c79 Mon Sep 17 00:00:00 2001 From: Cameron Adamez Date: Mon, 26 Nov 2012 11:04:31 -0800 Subject: [PATCH 5/8] Revert "Trying to add an optional decorator attr_accessor to ActiveRecord" This reverts commit db5fd486f3d210ff0edc50d2108b35b9def727c5. --- lib/active_decorator/decorator.rb | 10 +++------- lib/active_decorator/monkey/active_record/base.rb | 7 ------- spec/fake_app/authors/fancy_show.html.erb | 2 -- spec/fake_app/fake_app.rb | 11 ----------- spec/requests/controller_ivar_spec.rb | 6 ------ 5 files changed, 3 insertions(+), 33 deletions(-) delete mode 100644 lib/active_decorator/monkey/active_record/base.rb delete mode 100644 spec/fake_app/authors/fancy_show.html.erb diff --git a/lib/active_decorator/decorator.rb b/lib/active_decorator/decorator.rb index 4a20ba1b..f076fcb6 100644 --- a/lib/active_decorator/decorator.rb +++ b/lib/active_decorator/decorator.rb @@ -35,17 +35,13 @@ def to_a_with_decorator private def decorator_for(model_class) return @@decorators[model_class] if @@decorators.has_key? model_class - - d = decorator_for_model_class(model_class) + + decorator_name = "#{model_class.name}Decorator" + d = decorator_name.constantize d.send :include, ActiveDecorator::Helpers @@decorators[model_class] = d rescue NameError @@decorators[model_class] = nil end - - def decorator_for_model_class(model_class) - decorator_name = model_class.decorator || "#{model_class.name}Decorator" - decorator_name.constantize - end end end diff --git a/lib/active_decorator/monkey/active_record/base.rb b/lib/active_decorator/monkey/active_record/base.rb deleted file mode 100644 index 810913ec..00000000 --- a/lib/active_decorator/monkey/active_record/base.rb +++ /dev/null @@ -1,7 +0,0 @@ -class ActiveRecord - class Base - class << self - attr_accessor :decorator - end - end -end diff --git a/spec/fake_app/authors/fancy_show.html.erb b/spec/fake_app/authors/fancy_show.html.erb deleted file mode 100644 index db717c90..00000000 --- a/spec/fake_app/authors/fancy_show.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -<%= @author.name %> -<%= @author.number_name %> diff --git a/spec/fake_app/fake_app.rb b/spec/fake_app/fake_app.rb index 6ff8c3a2..ae9b00cb 100644 --- a/spec/fake_app/fake_app.rb +++ b/spec/fake_app/fake_app.rb @@ -17,7 +17,6 @@ class Application < Rails::Application # routes ActiveDecoratorTestApp::Application.routes.draw do resources :authors, :only => [:index, :show] do - member { get :fancy_show } resources :books, :only => :show end end @@ -43,11 +42,6 @@ def capitalized_name name.capitalize end end -module FancyAuthorDecorator - def number_name - name.sum - end -end module BookDecorator def reverse_title title.reverse @@ -82,11 +76,6 @@ def index def show @author = Author.find params[:id] end - - def fancy_show - Author.decorator = FancyAuthorDecorator - @author = Author.find params[:id] - end end class BooksController < ApplicationController def show diff --git a/spec/requests/controller_ivar_spec.rb b/spec/requests/controller_ivar_spec.rb index 0caa1ec4..bb88a954 100644 --- a/spec/requests/controller_ivar_spec.rb +++ b/spec/requests/controller_ivar_spec.rb @@ -26,10 +26,4 @@ page.should have_content 'takahashim' page.should have_content 'takahashim'.reverse end - - scenario 'decorating a model object with custom decorator' do - visit "/authors/#{@matz.id}/fancy_show" - page.should have_content 'matz' - page.should have_content '444' - end end From b0722095c2d6581bca96ecceba72cab81bd470a6 Mon Sep 17 00:00:00 2001 From: Cameron Adamez Date: Mon, 26 Nov 2012 11:36:54 -0800 Subject: [PATCH 6/8] Changed syntax to decorate :model_name, :with => ModelDecorator and raising error if with hash is not added. --- .../monkey/abstract_controller/rendering.rb | 13 ++++++------- spec/fake_app/fake_app.rb | 2 +- spec/requests/controller_ivar_spec.rb | 4 ++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/active_decorator/monkey/abstract_controller/rendering.rb b/lib/active_decorator/monkey/abstract_controller/rendering.rb index 24228862..407209b5 100644 --- a/lib/active_decorator/monkey/abstract_controller/rendering.rb +++ b/lib/active_decorator/monkey/abstract_controller/rendering.rb @@ -10,14 +10,13 @@ def view_assigns_with_decorator alias_method_chain :view_assigns, :decorator module ClassMethods - def decorate decorate_hash - decorate_hash.each do |k,v| - klass = k.to_s.camelize.constantize - class << klass - attr_accessor :decorated_with - end - klass.decorated_with = v + 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 = opts[:with] end end end diff --git a/spec/fake_app/fake_app.rb b/spec/fake_app/fake_app.rb index 84556b24..57c832c1 100644 --- a/spec/fake_app/fake_app.rb +++ b/spec/fake_app/fake_app.rb @@ -88,7 +88,7 @@ class ApplicationController < ActionController::Base end class AuthorsController < ApplicationController - decorate :author => ArbitraryDecorator + decorate :author, :with => ArbitraryDecorator def index if params[:variable_type] == 'array' diff --git a/spec/requests/controller_ivar_spec.rb b/spec/requests/controller_ivar_spec.rb index d8e707ab..362b44ed 100644 --- a/spec/requests/controller_ivar_spec.rb +++ b/spec/requests/controller_ivar_spec.rb @@ -32,4 +32,8 @@ 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 end From 167b323a98625fa0b7e9767cbeb975a66ee0b0c4 Mon Sep 17 00:00:00 2001 From: Cameron Adamez Date: Mon, 26 Nov 2012 11:55:01 -0800 Subject: [PATCH 7/8] Added support for decorate to take a symbol or constant in its declaration, eg: decorate :model, :with => :other_decorator or decorate :model, :with => OtherDecorator --- README.md | 19 +++++++++++++++++++ .../monkey/abstract_controller/rendering.rb | 11 ++++++++++- spec/requests/controller_ivar_spec.rb | 5 +++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index be507967..4b1af057 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,25 @@ You can use the generator for doing this ( `% rails g decorator user` ) <%= user.link %>
<% end %> +### Examples with explicit 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 + decorate :user, :with => UserJsonDecorator # or :user_json_decorator + def show + @user = User.find(params[:id]) + respond_with @user + end + end + + ## Contributing to ActiveDecorator ## diff --git a/lib/active_decorator/monkey/abstract_controller/rendering.rb b/lib/active_decorator/monkey/abstract_controller/rendering.rb index 407209b5..40803886 100644 --- a/lib/active_decorator/monkey/abstract_controller/rendering.rb +++ b/lib/active_decorator/monkey/abstract_controller/rendering.rb @@ -16,7 +16,16 @@ def decorate model_name, opts={} class << klass attr_accessor :decorated_with end - klass.decorated_with = opts[:with] + 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 diff --git a/spec/requests/controller_ivar_spec.rb b/spec/requests/controller_ivar_spec.rb index 362b44ed..82c6c0fc 100644 --- a/spec/requests/controller_ivar_spec.rb +++ b/spec/requests/controller_ivar_spec.rb @@ -36,4 +36,9 @@ 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 From bbbfd9c5dec7f2f5515889560106bcc9d6fc6a3c Mon Sep 17 00:00:00 2001 From: Cameron Adamez Date: Mon, 26 Nov 2012 12:03:38 -0800 Subject: [PATCH 8/8] Adjusted examples in README to give a clearer view of what this patch is intended for. --- README.md | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4b1af057..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,20 +48,10 @@ You can use the generator for doing this ( `% rails g decorator user` ) link_to full_name, website end end - - # app/controllers/users_controller.rb - class UsersController < ApplicationController - def index - @users = User.all - end - end - - # app/views/users/index.html.erb - <% @users.each do |user| %> - <%= user.link %>
- <% end %> -### Examples with explicit decorator ### + # 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={}) @@ -68,15 +59,29 @@ You can use the generator for doing this ( `% rails g decorator user` ) :first_name => first_name } end end + # app/controllers/users_controller.rb class UsersController < ApplicationController + def index + @users = User.all + 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 %> +