assaf-presenter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+