assaf-presenter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
File without changes
data/README ADDED
File without changes
@@ -0,0 +1,23 @@
1
+ require 'rake/gempackagetask'
2
+
3
+
4
+ spec = Gem::Specification.load(File.join(File.dirname(__FILE__), 'presenter.gemspec'))
5
+
6
+ task 'setup' do
7
+ end
8
+
9
+
10
+ package = Rake::GemPackageTask.new(spec) do |pkg|
11
+ pkg.need_tar = true
12
+ pkg.need_zip = true
13
+ end
14
+
15
+ desc "Install the package locally"
16
+ task 'install'=>['setup', 'package'] do |task|
17
+ system 'sudo', 'gem', 'install', "pkg/#{spec.name}-#{spec.version}.gem"
18
+ end
19
+
20
+ desc "Uninstall previously installed packaged"
21
+ task 'uninstall' do |task|
22
+ system 'sudo', 'gem', 'uninstall', spec.name, '-v', spec.version.to_s
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'presenter/method'
2
+ require 'presenter/base'
3
+ require 'presenter/rspec'
4
+
5
+ [ActionController::Base, ActionView::Base].each do |mod|
6
+ mod.class_eval do
7
+ protected ; include Presenter::PresentingMethod
8
+ end
9
+ end
@@ -0,0 +1,115 @@
1
+ module Presenter
2
+ class Base
3
+
4
+ class << self
5
+ # Item name, derived from the class name (e.g. TaskPresenter becomes 'task').
6
+ # Used for the XML document element name, also name of the accessor method used
7
+ # to read the presented object.
8
+ attr_reader :item_name
9
+
10
+ # Array name, derived from the class name (e.g. TaskPresenter becomes 'tasks').
11
+ # Used for the XML document element name, also name of the accessor method used
12
+ # to read the presented array of objects.
13
+ attr_reader :array_name
14
+
15
+ def inherited(klass)
16
+ name = klass.name[/(.*?)(Presenter)?$/, 1].underscore
17
+ klass.instance_variable_set(:@item_name, name)
18
+ klass.class_eval "def #{klass.item_name} ; @item ; end"
19
+ klass.instance_variable_set(:@array_name, name.pluralize)
20
+ klass.class_eval "def #{klass.array_name} ; @array ; end"
21
+ end
22
+
23
+ end
24
+
25
+ # Creates new presenter using the given controller and item/array.
26
+ def initialize(controller, value)
27
+ @controller = controller
28
+ if value.is_a?(Array)
29
+ @array = value
30
+ else
31
+ @item = value
32
+ end
33
+ end
34
+
35
+ include PresentingMethod
36
+
37
+ # Controller associated with this presenter. Used primarily to create URLs
38
+ # and access various helper methods.
39
+ attr_reader :controller
40
+
41
+ # Item being presented. This accessor has a value when presenting a single
42
+ # object, for arrays see #array. Can also be accessed from named attribute,
43
+ # e.g. the #task method on TaskPresenter.
44
+ attr_reader :item
45
+
46
+ # Array being presented. This accessor has a value when presenting an array
47
+ # of objects, for single item see #item. Can also be accessed from named attribute,
48
+ # e.g. the #tasks method on TaskPresenter.
49
+ attr_reader :array
50
+
51
+ # Converts to JSON document, returns a String. The default implementation
52
+ # uses #map to convert the instance or each member of the array, and calls
53
+ # #to_json on the result.
54
+ #
55
+ # For example:
56
+ # render :json=>presenting(@item)
57
+ def to_json(options = {})
58
+ @array ? @array.map { |i| hash_for(i) }.to_json(options) :
59
+ hash_for(@item).to_json(options)
60
+ end
61
+
62
+ # Converts to XML document, returns a String. The default implementation
63
+ # uses #map to convert the instance or each member of the array, and calls
64
+ # #to_xml on the result.
65
+ #
66
+ # For example:
67
+ # render :xml=>presenting(@items)
68
+ def to_xml(options = {})
69
+ @array ? @array.map { |i| hash_for(i) }.to_xml({:root=>self.class.array_name}.merge(options)) :
70
+ hash_for(@item).to_xml({:root=>self.class.item_name}.merge(options))
71
+ end
72
+
73
+ protected
74
+
75
+ # Shortcut for CGI::escapeHTML.
76
+ def h(text)
77
+ CGI::escapeHTML(text)
78
+ end
79
+
80
+ include ActionController::UrlWriter
81
+ # TODO: do we need this?
82
+ # default_url_options[:host] = 'test.host' if defined?(RAILS_ENV) && RAILS_ENV == 'test'
83
+
84
+ def url_for_with_controller(*args)
85
+ if controller
86
+ controller.url_for(*args)
87
+ else
88
+ url_for_without_controller(*args)
89
+ end
90
+ end
91
+ alias_method_chain :url_for, :controller
92
+
93
+ # Request to which this controller is responding.
94
+ def request
95
+ controller && controller.request
96
+ end
97
+
98
+ # Converts object into a hash. JSON, XML and other output formats use this
99
+ # before serializing the result into the respective content type.
100
+ #
101
+ # Override to do all sorts of fancy tricks. The defult implementation calls
102
+ # the attributes methods (works on ActiveRecord models), lacking that the to_hash
103
+ # method, and if neither is found return an empty hash.
104
+ def hash_for(object)
105
+ object.respond_to?(:attributes) && object.attributes ||
106
+ object.respond_to?(:to_hash) && object.to_hash || {}
107
+ end
108
+
109
+ # Returns an ID using the host name, object class and object identifier.
110
+ def id_for(object)
111
+ "tag:#{request.host}:#{object.class.underscore}/#{object.id}"
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,46 @@
1
+ module Presenter
2
+ module PresentingMethod
3
+
4
+ # Return a new presenter. This method can be called in several configurations to
5
+ # present both an instance and an array:
6
+ #
7
+ # You can call this method in several configurations to present both singular objects
8
+ # and arrays. Using an explicit type followed by value:
9
+ # presenting(class, object_or_array)
10
+ # The first argument specifies the expected type of the object/array that follows,
11
+ # and that argument is used to find the presenter class. For example, <tt>presenting(Foo,[])<tt>
12
+ # uses <tt>FooPresenter</tt>.
13
+ # presenting(symbol, object_or_array)
14
+ # This method is similar but replaces explicit class with symbol, so <tt>foo:<tt> instead
15
+ # of <tt>Foo</tt>.
16
+ # presenting(object)
17
+ # The third form takes a single argument and finds the presenter class based on the
18
+ # argument type, for example, <tt>presenting(Foo.new)</tt> will use the presenter class
19
+ # <tt>FooPresenter</tt>.
20
+ # presenting(array)
21
+ # Since arrays may be empty it is not always possible to use the array content to figure
22
+ # out the presenter class. Either specify it explicitly as the first argument (see above),
23
+ # or let the presenting method figure it out from the controller name, for example,
24
+ # inferring <tt>FooPresenter</tt> from <tt>FooController</tt>.
25
+ def presenting(*args)
26
+ controller = ActionController::Base === self ? self : self.controller
27
+ case args.first
28
+ when Class # Presented class followed by instance/array
29
+ name = args.shift.to_s
30
+ value = args.shift
31
+ when Symbol # Presenter name (symbol) followed by instance/array
32
+ name = args.shift.to_s.capitalize
33
+ value = args.shift
34
+ when Array # Array of values, pick presenter from item type
35
+ value = args.shift
36
+ name = controller.class.to_s.gsub(/Controller$/, '').classify
37
+ else # Presenter for single instance.
38
+ value = args.shift
39
+ name = value.class.name
40
+ end
41
+ raise ArgumentError, "Unexpected arguments #{args.inspect}" unless args.empty?
42
+ Class.const_get("#{name}Presenter").new(controller, value)
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,33 @@
1
+ module Spec
2
+ module Rails
3
+ module Matchers
4
+
5
+ class PresentMatcher
6
+ def initialize(presenter, format)
7
+ @presenter = presenter
8
+ @format = format
9
+ end
10
+
11
+ def matches?(response)
12
+ @expect = response.body
13
+ @actual = @presenter.send("to_#{@format.to_sym}")
14
+ @actual == @expect
15
+ end
16
+
17
+ def failure_message
18
+ "Expected #{@expect.inspect}, found #{@actual.inspect}"
19
+ end
20
+
21
+ def negative_failure_message
22
+ "Found unexpected #{@actual.inspect}"
23
+ end
24
+ end
25
+
26
+ include Presenter::PresentingMethod
27
+
28
+ def present(*args)
29
+ PresentMatcher.new(presenting(*args), request.format)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = 'presenter'
3
+ spec.version = '0.1.0'
4
+ spec.author = 'Rails Presenter'
5
+ spec.email = 'assaf@labnotes.org'
6
+ spec.homepage = "http://github.com/assaf/#{spec.name}"
7
+ spec.summary = "Add later ..." # TODO
8
+
9
+ spec.files = Dir['lib/**/*', 'rails/**/*', 'README', 'CHANGELOG', 'MIT-LICENSE',
10
+ '*.gemspec', 'Rakefile', 'spec/**/*', 'doc/**/*']
11
+ spec.require_paths = 'lib'
12
+
13
+ spec.has_rdoc = true
14
+ spec.extra_rdoc_files = 'README', 'CHANGELOG', 'MIT-LICENSE'
15
+ spec.rdoc_options = '--title', spec.name,
16
+ '--main', 'README', '--line-numbers', '--inline-source',
17
+ '--webcvs', "#{spec.homepage}/tree/master"
18
+ spec.rubyforge_project = spec.name
19
+
20
+ # Tested against these dependencies.
21
+ spec.add_dependency 'rails'
22
+ end
@@ -0,0 +1,9 @@
1
+ path = "#{Rails.root}/app/presenters"
2
+ # That way we're able to use everything in app/presenters.
3
+ ActiveSupport::Dependencies.load_paths << path
4
+ unless File.exist?(path)
5
+ puts "Creating #{path}"
6
+ Dir.mkdir path
7
+ end
8
+
9
+ require 'presenter'
@@ -0,0 +1,15 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
2
+ require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment")
3
+ require 'spec/rails'
4
+
5
+ # Object being presented.
6
+ class Foo
7
+ end
8
+
9
+ # Presenter for Foo.
10
+ class FooPresenter < Presenter::Base
11
+ end
12
+
13
+ # Test controller, using similar name so we can deduct presenter from controller.
14
+ class FooController < ApplicationController
15
+ end
@@ -0,0 +1,73 @@
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+
3
+
4
+ describe 'Presenter', :type=>:controller do
5
+ controller_name :foo
6
+
7
+ it 'should derive item name from class name' do
8
+ FooPresenter.item_name.should == 'foo'
9
+ end
10
+
11
+ it 'should derive array name from element name' do
12
+ FooPresenter.array_name.should == 'foos'
13
+ end
14
+
15
+ it 'should have accessor for controller' do
16
+ presenting(Foo.new).controller.should == controller
17
+ end
18
+
19
+ describe '(instance)' do
20
+ before { @presenter = presenting(@item = Foo.new) }
21
+
22
+ it 'should provide object from #item accessor' do
23
+ @presenter.item.should == @item
24
+ end
25
+
26
+ it 'should provide object from (item) accessor' do
27
+ @presenter.foo.should == @item
28
+ end
29
+
30
+ it 'should provide nothing from #array accessor' do
31
+ @presenter.array.should be_nil
32
+ end
33
+
34
+ it 'should use hash_for(instance) for to_json' do
35
+ @presenter.should_receive(:hash_for).with(@item).and_return('bar'=>'beer')
36
+ @presenter.to_json.should == %{{"bar": "beer"}}
37
+ end
38
+
39
+ it 'should use hash_for(instance) for to_xml with model name' do
40
+ @presenter.should_receive(:hash_for).with(@item).and_return('bar'=>'beer')
41
+ @presenter.to_xml(:indent=>0).should == %{<?xml version=\"1.0\" encoding=\"UTF-8\"?><foo><bar>beer</bar></foo>}
42
+ end
43
+
44
+ end
45
+
46
+ describe '(array)' do
47
+ before { @presenter = presenting(@array = [Foo.new, Foo.new]) }
48
+
49
+ it 'should provide array from #array accessor' do
50
+ @presenter.array.should == @array
51
+ end
52
+
53
+ it 'should provide array from (array) accessor' do
54
+ @presenter.foos.should == @array
55
+ end
56
+
57
+ it 'should provide nothing from #item accessor' do
58
+ @presenter.item.should be_nil
59
+ end
60
+
61
+ it 'should use array.map(hash_for) for to_json' do
62
+ @presenter.should_receive(:hash_for).twice.and_return({'bar'=>1}, {'bar'=>2})
63
+ @presenter.to_json.should == %{[{"bar": 1}, {"bar": 2}]}
64
+ end
65
+
66
+ it 'should use hash_for(instance) for to_xml with model name' do
67
+ @presenter.should_receive(:hash_for).twice.and_return({'bar'=>1}, {'bar'=>2})
68
+ @presenter.to_xml(:indent=>0, :types=>false).should == %{<?xml version=\"1.0\" encoding=\"UTF-8\"?><foos type="array"><foo><bar type="integer">1</bar></foo><foo><bar type="integer">2</bar></foo></foos>}
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,111 @@
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+
3
+
4
+ describe 'presenting', :type=>:controller do
5
+ controller_name :foo
6
+
7
+ it 'should be available on controller' do
8
+ ApplicationController.instance_method(:presenting).to_s.should =~ /presenting/
9
+ end
10
+
11
+ it 'should be available on view' do
12
+ ActionView::Base.instance_method(:presenting).to_s.should =~ /presenting/
13
+ end
14
+
15
+ it 'should be available on presenter' do
16
+ FooPresenter.instance_method(:presenting).to_s.should =~ /presenting/
17
+ end
18
+
19
+ describe '(class,instance)' do
20
+ it 'should guess presenter type from class' do
21
+ presenting(Foo, 1).should be_kind_of(FooPresenter)
22
+ end
23
+
24
+ it 'should return presenter for instance' do
25
+ presenting(Foo, 1).item.should == 1
26
+ end
27
+ end
28
+
29
+ describe '(class,array)' do
30
+ it 'should guess presenter type from class' do
31
+ presenting(Foo, [1, 2]).should be_kind_of(FooPresenter)
32
+ end
33
+
34
+ it 'should return presenter for array' do
35
+ presenting(Foo, [1, 2]).array.should == [1, 2]
36
+ end
37
+ end
38
+
39
+ describe '(symbol,instance)' do
40
+ it 'should guess presenter type from symbol' do
41
+ presenting(:foo, 1).should be_kind_of(FooPresenter)
42
+ end
43
+
44
+ it 'should return presenter for instance' do
45
+ presenting(:foo, 1).item.should == 1
46
+ end
47
+ end
48
+
49
+ describe '(symbol,array)' do
50
+ it 'should guess presenter type from class' do
51
+ presenting(:foo, [1, 2]).should be_kind_of(FooPresenter)
52
+ end
53
+
54
+ it 'should return presenter for array' do
55
+ presenting(:foo, [1, 2]).array.should == [1, 2]
56
+ end
57
+ end
58
+
59
+ describe '(instance)' do
60
+ before { @instance = Foo.new }
61
+
62
+ it 'should guess presenter type from value' do
63
+ presenting(@instance).should be_kind_of(FooPresenter)
64
+ end
65
+
66
+ it 'should return presenter for instance' do
67
+ presenting(@instance).item.should == @instance
68
+ end
69
+
70
+ it 'should fail if value is nil' do
71
+ lambda { presenting(nil) }.should raise_error(NameError)
72
+ end
73
+ end
74
+
75
+ describe '(array)' do
76
+ before { @array = [Foo.new, Foo.new] }
77
+
78
+ it 'should guess presenter type from controller' do
79
+ presenting(@array).should be_kind_of(FooPresenter)
80
+ end
81
+
82
+ it 'should return presenter for array' do
83
+ presenting(@array).array.should == @array
84
+ end
85
+ end
86
+
87
+ describe '(empty array)' do
88
+ before { @empty = [] }
89
+
90
+ it 'should guess presenter type from controller' do
91
+ presenting(@empty).should be_kind_of(FooPresenter)
92
+ end
93
+
94
+ it 'should return presenter for empty array' do
95
+ presenting(@empty).array.should be_empty
96
+ end
97
+ end
98
+
99
+ it 'should use context as controller if context is controller' do
100
+ controller.presenting([]).controller.should == controller
101
+ end
102
+
103
+ it 'should borrow controller from context if context is not controller' do
104
+ presenting([]).controller.should == controller
105
+ end
106
+
107
+ it 'should fail if passed too many arguments' do
108
+ lambda { presenting(:foo, [], :bar) }.should raise_error(ArgumentError)
109
+ end
110
+
111
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: assaf-presenter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rails Presenter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ description:
25
+ email: assaf@labnotes.org
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ - CHANGELOG
33
+ - MIT-LICENSE
34
+ files:
35
+ - lib/presenter.rb
36
+ - lib/presenter
37
+ - lib/presenter/rspec.rb
38
+ - lib/presenter/base.rb
39
+ - lib/presenter/method.rb
40
+ - rails/init.rb
41
+ - README
42
+ - CHANGELOG
43
+ - MIT-LICENSE
44
+ - presenter.gemspec
45
+ - Rakefile
46
+ - spec/presenting_method_spec.rb
47
+ - spec/presenter_spec.rb
48
+ - spec/helper.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/assaf/presenter
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --title
54
+ - presenter
55
+ - --main
56
+ - README
57
+ - --line-numbers
58
+ - --inline-source
59
+ - --webcvs
60
+ - http://github.com/assaf/presenter/tree/master
61
+ require_paths: lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project: presenter
77
+ rubygems_version: 1.2.0
78
+ signing_key:
79
+ specification_version: 2
80
+ summary: Add later ...
81
+ test_files: []
82
+