basic_presenter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .bundle
2
+ Gemfile.lock
3
+ pkg
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ -f d
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rake'
4
+ gem 'activesupport'
5
+ gem 'rspec'
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2014 vinsol.com
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name Spree nor the names of its contributors may be used to
13
+ endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,244 @@
1
+ [![Code Climate](https://codeclimate.com/github/vinsol/basic_presenter.png)](https://codeclimate.com/github/vinsol/basic_presenter)
2
+
3
+ # BasicPresenter
4
+ #
5
+
6
+
7
+
8
+ BasicPresenter adds a layer of presentation logic to your application.
9
+
10
+ Its inspired from https://github.com/railscasts/287-presenters-from-scratch/ and
11
+ http://svs.io/day/2012/12/27
12
+
13
+ ## Inspiration
14
+
15
+ Rails being a flexible framework starts with all methods being part of model and
16
+ when it gets big, developers are haunted with the daunting task of its maintenance.
17
+
18
+ Leveraging Presentation pattern with Presenters requires lot of context loading with
19
+ separating view/derived methods from actual objects and moving them to presenters and making
20
+ relevant changes to instantiate right object wherever needed.
21
+
22
+ By letting the knowledge of Presenters slip through model leveraging delegation helps us with
23
+ the movement of view/derived methods from actual objects to presenters without letting other
24
+ code to know about the delegation and change. Strict movement and no other code change.
25
+
26
+ ## Why use Presenters?
27
+
28
+ Presenters deal with presentation, so your models can concentrate on
29
+ domain logic. Instead of polluting models with presenter logic, you can implement the
30
+ presenter method in a presenter instead.
31
+
32
+ ## Installation
33
+
34
+ Requires active_support
35
+
36
+ Add this line to your application's Gemfile:
37
+
38
+ gem 'basic_presenter'
39
+
40
+ And then execute:
41
+
42
+ $ bundle
43
+
44
+ Or install it yourself as:
45
+
46
+ $ gem install basic_presenter
47
+
48
+ Or to use the edge version, add this to your Gemfile:
49
+
50
+ gem 'basic_presenter', :github => 'vinsol/basic_presenter'
51
+
52
+ ## Usage
53
+
54
+
55
+ #### Creating Presenter Directory
56
+
57
+ ```sh
58
+ rails g basic_presenter:install
59
+ ```
60
+
61
+ #### Generating Presenters
62
+
63
+ To generate a presenter by itself:
64
+
65
+ ```sh
66
+ rails g basic_presenter:presenter User
67
+ ```
68
+
69
+ ### Writing Presenters
70
+
71
+ Presenters are stored in `app/presenters`, they inherit from
72
+ `BasicPresenter::BasePresenter` and are named based on the model they
73
+ present. We also recommend you create an `ApplicationPresenter`.
74
+
75
+ ### Accessing the Model
76
+
77
+ You can access the model using the `domain_object` method, or the model name (in this case `user`). For example:
78
+
79
+
80
+ ```ruby
81
+ # app/models/user.rb
82
+ class User
83
+ attr_accessor :first_name, :last_name
84
+
85
+ def initialize(first_name, last_name)
86
+ @first_name = first_name
87
+ @last_name = last_name
88
+ end
89
+ end
90
+
91
+ # app/presenters/application_presenter.rb
92
+ class ApplicationPresenter < BasicPresenter::BasePresenter
93
+ end
94
+
95
+
96
+ # app/presenters/user_presenter.rb
97
+ class UserPresenter < ApplicationPresenter
98
+ # You can access the model using the `domain_object` method,
99
+ # or the model name (in this case `user`). For example:
100
+
101
+ presents :user
102
+
103
+ @delegation_methods = [:first_name, :last_name]
104
+
105
+ # Use ActiveSupport `delegate`
106
+ delegate *@delegation_methods, to: :user
107
+
108
+ def full_name
109
+ first_name + last_name
110
+ end
111
+ end
112
+ ```
113
+
114
+ The model name is either inferred from the model class name - taking
115
+ `User` and adding `Presenter` to it.
116
+ `presents` creates a wrapper for domain model object to call it close to
117
+ domain than something like `domain_object`
118
+
119
+
120
+ ### Wrapping Models with Presenters
121
+
122
+ #### Just pass the model to a new presenter instance:
123
+
124
+ ```ruby
125
+ user = User.new('Vinsol', 'User')
126
+ user_presenter = UserPresenter.new(user)
127
+ user_presenter.full_name # Output: VinsolUser
128
+ ```
129
+
130
+ #### Use model `#presenter` method
131
+
132
+ ```ruby
133
+ # app/models/user.rb
134
+ class User
135
+ include BasicPresenter::Concern
136
+
137
+ attr_accessor :first_name, :last_name
138
+
139
+ def initialize(first_name, last_name)
140
+ @first_name = first_name
141
+ @last_name = last_name
142
+ end
143
+ end
144
+
145
+ user = User.new('Vinsol', 'User')
146
+ user.presenter.full_name # Output: VinsolUser
147
+ ```
148
+
149
+ #### Override default_presenter class `UserPresenter` for User, you can set `AdminPresenter`
150
+
151
+ ```ruby
152
+ # app/presenters/admin_presenter.rb
153
+ class AdminPresenter < ApplicationPresenter
154
+ # You can access the model using the `domain_object` method,
155
+ # or the model name (in this case `admin`). For example:
156
+
157
+ presents :admin
158
+
159
+ @delegation_methods = [:first_name, :last_name]
160
+
161
+ # Use ActiveSupport `delegate`
162
+ delegate *@delegation_methods, to: :admin
163
+
164
+ def full_name
165
+ "#{first_name}-#{last_name}"
166
+ end
167
+ end
168
+
169
+ user = User.new('Vinsol', 'User')
170
+ user.presenter.full_name # Output: VinsolUser
171
+ user.presenter_class = AdminPresenter
172
+ user.presenter.full_name # Output: Vinsol-User
173
+ ```
174
+
175
+ #### Enhancing `User` to allow calls like `user.presenter.full_name` to be `user.full_name`
176
+
177
+ ```ruby
178
+ # app/models/user.rb
179
+ class User
180
+ include BasicPresenter::Concern
181
+
182
+ attr_accessor :first_name, :last_name
183
+
184
+ @delegation_methods = [:full_name]
185
+
186
+ # Use ActiveSupport `delegate`
187
+ # No change in this line needed
188
+ # As BasicPresenter::Concern Module always exposes presenter method
189
+ # to return presenter instance
190
+
191
+ delegate *@delegation_methods, to: :presenter
192
+
193
+ def initialize(first_name, last_name)
194
+ @first_name = first_name
195
+ @last_name = last_name
196
+ end
197
+ end
198
+
199
+ user = User.new('Vinsol', 'User')
200
+ user.presenter.full_name # Output: VinsolUser
201
+ user.full_name # Output: VinsolUser
202
+ user.presenter_class = AdminPresenter
203
+ user.presenter.full_name # Output: Vinsol-User
204
+ user.full_name # Output: Vinsol-User
205
+ ```
206
+
207
+
208
+ ### Testing
209
+
210
+ #### RSpec
211
+
212
+ **PENDING:** The specs are placed in `spec/presenters`. Add `type: :presenter` if they are placed elsewhere.
213
+
214
+ ## Acknowledgements
215
+
216
+ - https://github.com/railscasts/287-presenters-from-scratch/
217
+ - http://svs.io/day/2012/12/27
218
+
219
+ ## License
220
+
221
+ Mozilla Public License Version 2.0
222
+
223
+ Free to use in open source or proprietary applications, with the
224
+ caveat that any source code changes or additions to files covered
225
+ under MPL V2 can only be distributed under the MPL V2 or secondary
226
+ license as described in the full text.
227
+
228
+ ## Contributing
229
+
230
+ 1. Fork it
231
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
232
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
233
+ 4. Push to the branch (`git push origin my-new-feature`)
234
+ 5. Create new Pull Request
235
+
236
+ ## Credits
237
+
238
+ [![vinsol.com: Ruby on Rails, iOS and Android
239
+ developers](http://vinsol.com/vin_logo.png "Ruby on Rails, iOS and
240
+ Android developers")](http://vinsol.com)
241
+
242
+ Copyright (c) 2014 [vinsol.com](http://vinsol.com "Ruby on Rails, iOS
243
+ and Android developers"), released under the New MIT License
244
+
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new
7
+
8
+ task :default => [:spec]
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "basic_presenter"
3
+ s.version = '0.0.2'
4
+ s.platform = Gem::Platform::RUBY
5
+ s.authors = ["Pikender Sharma", "Hemant Khemani", "Akhil Bansal"]
6
+ s.email = "info@vinsol.com"
7
+ s.homepage = "http://vinsol.com"
8
+ s.summary = "Introduce presenters to your rails app"
9
+ s.description = "A simplified way to glue presenter methods to its domain object"
10
+
11
+ s.license = 'MIT'
12
+
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
16
+ #s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ #s.extra_rdoc_files = [ 'README.md' ]
18
+ s.rdoc_options = ["--charset=UTF-8"]
19
+ s.require_path = "lib"
20
+ s.add_dependency 'activesupport', '>= 3.0'
21
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_support/core_ext/module/delegation.rb'
2
+
3
+ module BasicPresenter
4
+ class BasePresenter
5
+ # extend Forwardable
6
+ # Presenter Class should allow DomainClass instance
7
+ # to be initialized
8
+
9
+ attr_reader :domain_object
10
+
11
+ def initialize(domain_object)
12
+ @domain_object = domain_object
13
+ end
14
+
15
+ class << self
16
+ def presents(name)
17
+ define_method(name) do
18
+ @domain_object
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ require 'active_support/inflector'
2
+
3
+ module BasicPresenter
4
+ module Concern
5
+ def presenter
6
+ if @presenter_class.nil?
7
+ @old_presenter_class = self.presenter_class = presenter_class
8
+ return @presenter = presenter_class.new(self)
9
+ end
10
+ return @presenter if presenter_class_not_changed?
11
+ @presenter = presenter_class.new(self)
12
+ @old_presenter_class = @presenter_class
13
+ @presenter
14
+ end
15
+
16
+ def default_presenter
17
+ "#{self.class}Presenter".constantize
18
+ end
19
+
20
+ def presenter_class
21
+ @presenter_class || default_presenter
22
+ end
23
+
24
+ def presenter_class=(vd)
25
+ @old_presenter_class = @presenter_class
26
+ @presenter_class = vd
27
+ end
28
+
29
+ def presenter_class_changed?
30
+ !(presenter_class_not_changed?)
31
+ end
32
+
33
+ def presenter_class_not_changed?
34
+ (@old_presenter_class == @presenter_class)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ require 'basic_presenter/concern'
2
+ require 'basic_presenter/base_presenter'
3
+
4
+ module BasicPresenter
5
+ end
@@ -0,0 +1,17 @@
1
+ module BasicPresenter
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ def create_presenters_dir
5
+ empty_directory('app/presenters')
6
+ end
7
+
8
+ def create_presenter_file
9
+ create_file "app/presenters/application_presenter.rb", <<-FILE
10
+ class ApplicationPresenter < BasicPresenter::BasePresenter
11
+ ## Shared Methods might come here
12
+ end
13
+ FILE
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ module BasicPresenter
2
+ module Generators
3
+ class PresenterGenerator < Rails::Generators::NamedBase
4
+
5
+ check_class_collision suffix: "Presenter"
6
+
7
+ def create_presenter_file
8
+ create_file "app/presenters/#{file_name}_presenter.rb", <<-FILE
9
+ class #{class_name}Presenter < ApplicationPresenter
10
+ presents :#{plural_name.singularize}
11
+
12
+ # Methods delegated to Presented Class #{class_name} object's #{plural_name.singularize}
13
+ @delegation_methods = []
14
+
15
+ delegate *@delegation_methods, to: :#{plural_name.singularize}
16
+
17
+ # Start the methods
18
+ # def full_name
19
+ # first_name + last_name
20
+ # end
21
+ end
22
+ FILE
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+ #require 'forwardable'
3
+
4
+ class DomainClass
5
+ include BasicPresenter::Concern
6
+
7
+ attr_accessor :first_name, :last_name
8
+
9
+ def initialize(first_name, last_name)
10
+ @first_name = first_name
11
+ @last_name = last_name
12
+ end
13
+ end
14
+
15
+ #class BasePresenter
16
+ # # extend Forwardable
17
+ # # Presenter Class should allow DomainClass instance
18
+ # # to be initialized
19
+ #
20
+ # attr_reader :domain_object
21
+ #
22
+ # def initialize(domain_object)
23
+ # @domain_object = domain_object
24
+ # end
25
+ #
26
+ # class << self
27
+ # def presents(name)
28
+ # define_method(name) do
29
+ # @domain_object
30
+ # end
31
+ # end
32
+ # end
33
+ #end
34
+
35
+ class DomainClassPresenter < BasicPresenter::BasePresenter
36
+ presents :domain_class
37
+
38
+ @delegation_methods = [:first_name, :last_name]
39
+
40
+ # def_delegators :domain_class, *@delegation_methods
41
+ delegate *@delegation_methods, to: :domain_class
42
+
43
+ def full_name
44
+ first_name + last_name
45
+ end
46
+ end
47
+
48
+ class AnotherPresenter < BasicPresenter::BasePresenter
49
+ presents :domain_class
50
+
51
+ def full_name
52
+ domain_class.first_name + domain_class.last_name
53
+ end
54
+ end
55
+
56
+ module BasicPresenter
57
+ describe Concern do
58
+ let(:dummy_domain_object) { DomainClass.new('Welcome', 'Presenter') }
59
+
60
+ context "Presenter Class Modification Interface" do
61
+ it "should determine default presenter class by the Domain Class" do
62
+ dummy_domain_object.default_presenter.should eq(DomainClassPresenter)
63
+ end
64
+
65
+ it "should tell current presenter class" do
66
+ dummy_domain_object.presenter_class.should eq(DomainClassPresenter)
67
+ end
68
+
69
+ it "should assume default presenter class as presenter class when not set" do
70
+ dummy_domain_object.presenter_class.should eq(DomainClassPresenter)
71
+ end
72
+
73
+ it "should recognize presenter class when set" do
74
+ dummy_domain_object.presenter_class.should eq(DomainClassPresenter)
75
+ dummy_domain_object.presenter_class = AnotherPresenter
76
+ dummy_domain_object.presenter_class.should eq(AnotherPresenter)
77
+ end
78
+ end
79
+
80
+ context "Helper Methods" do
81
+ it "should be able to inform change in Presenter Class" do
82
+ dummy_domain_object.presenter_class.should eq(DomainClassPresenter)
83
+ dummy_domain_object.should be_presenter_class_not_changed
84
+ dummy_domain_object.presenter_class = AnotherPresenter
85
+ dummy_domain_object.should be_presenter_class_changed
86
+ dummy_domain_object.presenter_class.should eq(AnotherPresenter)
87
+ end
88
+ end
89
+
90
+ context "Presenter Instance Creation to allow delegation of Presenter Methods" do
91
+ it "should delegate presenter methods on Default Presenter when not set" do
92
+ dummy_domain_object.presenter.should be_an_instance_of(DomainClassPresenter)
93
+ end
94
+
95
+ it "should delegate presenter methods on Explicit Presenter if set" do
96
+ dummy_domain_object.presenter_class = AnotherPresenter
97
+ dummy_domain_object.presenter.should be_an_instance_of(AnotherPresenter)
98
+ end
99
+
100
+ context "Create Presenter Instance Once and return same if Presenter Class not changed" do
101
+ it "when no Presenter Class set" do
102
+ dummy_domain_object_presenter = dummy_domain_object.presenter
103
+ dummy_domain_object.presenter.should eq(dummy_domain_object_presenter)
104
+ dummy_domain_object.presenter.should eq(dummy_domain_object_presenter)
105
+ dummy_domain_object.presenter.should eq(dummy_domain_object_presenter)
106
+ end
107
+
108
+ it "when Presenter Class changed" do
109
+ dummy_domain_object_presenter = dummy_domain_object.presenter
110
+ dummy_domain_object.presenter_class = AnotherPresenter
111
+ new_dummy_domain_object_presenter = dummy_domain_object.presenter
112
+ dummy_domain_object_presenter.should be_an_instance_of(DomainClassPresenter)
113
+ new_dummy_domain_object_presenter.should be_an_instance_of(AnotherPresenter)
114
+ dummy_domain_object.presenter.should eq(new_dummy_domain_object_presenter)
115
+ dummy_domain_object.presenter.should eq(new_dummy_domain_object_presenter)
116
+ dummy_domain_object.presenter.should eq(new_dummy_domain_object_presenter)
117
+ end
118
+ end
119
+ end
120
+
121
+ describe "Presenter Methods Available on Domain Object through #presenter" do
122
+ it "should allow presenter methods to be called through #presenter" do
123
+ dummy_domain_object.presenter.full_name.should eq('WelcomePresenter')
124
+ end
125
+
126
+ it "should allow domain_object to be accessed through #domain_object" do
127
+ dummy_domain_object.presenter.domain_object.should eq(dummy_domain_object)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1 @@
1
+ require 'basic_presenter'
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: basic_presenter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pikender Sharma
9
+ - Hemant Khemani
10
+ - Akhil Bansal
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2014-03-27 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ requirement: !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ! '>='
22
+ - !ruby/object:Gem::Version
23
+ version: '3.0'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ! '>='
30
+ - !ruby/object:Gem::Version
31
+ version: '3.0'
32
+ description: A simplified way to glue presenter methods to its domain object
33
+ email: info@vinsol.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - .rspec
40
+ - Gemfile
41
+ - LICENSE
42
+ - README.md
43
+ - Rakefile
44
+ - basic_presenter.gemspec
45
+ - lib/basic_presenter.rb
46
+ - lib/basic_presenter/base_presenter.rb
47
+ - lib/basic_presenter/concern.rb
48
+ - lib/generators/basic_presenter/install_generator.rb
49
+ - lib/generators/basic_presenter/presenter_generator.rb
50
+ - spec/basic_presenter/concern_spec.rb
51
+ - spec/spec_helper.rb
52
+ homepage: http://vinsol.com
53
+ licenses:
54
+ - MIT
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --charset=UTF-8
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubyforge_project:
74
+ rubygems_version: 1.8.25
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Introduce presenters to your rails app
78
+ test_files: []