resubject 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'rails', '~> 3.2.10'
4
+
3
5
  # Specify your gem's dependencies in resubject.gemspec
4
6
  gemspec
data/IDEAS.md ADDED
@@ -0,0 +1,51 @@
1
+ Present a object with a symbol
2
+
3
+ present user, context: :public
4
+ # => present user, PublicUserPresenter
5
+
6
+ present user, :public, :crazy
7
+ # => present user, PublicPresenter, CrazyPresenter
8
+
9
+ # best way to handle this?
10
+ present user, :public, :user
11
+ # => present user, PublicPresenter, UserPresenter
12
+
13
+ Helper class methods
14
+
15
+ class UserPresenter < Resubject::Presenter
16
+ # formats to default (to_s), only if present
17
+ # object.created_at
18
+ timestamp :created_at
19
+
20
+ # formats to long (to_s(:long))
21
+ timestamp :created_at, format: :long
22
+
23
+ # multiple
24
+ timestamps :created_at, :updated_at
25
+
26
+ autolink!
27
+ # create object.link
28
+
29
+ # currency format
30
+ currency :price
31
+
32
+ # change options
33
+ currency :price, format: 'whatever'
34
+ end
35
+
36
+ Test helpers
37
+
38
+ describe UserPresenter do
39
+ it 'has currency' do
40
+ user = stub price: 200.0
41
+ presented(user).currency.should == '$200.00'
42
+ end
43
+
44
+ # or
45
+
46
+ subject { UserPresenter.new(stub(price: 200.0)) }
47
+
48
+ it 'has currency' do
49
+ presented.currency.should == '$200'
50
+ end
51
+ end
data/README.md CHANGED
@@ -10,13 +10,17 @@ Uber simple presenters using Ruby's SimpleDelegator.
10
10
  Add this line to your application's Gemfile:
11
11
 
12
12
  ```ruby
13
- gem 'resubject', '~> 0.0.1'
13
+ gem 'resubject', '~> 0.0.2'
14
14
  ```
15
15
 
16
16
  And then execute:
17
17
 
18
18
  $ bundle
19
19
 
20
+ ## Documentation
21
+
22
+ Checkout the documentation in [rdoc.info/resubject](http://rdoc.info/github/felipeelias/resubject/master/frames)
23
+
20
24
  ## Usage
21
25
 
22
26
  Resubject works on top of `SimpleDelegator` which simply delegates every method call to the delegated object. Example:
@@ -37,7 +41,7 @@ Then use the delegator:
37
41
  ```ruby
38
42
  box = Box.new('Awkward Package', ['platypus', 'sloth', 'anteater'])
39
43
 
40
- presentable = BoxPresenter.new(box, nil)
44
+ presentable = BoxPresenter.new(box)
41
45
  presentable.contents
42
46
  # => platypus, sloth, anteater
43
47
  ```
@@ -84,6 +88,56 @@ Or if you prefer, you can use the `#present` method directly into your views
84
88
  <%= present(@box).contents %>
85
89
  ```
86
90
 
91
+ ## Helpers
92
+
93
+ You can define presentable attributes:
94
+
95
+ ```ruby
96
+ class PostPresenter < Resubject::Presenter
97
+ presents :title
98
+ presents :comments # or => presents :comments, CommentPresenter
99
+ end
100
+ ```
101
+
102
+ Then the attributes will return an instance of those presenters:
103
+
104
+ ```ruby
105
+ post.title
106
+ # => <TitlePresenter>
107
+
108
+ post.comments
109
+ # => [<CommentPresenter>, <CommentPresenter>, <CommentPresenter>]
110
+ ```
111
+
112
+ Or if you wish, you can use the `present` method inside your class
113
+
114
+ ```ruby
115
+ class PostPresenter < Resubject::Presenter
116
+ def comments
117
+ present(to_model.comments, SomePresenter)
118
+ end
119
+ end
120
+ ```
121
+
122
+ ### Helpers on Rails
123
+
124
+ `Resubject` can generate some rails helpers for your attributes:
125
+
126
+ ```ruby
127
+ class ProductPresenter < Resubject::Presenter
128
+ currency :price, precision: 2
129
+ end
130
+ ```
131
+
132
+ Will generate:
133
+
134
+ ```ruby
135
+ product.price
136
+ # => $10.00
137
+ ```
138
+
139
+ Check out [the extensions file](https://github.com/felipeelias/resubject/blob/master/lib/resubject/rails/extensions.rb) for other attribute helpers.
140
+
87
141
  ## Maintainers
88
142
 
89
143
  - Felipe Elias Philipp - [coderwall.com/felipeelias](http://coderwall.com/felipeelias)
@@ -0,0 +1,65 @@
1
+ module Resubject
2
+ module Builder
3
+ class InvalidPresenterArgument < StandardError #:nodoc:
4
+ end
5
+
6
+ # Presents a object or a collection of objects
7
+ #
8
+ # Examples:
9
+ #
10
+ # Builder.present box, template
11
+ # => <BoxPresenter>
12
+ #
13
+ # # Using a custom presenter
14
+ # Builder.present box, template, CustomPresenter
15
+ # => <CustomPresenter>
16
+ #
17
+ # # Using multiple presenters
18
+ # Builder.present box, template, OnePresenter, CustomPresenter
19
+ # => <OnePresenter<CustomPresenter>>
20
+ #
21
+ # # Using a collection
22
+ # Builder.present [box, box], template
23
+ # => [<BoxPresenter>, <BoxPresenter>]
24
+ #
25
+ def self.present(objects, template, *presenters)
26
+ if objects.respond_to?(:each)
27
+ Builder.present_all(objects, template, *presenters)
28
+ else
29
+ Builder.present_one(objects, template, *presenters)
30
+ end
31
+ end
32
+
33
+ # Presents a single object (see .present)
34
+ #
35
+ # Example:
36
+ #
37
+ # Builder.present box, template
38
+ # => <BoxPresenter>
39
+ #
40
+ def self.present_one(object, template, *presenters)
41
+ presenters = [Naming.presenter_for(object)] unless presenters.any?
42
+
43
+ unless presenters.all? { |p| p.is_a?(Class) && p.ancestors.include?(Resubject::Presenter) }
44
+ raise InvalidPresenterArgument.new("Expected a presenter in #{presenters.inspect}")
45
+ end
46
+
47
+ presenters.inject(object) do |presented, klass|
48
+ klass.new(presented, template)
49
+ end
50
+ end
51
+
52
+ # Presents a collection of objects (see .present)
53
+ #
54
+ # Example:
55
+ #
56
+ # Builder.present [box, box], template
57
+ # => [<BoxPresenter>, <BoxPresenter>]
58
+ #
59
+ def self.present_all(objects, template, *presenters)
60
+ objects.map do |o|
61
+ Builder.present_one(o, template, *presenters)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,20 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+
3
+ module Resubject
4
+ module Naming
5
+ def self.presenter_for(presentable)
6
+ klass = case presentable
7
+ when Symbol
8
+ presentable.to_s
9
+ when String
10
+ presentable
11
+ else
12
+ presentable.class.to_s
13
+ end
14
+
15
+ presenter = "#{klass.camelize}Presenter"
16
+
17
+ Object.const_get presenter
18
+ end
19
+ end
20
+ end
@@ -2,36 +2,55 @@ require 'delegate'
2
2
 
3
3
  module Resubject
4
4
  class Presenter < SimpleDelegator
5
- attr_reader :template
6
- alias :h :template
5
+ attr_reader :context
6
+ alias_method :template, :context
7
7
 
8
- def initialize(model, template)
9
- @template = template
8
+ def initialize(model, context = nil)
9
+ @context = context
10
10
  super(model)
11
11
  end
12
12
 
13
- def to_model
14
- __getobj__
13
+ alias_method :to_model, :__getobj__
14
+
15
+ # Builds a collection of presenters given an array of objects
16
+ #
17
+ # Example:
18
+ #
19
+ # boxes = [box1, box2, box3]
20
+ # BoxPresenter.all boxes
21
+ # # => [<BoxPresenter>, <BoxPresenter>, <BoxPresenter>]
22
+ #
23
+ def self.all(collection, context = nil)
24
+ collection.map { |c| new(c, context) }
15
25
  end
16
26
 
17
- def self.all(collection, template)
18
- collection.map { |object| new(object, template) }
27
+ # Creates a presenter from object or collection of objects
28
+ # See Builder.present for more details
29
+ # Example:
30
+ #
31
+ # present box
32
+ # => <BoxPresenter>
33
+ #
34
+ def present(objects, *presenters)
35
+ Builder.present objects, context, *presenters
19
36
  end
20
37
 
21
- private
22
-
23
- def self.inject_presenter_for(*options)
24
- options.each do |resource|
25
- presenter_name = "#{resource}_presenter"
26
- attr_writer presenter_name
27
-
28
- define_method presenter_name do
29
- instance_variable_get("@#{presenter_name}") || presenter_name.camelize.constantize
30
- end
31
-
32
- define_method "present_#{resource}" do |object|
33
- send(presenter_name).new(object, @template)
34
- end
38
+ # Generates a instance method with the attribute presented
39
+ #
40
+ # Example:
41
+ #
42
+ # class BoxPresenter < Resubject::Presenter
43
+ # presents :name
44
+ # # or
45
+ # # presents :name, CustomPresenter
46
+ # end
47
+ #
48
+ # BoxPresenter.new(box).name
49
+ # => <NamePresenter>
50
+ #
51
+ def self.presents(attribute, *presenters)
52
+ define_method attribute do
53
+ present to_model.send(attribute), *presenters
35
54
  end
36
55
  end
37
56
  end
@@ -0,0 +1,10 @@
1
+ module Resubject
2
+ class Engine < ::Rails::Engine #:nodoc:
3
+ initializer "resubject.helpers" do
4
+ ActiveSupport.on_load(:action_controller) do
5
+ require 'resubject/rails/helpers'
6
+ include Resubject::Helpers
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ module Resubject
2
+ module Rails #:nodoc:
3
+ module Extensions
4
+ # Generates an attribute using `number_to_currency` helper from rails
5
+ #
6
+ # Examples:
7
+ #
8
+ # class ProductPresenter < Resubject::Presenter
9
+ # currency :price
10
+ # end
11
+ #
12
+ # # Will create a `price` attribute using `number_to_currency`
13
+ #
14
+ # product.price
15
+ # # => '$10.00'
16
+ #
17
+ # Options:
18
+ #
19
+ # currency :price, precision: 3 # => '$123.456'
20
+ # currency :price, locale: :fr # => '123,51 €'
21
+ #
22
+ # See ActionView::Helpers::NumberHelper#number_to_currency[http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_to_currency] for other options
23
+ def currency(attribute, options = {})
24
+ define_method attribute do
25
+ template.number_to_currency to_model.send(attribute), options
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,27 +1,17 @@
1
1
  module Resubject
2
2
  module Helpers
3
- def self.included(base)
3
+ def self.included(base) #:nodoc:
4
4
  base.send(:helper_method, :present)
5
- base.send(:private, :present_object)
6
5
  end
7
6
 
8
- def present(object, *presenter_classes)
9
- presenter = if object.respond_to?(:each)
10
- object.map { |o| present_object(o, presenter_classes) }
11
- else
12
- present_object(object, presenter_classes)
13
- end
7
+ def present(objects, *presenters)
8
+ presenters = Builder.present(objects, view_context, *presenters)
14
9
 
15
- presenter.tap do |p|
10
+ presenters.tap do |p|
16
11
  if block_given?
17
12
  yield p
18
13
  end
19
14
  end
20
15
  end
21
-
22
- def present_object(object, presenter_classes)
23
- presenter_classes = ["#{object.class}Presenter".constantize] if presenter_classes.empty?
24
- presenter_classes.inject(object) { |o, klass| klass.new(o, view_context) }
25
- end
26
16
  end
27
17
  end
@@ -1,11 +1,34 @@
1
- require 'resubject/rails/helpers'
1
+ require 'resubject/rails/engine'
2
+ require 'resubject/rails/extensions'
2
3
 
3
4
  module Resubject
4
- class Engine < ::Rails::Engine
5
- initializer "resubject.helpers" do
6
- ActiveSupport.on_load(:action_controller) do
7
- include Resubject::Helpers
8
- end
5
+ class Presenter
6
+ extend Resubject::Rails::Extensions
7
+
8
+ private
9
+
10
+ def translate(*args, &block)
11
+ context.t(*args, &block)
12
+ end
13
+
14
+ alias_method :t, :translate
15
+
16
+ def localize(*args, &block)
17
+ context.l(*args, &block)
18
+ end
19
+
20
+ alias_method :l, :localize
21
+
22
+ def routes
23
+ ::Rails.application.routes.url_helpers
24
+ end
25
+
26
+ alias_method :r, :routes
27
+
28
+ def helpers
29
+ context
9
30
  end
31
+
32
+ alias_method :h, :helpers
10
33
  end
11
34
  end
@@ -1,3 +1,3 @@
1
1
  module Resubject
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/resubject.rb CHANGED
@@ -1,3 +1,5 @@
1
1
  require 'resubject/version'
2
+ require 'resubject/naming'
2
3
  require 'resubject/presenter'
4
+ require 'resubject/builder'
3
5
  require 'resubject/rails' if defined? Rails
data/resubject.gemspec CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
+ gem.add_dependency 'activesupport', '~> 3.2'
22
+
21
23
  gem.add_development_dependency "rake"
22
24
  gem.add_development_dependency "rspec", "~> 2.12.0"
23
25
  end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe Resubject::Builder do
4
+ let(:template) { stub }
5
+
6
+ before do
7
+ stub_const 'Box', Class.new
8
+ stub_const 'BoxPresenter', Class.new(Resubject::Presenter)
9
+ stub_const 'OtherBoxPresenter', Class.new(Resubject::Presenter)
10
+ end
11
+
12
+ describe '.present_one' do
13
+ it 'presents the object with related class' do
14
+ presented = Resubject::Builder.present_one Box.new, template
15
+ expect(presented).to be_a BoxPresenter
16
+ end
17
+
18
+ it 'presents the object with custom class' do
19
+ presented = Resubject::Builder.present_one Box.new, template, OtherBoxPresenter
20
+ expect(presented).to be_a OtherBoxPresenter
21
+ end
22
+
23
+ it 'presents the object with multiple classes' do
24
+ OtherBoxPresenter.should_receive(:new).twice
25
+ presenters = [OtherBoxPresenter, OtherBoxPresenter]
26
+ presented = Resubject::Builder.present_one Box.new, template, *presenters
27
+ end
28
+
29
+ it 'raises an error if custom presenter is not a presenter' do
30
+ expect do
31
+ Resubject::Builder.present_one Box.new, template, *[nil, Class.new]
32
+ end.to raise_error(Resubject::Builder::InvalidPresenterArgument)
33
+ end
34
+ end
35
+
36
+ describe '.present_all' do
37
+ it 'presents multiple objects' do
38
+ box = Box.new
39
+ presented = Resubject::Builder.present_all [box, box], template
40
+
41
+ expect(presented.map(&:class)).to eq [BoxPresenter, BoxPresenter]
42
+ end
43
+
44
+ it 'presents multiple objects with custom presenters' do
45
+ box = Box.new
46
+ presented = Resubject::Builder.present_all [box, box], template, OtherBoxPresenter
47
+
48
+ expect(presented.map(&:class)).to eq [OtherBoxPresenter, OtherBoxPresenter]
49
+ end
50
+ end
51
+
52
+ describe '.present' do
53
+ it 'presents single object' do
54
+ presented = Resubject::Builder.present Box.new, template
55
+ expect(presented).to be_a BoxPresenter
56
+ end
57
+
58
+ it 'presents multiple objects' do
59
+ box = Box.new
60
+ presented = Resubject::Builder.present [box, box], template
61
+
62
+ expect(presented.map(&:class)).to eq [BoxPresenter, BoxPresenter]
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Resubject::Naming do
4
+ before do
5
+ stub_const 'Box', Class.new
6
+ stub_const 'BoxPresenter', Class.new
7
+ end
8
+
9
+ it 'gets the presenter class from an object' do
10
+ presenter = Resubject::Naming.presenter_for Box.new
11
+
12
+ expect(presenter).to eq BoxPresenter
13
+ end
14
+
15
+ it 'gets the presenter class from a symbol' do
16
+ presenter = Resubject::Naming.presenter_for :box
17
+
18
+ expect(presenter).to eq BoxPresenter
19
+ end
20
+
21
+ it 'gets the presenter class from a string' do
22
+ presenter = Resubject::Naming.presenter_for 'box'
23
+
24
+ expect(presenter).to eq BoxPresenter
25
+ end
26
+ end
@@ -2,12 +2,62 @@ require 'spec_helper'
2
2
 
3
3
  describe Resubject::Presenter do
4
4
  let(:object) { mock :object }
5
- let(:template) { mock :template }
6
5
 
7
- subject { Resubject::Presenter.new(object, template) }
6
+ subject { Resubject::Presenter.new(object) }
8
7
 
9
8
  it 'delegate methods to object' do
10
9
  object.should_receive(:pretty)
11
10
  subject.pretty
12
11
  end
12
+
13
+ describe :to_model do
14
+ it 'returns the delegated object' do
15
+ expect(subject.to_model).to eq object
16
+ end
17
+ end
18
+
19
+ describe '.all' do
20
+ it 'creates various instances of the presenter' do
21
+ presented = Resubject::Presenter.all([object, object])
22
+
23
+ expect(presented.map(&:class)).to eq [Resubject::Presenter,
24
+ Resubject::Presenter]
25
+ end
26
+ end
27
+
28
+ describe :present do
29
+ it 'creates a presenter' do
30
+ stub_const 'Box', Class.new
31
+ stub_const 'BoxPresenter', Class.new(Resubject::Presenter)
32
+
33
+ presented = subject.present Box.new
34
+ expect(presented).to be_a BoxPresenter
35
+ end
36
+ end
37
+
38
+ describe '.presents' do
39
+ let :presenter do
40
+ Class.new(Resubject::Presenter) do
41
+ presents :item
42
+ presents :other_item, ItemPresenter
43
+ end
44
+ end
45
+
46
+ before do
47
+ stub_const 'Item', Class.new
48
+ stub_const 'ItemPresenter', Class.new(Resubject::Presenter)
49
+ end
50
+
51
+ it 'generates a method preseting the attribute' do
52
+ box = stub :box, :item => Item.new
53
+
54
+ expect(presenter.new(box).item).to be_a ItemPresenter
55
+ end
56
+
57
+ it 'presents the attributes with custom presenter' do
58
+ box = stub :box, :other_item => stub
59
+
60
+ expect(presenter.new(box).other_item).to be_a ItemPresenter
61
+ end
62
+ end
13
63
  end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'rails/all'
3
+ require 'resubject/rails'
4
+
5
+ describe Resubject::Presenter, 'extensions' do
6
+ let(:model) { mock :model }
7
+
8
+ let(:presenter) do
9
+ Class.new(Resubject::Presenter) do
10
+ currency :price
11
+ end
12
+ end
13
+
14
+ subject do
15
+ presenter.new(model, ActionView::Base.new)
16
+ end
17
+
18
+ describe '.currency' do
19
+ it 'returns currency format' do
20
+ model.should_receive(:price).and_return(10.0)
21
+ expect(subject.price).to eq "$10.00"
22
+ end
23
+ end
24
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resubject
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,8 +10,24 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-12-19 00:00:00.000000000 Z
13
+ date: 2013-01-06 00:00:00.000000000 Z
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ type: :runtime
17
+ version_requirements: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '3.2'
23
+ name: activesupport
24
+ prerelease: false
25
+ requirement: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: '3.2'
15
31
  - !ruby/object:Gem::Dependency
16
32
  type: :development
17
33
  version_requirements: !ruby/object:Gem::Requirement
@@ -56,16 +72,24 @@ files:
56
72
  - .rspec
57
73
  - .travis.yml
58
74
  - Gemfile
75
+ - IDEAS.md
59
76
  - LICENSE.txt
60
77
  - README.md
61
78
  - Rakefile
62
79
  - lib/resubject.rb
80
+ - lib/resubject/builder.rb
81
+ - lib/resubject/naming.rb
63
82
  - lib/resubject/presenter.rb
64
83
  - lib/resubject/rails.rb
84
+ - lib/resubject/rails/engine.rb
85
+ - lib/resubject/rails/extensions.rb
65
86
  - lib/resubject/rails/helpers.rb
66
87
  - lib/resubject/version.rb
67
88
  - resubject.gemspec
89
+ - spec/resubject/builder_spec.rb
90
+ - spec/resubject/naming_spec.rb
68
91
  - spec/resubject/presenter_spec.rb
92
+ - spec/resubject/rails/extensions_spec.rb
69
93
  - spec/spec_helper.rb
70
94
  homepage: https://github.com/felipeelias/resubject
71
95
  licenses:
@@ -82,7 +106,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
106
  segments:
83
107
  - 0
84
108
  version: '0'
85
- hash: 3361348896197597575
109
+ hash: 134183276947229850
86
110
  required_rubygems_version: !ruby/object:Gem::Requirement
87
111
  none: false
88
112
  requirements:
@@ -91,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
115
  segments:
92
116
  - 0
93
117
  version: '0'
94
- hash: 3361348896197597575
118
+ hash: 134183276947229850
95
119
  requirements: []
96
120
  rubyforge_project:
97
121
  rubygems_version: 1.8.24
@@ -99,5 +123,8 @@ signing_key:
99
123
  specification_version: 3
100
124
  summary: Uber simple presenters using Ruby's SimpleDelegator
101
125
  test_files:
126
+ - spec/resubject/builder_spec.rb
127
+ - spec/resubject/naming_spec.rb
102
128
  - spec/resubject/presenter_spec.rb
129
+ - spec/resubject/rails/extensions_spec.rb
103
130
  - spec/spec_helper.rb