delegated_presenter 0.1.1 → 1.0.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.
Files changed (66) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +15 -2
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +69 -2
  7. data/README.rdoc +3 -0
  8. data/Rakefile +27 -1
  9. data/delegated_presenter.gemspec +8 -0
  10. data/lib/delegated_presenter/base.rb +122 -10
  11. data/lib/delegated_presenter/error.rb +11 -0
  12. data/lib/delegated_presenter/presents_before_rendering.rb +46 -0
  13. data/lib/delegated_presenter/railtie.rb +6 -0
  14. data/lib/delegated_presenter/version.rb +1 -1
  15. data/lib/delegated_presenter.rb +2 -0
  16. data/lib/generators/templates/presenter.rb.erb +1 -1
  17. data/lib/tasks/delegated_presenter_tasks.rake +4 -0
  18. data/spec/dummy/.rspec +1 -0
  19. data/spec/dummy/README.rdoc +261 -0
  20. data/spec/dummy/Rakefile +7 -0
  21. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  22. data/spec/dummy/app/assets/javascripts/sample_objects.js +2 -0
  23. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  24. data/spec/dummy/app/assets/stylesheets/sample_objects.css +4 -0
  25. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  26. data/spec/dummy/app/controllers/sample_objects_controller.rb +13 -0
  27. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  28. data/spec/dummy/app/helpers/sample_objects_helper.rb +2 -0
  29. data/spec/dummy/app/mailers/.gitkeep +0 -0
  30. data/spec/dummy/app/models/.gitkeep +0 -0
  31. data/spec/dummy/app/models/inherited_sample_object.rb +3 -0
  32. data/spec/dummy/app/models/sample_object.rb +3 -0
  33. data/spec/dummy/app/presenters/sample_object_presenter.rb +16 -0
  34. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  35. data/spec/dummy/app/views/sample_objects/index.html.erb +0 -0
  36. data/spec/dummy/app/views/sample_objects/show.html.erb +0 -0
  37. data/spec/dummy/config/application.rb +65 -0
  38. data/spec/dummy/config/boot.rb +10 -0
  39. data/spec/dummy/config/database.yml +25 -0
  40. data/spec/dummy/config/environment.rb +5 -0
  41. data/spec/dummy/config/environments/development.rb +37 -0
  42. data/spec/dummy/config/environments/production.rb +67 -0
  43. data/spec/dummy/config/environments/test.rb +37 -0
  44. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  45. data/spec/dummy/config/initializers/inflections.rb +15 -0
  46. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  47. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  48. data/spec/dummy/config/initializers/session_store.rb +8 -0
  49. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  50. data/spec/dummy/config/locales/en.yml +5 -0
  51. data/spec/dummy/config/routes.rb +3 -0
  52. data/spec/dummy/config.ru +4 -0
  53. data/spec/dummy/db/migrate/20121006185000_create_sample_objects.rb +13 -0
  54. data/spec/dummy/db/schema.rb +27 -0
  55. data/spec/dummy/lib/assets/.gitkeep +0 -0
  56. data/spec/dummy/log/.gitkeep +0 -0
  57. data/spec/dummy/public/404.html +26 -0
  58. data/spec/dummy/public/422.html +26 -0
  59. data/spec/dummy/public/500.html +25 -0
  60. data/spec/dummy/public/favicon.ico +0 -0
  61. data/spec/dummy/script/rails +6 -0
  62. data/spec/factories/sample_object.rb +18 -0
  63. data/spec/presenter_spec_spec.rb +17 -0
  64. data/spec/presents_before_rendering_spec.rb +19 -0
  65. data/spec/spec_helper.rb +42 -0
  66. metadata +201 -3
data/.gitignore CHANGED
@@ -2,6 +2,13 @@
2
2
  *.rbc
3
3
  .bundle
4
4
  .config
5
+ .idea
6
+ log/*.log
7
+ pkg/
8
+ spec/dummy/db/*.sqlite3
9
+ spec/dummy/log/*.log
10
+ spec/dummy/tmp/
11
+ spec/dummy/.sass-cache
5
12
  .yardoc
6
13
  Gemfile.lock
7
14
  InstalledFiles
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - 1.9.1
6
+ - jruby-19mode
7
+ - rbx-19mode
data/Gemfile CHANGED
@@ -1,4 +1,17 @@
1
- source 'https://rubygems.org'
1
+ source "http://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in delegated_presenter.gemspec
3
+ # Declare your gem's dependencies in delegated_presenter.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
4
6
  gemspec
7
+
8
+ # jquery-rails is used by the dummy application
9
+ gem "jquery-rails"
10
+
11
+ # Declare any dependencies that are still in development here instead of in
12
+ # your gemspec. These might include edge Rails or gems from your path or
13
+ # Git. Remember to move these dependencies to your gemspec before releasing
14
+ # your gem to rubygems.org.
15
+
16
+ # To use debugger
17
+ # gem 'debugger'
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DelegatedPresenter
2
2
 
3
- TODO: Write a gem description
3
+ DelegatedPresenter gives you an easy way to present objects and collections to your models. It uses ruby's Delegate model so it is lightweight and functional!
4
4
 
5
5
  ## Installation
6
6
 
@@ -18,7 +18,74 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- TODO: Write usage instructions here
21
+ ### Generate a presenter using a rails generator:
22
+
23
+ $ rails g delegated_presenter contact
24
+
25
+ ### Add some functionality to your presenter
26
+
27
+ ```ruby
28
+ class ContactPresenter < DelegatedPresenter::Base
29
+
30
+ # By default this presenter will try and present a Contact if it exists.
31
+ # You can explicitly tell the presenter to present other models using the following syntax:
32
+
33
+ presents OtherContact, SomeOtherModel
34
+
35
+ # Add some functionality to your presenter!
36
+ # The presenter will always look to the model it is presenting for methods and attributes not defined in the presenter.
37
+ # If you want to override model method, you can always call `presented_model.{method_name}` to access the original method.
38
+
39
+ def middle_initial
40
+ "#{middle_name.first}." if middle_name
41
+ end
42
+
43
+ def full_name
44
+ [prefix, first_name, middle_initial, last_name, suffix].compact.join(' ')
45
+ end
46
+
47
+ end
48
+ ```
49
+
50
+ ### Use the controller helper
51
+ See: {DelegatedPresenter::PresentsBeforeRendering}
52
+
53
+ Use the following to present a model instance or collection with a presenter, by default it will try and use a presenter with the same class as the instance or collection.
54
+
55
+ ```ruby
56
+ class ContactsController < ApplicationController
57
+
58
+ presents :contact
59
+
60
+ end
61
+ ```
62
+
63
+ If you for any reason need to explicitly define the presenter you may define a ```with: :presenter_name``` option, like so.
64
+
65
+ ```ruby
66
+ class UsersController < ApplicationController
67
+
68
+ presents :user, with: :contact_presenter
69
+
70
+ end
71
+ ```
72
+
73
+ Or if you like you can just manually initialize the presenter in your actions.
74
+
75
+ ```ruby
76
+ class ContactsController < ApplicationController
77
+
78
+ def index
79
+ @contacts = ContactPresenter.new Contact.all
80
+ end
81
+
82
+ def show
83
+ @contact = ContactPresenter.new Contact.find(params[:id])
84
+ end
85
+
86
+ end
87
+
88
+ ```
22
89
 
23
90
  ## Contributing
24
91
 
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = DelegatedPresenter
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile CHANGED
@@ -1 +1,27 @@
1
- require "bundler/gem_tasks"
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'DelegatedPresenter'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
@@ -17,4 +17,12 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
  gem.add_dependency "activesupport"
20
+
21
+ gem.add_development_dependency "rspec"
22
+ gem.add_development_dependency "sqlite3"
23
+ gem.add_development_dependency "rspec-rails"
24
+ gem.add_development_dependency "rails"
25
+ gem.add_development_dependency "factory_girl"
26
+ gem.add_development_dependency "database_cleaner"
27
+
20
28
  end
@@ -1,34 +1,146 @@
1
+ # @abstract Inherit from it to create a presenter.
2
+ # @raise [DelegatedPresenter::Error::MethodHidden]
3
+ # If a method is specified as hidden.
4
+ # @raise [DelegatedPresenter::Error::MethodNotExposed]
5
+ # If a method is not exposed.
1
6
  class DelegatedPresenter::Base < SimpleDelegator
2
7
 
3
- @@presentable = {}
8
+ PRESENTABLE = {}
9
+ HIDDEN_METHODS = {}
10
+ EXPOSED_METHODS = {}
11
+
12
+ private_constant :PRESENTABLE, :HIDDEN_METHODS, :EXPOSED_METHODS
13
+
14
+ delegate :exposed_methods, :hidden_methods, :presentable_objects, to: :presenter_class
4
15
 
5
16
  alias :presented_model :__getobj__
6
17
 
18
+ # Initializes the presenter with an object.
19
+ # @param object Can be an collection or instance of a presentable models.
20
+ # @raise [DelegatedPresenter::Error::NotPresentable] if the object is not instance or collection
21
+ # of a presentable model.
7
22
  def initialize(object)
8
- raise Presenter::NotPresentable, "#{self.presenter_class} cannot present a #{object.class}" unless self.presentable.include?(object.class.name)
9
- super(object)
10
- freeze
23
+ if object.is_a?(Array)
24
+ map_array(object)
25
+ else
26
+ raise DelegatedPresenter::Error::NotPresentable, "#{self.presenter_class} cannot present a #{object.class}" unless object_is_presentable?(object)
27
+ __setobj__(object)
28
+ if exposed_methods
29
+ expose_methods
30
+ elsif hidden_methods
31
+ hide_methods
32
+ end
33
+ end
11
34
  end
12
35
 
36
+ # The class of the presenter.
13
37
  alias_method :presenter_class, :class
14
38
 
39
+ # The class of the presented object.
15
40
  def class
16
- __getobj__.class
41
+ presented_model.class
17
42
  end
18
43
 
19
- def presentable
20
- (@@presentable[self.presenter_class.name] || [])
21
- end
44
+ private
22
45
 
46
+ # Determine what objects what to present.
47
+ # @!visibility public
48
+ # @param :objects One or more models to be presented.
49
+ # @example Add some presentable objects:
50
+ # presents :contact, :user, :client
23
51
  def self.presents(*objects)
24
- @@presentable[name] ||= []
25
- @@presentable[name] += objects.flatten.collect{ |i| i.to_s.camelize.singularize }
52
+ PRESENTABLE[name] ||= []
53
+ PRESENTABLE[name] += objects.flatten.collect{ |i| i.to_s.camelize.singularize }
54
+ end
55
+
56
+ # @!visibility public
57
+ # Hide methods from the presenter.
58
+ # @param methods one ore more methods from the model to hide from the presenter.
59
+ # @example Hide methods:
60
+ # hide :id, :crypted_password, :password_salt
61
+ def self.hide(*hidden_methods)
62
+ self.hidden_methods[name] ||= []
63
+ self.hidden_methods[name] += hidden_methods
64
+ end
65
+
66
+ # @!visibility public
67
+ # Exposes methods to the presenter, when defined all others will be hidden.
68
+ # @param :methods one ore more methods from the model to expose from the presenter.
69
+ # @example Expose methods:
70
+ # expose :first_name, :last_name
71
+ def self.expose(*exposed_methods)
72
+ EXPOSED_METHODS[name] ||= []
73
+ EXPOSED_METHODS[name] += exposed_methods.flatten
26
74
  end
27
75
 
76
+ ## Internal Methods: Not Documented
77
+
78
+ # @api private
28
79
  def self.inherited(subclass)
29
80
  presentable_class = subclass.name.gsub(/Presenter$/,'')
30
81
  presentable_class.constantize rescue nil
31
82
  subclass.presents presentable_class if Object.const_defined?(presentable_class)
32
83
  end
33
84
 
85
+ ## Inherited Object Getters
86
+
87
+ # Presentable Objects
88
+ # @api private
89
+ def self.presentable_objects
90
+ (PRESENTABLE[name] || [])
91
+ end
92
+
93
+ # Methods to expose
94
+ # @api private
95
+ def self.exposed_methods
96
+ EXPOSED_METHODS[name]
97
+ end
98
+
99
+ # Methods to hide
100
+ # @api private
101
+ def self.hidden_methods
102
+ HIDDEN_METHODS[name]
103
+ end
104
+
105
+ # Raises an error when hidden methods are called.
106
+ # @api private
107
+ def hide_methods
108
+ hidden_methods.flatten.each do |i|
109
+ singleton_class.send :define_method, i do |*args|
110
+ raise DelegatedPresenter::Error::MethodHidden, "Method `#{i} is hidden."
111
+ end
112
+ end
113
+ end
114
+
115
+ # Hides all methods except the ones exposed
116
+ # @api private
117
+ def expose_methods
118
+ (unique_model_methods - exposed_methods).flatten.each do |i|
119
+ singleton_class.send :define_method, i do |*args|
120
+ raise DelegatedPresenter::Error::MethodNotExposed, "Method `#{i} is not exposed."
121
+ end
122
+ end
123
+ end
124
+
125
+ # Determines if an object is presentable
126
+ # @api private
127
+ def object_is_presentable?(object)
128
+ presentable_objects.include?(object.class.name)
129
+ end
130
+
131
+ # Extracts the the models unique methods from the superclass
132
+ # @api private
133
+ def unique_model_methods
134
+ presented_model.methods - presented_model.class.superclass.instance_methods
135
+ end
136
+
137
+ # Maps an array of instances to delegated presented instances
138
+ # @api private
139
+ def map_array(array)
140
+ array.map!{ |object| presenter_class.new(object) }
141
+ inherited_methods = presenter_class.instance_methods - DelegatedPresenter::Base.instance_methods
142
+ inherited_methods.each { |i| singleton_class.send(:undef_method, i) }
143
+ __setobj__ array
144
+ end
145
+
34
146
  end
@@ -1,4 +1,15 @@
1
1
  module DelegatedPresenter::Error
2
+
3
+ # Error raised when an object is not presentable.
2
4
  class NotPresentable < StandardError
3
5
  end
6
+
7
+ # Error raised when a method is not exposed.
8
+ class MethodNotExposed < StandardError
9
+ end
10
+
11
+ # Error raised when a method is not exposed.
12
+ class MethodHidden < StandardError
13
+ end
14
+
4
15
  end
@@ -0,0 +1,46 @@
1
+ module DelegatedPresenter::PresentsBeforeRendering
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ class_attribute :presents_before_rendering
6
+ self.presents_before_rendering = []
7
+ end
8
+
9
+ private
10
+
11
+ # Presents specified instance variables before rendering.
12
+ def render
13
+ presents_before_rendering.each do |var|
14
+ ivar = instance_variable_get("@#{var}")
15
+ if ivar.present?
16
+ object_class = [ivar].flatten.collect(&:class).first.to_s
17
+ instance_variable_set("@#{var}", (object_class + 'Presenter').constantize.new(ivar))
18
+ end
19
+ end
20
+
21
+ super
22
+ end
23
+
24
+ module ClassMethods
25
+
26
+ private
27
+
28
+ # @!visibility public
29
+ # Sets up a presenter for instance variables. By default it will try to determine the presenter but this can be overridden via the "*with*" option.
30
+ # @overload presents(instance_var1, instance_var2, [...])
31
+ # Specifies which instance variables to present, assumes the presenter has a name of *InstanceClassPresenter*.
32
+ # @param instance_vars the instance variables to present.
33
+ # @overload presents(instance_var1, instance_var2, [...], options)
34
+ # Specifies which instance variables to present, assumes the presenter has a name of *InstanceClassPresenter*.
35
+ # @param instance_vars the instance variables to present.
36
+ # @option options [Symbol] :with The presenter to use.
37
+ def presents(*instance_vars)
38
+ options = instance_vars.extract_options!
39
+ self.presents_before_rendering += instance_vars.flatten.map(&:to_s)
40
+ end
41
+
42
+ alias :present :presents
43
+
44
+ end
45
+
46
+ end
@@ -4,4 +4,10 @@ class DelegatedPresenter::Railtie < Rails::Railtie
4
4
  require "generators/delegated_presenter_generator"
5
5
  end
6
6
 
7
+ initializer "Add presents before rendering to controller" do
8
+ ActiveSupport.on_load :action_controller do
9
+ ActionController::Base.send :include, DelegatedPresenter::PresentsBeforeRendering
10
+ end
11
+ end
12
+
7
13
  end
@@ -1,3 +1,3 @@
1
1
  module DelegatedPresenter
2
- VERSION = "0.1.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -5,6 +5,8 @@ module DelegatedPresenter
5
5
 
6
6
  autoload :Base
7
7
  autoload :Error
8
+ autoload :PresentsBeforeRendering
9
+
8
10
  end
9
11
 
10
12
  require 'delegated_presenter/railtie' if defined?(Rails)
@@ -1,6 +1,6 @@
1
1
  class <%= class_name %>Presenter < DelegatedPresenter::Base
2
2
 
3
- # By default this presenter wil try and present a `<%= class_name %>` if it exists.
3
+ # By default this presenter will try and present a `<%= class_name %>` if it exists.
4
4
  # You can explicitly tell the presenter to present other models using the following syntax:
5
5
 
6
6
  # presents MyModel1, MyModel2
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :delegated_presenter do
3
+ # # Task goes here
4
+ # end
data/spec/dummy/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color