delegated_presenter 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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