basic_presenter 0.0.2

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.
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: []